diff options
Diffstat (limited to 'drivers/mtd')
270 files changed, 92519 insertions, 27286 deletions
diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index a03e862851d..94b821042d9 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -1,9 +1,6 @@ -# $Id: Kconfig,v 1.11 2005/11/07 11:14:19 gleixner Exp $ - -menu "Memory Technology Devices (MTD)" - -config MTD +menuconfig MTD tristate "Memory Technology Device (MTD) support" + depends on GENERIC_IO help Memory Technology Devices are flash, RAM and similar chips, often used for solid state file systems on embedded devices. This option @@ -13,45 +10,21 @@ config MTD them. It will also allow you to select individual drivers for particular hardware and users of MTD devices. If unsure, say N. -config MTD_DEBUG - bool "Debugging" - depends on MTD - help - This turns on low-level debugging for the entire MTD sub-system. - Normally, you should say 'N'. - -config MTD_DEBUG_VERBOSE - int "Debugging verbosity (0 = quiet, 3 = noisy)" - depends on MTD_DEBUG - default "0" - help - Determines the verbosity level of the MTD debugging messages. +if MTD -config MTD_CONCAT - tristate "MTD concatenating support" - depends on MTD - help - Support for concatenating several MTD devices into a single - (virtual) one. This allows you to have -for example- a JFFS(2) - file system spanning multiple physical flash chips. If unsure, - say 'Y'. - -config MTD_PARTITIONS - bool "MTD partitioning support" - depends on MTD +config MTD_TESTS + tristate "MTD tests support (DANGEROUS)" + depends on m help - If you have a device which needs to divide its flash chip(s) up - into multiple 'partitions', each of which appears to the user as - a separate MTD device, you require this option to be enabled. If - unsure, say 'Y'. + This option includes various MTD tests into compilation. The tests + should normally be compiled as kernel modules. The modules perform + various checks and verifications when loaded. - Note, however, that you don't need this option for the DiskOnChip - devices. Partitioning on NFTL 'devices' is a different - that's the - 'normal' form of partitioning used on a block device. + WARNING: some of the tests will ERASE entire MTD device which they + test. Do not use these tests unless you really know what you do. config MTD_REDBOOT_PARTS tristate "RedBoot partition table parsing" - depends on MTD_PARTITIONS ---help--- RedBoot is a ROM monitor and bootloader which deals with multiple 'images' in flash devices by putting a table one of the erase @@ -68,9 +41,10 @@ config MTD_REDBOOT_PARTS SA1100 map driver (CONFIG_MTD_SA1100) has an option for this, for example. +if MTD_REDBOOT_PARTS + config MTD_REDBOOT_DIRECTORY_BLOCK int "Location of RedBoot partition table" - depends on MTD_REDBOOT_PARTS default "-1" ---help--- This option is the Linux counterpart to the @@ -87,21 +61,21 @@ config MTD_REDBOOT_DIRECTORY_BLOCK config MTD_REDBOOT_PARTS_UNALLOCATED bool "Include unallocated flash regions" - depends on MTD_REDBOOT_PARTS help If you need to register each unallocated flash region as a MTD 'partition', enable this option. config MTD_REDBOOT_PARTS_READONLY bool "Force read-only for RedBoot system images" - depends on MTD_REDBOOT_PARTS help If you need to force read-only for 'RedBoot', 'RedBoot Config' and 'FIS directory' images, enable this option. +endif # MTD_REDBOOT_PARTS + config MTD_CMDLINE_PARTS - bool "Command line partition table parsing" - depends on MTD_PARTITIONS = "y" && MTD = "y" + tristate "Command line partition table parsing" + depends on MTD ---help--- Allow generic configuration of the MTD partition tables via the kernel command line. Multiple flash resources are supported for hardware where @@ -138,7 +112,7 @@ config MTD_CMDLINE_PARTS config MTD_AFS_PARTS tristate "ARM Firmware Suite partition parsing" - depends on ARM && MTD_PARTITIONS + depends on ARM ---help--- The ARM Firmware Suite allows the user to divide flash devices into multiple 'images'. Each such image has a header containing its name @@ -150,23 +124,49 @@ config MTD_AFS_PARTS You will still need the parsing functions to be called by the driver for your particular device. It won't happen automatically. The - 'armflash' map driver (CONFIG_MTD_ARMFLASH) does this, for example. + 'physmap' map driver (CONFIG_MTD_PHYSMAP) does this, for example. -comment "User Modules And Translation Layers" - depends on MTD +config MTD_OF_PARTS + tristate "OpenFirmware partitioning information support" + default y + depends on OF + help + This provides a partition parsing function which derives + the partition map from the children of the flash node, + as described in Documentation/devicetree/booting-without-of.txt. -config MTD_CHAR - tristate "Direct char device access to MTD devices" - depends on MTD +config MTD_AR7_PARTS + tristate "TI AR7 partitioning support" + ---help--- + TI AR7 partitioning support + +config MTD_BCM63XX_PARTS + tristate "BCM63XX CFE partitioning support" + depends on BCM63XX + select CRC32 help - This provides a character device for each MTD device present in - the system, allowing the user to read and write directly to the - memory chips, and also use ioctl() to obtain information about - the device, or to erase parts of it. + This provides partions parsing for BCM63xx devices with CFE + bootloaders. + +config MTD_BCM47XX_PARTS + tristate "BCM47XX partitioning support" + depends on BCM47XX || ARCH_BCM_5301X + help + This provides partitions parser for devices based on BCM47xx + boards. + +comment "User Modules And Translation Layers" + +# +# MTD block device support is select'ed if needed +# +config MTD_BLKDEVS + tristate config MTD_BLOCK tristate "Caching block device access to MTD devices" - depends on MTD + depends on BLOCK + select MTD_BLKDEVS ---help--- Although most flash chips have an erase size too large to be useful as block devices, it is possible to use MTD devices which are based @@ -188,7 +188,8 @@ config MTD_BLOCK config MTD_BLOCK_RO tristate "Readonly block device access to MTD devices" - depends on MTD_BLOCK!=y && MTD + depends on MTD_BLOCK!=y && BLOCK + select MTD_BLKDEVS help This allows you to mount read-only file systems (such as cramfs) from an MTD device, without the overhead (and danger) of the caching @@ -199,7 +200,8 @@ config MTD_BLOCK_RO config FTL tristate "FTL (Flash Translation Layer) support" - depends on MTD + depends on BLOCK + select MTD_BLKDEVS ---help--- This provides support for the original Flash Translation Layer which is part of the PCMCIA specification. It uses a kind of pseudo- @@ -215,7 +217,8 @@ config FTL config NFTL tristate "NFTL (NAND Flash Translation Layer) support" - depends on MTD + depends on BLOCK + select MTD_BLKDEVS ---help--- This provides support for the NAND Flash Translation Layer which is used on M-Systems' DiskOnChip devices. It uses a kind of pseudo- @@ -238,7 +241,8 @@ config NFTL_RW config INFTL tristate "INFTL (Inverse NAND Flash Translation Layer) support" - depends on MTD + depends on BLOCK + select MTD_BLKDEVS ---help--- This provides support for the Inverse NAND Flash Translation Layer which is used on M-Systems' newer DiskOnChip devices. It @@ -255,7 +259,8 @@ config INFTL config RFD_FTL tristate "Resident Flash Disk (Flash Translation Layer) support" - depends on MTD + depends on BLOCK + select MTD_BLKDEVS ---help--- This provides support for the flash translation layer known as the Resident Flash Disk (RFD), as used by the Embedded BIOS @@ -265,12 +270,45 @@ config RFD_FTL config SSFDC tristate "NAND SSFDC (SmartMedia) read only translation layer" - depends on MTD - default n + depends on BLOCK + select MTD_BLKDEVS help This enables read only access to SmartMedia formatted NAND flash. You can mount it with FAT file system. + +config SM_FTL + tristate "SmartMedia/xD new translation layer" + depends on BLOCK + select MTD_BLKDEVS + select MTD_NAND_ECC + help + This enables EXPERIMENTAL R/W support for SmartMedia/xD + FTL (Flash translation layer). + Write support is only lightly tested, therefore this driver + isn't recommended to use with valuable data (anyway if you have + valuable data, do backups regardless of software/hardware you + use, because you never know what will eat your data...) + If you only need R/O access, you can use older R/O driver + (CONFIG_SSFDC) + +config MTD_OOPS + tristate "Log panic/oops to an MTD buffer" + help + This enables panic and oops messages to be logged to a circular + buffer in a flash partition where it can be read back at some + later point. + +config MTD_SWAP + tristate "Swap on MTD device support" + depends on MTD && SWAP + select MTD_BLKDEVS + help + Provides volatile block device driver on top of mtd partition + suitable for swapping. The mapping of written blocks is not saved. + The driver provides wear leveling by storing erase counter into the + OOB. + source "drivers/mtd/chips/Kconfig" source "drivers/mtd/maps/Kconfig" @@ -281,5 +319,10 @@ source "drivers/mtd/nand/Kconfig" source "drivers/mtd/onenand/Kconfig" -endmenu +source "drivers/mtd/lpddr/Kconfig" + +source "drivers/mtd/spi-nor/Kconfig" + +source "drivers/mtd/ubi/Kconfig" +endif # MTD diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile index 1e36b9aed98..99bb9a1f6e1 100644 --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile @@ -1,29 +1,36 @@ # # Makefile for the memory technology device drivers. # -# $Id: Makefile.common,v 1.7 2005/07/11 10:39:27 gleixner Exp $ # Core functionality. -mtd-y := mtdcore.o -mtd-$(CONFIG_MTD_PARTITIONS) += mtdpart.o -obj-$(CONFIG_MTD) += $(mtd-y) +obj-$(CONFIG_MTD) += mtd.o +mtd-y := mtdcore.o mtdsuper.o mtdconcat.o mtdpart.o mtdchar.o -obj-$(CONFIG_MTD_CONCAT) += mtdconcat.o +obj-$(CONFIG_MTD_OF_PARTS) += ofpart.o obj-$(CONFIG_MTD_REDBOOT_PARTS) += redboot.o obj-$(CONFIG_MTD_CMDLINE_PARTS) += cmdlinepart.o obj-$(CONFIG_MTD_AFS_PARTS) += afs.o +obj-$(CONFIG_MTD_AR7_PARTS) += ar7part.o +obj-$(CONFIG_MTD_BCM63XX_PARTS) += bcm63xxpart.o +obj-$(CONFIG_MTD_BCM47XX_PARTS) += bcm47xxpart.o # 'Users' - code which presents functionality to userspace. -obj-$(CONFIG_MTD_CHAR) += mtdchar.o -obj-$(CONFIG_MTD_BLOCK) += mtdblock.o mtd_blkdevs.o -obj-$(CONFIG_MTD_BLOCK_RO) += mtdblock_ro.o mtd_blkdevs.o -obj-$(CONFIG_FTL) += ftl.o mtd_blkdevs.o -obj-$(CONFIG_NFTL) += nftl.o mtd_blkdevs.o -obj-$(CONFIG_INFTL) += inftl.o mtd_blkdevs.o -obj-$(CONFIG_RFD_FTL) += rfd_ftl.o mtd_blkdevs.o -obj-$(CONFIG_SSFDC) += ssfdc.o mtd_blkdevs.o +obj-$(CONFIG_MTD_BLKDEVS) += mtd_blkdevs.o +obj-$(CONFIG_MTD_BLOCK) += mtdblock.o +obj-$(CONFIG_MTD_BLOCK_RO) += mtdblock_ro.o +obj-$(CONFIG_FTL) += ftl.o +obj-$(CONFIG_NFTL) += nftl.o +obj-$(CONFIG_INFTL) += inftl.o +obj-$(CONFIG_RFD_FTL) += rfd_ftl.o +obj-$(CONFIG_SSFDC) += ssfdc.o +obj-$(CONFIG_SM_FTL) += sm_ftl.o +obj-$(CONFIG_MTD_OOPS) += mtdoops.o +obj-$(CONFIG_MTD_SWAP) += mtdswap.o nftl-objs := nftlcore.o nftlmount.o inftl-objs := inftlcore.o inftlmount.o -obj-y += chips/ maps/ devices/ nand/ onenand/ +obj-y += chips/ lpddr/ maps/ devices/ nand/ onenand/ tests/ + +obj-$(CONFIG_MTD_SPI_NOR) += spi-nor/ +obj-$(CONFIG_MTD_UBI) += ubi/ diff --git a/drivers/mtd/afs.c b/drivers/mtd/afs.c index 6a45be04564..96a33e3f7b0 100644 --- a/drivers/mtd/afs.c +++ b/drivers/mtd/afs.c @@ -2,7 +2,7 @@ drivers/mtd/afs.c: ARM Flash Layout/Partitioning - Copyright (C) 2000 ARM Limited + Copyright © 2000 ARM Limited 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 @@ -21,8 +21,6 @@ This is access code for flashes using ARM's flash partitioning standards. - $Id: afs.c,v 1.15 2005/11/07 11:14:19 gleixner Exp $ - ======================================================================*/ #include <linux/module.h> @@ -77,7 +75,7 @@ afs_read_footer(struct mtd_info *mtd, u_int *img_start, u_int *iis_start, size_t sz; int ret; - ret = mtd->read(mtd, ptr, sizeof(fs), &sz, (u_char *) &fs); + ret = mtd_read(mtd, ptr, sizeof(fs), &sz, (u_char *)&fs); if (ret >= 0 && sz != sizeof(fs)) ret = -EINVAL; @@ -134,7 +132,7 @@ afs_read_iis(struct mtd_info *mtd, struct image_info_struct *iis, u_int ptr) int ret, i; memset(iis, 0, sizeof(*iis)); - ret = mtd->read(mtd, ptr, sizeof(*iis), &sz, (u_char *) iis); + ret = mtd_read(mtd, ptr, sizeof(*iis), &sz, (u_char *)iis); if (ret < 0) goto failed; @@ -164,8 +162,8 @@ afs_read_iis(struct mtd_info *mtd, struct image_info_struct *iis, u_int ptr) } static int parse_afs_partitions(struct mtd_info *mtd, - struct mtd_partition **pparts, - unsigned long origin) + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) { struct mtd_partition *parts; u_int mask, off, idx, sz; @@ -207,11 +205,10 @@ static int parse_afs_partitions(struct mtd_info *mtd, if (!sz) return ret; - parts = kmalloc(sz, GFP_KERNEL); + parts = kzalloc(sz, GFP_KERNEL); if (!parts) return -ENOMEM; - memset(parts, 0, sz); str = (char *)(parts + idx); /* @@ -242,7 +239,7 @@ static int parse_afs_partitions(struct mtd_info *mtd, parts[idx].offset = img_ptr; parts[idx].mask_flags = 0; - printk(" mtd%d: at 0x%08x, %5dKB, %8u, %s\n", + printk(" mtd%d: at 0x%08x, %5lluKiB, %8u, %s\n", idx, img_ptr, parts[idx].size / 1024, iis.imageNumber, str); @@ -267,7 +264,8 @@ static struct mtd_part_parser afs_parser = { static int __init afs_parser_init(void) { - return register_mtd_parser(&afs_parser); + register_mtd_parser(&afs_parser); + return 0; } static void __exit afs_parser_exit(void) diff --git a/drivers/mtd/ar7part.c b/drivers/mtd/ar7part.c new file mode 100644 index 00000000000..7c9172ad262 --- /dev/null +++ b/drivers/mtd/ar7part.c @@ -0,0 +1,157 @@ +/* + * Copyright © 2007 Eugene Konev <ejka@openwrt.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * TI AR7 flash partition table. + * Based on ar7 map by Felix Fietkau <nbd@openwrt.org> + * + */ + +#include <linux/kernel.h> +#include <linux/slab.h> + +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/bootmem.h> +#include <linux/module.h> + +#include <uapi/linux/magic.h> + +#define AR7_PARTS 4 +#define ROOT_OFFSET 0xe0000 + +#define LOADER_MAGIC1 le32_to_cpu(0xfeedfa42) +#define LOADER_MAGIC2 le32_to_cpu(0xfeed1281) + +struct ar7_bin_rec { + unsigned int checksum; + unsigned int length; + unsigned int address; +}; + +static int create_mtd_partitions(struct mtd_info *master, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct ar7_bin_rec header; + unsigned int offset; + size_t len; + unsigned int pre_size = master->erasesize, post_size = 0; + 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; + ar7_parts[0].mask_flags = MTD_WRITEABLE; + + ar7_parts[1].name = "config"; + ar7_parts[1].offset = 0; + ar7_parts[1].size = master->erasesize; + ar7_parts[1].mask_flags = 0; + + do { /* Try 10 blocks starting from master->erasesize */ + offset = pre_size; + mtd_read(master, offset, sizeof(header), &len, + (uint8_t *)&header); + if (!strncmp((char *)&header, "TIENV0.8", 8)) + ar7_parts[1].offset = pre_size; + if (header.checksum == LOADER_MAGIC1) + break; + if (header.checksum == LOADER_MAGIC2) + break; + pre_size += master->erasesize; + } while (retries--); + + pre_size = offset; + + if (!ar7_parts[1].offset) { + ar7_parts[1].offset = master->size - master->erasesize; + post_size = master->erasesize; + } + + switch (header.checksum) { + case LOADER_MAGIC1: + while (header.length) { + offset += sizeof(header) + header.length; + mtd_read(master, offset, sizeof(header), &len, + (uint8_t *)&header); + } + root_offset = offset + sizeof(header) + 4; + break; + case LOADER_MAGIC2: + while (header.length) { + offset += sizeof(header) + header.length; + mtd_read(master, offset, sizeof(header), &len, + (uint8_t *)&header); + } + root_offset = offset + sizeof(header) + 4 + 0xff; + root_offset &= ~(uint32_t)0xff; + break; + default: + printk(KERN_WARNING "Unknown magic: %08x\n", header.checksum); + break; + } + + mtd_read(master, root_offset, sizeof(header), &len, (u8 *)&header); + if (header.checksum != SQUASHFS_MAGIC) { + root_offset += master->erasesize - 1; + root_offset &= ~(master->erasesize - 1); + } + + ar7_parts[2].name = "linux"; + ar7_parts[2].offset = pre_size; + ar7_parts[2].size = master->size - pre_size - post_size; + ar7_parts[2].mask_flags = 0; + + ar7_parts[3].name = "rootfs"; + ar7_parts[3].offset = root_offset; + ar7_parts[3].size = master->size - root_offset - post_size; + ar7_parts[3].mask_flags = 0; + + *pparts = ar7_parts; + return AR7_PARTS; +} + +static struct mtd_part_parser ar7_parser = { + .owner = THIS_MODULE, + .parse_fn = create_mtd_partitions, + .name = "ar7part", +}; + +static int __init ar7_parser_init(void) +{ + register_mtd_parser(&ar7_parser); + return 0; +} + +static void __exit ar7_parser_exit(void) +{ + deregister_mtd_parser(&ar7_parser); +} + +module_init(ar7_parser_init); +module_exit(ar7_parser_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR( "Felix Fietkau <nbd@openwrt.org>, " + "Eugene Konev <ejka@openwrt.org>"); +MODULE_DESCRIPTION("MTD partitioning for TI AR7"); diff --git a/drivers/mtd/bcm47xxpart.c b/drivers/mtd/bcm47xxpart.c new file mode 100644 index 00000000000..adfa74c1bc4 --- /dev/null +++ b/drivers/mtd/bcm47xxpart.c @@ -0,0 +1,282 @@ +/* + * BCM47XX MTD partitioning + * + * Copyright © 2012 Rafał Miłecki <zajec5@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> + +/* 10 parts were found on sflash on Netgear WNDR4500 */ +#define BCM47XXPART_MAX_PARTS 12 + +/* + * Amount of bytes we read when analyzing each block of flash memory. + * Set it big enough to allow detecting partition and reading important data. + */ +#define BCM47XXPART_BYTES_TO_READ 0x4e8 + +/* Magics */ +#define BOARD_DATA_MAGIC 0x5246504D /* MPFR */ +#define BOARD_DATA_MAGIC2 0xBD0D0BBD +#define CFE_MAGIC 0x43464531 /* 1EFC */ +#define FACTORY_MAGIC 0x59544346 /* FCTY */ +#define NVRAM_HEADER 0x48534C46 /* FLSH */ +#define POT_MAGIC1 0x54544f50 /* POTT */ +#define POT_MAGIC2 0x504f /* OP */ +#define ML_MAGIC1 0x39685a42 +#define ML_MAGIC2 0x26594131 +#define TRX_MAGIC 0x30524448 +#define SQSH_MAGIC 0x71736873 /* shsq */ + +struct trx_header { + uint32_t magic; + uint32_t length; + uint32_t crc32; + uint16_t flags; + uint16_t version; + uint32_t offset[3]; +} __packed; + +static void bcm47xxpart_add_part(struct mtd_partition *part, char *name, + u64 offset, uint32_t mask_flags) +{ + part->name = name; + part->offset = offset; + part->mask_flags = mask_flags; +} + +static int bcm47xxpart_parse(struct mtd_info *master, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *parts; + uint8_t i, curr_part = 0; + uint32_t *buf; + size_t bytes_read; + uint32_t offset; + uint32_t blocksize = master->erasesize; + struct trx_header *trx; + int trx_part = -1; + int last_trx_part = -1; + int possible_nvram_sizes[] = { 0x8000, 0xF000, 0x10000, }; + + if (blocksize <= 0x10000) + blocksize = 0x10000; + + /* Alloc */ + parts = kzalloc(sizeof(struct mtd_partition) * BCM47XXPART_MAX_PARTS, + GFP_KERNEL); + if (!parts) + return -ENOMEM; + + buf = kzalloc(BCM47XXPART_BYTES_TO_READ, GFP_KERNEL); + if (!buf) { + kfree(parts); + return -ENOMEM; + } + + /* Parse block by block looking for magics */ + for (offset = 0; offset <= master->size - blocksize; + offset += blocksize) { + /* Nothing more in higher memory */ + if (offset >= 0x2000000) + break; + + if (curr_part >= BCM47XXPART_MAX_PARTS) { + pr_warn("Reached maximum number of partitions, scanning stopped!\n"); + break; + } + + /* Read beginning of the block */ + if (mtd_read(master, offset, BCM47XXPART_BYTES_TO_READ, + &bytes_read, (uint8_t *)buf) < 0) { + pr_err("mtd_read error while parsing (offset: 0x%X)!\n", + offset); + continue; + } + + /* Magic or small NVRAM at 0x400 */ + if ((buf[0x4e0 / 4] == CFE_MAGIC && buf[0x4e4 / 4] == CFE_MAGIC) || + (buf[0x400 / 4] == NVRAM_HEADER)) { + bcm47xxpart_add_part(&parts[curr_part++], "boot", + offset, MTD_WRITEABLE); + continue; + } + + /* + * board_data starts with board_id which differs across boards, + * but we can use 'MPFR' (hopefully) magic at 0x100 + */ + if (buf[0x100 / 4] == BOARD_DATA_MAGIC) { + bcm47xxpart_add_part(&parts[curr_part++], "board_data", + offset, MTD_WRITEABLE); + continue; + } + + /* Found on Huawei E970 */ + if (buf[0x000 / 4] == FACTORY_MAGIC) { + bcm47xxpart_add_part(&parts[curr_part++], "factory", + offset, MTD_WRITEABLE); + continue; + } + + /* POT(TOP) */ + if (buf[0x000 / 4] == POT_MAGIC1 && + (buf[0x004 / 4] & 0xFFFF) == POT_MAGIC2) { + bcm47xxpart_add_part(&parts[curr_part++], "POT", offset, + MTD_WRITEABLE); + continue; + } + + /* ML */ + if (buf[0x010 / 4] == ML_MAGIC1 && + buf[0x014 / 4] == ML_MAGIC2) { + bcm47xxpart_add_part(&parts[curr_part++], "ML", offset, + MTD_WRITEABLE); + continue; + } + + /* TRX */ + if (buf[0x000 / 4] == TRX_MAGIC) { + if (BCM47XXPART_MAX_PARTS - curr_part < 4) { + pr_warn("Not enough partitions left to register trx, scanning stopped!\n"); + break; + } + + trx = (struct trx_header *)buf; + + trx_part = curr_part; + bcm47xxpart_add_part(&parts[curr_part++], "firmware", + offset, 0); + + i = 0; + /* We have LZMA loader if offset[2] points to sth */ + if (trx->offset[2]) { + bcm47xxpart_add_part(&parts[curr_part++], + "loader", + offset + trx->offset[i], + 0); + i++; + } + + bcm47xxpart_add_part(&parts[curr_part++], "linux", + offset + trx->offset[i], 0); + i++; + + /* + * Pure rootfs size is known and can be calculated as: + * trx->length - trx->offset[i]. We don't fill it as + * we want to have jffs2 (overlay) in the same mtd. + */ + bcm47xxpart_add_part(&parts[curr_part++], "rootfs", + offset + trx->offset[i], 0); + i++; + + last_trx_part = curr_part - 1; + + /* + * We have whole TRX scanned, skip to the next part. Use + * roundown (not roundup), as the loop will increase + * offset in next step. + */ + offset = rounddown(offset + trx->length, blocksize); + continue; + } + + /* Squashfs on devices not using TRX */ + if (buf[0x000 / 4] == SQSH_MAGIC) { + bcm47xxpart_add_part(&parts[curr_part++], "rootfs", + offset, 0); + continue; + } + + /* Read middle of the block */ + if (mtd_read(master, offset + 0x8000, 0x4, + &bytes_read, (uint8_t *)buf) < 0) { + pr_err("mtd_read error while parsing (offset: 0x%X)!\n", + offset); + continue; + } + + /* Some devices (ex. WNDR3700v3) don't have a standard 'MPFR' */ + if (buf[0x000 / 4] == BOARD_DATA_MAGIC2) { + bcm47xxpart_add_part(&parts[curr_part++], "board_data", + offset, MTD_WRITEABLE); + continue; + } + } + + /* Look for NVRAM at the end of the last block. */ + for (i = 0; i < ARRAY_SIZE(possible_nvram_sizes); i++) { + if (curr_part >= BCM47XXPART_MAX_PARTS) { + pr_warn("Reached maximum number of partitions, scanning stopped!\n"); + break; + } + + offset = master->size - possible_nvram_sizes[i]; + if (mtd_read(master, offset, 0x4, &bytes_read, + (uint8_t *)buf) < 0) { + pr_err("mtd_read error while reading at offset 0x%X!\n", + offset); + continue; + } + + /* Standard NVRAM */ + if (buf[0] == NVRAM_HEADER) { + bcm47xxpart_add_part(&parts[curr_part++], "nvram", + master->size - blocksize, 0); + break; + } + } + + kfree(buf); + + /* + * Assume that partitions end at the beginning of the one they are + * followed by. + */ + for (i = 0; i < curr_part; i++) { + u64 next_part_offset = (i < curr_part - 1) ? + parts[i + 1].offset : master->size; + + parts[i].size = next_part_offset - parts[i].offset; + if (i == last_trx_part && trx_part >= 0) + parts[trx_part].size = next_part_offset - + parts[trx_part].offset; + } + + *pparts = parts; + return curr_part; +}; + +static struct mtd_part_parser bcm47xxpart_mtd_parser = { + .owner = THIS_MODULE, + .parse_fn = bcm47xxpart_parse, + .name = "bcm47xxpart", +}; + +static int __init bcm47xxpart_init(void) +{ + register_mtd_parser(&bcm47xxpart_mtd_parser); + return 0; +} + +static void __exit bcm47xxpart_exit(void) +{ + deregister_mtd_parser(&bcm47xxpart_mtd_parser); +} + +module_init(bcm47xxpart_init); +module_exit(bcm47xxpart_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MTD partitioning for BCM47XX flash memories"); diff --git a/drivers/mtd/bcm63xxpart.c b/drivers/mtd/bcm63xxpart.c new file mode 100644 index 00000000000..b2443f7031c --- /dev/null +++ b/drivers/mtd/bcm63xxpart.c @@ -0,0 +1,241 @@ +/* + * BCM63XX CFE image tag parser + * + * Copyright © 2006-2008 Florian Fainelli <florian@openwrt.org> + * Mike Albon <malbon@openwrt.org> + * Copyright © 2009-2010 Daniel Dickinson <openwrt@cshore.neomailbox.net> + * Copyright © 2011-2013 Jonas Gorski <jonas.gorski@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/crc32.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> + +#include <asm/mach-bcm63xx/bcm63xx_nvram.h> +#include <asm/mach-bcm63xx/bcm963xx_tag.h> +#include <asm/mach-bcm63xx/board_bcm963xx.h> + +#define BCM63XX_EXTENDED_SIZE 0xBFC00000 /* Extended flash address */ + +#define BCM63XX_CFE_BLOCK_SIZE SZ_64K /* always at least 64KiB */ + +#define BCM63XX_CFE_MAGIC_OFFSET 0x4e0 + +static int bcm63xx_detect_cfe(struct mtd_info *master) +{ + char buf[9]; + int ret; + size_t retlen; + + ret = mtd_read(master, BCM963XX_CFE_VERSION_OFFSET, 5, &retlen, + (void *)buf); + buf[retlen] = 0; + + if (ret) + return ret; + + if (strncmp("cfe-v", buf, 5) == 0) + return 0; + + /* very old CFE's do not have the cfe-v string, so check for magic */ + ret = mtd_read(master, BCM63XX_CFE_MAGIC_OFFSET, 8, &retlen, + (void *)buf); + buf[retlen] = 0; + + return strncmp("CFE1CFE1", buf, 8); +} + +static int bcm63xx_parse_cfe_partitions(struct mtd_info *master, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + /* CFE, NVRAM and global Linux are always present */ + int nrparts = 3, curpart = 0; + struct bcm_tag *buf; + struct mtd_partition *parts; + int ret; + size_t retlen; + unsigned int rootfsaddr, kerneladdr, spareaddr; + unsigned int rootfslen, kernellen, sparelen, totallen; + unsigned int cfelen, nvramlen; + unsigned int cfe_erasesize; + int i; + u32 computed_crc; + bool rootfs_first = false; + + if (bcm63xx_detect_cfe(master)) + return -EINVAL; + + cfe_erasesize = max_t(uint32_t, master->erasesize, + BCM63XX_CFE_BLOCK_SIZE); + + cfelen = cfe_erasesize; + nvramlen = bcm63xx_nvram_get_psi_size() * SZ_1K; + nvramlen = roundup(nvramlen, cfe_erasesize); + + /* Allocate memory for buffer */ + buf = vmalloc(sizeof(struct bcm_tag)); + if (!buf) + return -ENOMEM; + + /* Get the tag */ + ret = mtd_read(master, cfelen, sizeof(struct bcm_tag), &retlen, + (void *)buf); + + if (retlen != sizeof(struct bcm_tag)) { + vfree(buf); + return -EIO; + } + + computed_crc = crc32_le(IMAGETAG_CRC_START, (u8 *)buf, + offsetof(struct bcm_tag, header_crc)); + if (computed_crc == buf->header_crc) { + char *boardid = &(buf->board_id[0]); + char *tagversion = &(buf->tag_version[0]); + + sscanf(buf->flash_image_start, "%u", &rootfsaddr); + sscanf(buf->kernel_address, "%u", &kerneladdr); + sscanf(buf->kernel_length, "%u", &kernellen); + sscanf(buf->total_length, "%u", &totallen); + + pr_info("CFE boot tag found with version %s and board type %s\n", + tagversion, boardid); + + kerneladdr = kerneladdr - BCM63XX_EXTENDED_SIZE; + rootfsaddr = rootfsaddr - BCM63XX_EXTENDED_SIZE; + spareaddr = roundup(totallen, master->erasesize) + cfelen; + + if (rootfsaddr < kerneladdr) { + /* default Broadcom layout */ + rootfslen = kerneladdr - rootfsaddr; + rootfs_first = true; + } else { + /* OpenWrt layout */ + rootfsaddr = kerneladdr + kernellen; + rootfslen = spareaddr - rootfsaddr; + } + } else { + pr_warn("CFE boot tag CRC invalid (expected %08x, actual %08x)\n", + buf->header_crc, computed_crc); + kernellen = 0; + rootfslen = 0; + rootfsaddr = 0; + spareaddr = cfelen; + } + sparelen = master->size - spareaddr - nvramlen; + + /* Determine number of partitions */ + if (rootfslen > 0) + nrparts++; + + if (kernellen > 0) + nrparts++; + + /* Ask kernel for more memory */ + parts = kzalloc(sizeof(*parts) * nrparts + 10 * nrparts, GFP_KERNEL); + if (!parts) { + vfree(buf); + return -ENOMEM; + } + + /* Start building partition list */ + parts[curpart].name = "CFE"; + parts[curpart].offset = 0; + parts[curpart].size = cfelen; + curpart++; + + if (kernellen > 0) { + int kernelpart = curpart; + + if (rootfslen > 0 && rootfs_first) + kernelpart++; + parts[kernelpart].name = "kernel"; + parts[kernelpart].offset = kerneladdr; + parts[kernelpart].size = kernellen; + curpart++; + } + + if (rootfslen > 0) { + int rootfspart = curpart; + + if (kernellen > 0 && rootfs_first) + rootfspart--; + parts[rootfspart].name = "rootfs"; + parts[rootfspart].offset = rootfsaddr; + parts[rootfspart].size = rootfslen; + if (sparelen > 0 && !rootfs_first) + parts[rootfspart].size += sparelen; + curpart++; + } + + parts[curpart].name = "nvram"; + parts[curpart].offset = master->size - nvramlen; + parts[curpart].size = nvramlen; + curpart++; + + /* Global partition "linux" to make easy firmware upgrade */ + parts[curpart].name = "linux"; + parts[curpart].offset = cfelen; + parts[curpart].size = master->size - cfelen - nvramlen; + + for (i = 0; i < nrparts; i++) + pr_info("Partition %d is %s offset %llx and length %llx\n", i, + parts[i].name, parts[i].offset, parts[i].size); + + pr_info("Spare partition is offset %x and length %x\n", spareaddr, + sparelen); + + *pparts = parts; + vfree(buf); + + return nrparts; +}; + +static struct mtd_part_parser bcm63xx_cfe_parser = { + .owner = THIS_MODULE, + .parse_fn = bcm63xx_parse_cfe_partitions, + .name = "bcm63xxpart", +}; + +static int __init bcm63xx_cfe_parser_init(void) +{ + register_mtd_parser(&bcm63xx_cfe_parser); + return 0; +} + +static void __exit bcm63xx_cfe_parser_exit(void) +{ + deregister_mtd_parser(&bcm63xx_cfe_parser); +} + +module_init(bcm63xx_cfe_parser_init); +module_exit(bcm63xx_cfe_parser_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Daniel Dickinson <openwrt@cshore.neomailbox.net>"); +MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); +MODULE_AUTHOR("Mike Albon <malbon@openwrt.org>"); +MODULE_AUTHOR("Jonas Gorski <jonas.gorski@gmail.com"); +MODULE_DESCRIPTION("MTD partitioning for BCM63XX CFE bootloaders"); diff --git a/drivers/mtd/chips/Kconfig b/drivers/mtd/chips/Kconfig index 6d8f30deb86..9f02c28c020 100644 --- a/drivers/mtd/chips/Kconfig +++ b/drivers/mtd/chips/Kconfig @@ -1,13 +1,10 @@ -# drivers/mtd/chips/Kconfig -# $Id: Kconfig,v 1.18 2005/11/07 11:14:22 gleixner Exp $ - menu "RAM/ROM/Flash chip drivers" depends on MTD!=n config MTD_CFI tristate "Detect flash chips by Common Flash Interface (CFI) probe" - depends on MTD select MTD_GEN_PROBE + select MTD_CFI_UTIL help The Common Flash Interface specification was developed by Intel, AMD and other flash manufactures that provides a universal method @@ -18,12 +15,11 @@ config MTD_CFI config MTD_JEDECPROBE tristate "Detect non-CFI AMD/JEDEC-compatible flash chips" - depends on MTD select MTD_GEN_PROBE help This option enables JEDEC-style probing of flash chips which are not compatible with the Common Flash Interface, but will use the common - CFI-targetted flash drivers for any chips which are identified which + CFI-targeted flash drivers for any chips which are identified which are in fact compatible in all but the probe method. This actually covers most AMD/Fujitsu-compatible chips and also non-CFI Intel chips. @@ -47,9 +43,6 @@ choice prompt "Flash cmd/query data swapping" depends on MTD_CFI_ADV_OPTIONS default MTD_CFI_NOSWAP - -config MTD_CFI_NOSWAP - bool "NO" ---help--- This option defines the way in which the CPU attempts to arrange data bits when writing the 'magic' commands to the chips. Saying @@ -59,12 +52,8 @@ config MTD_CFI_NOSWAP Specific arrangements are possible with the BIG_ENDIAN_BYTE and LITTLE_ENDIAN_BYTE, if the bytes are reversed. - If you have a LART, on which the data (and address) lines were - connected in a fashion which ensured that the nets were as short - as possible, resulting in a bit-shuffling which seems utterly - random to the untrained eye, you need the LART_ENDIAN_BYTE option. - - Yes, there really exists something sicker than PDP-endian :) +config MTD_CFI_NOSWAP + bool "NO" config MTD_CFI_BE_BYTE_SWAP bool "BIG_ENDIAN_BYTE" @@ -180,54 +169,51 @@ config MTD_OTP in the programming of OTP bits will waste them. config MTD_CFI_INTELEXT - tristate "Support for Intel/Sharp flash chips" + tristate "Support for CFI command set 0001 (Intel/Sharp chips)" depends on MTD_GEN_PROBE select MTD_CFI_UTIL help The Common Flash Interface defines a number of different command sets which a CFI-compliant chip may claim to implement. This code - provides support for one of those command sets, used on Intel - StrataFlash and other parts. + provides support for command set 0001, used on Intel StrataFlash + and other parts. config MTD_CFI_AMDSTD - tristate "Support for AMD/Fujitsu flash chips" + tristate "Support for CFI command set 0002 (AMD/Fujitsu/Spansion chips)" depends on MTD_GEN_PROBE select MTD_CFI_UTIL help The Common Flash Interface defines a number of different command sets which a CFI-compliant chip may claim to implement. This code - provides support for one of those command sets, used on chips - including the AMD Am29LV320. + provides support for command set 0002, used on chips including + the AMD Am29LV320. config MTD_CFI_STAA - tristate "Support for ST (Advanced Architecture) flash chips" + tristate "Support for CFI command set 0020 (ST (Advanced Architecture) chips)" depends on MTD_GEN_PROBE select MTD_CFI_UTIL help The Common Flash Interface defines a number of different command sets which a CFI-compliant chip may claim to implement. This code - provides support for one of those command sets. + provides support for command set 0020. config MTD_CFI_UTIL tristate config MTD_RAM tristate "Support for RAM chips in bus mapping" - depends on MTD help This option enables basic support for RAM chips accessed through a bus mapping driver. config MTD_ROM tristate "Support for ROM chips in bus mapping" - depends on MTD help This option enables basic support for ROM chips accessed through a bus mapping driver. config MTD_ABSENT tristate "Support for absent chips in bus mapping" - depends on MTD help This option enables support for a dummy probing driver used to allocated placeholder MTD devices on systems that have socketed @@ -236,49 +222,9 @@ config MTD_ABSENT the system regardless of media presence. Device nodes created with this driver will return -ENODEV upon access. -config MTD_OBSOLETE_CHIPS - depends on MTD - bool "Older (theoretically obsoleted now) drivers for non-CFI chips" - help - This option does not enable any code directly, but will allow you to - select some other chip drivers which are now considered obsolete, - because the generic CONFIG_JEDECPROBE code above should now detect - the chips which are supported by these drivers, and allow the generic - CFI-compatible drivers to drive the chips. Say 'N' here unless you have - already tried the CONFIG_JEDECPROBE method and reported its failure - to the MTD mailing list at <linux-mtd@lists.infradead.org> - -config MTD_AMDSTD - tristate "AMD compatible flash chip support (non-CFI)" - depends on MTD && MTD_OBSOLETE_CHIPS && BROKEN - help - This option enables support for flash chips using AMD-compatible - commands, including some which are not CFI-compatible and hence - cannot be used with the CONFIG_MTD_CFI_AMDSTD option. - - It also works on AMD compatible chips that do conform to CFI. - -config MTD_SHARP - tristate "pre-CFI Sharp chip support" - depends on MTD && MTD_OBSOLETE_CHIPS - help - This option enables support for flash chips using Sharp-compatible - commands, including some which are not CFI-compatible and hence - cannot be used with the CONFIG_MTD_CFI_INTELxxx options. - -config MTD_JEDEC - tristate "JEDEC device support" - depends on MTD && MTD_OBSOLETE_CHIPS && BROKEN - help - Enable older older JEDEC flash interface devices for self - programming flash. It is commonly used in older AMD chips. It is - only called JEDEC because the JEDEC association - <http://www.jedec.org/> distributes the identification codes for the - chips. - config MTD_XIP bool "XIP aware MTD support" - depends on !SMP && (MTD_CFI_INTELEXT || MTD_CFI_AMDSTD) && EXPERIMENTAL && ARCH_MTD_XIP + depends on !SMP && (MTD_CFI_INTELEXT || MTD_CFI_AMDSTD) && ARCH_MTD_XIP default y if XIP_KERNEL help This allows MTD support to work with flash memory which is also @@ -286,4 +232,3 @@ config MTD_XIP then say N. endmenu - diff --git a/drivers/mtd/chips/Makefile b/drivers/mtd/chips/Makefile index 75bc1c2a0f4..36582412ccd 100644 --- a/drivers/mtd/chips/Makefile +++ b/drivers/mtd/chips/Makefile @@ -1,19 +1,15 @@ # # linux/drivers/chips/Makefile # -# $Id: Makefile.common,v 1.5 2005/11/07 11:14:22 gleixner Exp $ obj-$(CONFIG_MTD) += chipreg.o -obj-$(CONFIG_MTD_AMDSTD) += amd_flash.o obj-$(CONFIG_MTD_CFI) += cfi_probe.o obj-$(CONFIG_MTD_CFI_UTIL) += cfi_util.o obj-$(CONFIG_MTD_CFI_STAA) += cfi_cmdset_0020.o obj-$(CONFIG_MTD_CFI_AMDSTD) += cfi_cmdset_0002.o obj-$(CONFIG_MTD_CFI_INTELEXT) += cfi_cmdset_0001.o obj-$(CONFIG_MTD_GEN_PROBE) += gen_probe.o -obj-$(CONFIG_MTD_JEDEC) += jedec.o obj-$(CONFIG_MTD_JEDECPROBE) += jedec_probe.o obj-$(CONFIG_MTD_RAM) += map_ram.o obj-$(CONFIG_MTD_ROM) += map_rom.o -obj-$(CONFIG_MTD_SHARP) += sharp.o obj-$(CONFIG_MTD_ABSENT) += map_absent.o diff --git a/drivers/mtd/chips/amd_flash.c b/drivers/mtd/chips/amd_flash.c deleted file mode 100644 index 16eaca69fb5..00000000000 --- a/drivers/mtd/chips/amd_flash.c +++ /dev/null @@ -1,1397 +0,0 @@ -/* - * MTD map driver for AMD compatible flash chips (non-CFI) - * - * Author: Jonas Holmberg <jonas.holmberg@axis.com> - * - * $Id: amd_flash.c,v 1.28 2005/11/07 11:14:22 gleixner Exp $ - * - * Copyright (c) 2001 Axis Communications AB - * - * This file is under GPL. - * - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/sched.h> -#include <linux/errno.h> -#include <linux/slab.h> -#include <linux/delay.h> -#include <linux/interrupt.h> -#include <linux/init.h> -#include <linux/mtd/map.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/flashchip.h> - -/* There's no limit. It exists only to avoid realloc. */ -#define MAX_AMD_CHIPS 8 - -#define DEVICE_TYPE_X8 (8 / 8) -#define DEVICE_TYPE_X16 (16 / 8) -#define DEVICE_TYPE_X32 (32 / 8) - -/* Addresses */ -#define ADDR_MANUFACTURER 0x0000 -#define ADDR_DEVICE_ID 0x0001 -#define ADDR_SECTOR_LOCK 0x0002 -#define ADDR_HANDSHAKE 0x0003 -#define ADDR_UNLOCK_1 0x0555 -#define ADDR_UNLOCK_2 0x02AA - -/* Commands */ -#define CMD_UNLOCK_DATA_1 0x00AA -#define CMD_UNLOCK_DATA_2 0x0055 -#define CMD_MANUFACTURER_UNLOCK_DATA 0x0090 -#define CMD_UNLOCK_BYPASS_MODE 0x0020 -#define CMD_PROGRAM_UNLOCK_DATA 0x00A0 -#define CMD_RESET_DATA 0x00F0 -#define CMD_SECTOR_ERASE_UNLOCK_DATA 0x0080 -#define CMD_SECTOR_ERASE_UNLOCK_DATA_2 0x0030 - -#define CMD_UNLOCK_SECTOR 0x0060 - -/* Manufacturers */ -#define MANUFACTURER_AMD 0x0001 -#define MANUFACTURER_ATMEL 0x001F -#define MANUFACTURER_FUJITSU 0x0004 -#define MANUFACTURER_ST 0x0020 -#define MANUFACTURER_SST 0x00BF -#define MANUFACTURER_TOSHIBA 0x0098 - -/* AMD */ -#define AM29F800BB 0x2258 -#define AM29F800BT 0x22D6 -#define AM29LV800BB 0x225B -#define AM29LV800BT 0x22DA -#define AM29LV160DT 0x22C4 -#define AM29LV160DB 0x2249 -#define AM29BDS323D 0x22D1 - -/* Atmel */ -#define AT49xV16x 0x00C0 -#define AT49xV16xT 0x00C2 - -/* Fujitsu */ -#define MBM29LV160TE 0x22C4 -#define MBM29LV160BE 0x2249 -#define MBM29LV800BB 0x225B - -/* ST - www.st.com */ -#define M29W800T 0x00D7 -#define M29W160DT 0x22C4 -#define M29W160DB 0x2249 - -/* SST */ -#define SST39LF800 0x2781 -#define SST39LF160 0x2782 - -/* Toshiba */ -#define TC58FVT160 0x00C2 -#define TC58FVB160 0x0043 - -#define D6_MASK 0x40 - -struct amd_flash_private { - int device_type; - int interleave; - int numchips; - unsigned long chipshift; - struct flchip chips[0]; -}; - -struct amd_flash_info { - const __u16 mfr_id; - const __u16 dev_id; - const char *name; - const u_long size; - const int numeraseregions; - const struct mtd_erase_region_info regions[4]; -}; - - - -static int amd_flash_read(struct mtd_info *, loff_t, size_t, size_t *, - u_char *); -static int amd_flash_write(struct mtd_info *, loff_t, size_t, size_t *, - const u_char *); -static int amd_flash_erase(struct mtd_info *, struct erase_info *); -static void amd_flash_sync(struct mtd_info *); -static int amd_flash_suspend(struct mtd_info *); -static void amd_flash_resume(struct mtd_info *); -static void amd_flash_destroy(struct mtd_info *); -static struct mtd_info *amd_flash_probe(struct map_info *map); - - -static struct mtd_chip_driver amd_flash_chipdrv = { - .probe = amd_flash_probe, - .destroy = amd_flash_destroy, - .name = "amd_flash", - .module = THIS_MODULE -}; - -static inline __u32 wide_read(struct map_info *map, __u32 addr) -{ - if (map->buswidth == 1) { - return map_read8(map, addr); - } else if (map->buswidth == 2) { - return map_read16(map, addr); - } else if (map->buswidth == 4) { - return map_read32(map, addr); - } - - return 0; -} - -static inline void wide_write(struct map_info *map, __u32 val, __u32 addr) -{ - if (map->buswidth == 1) { - map_write8(map, val, addr); - } else if (map->buswidth == 2) { - map_write16(map, val, addr); - } else if (map->buswidth == 4) { - map_write32(map, val, addr); - } -} - -static inline __u32 make_cmd(struct map_info *map, __u32 cmd) -{ - const struct amd_flash_private *private = map->fldrv_priv; - if ((private->interleave == 2) && - (private->device_type == DEVICE_TYPE_X16)) { - cmd |= (cmd << 16); - } - - return cmd; -} - -static inline void send_unlock(struct map_info *map, unsigned long base) -{ - wide_write(map, (CMD_UNLOCK_DATA_1 << 16) | CMD_UNLOCK_DATA_1, - base + (map->buswidth * ADDR_UNLOCK_1)); - wide_write(map, (CMD_UNLOCK_DATA_2 << 16) | CMD_UNLOCK_DATA_2, - base + (map->buswidth * ADDR_UNLOCK_2)); -} - -static inline void send_cmd(struct map_info *map, unsigned long base, __u32 cmd) -{ - send_unlock(map, base); - wide_write(map, make_cmd(map, cmd), - base + (map->buswidth * ADDR_UNLOCK_1)); -} - -static inline void send_cmd_to_addr(struct map_info *map, unsigned long base, - __u32 cmd, unsigned long addr) -{ - send_unlock(map, base); - wide_write(map, make_cmd(map, cmd), addr); -} - -static inline int flash_is_busy(struct map_info *map, unsigned long addr, - int interleave) -{ - - if ((interleave == 2) && (map->buswidth == 4)) { - __u32 read1, read2; - - read1 = wide_read(map, addr); - read2 = wide_read(map, addr); - - return (((read1 >> 16) & D6_MASK) != - ((read2 >> 16) & D6_MASK)) || - (((read1 & 0xffff) & D6_MASK) != - ((read2 & 0xffff) & D6_MASK)); - } - - return ((wide_read(map, addr) & D6_MASK) != - (wide_read(map, addr) & D6_MASK)); -} - -static inline void unlock_sector(struct map_info *map, unsigned long sect_addr, - int unlock) -{ - /* Sector lock address. A6 = 1 for unlock, A6 = 0 for lock */ - int SLA = unlock ? - (sect_addr | (0x40 * map->buswidth)) : - (sect_addr & ~(0x40 * map->buswidth)) ; - - __u32 cmd = make_cmd(map, CMD_UNLOCK_SECTOR); - - wide_write(map, make_cmd(map, CMD_RESET_DATA), 0); - wide_write(map, cmd, SLA); /* 1st cycle: write cmd to any address */ - wide_write(map, cmd, SLA); /* 2nd cycle: write cmd to any address */ - wide_write(map, cmd, SLA); /* 3rd cycle: write cmd to SLA */ -} - -static inline int is_sector_locked(struct map_info *map, - unsigned long sect_addr) -{ - int status; - - wide_write(map, CMD_RESET_DATA, 0); - send_cmd(map, sect_addr, CMD_MANUFACTURER_UNLOCK_DATA); - - /* status is 0x0000 for unlocked and 0x0001 for locked */ - status = wide_read(map, sect_addr + (map->buswidth * ADDR_SECTOR_LOCK)); - wide_write(map, CMD_RESET_DATA, 0); - return status; -} - -static int amd_flash_do_unlock(struct mtd_info *mtd, loff_t ofs, size_t len, - int is_unlock) -{ - struct map_info *map; - struct mtd_erase_region_info *merip; - int eraseoffset, erasesize, eraseblocks; - int i; - int retval = 0; - int lock_status; - - map = mtd->priv; - - /* Pass the whole chip through sector by sector and check for each - sector if the sector and the given interval overlap */ - for(i = 0; i < mtd->numeraseregions; i++) { - merip = &mtd->eraseregions[i]; - - eraseoffset = merip->offset; - erasesize = merip->erasesize; - eraseblocks = merip->numblocks; - - if (ofs > eraseoffset + erasesize) - continue; - - while (eraseblocks > 0) { - if (ofs < eraseoffset + erasesize && ofs + len > eraseoffset) { - unlock_sector(map, eraseoffset, is_unlock); - - lock_status = is_sector_locked(map, eraseoffset); - - if (is_unlock && lock_status) { - printk("Cannot unlock sector at address %x length %xx\n", - eraseoffset, merip->erasesize); - retval = -1; - } else if (!is_unlock && !lock_status) { - printk("Cannot lock sector at address %x length %x\n", - eraseoffset, merip->erasesize); - retval = -1; - } - } - eraseoffset += erasesize; - eraseblocks --; - } - } - return retval; -} - -static int amd_flash_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) -{ - return amd_flash_do_unlock(mtd, ofs, len, 1); -} - -static int amd_flash_lock(struct mtd_info *mtd, loff_t ofs, size_t len) -{ - return amd_flash_do_unlock(mtd, ofs, len, 0); -} - - -/* - * Reads JEDEC manufacturer ID and device ID and returns the index of the first - * matching table entry (-1 if not found or alias for already found chip). - */ -static int probe_new_chip(struct mtd_info *mtd, __u32 base, - struct flchip *chips, - struct amd_flash_private *private, - const struct amd_flash_info *table, int table_size) -{ - __u32 mfr_id; - __u32 dev_id; - struct map_info *map = mtd->priv; - struct amd_flash_private temp; - int i; - - temp.device_type = DEVICE_TYPE_X16; // Assume X16 (FIXME) - temp.interleave = 2; - map->fldrv_priv = &temp; - - /* Enter autoselect mode. */ - send_cmd(map, base, CMD_RESET_DATA); - send_cmd(map, base, CMD_MANUFACTURER_UNLOCK_DATA); - - mfr_id = wide_read(map, base + (map->buswidth * ADDR_MANUFACTURER)); - dev_id = wide_read(map, base + (map->buswidth * ADDR_DEVICE_ID)); - - if ((map->buswidth == 4) && ((mfr_id >> 16) == (mfr_id & 0xffff)) && - ((dev_id >> 16) == (dev_id & 0xffff))) { - mfr_id &= 0xffff; - dev_id &= 0xffff; - } else { - temp.interleave = 1; - } - - for (i = 0; i < table_size; i++) { - if ((mfr_id == table[i].mfr_id) && - (dev_id == table[i].dev_id)) { - if (chips) { - int j; - - /* Is this an alias for an already found chip? - * In that case that chip should be in - * autoselect mode now. - */ - for (j = 0; j < private->numchips; j++) { - __u32 mfr_id_other; - __u32 dev_id_other; - - mfr_id_other = - wide_read(map, chips[j].start + - (map->buswidth * - ADDR_MANUFACTURER - )); - dev_id_other = - wide_read(map, chips[j].start + - (map->buswidth * - ADDR_DEVICE_ID)); - if (temp.interleave == 2) { - mfr_id_other &= 0xffff; - dev_id_other &= 0xffff; - } - if ((mfr_id_other == mfr_id) && - (dev_id_other == dev_id)) { - - /* Exit autoselect mode. */ - send_cmd(map, base, - CMD_RESET_DATA); - - return -1; - } - } - - if (private->numchips == MAX_AMD_CHIPS) { - printk(KERN_WARNING - "%s: Too many flash chips " - "detected. Increase " - "MAX_AMD_CHIPS from %d.\n", - map->name, MAX_AMD_CHIPS); - - return -1; - } - - chips[private->numchips].start = base; - chips[private->numchips].state = FL_READY; - chips[private->numchips].mutex = - &chips[private->numchips]._spinlock; - private->numchips++; - } - - printk("%s: Found %d x %ldMiB %s at 0x%x\n", map->name, - temp.interleave, (table[i].size)/(1024*1024), - table[i].name, base); - - mtd->size += table[i].size * temp.interleave; - mtd->numeraseregions += table[i].numeraseregions; - - break; - } - } - - /* Exit autoselect mode. */ - send_cmd(map, base, CMD_RESET_DATA); - - if (i == table_size) { - printk(KERN_DEBUG "%s: unknown flash device at 0x%x, " - "mfr id 0x%x, dev id 0x%x\n", map->name, - base, mfr_id, dev_id); - map->fldrv_priv = NULL; - - return -1; - } - - private->device_type = temp.device_type; - private->interleave = temp.interleave; - - return i; -} - - - -static struct mtd_info *amd_flash_probe(struct map_info *map) -{ - static const struct amd_flash_info table[] = { - { - .mfr_id = MANUFACTURER_AMD, - .dev_id = AM29LV160DT, - .name = "AMD AM29LV160DT", - .size = 0x00200000, - .numeraseregions = 4, - .regions = { - { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 31 }, - { .offset = 0x1F0000, .erasesize = 0x08000, .numblocks = 1 }, - { .offset = 0x1F8000, .erasesize = 0x02000, .numblocks = 2 }, - { .offset = 0x1FC000, .erasesize = 0x04000, .numblocks = 1 } - } - }, { - .mfr_id = MANUFACTURER_AMD, - .dev_id = AM29LV160DB, - .name = "AMD AM29LV160DB", - .size = 0x00200000, - .numeraseregions = 4, - .regions = { - { .offset = 0x000000, .erasesize = 0x04000, .numblocks = 1 }, - { .offset = 0x004000, .erasesize = 0x02000, .numblocks = 2 }, - { .offset = 0x008000, .erasesize = 0x08000, .numblocks = 1 }, - { .offset = 0x010000, .erasesize = 0x10000, .numblocks = 31 } - } - }, { - .mfr_id = MANUFACTURER_TOSHIBA, - .dev_id = TC58FVT160, - .name = "Toshiba TC58FVT160", - .size = 0x00200000, - .numeraseregions = 4, - .regions = { - { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 31 }, - { .offset = 0x1F0000, .erasesize = 0x08000, .numblocks = 1 }, - { .offset = 0x1F8000, .erasesize = 0x02000, .numblocks = 2 }, - { .offset = 0x1FC000, .erasesize = 0x04000, .numblocks = 1 } - } - }, { - .mfr_id = MANUFACTURER_FUJITSU, - .dev_id = MBM29LV160TE, - .name = "Fujitsu MBM29LV160TE", - .size = 0x00200000, - .numeraseregions = 4, - .regions = { - { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 31 }, - { .offset = 0x1F0000, .erasesize = 0x08000, .numblocks = 1 }, - { .offset = 0x1F8000, .erasesize = 0x02000, .numblocks = 2 }, - { .offset = 0x1FC000, .erasesize = 0x04000, .numblocks = 1 } - } - }, { - .mfr_id = MANUFACTURER_TOSHIBA, - .dev_id = TC58FVB160, - .name = "Toshiba TC58FVB160", - .size = 0x00200000, - .numeraseregions = 4, - .regions = { - { .offset = 0x000000, .erasesize = 0x04000, .numblocks = 1 }, - { .offset = 0x004000, .erasesize = 0x02000, .numblocks = 2 }, - { .offset = 0x008000, .erasesize = 0x08000, .numblocks = 1 }, - { .offset = 0x010000, .erasesize = 0x10000, .numblocks = 31 } - } - }, { - .mfr_id = MANUFACTURER_FUJITSU, - .dev_id = MBM29LV160BE, - .name = "Fujitsu MBM29LV160BE", - .size = 0x00200000, - .numeraseregions = 4, - .regions = { - { .offset = 0x000000, .erasesize = 0x04000, .numblocks = 1 }, - { .offset = 0x004000, .erasesize = 0x02000, .numblocks = 2 }, - { .offset = 0x008000, .erasesize = 0x08000, .numblocks = 1 }, - { .offset = 0x010000, .erasesize = 0x10000, .numblocks = 31 } - } - }, { - .mfr_id = MANUFACTURER_AMD, - .dev_id = AM29LV800BB, - .name = "AMD AM29LV800BB", - .size = 0x00100000, - .numeraseregions = 4, - .regions = { - { .offset = 0x000000, .erasesize = 0x04000, .numblocks = 1 }, - { .offset = 0x004000, .erasesize = 0x02000, .numblocks = 2 }, - { .offset = 0x008000, .erasesize = 0x08000, .numblocks = 1 }, - { .offset = 0x010000, .erasesize = 0x10000, .numblocks = 15 } - } - }, { - .mfr_id = MANUFACTURER_AMD, - .dev_id = AM29F800BB, - .name = "AMD AM29F800BB", - .size = 0x00100000, - .numeraseregions = 4, - .regions = { - { .offset = 0x000000, .erasesize = 0x04000, .numblocks = 1 }, - { .offset = 0x004000, .erasesize = 0x02000, .numblocks = 2 }, - { .offset = 0x008000, .erasesize = 0x08000, .numblocks = 1 }, - { .offset = 0x010000, .erasesize = 0x10000, .numblocks = 15 } - } - }, { - .mfr_id = MANUFACTURER_AMD, - .dev_id = AM29LV800BT, - .name = "AMD AM29LV800BT", - .size = 0x00100000, - .numeraseregions = 4, - .regions = { - { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 15 }, - { .offset = 0x0F0000, .erasesize = 0x08000, .numblocks = 1 }, - { .offset = 0x0F8000, .erasesize = 0x02000, .numblocks = 2 }, - { .offset = 0x0FC000, .erasesize = 0x04000, .numblocks = 1 } - } - }, { - .mfr_id = MANUFACTURER_AMD, - .dev_id = AM29F800BT, - .name = "AMD AM29F800BT", - .size = 0x00100000, - .numeraseregions = 4, - .regions = { - { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 15 }, - { .offset = 0x0F0000, .erasesize = 0x08000, .numblocks = 1 }, - { .offset = 0x0F8000, .erasesize = 0x02000, .numblocks = 2 }, - { .offset = 0x0FC000, .erasesize = 0x04000, .numblocks = 1 } - } - }, { - .mfr_id = MANUFACTURER_AMD, - .dev_id = AM29LV800BB, - .name = "AMD AM29LV800BB", - .size = 0x00100000, - .numeraseregions = 4, - .regions = { - { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 15 }, - { .offset = 0x0F0000, .erasesize = 0x08000, .numblocks = 1 }, - { .offset = 0x0F8000, .erasesize = 0x02000, .numblocks = 2 }, - { .offset = 0x0FC000, .erasesize = 0x04000, .numblocks = 1 } - } - }, { - .mfr_id = MANUFACTURER_FUJITSU, - .dev_id = MBM29LV800BB, - .name = "Fujitsu MBM29LV800BB", - .size = 0x00100000, - .numeraseregions = 4, - .regions = { - { .offset = 0x000000, .erasesize = 0x04000, .numblocks = 1 }, - { .offset = 0x004000, .erasesize = 0x02000, .numblocks = 2 }, - { .offset = 0x008000, .erasesize = 0x08000, .numblocks = 1 }, - { .offset = 0x010000, .erasesize = 0x10000, .numblocks = 15 } - } - }, { - .mfr_id = MANUFACTURER_ST, - .dev_id = M29W800T, - .name = "ST M29W800T", - .size = 0x00100000, - .numeraseregions = 4, - .regions = { - { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 15 }, - { .offset = 0x0F0000, .erasesize = 0x08000, .numblocks = 1 }, - { .offset = 0x0F8000, .erasesize = 0x02000, .numblocks = 2 }, - { .offset = 0x0FC000, .erasesize = 0x04000, .numblocks = 1 } - } - }, { - .mfr_id = MANUFACTURER_ST, - .dev_id = M29W160DT, - .name = "ST M29W160DT", - .size = 0x00200000, - .numeraseregions = 4, - .regions = { - { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 31 }, - { .offset = 0x1F0000, .erasesize = 0x08000, .numblocks = 1 }, - { .offset = 0x1F8000, .erasesize = 0x02000, .numblocks = 2 }, - { .offset = 0x1FC000, .erasesize = 0x04000, .numblocks = 1 } - } - }, { - .mfr_id = MANUFACTURER_ST, - .dev_id = M29W160DB, - .name = "ST M29W160DB", - .size = 0x00200000, - .numeraseregions = 4, - .regions = { - { .offset = 0x000000, .erasesize = 0x04000, .numblocks = 1 }, - { .offset = 0x004000, .erasesize = 0x02000, .numblocks = 2 }, - { .offset = 0x008000, .erasesize = 0x08000, .numblocks = 1 }, - { .offset = 0x010000, .erasesize = 0x10000, .numblocks = 31 } - } - }, { - .mfr_id = MANUFACTURER_AMD, - .dev_id = AM29BDS323D, - .name = "AMD AM29BDS323D", - .size = 0x00400000, - .numeraseregions = 3, - .regions = { - { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 48 }, - { .offset = 0x300000, .erasesize = 0x10000, .numblocks = 15 }, - { .offset = 0x3f0000, .erasesize = 0x02000, .numblocks = 8 }, - } - }, { - .mfr_id = MANUFACTURER_ATMEL, - .dev_id = AT49xV16x, - .name = "Atmel AT49xV16x", - .size = 0x00200000, - .numeraseregions = 2, - .regions = { - { .offset = 0x000000, .erasesize = 0x02000, .numblocks = 8 }, - { .offset = 0x010000, .erasesize = 0x10000, .numblocks = 31 } - } - }, { - .mfr_id = MANUFACTURER_ATMEL, - .dev_id = AT49xV16xT, - .name = "Atmel AT49xV16xT", - .size = 0x00200000, - .numeraseregions = 2, - .regions = { - { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 31 }, - { .offset = 0x1F0000, .erasesize = 0x02000, .numblocks = 8 } - } - } - }; - - struct mtd_info *mtd; - struct flchip chips[MAX_AMD_CHIPS]; - int table_pos[MAX_AMD_CHIPS]; - struct amd_flash_private temp; - struct amd_flash_private *private; - u_long size; - unsigned long base; - int i; - int reg_idx; - int offset; - - mtd = (struct mtd_info*)kmalloc(sizeof(*mtd), GFP_KERNEL); - if (!mtd) { - printk(KERN_WARNING - "%s: kmalloc failed for info structure\n", map->name); - return NULL; - } - memset(mtd, 0, sizeof(*mtd)); - mtd->priv = map; - - memset(&temp, 0, sizeof(temp)); - - printk("%s: Probing for AMD compatible flash...\n", map->name); - - if ((table_pos[0] = probe_new_chip(mtd, 0, NULL, &temp, table, - ARRAY_SIZE(table))) - == -1) { - printk(KERN_WARNING - "%s: Found no AMD compatible device at location zero\n", - map->name); - kfree(mtd); - - return NULL; - } - - chips[0].start = 0; - chips[0].state = FL_READY; - chips[0].mutex = &chips[0]._spinlock; - temp.numchips = 1; - for (size = mtd->size; size > 1; size >>= 1) { - temp.chipshift++; - } - switch (temp.interleave) { - case 2: - temp.chipshift += 1; - break; - case 4: - temp.chipshift += 2; - break; - } - - /* Find out if there are any more chips in the map. */ - for (base = (1 << temp.chipshift); - base < map->size; - base += (1 << temp.chipshift)) { - int numchips = temp.numchips; - table_pos[numchips] = probe_new_chip(mtd, base, chips, - &temp, table, ARRAY_SIZE(table)); - } - - mtd->eraseregions = kmalloc(sizeof(struct mtd_erase_region_info) * - mtd->numeraseregions, GFP_KERNEL); - if (!mtd->eraseregions) { - printk(KERN_WARNING "%s: Failed to allocate " - "memory for MTD erase region info\n", map->name); - kfree(mtd); - map->fldrv_priv = NULL; - return NULL; - } - - reg_idx = 0; - offset = 0; - for (i = 0; i < temp.numchips; i++) { - int dev_size; - int j; - - dev_size = 0; - for (j = 0; j < table[table_pos[i]].numeraseregions; j++) { - mtd->eraseregions[reg_idx].offset = offset + - (table[table_pos[i]].regions[j].offset * - temp.interleave); - mtd->eraseregions[reg_idx].erasesize = - table[table_pos[i]].regions[j].erasesize * - temp.interleave; - mtd->eraseregions[reg_idx].numblocks = - table[table_pos[i]].regions[j].numblocks; - if (mtd->erasesize < - mtd->eraseregions[reg_idx].erasesize) { - mtd->erasesize = - mtd->eraseregions[reg_idx].erasesize; - } - dev_size += mtd->eraseregions[reg_idx].erasesize * - mtd->eraseregions[reg_idx].numblocks; - reg_idx++; - } - offset += dev_size; - } - mtd->type = MTD_NORFLASH; - mtd->writesize = 1; - mtd->flags = MTD_CAP_NORFLASH; - mtd->name = map->name; - mtd->erase = amd_flash_erase; - mtd->read = amd_flash_read; - mtd->write = amd_flash_write; - mtd->sync = amd_flash_sync; - mtd->suspend = amd_flash_suspend; - mtd->resume = amd_flash_resume; - mtd->lock = amd_flash_lock; - mtd->unlock = amd_flash_unlock; - - private = kmalloc(sizeof(*private) + (sizeof(struct flchip) * - temp.numchips), GFP_KERNEL); - if (!private) { - printk(KERN_WARNING - "%s: kmalloc failed for private structure\n", map->name); - kfree(mtd); - map->fldrv_priv = NULL; - return NULL; - } - memcpy(private, &temp, sizeof(temp)); - memcpy(private->chips, chips, - sizeof(struct flchip) * private->numchips); - for (i = 0; i < private->numchips; i++) { - init_waitqueue_head(&private->chips[i].wq); - spin_lock_init(&private->chips[i]._spinlock); - } - - map->fldrv_priv = private; - - map->fldrv = &amd_flash_chipdrv; - - __module_get(THIS_MODULE); - return mtd; -} - - - -static inline int read_one_chip(struct map_info *map, struct flchip *chip, - loff_t adr, size_t len, u_char *buf) -{ - DECLARE_WAITQUEUE(wait, current); - unsigned long timeo = jiffies + HZ; - -retry: - spin_lock_bh(chip->mutex); - - if (chip->state != FL_READY){ - printk(KERN_INFO "%s: waiting for chip to read, state = %d\n", - map->name, chip->state); - set_current_state(TASK_UNINTERRUPTIBLE); - add_wait_queue(&chip->wq, &wait); - - spin_unlock_bh(chip->mutex); - - schedule(); - remove_wait_queue(&chip->wq, &wait); - - if(signal_pending(current)) { - return -EINTR; - } - - timeo = jiffies + HZ; - - goto retry; - } - - adr += chip->start; - - chip->state = FL_READY; - - map_copy_from(map, buf, adr, len); - - wake_up(&chip->wq); - spin_unlock_bh(chip->mutex); - - return 0; -} - - - -static int amd_flash_read(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf) -{ - struct map_info *map = mtd->priv; - struct amd_flash_private *private = map->fldrv_priv; - unsigned long ofs; - int chipnum; - int ret = 0; - - if ((from + len) > mtd->size) { - printk(KERN_WARNING "%s: read request past end of device " - "(0x%lx)\n", map->name, (unsigned long)from + len); - - return -EINVAL; - } - - /* Offset within the first chip that the first read should start. */ - chipnum = (from >> private->chipshift); - ofs = from - (chipnum << private->chipshift); - - *retlen = 0; - - while (len) { - unsigned long this_len; - - if (chipnum >= private->numchips) { - break; - } - - if ((len + ofs - 1) >> private->chipshift) { - this_len = (1 << private->chipshift) - ofs; - } else { - this_len = len; - } - - ret = read_one_chip(map, &private->chips[chipnum], ofs, - this_len, buf); - if (ret) { - break; - } - - *retlen += this_len; - len -= this_len; - buf += this_len; - - ofs = 0; - chipnum++; - } - - return ret; -} - - - -static int write_one_word(struct map_info *map, struct flchip *chip, - unsigned long adr, __u32 datum) -{ - unsigned long timeo = jiffies + HZ; - struct amd_flash_private *private = map->fldrv_priv; - DECLARE_WAITQUEUE(wait, current); - int ret = 0; - int times_left; - -retry: - spin_lock_bh(chip->mutex); - - if (chip->state != FL_READY){ - printk("%s: waiting for chip to write, state = %d\n", - map->name, chip->state); - set_current_state(TASK_UNINTERRUPTIBLE); - add_wait_queue(&chip->wq, &wait); - - spin_unlock_bh(chip->mutex); - - schedule(); - remove_wait_queue(&chip->wq, &wait); - printk(KERN_INFO "%s: woke up to write\n", map->name); - if(signal_pending(current)) - return -EINTR; - - timeo = jiffies + HZ; - - goto retry; - } - - chip->state = FL_WRITING; - - adr += chip->start; - ENABLE_VPP(map); - send_cmd(map, chip->start, CMD_PROGRAM_UNLOCK_DATA); - wide_write(map, datum, adr); - - times_left = 500000; - while (times_left-- && flash_is_busy(map, adr, private->interleave)) { - if (need_resched()) { - spin_unlock_bh(chip->mutex); - schedule(); - spin_lock_bh(chip->mutex); - } - } - - if (!times_left) { - printk(KERN_WARNING "%s: write to 0x%lx timed out!\n", - map->name, adr); - ret = -EIO; - } else { - __u32 verify; - if ((verify = wide_read(map, adr)) != datum) { - printk(KERN_WARNING "%s: write to 0x%lx failed. " - "datum = %x, verify = %x\n", - map->name, adr, datum, verify); - ret = -EIO; - } - } - - DISABLE_VPP(map); - chip->state = FL_READY; - wake_up(&chip->wq); - spin_unlock_bh(chip->mutex); - - return ret; -} - - - -static int amd_flash_write(struct mtd_info *mtd, loff_t to , size_t len, - size_t *retlen, const u_char *buf) -{ - struct map_info *map = mtd->priv; - struct amd_flash_private *private = map->fldrv_priv; - int ret = 0; - int chipnum; - unsigned long ofs; - unsigned long chipstart; - - *retlen = 0; - if (!len) { - return 0; - } - - chipnum = to >> private->chipshift; - ofs = to - (chipnum << private->chipshift); - chipstart = private->chips[chipnum].start; - - /* If it's not bus-aligned, do the first byte write. */ - if (ofs & (map->buswidth - 1)) { - unsigned long bus_ofs = ofs & ~(map->buswidth - 1); - int i = ofs - bus_ofs; - int n = 0; - u_char tmp_buf[4]; - __u32 datum; - - map_copy_from(map, tmp_buf, - bus_ofs + private->chips[chipnum].start, - map->buswidth); - while (len && i < map->buswidth) - tmp_buf[i++] = buf[n++], len--; - - if (map->buswidth == 2) { - datum = *(__u16*)tmp_buf; - } else if (map->buswidth == 4) { - datum = *(__u32*)tmp_buf; - } else { - return -EINVAL; /* should never happen, but be safe */ - } - - ret = write_one_word(map, &private->chips[chipnum], bus_ofs, - datum); - if (ret) { - return ret; - } - - ofs += n; - buf += n; - (*retlen) += n; - - if (ofs >> private->chipshift) { - chipnum++; - ofs = 0; - if (chipnum == private->numchips) { - return 0; - } - } - } - - /* We are now aligned, write as much as possible. */ - while(len >= map->buswidth) { - __u32 datum; - - if (map->buswidth == 1) { - datum = *(__u8*)buf; - } else if (map->buswidth == 2) { - datum = *(__u16*)buf; - } else if (map->buswidth == 4) { - datum = *(__u32*)buf; - } else { - return -EINVAL; - } - - ret = write_one_word(map, &private->chips[chipnum], ofs, datum); - - if (ret) { - return ret; - } - - ofs += map->buswidth; - buf += map->buswidth; - (*retlen) += map->buswidth; - len -= map->buswidth; - - if (ofs >> private->chipshift) { - chipnum++; - ofs = 0; - if (chipnum == private->numchips) { - return 0; - } - chipstart = private->chips[chipnum].start; - } - } - - if (len & (map->buswidth - 1)) { - int i = 0, n = 0; - u_char tmp_buf[2]; - __u32 datum; - - map_copy_from(map, tmp_buf, - ofs + private->chips[chipnum].start, - map->buswidth); - while (len--) { - tmp_buf[i++] = buf[n++]; - } - - if (map->buswidth == 2) { - datum = *(__u16*)tmp_buf; - } else if (map->buswidth == 4) { - datum = *(__u32*)tmp_buf; - } else { - return -EINVAL; /* should never happen, but be safe */ - } - - ret = write_one_word(map, &private->chips[chipnum], ofs, datum); - - if (ret) { - return ret; - } - - (*retlen) += n; - } - - return 0; -} - - - -static inline int erase_one_block(struct map_info *map, struct flchip *chip, - unsigned long adr, u_long size) -{ - unsigned long timeo = jiffies + HZ; - struct amd_flash_private *private = map->fldrv_priv; - DECLARE_WAITQUEUE(wait, current); - -retry: - spin_lock_bh(chip->mutex); - - if (chip->state != FL_READY){ - set_current_state(TASK_UNINTERRUPTIBLE); - add_wait_queue(&chip->wq, &wait); - - spin_unlock_bh(chip->mutex); - - schedule(); - remove_wait_queue(&chip->wq, &wait); - - if (signal_pending(current)) { - return -EINTR; - } - - timeo = jiffies + HZ; - - goto retry; - } - - chip->state = FL_ERASING; - - adr += chip->start; - ENABLE_VPP(map); - send_cmd(map, chip->start, CMD_SECTOR_ERASE_UNLOCK_DATA); - send_cmd_to_addr(map, chip->start, CMD_SECTOR_ERASE_UNLOCK_DATA_2, adr); - - timeo = jiffies + (HZ * 20); - - spin_unlock_bh(chip->mutex); - msleep(1000); - spin_lock_bh(chip->mutex); - - while (flash_is_busy(map, adr, private->interleave)) { - - if (chip->state != FL_ERASING) { - /* Someone's suspended the erase. Sleep */ - set_current_state(TASK_UNINTERRUPTIBLE); - add_wait_queue(&chip->wq, &wait); - - spin_unlock_bh(chip->mutex); - printk(KERN_INFO "%s: erase suspended. Sleeping\n", - map->name); - schedule(); - remove_wait_queue(&chip->wq, &wait); - - if (signal_pending(current)) { - return -EINTR; - } - - timeo = jiffies + (HZ*2); /* FIXME */ - spin_lock_bh(chip->mutex); - continue; - } - - /* OK Still waiting */ - if (time_after(jiffies, timeo)) { - chip->state = FL_READY; - spin_unlock_bh(chip->mutex); - printk(KERN_WARNING "%s: waiting for erase to complete " - "timed out.\n", map->name); - DISABLE_VPP(map); - - return -EIO; - } - - /* Latency issues. Drop the lock, wait a while and retry */ - spin_unlock_bh(chip->mutex); - - if (need_resched()) - schedule(); - else - udelay(1); - - spin_lock_bh(chip->mutex); - } - - /* Verify every single word */ - { - int address; - int error = 0; - __u8 verify; - - for (address = adr; address < (adr + size); address++) { - if ((verify = map_read8(map, address)) != 0xFF) { - error = 1; - break; - } - } - if (error) { - chip->state = FL_READY; - spin_unlock_bh(chip->mutex); - printk(KERN_WARNING - "%s: verify error at 0x%x, size %ld.\n", - map->name, address, size); - DISABLE_VPP(map); - - return -EIO; - } - } - - DISABLE_VPP(map); - chip->state = FL_READY; - wake_up(&chip->wq); - spin_unlock_bh(chip->mutex); - - return 0; -} - - - -static int amd_flash_erase(struct mtd_info *mtd, struct erase_info *instr) -{ - struct map_info *map = mtd->priv; - struct amd_flash_private *private = map->fldrv_priv; - unsigned long adr, len; - int chipnum; - int ret = 0; - int i; - int first; - struct mtd_erase_region_info *regions = mtd->eraseregions; - - if (instr->addr > mtd->size) { - return -EINVAL; - } - - if ((instr->len + instr->addr) > mtd->size) { - return -EINVAL; - } - - /* Check that both start and end of the requested erase are - * aligned with the erasesize at the appropriate addresses. - */ - - i = 0; - - /* Skip all erase regions which are ended before the start of - the requested erase. Actually, to save on the calculations, - we skip to the first erase region which starts after the - start of the requested erase, and then go back one. - */ - - while ((i < mtd->numeraseregions) && - (instr->addr >= regions[i].offset)) { - i++; - } - i--; - - /* OK, now i is pointing at the erase region in which this - * erase request starts. Check the start of the requested - * erase range is aligned with the erase size which is in - * effect here. - */ - - if (instr->addr & (regions[i].erasesize-1)) { - return -EINVAL; - } - - /* Remember the erase region we start on. */ - - first = i; - - /* Next, check that the end of the requested erase is aligned - * with the erase region at that address. - */ - - while ((i < mtd->numeraseregions) && - ((instr->addr + instr->len) >= regions[i].offset)) { - i++; - } - - /* As before, drop back one to point at the region in which - * the address actually falls. - */ - - i--; - - if ((instr->addr + instr->len) & (regions[i].erasesize-1)) { - return -EINVAL; - } - - chipnum = instr->addr >> private->chipshift; - adr = instr->addr - (chipnum << private->chipshift); - len = instr->len; - - i = first; - - while (len) { - ret = erase_one_block(map, &private->chips[chipnum], adr, - regions[i].erasesize); - - if (ret) { - return ret; - } - - adr += regions[i].erasesize; - len -= regions[i].erasesize; - - if ((adr % (1 << private->chipshift)) == - ((regions[i].offset + (regions[i].erasesize * - regions[i].numblocks)) - % (1 << private->chipshift))) { - i++; - } - - if (adr >> private->chipshift) { - adr = 0; - chipnum++; - if (chipnum >= private->numchips) { - break; - } - } - } - - instr->state = MTD_ERASE_DONE; - mtd_erase_callback(instr); - - return 0; -} - - - -static void amd_flash_sync(struct mtd_info *mtd) -{ - struct map_info *map = mtd->priv; - struct amd_flash_private *private = map->fldrv_priv; - int i; - struct flchip *chip; - int ret = 0; - DECLARE_WAITQUEUE(wait, current); - - for (i = 0; !ret && (i < private->numchips); i++) { - chip = &private->chips[i]; - - retry: - spin_lock_bh(chip->mutex); - - switch(chip->state) { - case FL_READY: - case FL_STATUS: - case FL_CFI_QUERY: - case FL_JEDEC_QUERY: - chip->oldstate = chip->state; - chip->state = FL_SYNCING; - /* No need to wake_up() on this state change - - * as the whole point is that nobody can do anything - * with the chip now anyway. - */ - case FL_SYNCING: - spin_unlock_bh(chip->mutex); - break; - - default: - /* Not an idle state */ - add_wait_queue(&chip->wq, &wait); - - spin_unlock_bh(chip->mutex); - - schedule(); - - remove_wait_queue(&chip->wq, &wait); - - goto retry; - } - } - - /* Unlock the chips again */ - for (i--; i >= 0; i--) { - chip = &private->chips[i]; - - spin_lock_bh(chip->mutex); - - if (chip->state == FL_SYNCING) { - chip->state = chip->oldstate; - wake_up(&chip->wq); - } - spin_unlock_bh(chip->mutex); - } -} - - - -static int amd_flash_suspend(struct mtd_info *mtd) -{ -printk("amd_flash_suspend(): not implemented!\n"); - return -EINVAL; -} - - - -static void amd_flash_resume(struct mtd_info *mtd) -{ -printk("amd_flash_resume(): not implemented!\n"); -} - - - -static void amd_flash_destroy(struct mtd_info *mtd) -{ - struct map_info *map = mtd->priv; - struct amd_flash_private *private = map->fldrv_priv; - kfree(private); -} - -int __init amd_flash_init(void) -{ - register_mtd_chip_driver(&amd_flash_chipdrv); - return 0; -} - -void __exit amd_flash_exit(void) -{ - unregister_mtd_chip_driver(&amd_flash_chipdrv); -} - -module_init(amd_flash_init); -module_exit(amd_flash_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Jonas Holmberg <jonas.holmberg@axis.com>"); -MODULE_DESCRIPTION("Old MTD chip driver for AMD flash chips"); diff --git a/drivers/mtd/chips/cfi_cmdset_0001.c b/drivers/mtd/chips/cfi_cmdset_0001.c index 7ea49a0d5ec..a7543ba3e19 100644 --- a/drivers/mtd/chips/cfi_cmdset_0001.c +++ b/drivers/mtd/chips/cfi_cmdset_0001.c @@ -4,10 +4,8 @@ * * (C) 2000 Red Hat. GPL'd * - * $Id: cfi_cmdset_0001.c,v 1.186 2005/11/23 22:07:52 nico Exp $ * - * - * 10/10/2000 Nicolas Pitre <nico@cam.org> + * 10/10/2000 Nicolas Pitre <nico@fluxnic.net> * - completely revamped method functions so they are aware and * independent of the flash geometry (buswidth, interleave, etc.) * - scalability vs code size is completely set at compile-time @@ -15,13 +13,14 @@ * - optimized write buffer method * 02/05/2002 Christopher Hoover <ch@hpl.hp.com>/<ch@murgatroid.com> * - reworked lock/unlock/erase support for var size flash + * 21/03/2007 Rodolfo Giometti <giometti@linux.it> + * - auto unlock sectors on resume for auto locking flash on power up */ #include <linux/module.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/sched.h> -#include <linux/init.h> #include <asm/io.h> #include <asm/byteorder.h> @@ -30,10 +29,10 @@ #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/reboot.h> +#include <linux/bitmap.h> #include <linux/mtd/xip.h> #include <linux/mtd/map.h> #include <linux/mtd/mtd.h> -#include <linux/mtd/compatmac.h> #include <linux/mtd/cfi.h> /* #define CMDSET0001_DISABLE_ERASE_SUSPEND_ON_WRITE */ @@ -42,11 +41,22 @@ // debugging, turns off buffer write mode if set to 1 #define FORCE_WORD_WRITE 0 -#define MANUFACTURER_INTEL 0x0089 +/* Intel chips */ #define I82802AB 0x00ad #define I82802AC 0x00ac -#define MANUFACTURER_ST 0x0020 +#define PF38F4476 0x881c +/* STMicroelectronics chips */ #define M50LPW080 0x002F +#define M50FLW080A 0x0080 +#define M50FLW080B 0x0081 +/* Atmel chips */ +#define AT49BV640D 0x02de +#define AT49BV640DT 0x02db +/* Sharp chips */ +#define LH28F640BFHE_PTTL90 0x00b0 +#define LH28F640BFHE_PBTL90 0x00b1 +#define LH28F640BFHE_PTTL70A 0x00b2 +#define LH28F640BFHE_PBTL70A 0x00b3 static int cfi_intelext_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *); static int cfi_intelext_write_words(struct mtd_info *, loff_t, size_t, size_t *, const u_char *); @@ -54,17 +64,19 @@ static int cfi_intelext_write_buffers(struct mtd_info *, loff_t, size_t, size_t static int cfi_intelext_writev(struct mtd_info *, const struct kvec *, unsigned long, loff_t, size_t *); static int cfi_intelext_erase_varsize(struct mtd_info *, struct erase_info *); static void cfi_intelext_sync (struct mtd_info *); -static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, size_t len); -static int cfi_intelext_unlock(struct mtd_info *mtd, loff_t ofs, size_t len); +static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len); +static int cfi_intelext_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len); +static int cfi_intelext_is_locked(struct mtd_info *mtd, loff_t ofs, + uint64_t len); #ifdef CONFIG_MTD_OTP static int cfi_intelext_read_fact_prot_reg (struct mtd_info *, loff_t, size_t, size_t *, u_char *); static int cfi_intelext_read_user_prot_reg (struct mtd_info *, loff_t, size_t, size_t *, u_char *); static int cfi_intelext_write_user_prot_reg (struct mtd_info *, loff_t, size_t, size_t *, u_char *); static int cfi_intelext_lock_user_prot_reg (struct mtd_info *, loff_t, size_t); -static int cfi_intelext_get_fact_prot_info (struct mtd_info *, - struct otp_info *, size_t); -static int cfi_intelext_get_user_prot_info (struct mtd_info *, - struct otp_info *, size_t); +static int cfi_intelext_get_fact_prot_info(struct mtd_info *, size_t, + size_t *, struct otp_info *); +static int cfi_intelext_get_user_prot_info(struct mtd_info *, size_t, + size_t *, struct otp_info *); #endif static int cfi_intelext_suspend (struct mtd_info *); static void cfi_intelext_resume (struct mtd_info *); @@ -78,10 +90,10 @@ static struct mtd_info *cfi_intelext_setup (struct mtd_info *); static int cfi_intelext_partition_fixup(struct mtd_info *, struct cfi_private **); static int cfi_intelext_point (struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char **mtdbuf); -static void cfi_intelext_unpoint (struct mtd_info *mtd, u_char *addr, loff_t from, - size_t len); + size_t *retlen, void **virt, resource_size_t *phys); +static int cfi_intelext_unpoint(struct mtd_info *mtd, loff_t from, size_t len); +static int chip_ready (struct map_info *map, struct flchip *chip, unsigned long adr, int mode); static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode); static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr); #include "fwh_lock.h" @@ -153,13 +165,64 @@ static void cfi_tell_features(struct cfi_pri_intelext *extp) } #endif +/* Atmel chips don't use the same PRI format as Intel chips */ +static void fixup_convert_atmel_pri(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_intelext *extp = cfi->cmdset_priv; + struct cfi_pri_atmel atmel_pri; + uint32_t features = 0; + + /* Reverse byteswapping */ + extp->FeatureSupport = cpu_to_le32(extp->FeatureSupport); + extp->BlkStatusRegMask = cpu_to_le16(extp->BlkStatusRegMask); + extp->ProtRegAddr = cpu_to_le16(extp->ProtRegAddr); + + memcpy(&atmel_pri, extp, sizeof(atmel_pri)); + memset((char *)extp + 5, 0, sizeof(*extp) - 5); + + printk(KERN_ERR "atmel Features: %02x\n", atmel_pri.Features); + + if (atmel_pri.Features & 0x01) /* chip erase supported */ + features |= (1<<0); + if (atmel_pri.Features & 0x02) /* erase suspend supported */ + features |= (1<<1); + if (atmel_pri.Features & 0x04) /* program suspend supported */ + features |= (1<<2); + if (atmel_pri.Features & 0x08) /* simultaneous operations supported */ + features |= (1<<9); + if (atmel_pri.Features & 0x20) /* page mode read supported */ + features |= (1<<7); + if (atmel_pri.Features & 0x40) /* queued erase supported */ + features |= (1<<4); + if (atmel_pri.Features & 0x80) /* Protection bits supported */ + features |= (1<<6); + + extp->FeatureSupport = features; + + /* burst write mode not supported */ + cfi->cfiq->BufWriteTimeoutTyp = 0; + cfi->cfiq->BufWriteTimeoutMax = 0; +} + +static void fixup_at49bv640dx_lock(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_intelext *cfip = cfi->cmdset_priv; + + cfip->FeatureSupport |= (1 << 5); + mtd->flags |= MTD_POWERUP_LOCK; +} + #ifdef CMDSET0001_DISABLE_ERASE_SUSPEND_ON_WRITE /* Some Intel Strata Flash prior to FPO revision C has bugs in this area */ -static void fixup_intel_strataflash(struct mtd_info *mtd, void* param) +static void fixup_intel_strataflash(struct mtd_info *mtd) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; - struct cfi_pri_amdstd *extp = cfi->cmdset_priv; + struct cfi_pri_intelext *extp = cfi->cmdset_priv; printk(KERN_WARNING "cfi_cmdset_0001: Suspend " "erase on write disabled.\n"); @@ -168,7 +231,7 @@ static void fixup_intel_strataflash(struct mtd_info *mtd, void* param) #endif #ifdef CMDSET0001_DISABLE_WRITE_SUSPEND -static void fixup_no_write_suspend(struct mtd_info *mtd, void* param) +static void fixup_no_write_suspend(struct mtd_info *mtd) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; @@ -181,7 +244,7 @@ static void fixup_no_write_suspend(struct mtd_info *mtd, void* param) } #endif -static void fixup_st_m28w320ct(struct mtd_info *mtd, void* param) +static void fixup_st_m28w320ct(struct mtd_info *mtd) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; @@ -190,7 +253,7 @@ static void fixup_st_m28w320ct(struct mtd_info *mtd, void* param) cfi->cfiq->BufWriteTimeoutMax = 0; /* Not supported */ } -static void fixup_st_m28w320cb(struct mtd_info *mtd, void* param) +static void fixup_st_m28w320cb(struct mtd_info *mtd) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; @@ -200,46 +263,99 @@ static void fixup_st_m28w320cb(struct mtd_info *mtd, void* param) (cfi->cfiq->EraseRegionInfo[1] & 0xffff0000) | 0x3e; }; -static void fixup_use_point(struct mtd_info *mtd, void *param) +static int is_LH28F640BF(struct cfi_private *cfi) +{ + /* Sharp LH28F640BF Family */ + if (cfi->mfr == CFI_MFR_SHARP && ( + cfi->id == LH28F640BFHE_PTTL90 || cfi->id == LH28F640BFHE_PBTL90 || + cfi->id == LH28F640BFHE_PTTL70A || cfi->id == LH28F640BFHE_PBTL70A)) + return 1; + return 0; +} + +static void fixup_LH28F640BF(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_intelext *extp = cfi->cmdset_priv; + + /* Reset the Partition Configuration Register on LH28F640BF + * to a single partition (PCR = 0x000): PCR is embedded into A0-A15. */ + if (is_LH28F640BF(cfi)) { + printk(KERN_INFO "Reset Partition Config. Register: 1 Partition of 4 planes\n"); + map_write(map, CMD(0x60), 0); + map_write(map, CMD(0x04), 0); + + /* We have set one single partition thus + * Simultaneous Operations are not allowed */ + printk(KERN_INFO "cfi_cmdset_0001: Simultaneous Operations disabled\n"); + extp->FeatureSupport &= ~512; + } +} + +static void fixup_use_point(struct mtd_info *mtd) { struct map_info *map = mtd->priv; - if (!mtd->point && map_is_linear(map)) { - mtd->point = cfi_intelext_point; - mtd->unpoint = cfi_intelext_unpoint; + if (!mtd->_point && map_is_linear(map)) { + mtd->_point = cfi_intelext_point; + mtd->_unpoint = cfi_intelext_unpoint; } } -static void fixup_use_write_buffers(struct mtd_info *mtd, void *param) +static void fixup_use_write_buffers(struct mtd_info *mtd) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; if (cfi->cfiq->BufWriteTimeoutTyp) { printk(KERN_INFO "Using buffer write method\n" ); - mtd->write = cfi_intelext_write_buffers; - mtd->writev = cfi_intelext_writev; + mtd->_write = cfi_intelext_write_buffers; + mtd->_writev = cfi_intelext_writev; + } +} + +/* + * Some chips power-up with all sectors locked by default. + */ +static void fixup_unlock_powerup_lock(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_intelext *cfip = cfi->cmdset_priv; + + if (cfip->FeatureSupport&32) { + printk(KERN_INFO "Using auto-unlock on power-up/resume\n" ); + mtd->flags |= MTD_POWERUP_LOCK; } } static struct cfi_fixup cfi_fixup_table[] = { + { CFI_MFR_ATMEL, CFI_ID_ANY, fixup_convert_atmel_pri }, + { CFI_MFR_ATMEL, AT49BV640D, fixup_at49bv640dx_lock }, + { CFI_MFR_ATMEL, AT49BV640DT, fixup_at49bv640dx_lock }, #ifdef CMDSET0001_DISABLE_ERASE_SUSPEND_ON_WRITE - { CFI_MFR_ANY, CFI_ID_ANY, fixup_intel_strataflash, NULL }, + { CFI_MFR_ANY, CFI_ID_ANY, fixup_intel_strataflash }, #endif #ifdef CMDSET0001_DISABLE_WRITE_SUSPEND - { CFI_MFR_ANY, CFI_ID_ANY, fixup_no_write_suspend, NULL }, + { CFI_MFR_ANY, CFI_ID_ANY, fixup_no_write_suspend }, #endif #if !FORCE_WORD_WRITE - { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_write_buffers, NULL }, + { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_write_buffers }, #endif - { CFI_MFR_ST, 0x00ba, /* M28W320CT */ fixup_st_m28w320ct, NULL }, - { CFI_MFR_ST, 0x00bb, /* M28W320CB */ fixup_st_m28w320cb, NULL }, - { 0, 0, NULL, NULL } + { CFI_MFR_ST, 0x00ba, /* M28W320CT */ fixup_st_m28w320ct }, + { CFI_MFR_ST, 0x00bb, /* M28W320CB */ fixup_st_m28w320cb }, + { CFI_MFR_INTEL, CFI_ID_ANY, fixup_unlock_powerup_lock }, + { CFI_MFR_SHARP, CFI_ID_ANY, fixup_unlock_powerup_lock }, + { CFI_MFR_SHARP, CFI_ID_ANY, fixup_LH28F640BF }, + { 0, 0, NULL } }; static struct cfi_fixup jedec_fixup_table[] = { - { MANUFACTURER_INTEL, I82802AB, fixup_use_fwh_lock, NULL, }, - { MANUFACTURER_INTEL, I82802AC, fixup_use_fwh_lock, NULL, }, - { MANUFACTURER_ST, M50LPW080, fixup_use_fwh_lock, NULL, }, - { 0, 0, NULL, NULL } + { CFI_MFR_INTEL, I82802AB, fixup_use_fwh_lock }, + { CFI_MFR_INTEL, I82802AC, fixup_use_fwh_lock }, + { CFI_MFR_ST, M50LPW080, fixup_use_fwh_lock }, + { CFI_MFR_ST, M50FLW080A, fixup_use_fwh_lock }, + { CFI_MFR_ST, M50FLW080B, fixup_use_fwh_lock }, + { 0, 0, NULL } }; static struct cfi_fixup fixup_table[] = { /* The CFI vendor ids and the JEDEC vendor IDs appear @@ -247,14 +363,24 @@ static struct cfi_fixup fixup_table[] = { * well. This table is to pick all cases where * we know that is the case. */ - { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_point, NULL }, - { 0, 0, NULL, NULL } + { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_point }, + { 0, 0, NULL } }; +static void cfi_fixup_major_minor(struct cfi_private *cfi, + struct cfi_pri_intelext *extp) +{ + if (cfi->mfr == CFI_MFR_INTEL && + cfi->id == PF38F4476 && extp->MinorVersion == '3') + extp->MinorVersion = '1'; +} + static inline struct cfi_pri_intelext * read_pri_intelext(struct map_info *map, __u16 adr) { + struct cfi_private *cfi = map->fldrv_priv; struct cfi_pri_intelext *extp; + unsigned int extra_size = 0; unsigned int extp_size = sizeof(*extp); again: @@ -262,8 +388,10 @@ read_pri_intelext(struct map_info *map, __u16 adr) if (!extp) return NULL; + cfi_fixup_major_minor(cfi, extp); + if (extp->MajorVersion != '1' || - (extp->MinorVersion < '0' || extp->MinorVersion > '4')) { + (extp->MinorVersion < '0' || extp->MinorVersion > '5')) { printk(KERN_ERR " Unknown Intel/Sharp Extended Query " "version %c.%c.\n", extp->MajorVersion, extp->MinorVersion); @@ -276,19 +404,24 @@ read_pri_intelext(struct map_info *map, __u16 adr) extp->BlkStatusRegMask = le16_to_cpu(extp->BlkStatusRegMask); extp->ProtRegAddr = le16_to_cpu(extp->ProtRegAddr); - if (extp->MajorVersion == '1' && extp->MinorVersion >= '3') { - unsigned int extra_size = 0; - int nb_parts, i; + if (extp->MinorVersion >= '0') { + extra_size = 0; /* Protection Register info */ extra_size += (extp->NumProtectionFields - 1) * sizeof(struct cfi_intelext_otpinfo); + } + if (extp->MinorVersion >= '1') { /* Burst Read info */ extra_size += 2; if (extp_size < sizeof(*extp) + extra_size) goto need_more; - extra_size += extp->extra[extra_size-1]; + extra_size += extp->extra[extra_size - 1]; + } + + if (extp->MinorVersion >= '3') { + int nb_parts, i; /* Number of hardware-partitions */ extra_size += 1; @@ -321,7 +454,7 @@ read_pri_intelext(struct map_info *map, __u16 adr) if (extp_size > 4096) { printk(KERN_ERR "%s: cfi_pri_intelext is too fat\n", - __FUNCTION__); + __func__); return NULL; } goto again; @@ -337,27 +470,26 @@ struct mtd_info *cfi_cmdset_0001(struct map_info *map, int primary) struct mtd_info *mtd; int i; - mtd = kmalloc(sizeof(*mtd), GFP_KERNEL); - if (!mtd) { - printk(KERN_ERR "Failed to allocate memory for MTD device\n"); + mtd = kzalloc(sizeof(*mtd), GFP_KERNEL); + if (!mtd) return NULL; - } - memset(mtd, 0, sizeof(*mtd)); mtd->priv = map; mtd->type = MTD_NORFLASH; /* Fill in the default mtd operations */ - mtd->erase = cfi_intelext_erase_varsize; - mtd->read = cfi_intelext_read; - mtd->write = cfi_intelext_write_words; - mtd->sync = cfi_intelext_sync; - mtd->lock = cfi_intelext_lock; - mtd->unlock = cfi_intelext_unlock; - mtd->suspend = cfi_intelext_suspend; - mtd->resume = cfi_intelext_resume; + mtd->_erase = cfi_intelext_erase_varsize; + mtd->_read = cfi_intelext_read; + mtd->_write = cfi_intelext_write_words; + mtd->_sync = cfi_intelext_sync; + mtd->_lock = cfi_intelext_lock; + mtd->_unlock = cfi_intelext_unlock; + mtd->_is_locked = cfi_intelext_is_locked; + mtd->_suspend = cfi_intelext_suspend; + mtd->_resume = cfi_intelext_resume; mtd->flags = MTD_CAP_NORFLASH; mtd->name = map->name; mtd->writesize = 1; + mtd->writebufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize; mtd->reboot_notifier.notifier_call = cfi_intelext_reboot; @@ -398,9 +530,45 @@ struct mtd_info *cfi_cmdset_0001(struct map_info *map, int primary) cfi_fixup(mtd, fixup_table); for (i=0; i< cfi->numchips; i++) { - cfi->chips[i].word_write_time = 1<<cfi->cfiq->WordWriteTimeoutTyp; - cfi->chips[i].buffer_write_time = 1<<cfi->cfiq->BufWriteTimeoutTyp; - cfi->chips[i].erase_time = 1000<<cfi->cfiq->BlockEraseTimeoutTyp; + if (cfi->cfiq->WordWriteTimeoutTyp) + cfi->chips[i].word_write_time = + 1<<cfi->cfiq->WordWriteTimeoutTyp; + else + cfi->chips[i].word_write_time = 50000; + + if (cfi->cfiq->BufWriteTimeoutTyp) + cfi->chips[i].buffer_write_time = + 1<<cfi->cfiq->BufWriteTimeoutTyp; + /* No default; if it isn't specified, we won't use it */ + + if (cfi->cfiq->BlockEraseTimeoutTyp) + cfi->chips[i].erase_time = + 1000<<cfi->cfiq->BlockEraseTimeoutTyp; + else + cfi->chips[i].erase_time = 2000000; + + if (cfi->cfiq->WordWriteTimeoutTyp && + cfi->cfiq->WordWriteTimeoutMax) + cfi->chips[i].word_write_time_max = + 1<<(cfi->cfiq->WordWriteTimeoutTyp + + cfi->cfiq->WordWriteTimeoutMax); + else + cfi->chips[i].word_write_time_max = 50000 * 8; + + if (cfi->cfiq->BufWriteTimeoutTyp && + cfi->cfiq->BufWriteTimeoutMax) + cfi->chips[i].buffer_write_time_max = + 1<<(cfi->cfiq->BufWriteTimeoutTyp + + cfi->cfiq->BufWriteTimeoutMax); + + if (cfi->cfiq->BlockEraseTimeoutTyp && + cfi->cfiq->BlockEraseTimeoutMax) + cfi->chips[i].erase_time_max = + 1000<<(cfi->cfiq->BlockEraseTimeoutTyp + + cfi->cfiq->BlockEraseTimeoutMax); + else + cfi->chips[i].erase_time_max = 2000000 * 8; + cfi->chips[i].ref_point_counter = 0; init_waitqueue_head(&(cfi->chips[i].wq)); } @@ -430,10 +598,8 @@ static struct mtd_info *cfi_intelext_setup(struct mtd_info *mtd) mtd->numeraseregions = cfi->cfiq->NumEraseRegions * cfi->numchips; mtd->eraseregions = kmalloc(sizeof(struct mtd_erase_region_info) * mtd->numeraseregions, GFP_KERNEL); - if (!mtd->eraseregions) { - printk(KERN_ERR "Failed to allocate memory for MTD erase region info\n"); + if (!mtd->eraseregions) goto setup_err; - } for (i=0; i<cfi->cfiq->NumEraseRegions; i++) { unsigned long ernum, ersize; @@ -447,6 +613,7 @@ static struct mtd_info *cfi_intelext_setup(struct mtd_info *mtd) mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].offset = (j*devsize)+offset; mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].erasesize = ersize; mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].numblocks = ernum; + mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].lockmap = kmalloc(ernum / 8 + 1, GFP_KERNEL); } offset += (ersize * ernum); } @@ -458,19 +625,19 @@ static struct mtd_info *cfi_intelext_setup(struct mtd_info *mtd) } for (i=0; i<mtd->numeraseregions;i++){ - printk(KERN_DEBUG "erase region %d: offset=0x%x,size=0x%x,blocks=%d\n", - i,mtd->eraseregions[i].offset, + printk(KERN_DEBUG "erase region %d: offset=0x%llx,size=0x%x,blocks=%d\n", + i,(unsigned long long)mtd->eraseregions[i].offset, mtd->eraseregions[i].erasesize, mtd->eraseregions[i].numblocks); } #ifdef CONFIG_MTD_OTP - mtd->read_fact_prot_reg = cfi_intelext_read_fact_prot_reg; - mtd->read_user_prot_reg = cfi_intelext_read_user_prot_reg; - mtd->write_user_prot_reg = cfi_intelext_write_user_prot_reg; - mtd->lock_user_prot_reg = cfi_intelext_lock_user_prot_reg; - mtd->get_fact_prot_info = cfi_intelext_get_fact_prot_info; - mtd->get_user_prot_info = cfi_intelext_get_user_prot_info; + mtd->_read_fact_prot_reg = cfi_intelext_read_fact_prot_reg; + mtd->_read_user_prot_reg = cfi_intelext_read_user_prot_reg; + mtd->_write_user_prot_reg = cfi_intelext_write_user_prot_reg; + mtd->_lock_user_prot_reg = cfi_intelext_lock_user_prot_reg; + mtd->_get_fact_prot_info = cfi_intelext_get_fact_prot_info; + mtd->_get_user_prot_info = cfi_intelext_get_user_prot_info; #endif /* This function has the potential to distort the reality @@ -483,10 +650,8 @@ static struct mtd_info *cfi_intelext_setup(struct mtd_info *mtd) return mtd; setup_err: - if(mtd) { - kfree(mtd->eraseregions); - kfree(mtd); - } + kfree(mtd->eraseregions); + kfree(mtd); kfree(cfi->cmdset_priv); return NULL; } @@ -499,7 +664,7 @@ static int cfi_intelext_partition_fixup(struct mtd_info *mtd, struct cfi_pri_intelext *extp = cfi->cmdset_priv; /* - * Probing of multi-partition flash ships. + * Probing of multi-partition flash chips. * * To support multiple partitions when available, we simply arrange * for each of them to have their own flchip structure even if they @@ -542,18 +707,19 @@ static int cfi_intelext_partition_fixup(struct mtd_info *mtd, sizeof(struct cfi_intelext_blockinfo); } + if (!numparts) + numparts = 1; + /* Programming Region info */ if (extp->MinorVersion >= '4') { struct cfi_intelext_programming_regioninfo *prinfo; prinfo = (struct cfi_intelext_programming_regioninfo *)&extp->extra[offs]; mtd->writesize = cfi->interleave << prinfo->ProgRegShift; - MTD_PROGREGION_CTRLMODE_VALID(mtd) = cfi->interleave * prinfo->ControlValid; - MTD_PROGREGION_CTRLMODE_INVALID(mtd) = cfi->interleave * prinfo->ControlInvalid; mtd->flags &= ~MTD_BIT_WRITEABLE; printk(KERN_DEBUG "%s: program region size/ctrl_valid/ctrl_inval = %d/%d/%d\n", map->name, mtd->writesize, - MTD_PROGREGION_CTRLMODE_VALID(mtd), - MTD_PROGREGION_CTRLMODE_INVALID(mtd)); + cfi->interleave * prinfo->ControlValid, + cfi->interleave * prinfo->ControlInvalid); } /* @@ -566,7 +732,7 @@ static int cfi_intelext_partition_fixup(struct mtd_info *mtd, if ((1 << partshift) < mtd->erasesize) { printk( KERN_ERR "%s: bad number of hw partitions (%d)\n", - __FUNCTION__, numparts); + __func__, numparts); return -EINVAL; } @@ -586,7 +752,7 @@ static int cfi_intelext_partition_fixup(struct mtd_info *mtd, chip = &newcfi->chips[0]; for (i = 0; i < cfi->numchips; i++) { shared[i].writing = shared[i].erasing = NULL; - spin_lock_init(&shared[i].lock); + mutex_init(&shared[i].lock); for (j = 0; j < numparts; j++) { *chip = cfi->chips[i]; chip->start += j << partshift; @@ -594,8 +760,7 @@ static int cfi_intelext_partition_fixup(struct mtd_info *mtd, /* those should be reset too since they create memory references. */ init_waitqueue_head(&chip->wq); - spin_lock_init(&chip->_spinlock); - chip->mutex = &chip->_spinlock; + mutex_init(&chip->mutex); chip++; } } @@ -616,73 +781,17 @@ static int cfi_intelext_partition_fixup(struct mtd_info *mtd, /* * *********** CHIP ACCESS FUNCTIONS *********** */ - -static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode) +static int chip_ready (struct map_info *map, struct flchip *chip, unsigned long adr, int mode) { DECLARE_WAITQUEUE(wait, current); struct cfi_private *cfi = map->fldrv_priv; map_word status, status_OK = CMD(0x80), status_PWS = CMD(0x01); - unsigned long timeo; struct cfi_pri_intelext *cfip = cfi->cmdset_priv; + unsigned long timeo = jiffies + HZ; - resettime: - timeo = jiffies + HZ; - retry: - if (chip->priv && (mode == FL_WRITING || mode == FL_ERASING || mode == FL_OTP_WRITE)) { - /* - * OK. We have possibility for contension on the write/erase - * operations which are global to the real chip and not per - * partition. So let's fight it over in the partition which - * currently has authority on the operation. - * - * The rules are as follows: - * - * - any write operation must own shared->writing. - * - * - any erase operation must own _both_ shared->writing and - * shared->erasing. - * - * - contension arbitration is handled in the owner's context. - * - * The 'shared' struct can be read and/or written only when - * its lock is taken. - */ - struct flchip_shared *shared = chip->priv; - struct flchip *contender; - spin_lock(&shared->lock); - contender = shared->writing; - if (contender && contender != chip) { - /* - * The engine to perform desired operation on this - * partition is already in use by someone else. - * Let's fight over it in the context of the chip - * currently using it. If it is possible to suspend, - * that other partition will do just that, otherwise - * it'll happily send us to sleep. In any case, when - * get_chip returns success we're clear to go ahead. - */ - int ret = spin_trylock(contender->mutex); - spin_unlock(&shared->lock); - if (!ret) - goto retry; - spin_unlock(chip->mutex); - ret = get_chip(map, contender, contender->start, mode); - spin_lock(chip->mutex); - if (ret) { - spin_unlock(contender->mutex); - return ret; - } - timeo = jiffies + HZ; - spin_lock(&shared->lock); - spin_unlock(contender->mutex); - } - - /* We now own it */ - shared->writing = chip; - if (mode == FL_ERASING) - shared->erasing = chip; - spin_unlock(&shared->lock); - } + /* Prevent setting state FL_SYNCING for chip in suspended state. */ + if (mode == FL_SYNCING && chip->oldstate != FL_READY) + goto sleep; switch (chip->state) { @@ -697,18 +806,13 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr if (chip->priv && map_word_andequal(map, status, status_PWS, status_PWS)) break; - if (time_after(jiffies, timeo)) { - printk(KERN_ERR "%s: Waiting for chip to be ready timed out. Status %lx\n", - map->name, status.x[0]); - return -EIO; - } - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); cfi_udelay(1); - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); /* Someone else might have been playing with it. */ - goto retry; + return -EAGAIN; } - + /* Fall through */ case FL_READY: case FL_CFI_QUERY: case FL_JEDEC_QUERY: @@ -740,20 +844,17 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr break; if (time_after(jiffies, timeo)) { - /* Urgh. Resume and pretend we weren't here. */ - map_write(map, CMD(0xd0), adr); - /* Make sure we're in 'read status' mode if it had finished */ - map_write(map, CMD(0x70), adr); - chip->state = FL_ERASING; - chip->oldstate = FL_READY; + /* Urgh. Resume and pretend we weren't here. + * Make sure we're in 'read status' mode if it had finished */ + put_chip(map, chip, adr); printk(KERN_ERR "%s: Chip not ready after erase " "suspended: status = 0x%lx\n", map->name, status.x[0]); return -EIO; } - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); cfi_udelay(1); - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); /* Nobody will touch it while it's in state FL_ERASE_SUSPENDING. So we can just loop here. */ } @@ -768,42 +869,141 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr chip->state = FL_READY; return 0; + case FL_SHUTDOWN: + /* The machine is rebooting now,so no one can get chip anymore */ + return -EIO; case FL_POINT: /* Only if there's no operation suspended... */ if (mode == FL_READY && chip->oldstate == FL_READY) return 0; - + /* Fall through */ default: sleep: set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&chip->wq, &wait); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); schedule(); remove_wait_queue(&chip->wq, &wait); - spin_lock(chip->mutex); - goto resettime; + mutex_lock(&chip->mutex); + return -EAGAIN; } } +static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode) +{ + int ret; + DECLARE_WAITQUEUE(wait, current); + + retry: + if (chip->priv && + (mode == FL_WRITING || mode == FL_ERASING || mode == FL_OTP_WRITE + || mode == FL_SHUTDOWN) && chip->state != FL_SYNCING) { + /* + * OK. We have possibility for contention on the write/erase + * operations which are global to the real chip and not per + * partition. So let's fight it over in the partition which + * currently has authority on the operation. + * + * The rules are as follows: + * + * - any write operation must own shared->writing. + * + * - any erase operation must own _both_ shared->writing and + * shared->erasing. + * + * - contention arbitration is handled in the owner's context. + * + * The 'shared' struct can be read and/or written only when + * its lock is taken. + */ + struct flchip_shared *shared = chip->priv; + struct flchip *contender; + mutex_lock(&shared->lock); + contender = shared->writing; + if (contender && contender != chip) { + /* + * The engine to perform desired operation on this + * partition is already in use by someone else. + * Let's fight over it in the context of the chip + * currently using it. If it is possible to suspend, + * that other partition will do just that, otherwise + * it'll happily send us to sleep. In any case, when + * get_chip returns success we're clear to go ahead. + */ + ret = mutex_trylock(&contender->mutex); + mutex_unlock(&shared->lock); + if (!ret) + goto retry; + mutex_unlock(&chip->mutex); + ret = chip_ready(map, contender, contender->start, mode); + mutex_lock(&chip->mutex); + + if (ret == -EAGAIN) { + mutex_unlock(&contender->mutex); + goto retry; + } + if (ret) { + mutex_unlock(&contender->mutex); + return ret; + } + mutex_lock(&shared->lock); + + /* We should not own chip if it is already + * in FL_SYNCING state. Put contender and retry. */ + if (chip->state == FL_SYNCING) { + put_chip(map, contender, contender->start); + mutex_unlock(&contender->mutex); + goto retry; + } + mutex_unlock(&contender->mutex); + } + + /* Check if we already have suspended erase + * on this chip. Sleep. */ + if (mode == FL_ERASING && shared->erasing + && shared->erasing->oldstate == FL_ERASING) { + mutex_unlock(&shared->lock); + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + mutex_lock(&chip->mutex); + goto retry; + } + + /* We now own it */ + shared->writing = chip; + if (mode == FL_ERASING) + shared->erasing = chip; + mutex_unlock(&shared->lock); + } + ret = chip_ready(map, chip, adr, mode); + if (ret == -EAGAIN) + goto retry; + + return ret; +} + static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr) { struct cfi_private *cfi = map->fldrv_priv; if (chip->priv) { struct flchip_shared *shared = chip->priv; - spin_lock(&shared->lock); + mutex_lock(&shared->lock); if (shared->writing == chip && chip->oldstate == FL_READY) { /* We own the ability to write, but we're done */ shared->writing = shared->erasing; if (shared->writing && shared->writing != chip) { /* give back ownership to who we loaned it from */ struct flchip *loaner = shared->writing; - spin_lock(loaner->mutex); - spin_unlock(&shared->lock); - spin_unlock(chip->mutex); + mutex_lock(&loaner->mutex); + mutex_unlock(&shared->lock); + mutex_unlock(&chip->mutex); put_chip(map, loaner, loaner->start); - spin_lock(chip->mutex); - spin_unlock(loaner->mutex); + mutex_lock(&chip->mutex); + mutex_unlock(&loaner->mutex); wake_up(&chip->wq); return; } @@ -817,16 +1017,15 @@ static void put_chip(struct map_info *map, struct flchip *chip, unsigned long ad * Don't let the switch below mess things up since * we don't have ownership to resume anything. */ - spin_unlock(&shared->lock); + mutex_unlock(&shared->lock); wake_up(&chip->wq); return; } - spin_unlock(&shared->lock); + mutex_unlock(&shared->lock); } switch(chip->oldstate) { case FL_ERASING: - chip->state = chip->oldstate; /* What if one interleaved chip has finished and the other hasn't? The old code would leave the finished one in READY mode. That's bad, and caused -EROFS @@ -850,8 +1049,6 @@ static void put_chip(struct map_info *map, struct flchip *chip, unsigned long ad case FL_READY: case FL_STATUS: case FL_JEDEC_QUERY: - /* We should really make set_vpp() count, rather than doing this */ - DISABLE_VPP(map); break; default: printk(KERN_ERR "%s: put_chip() called with oldstate %d!!\n", map->name, chip->oldstate); @@ -908,7 +1105,7 @@ static void __xipram xip_enable(struct map_info *map, struct flchip *chip, static int __xipram xip_wait_for_operation( struct map_info *map, struct flchip *chip, - unsigned long adr, unsigned int chip_op_time ) + unsigned long adr, unsigned int chip_op_time_max) { struct cfi_private *cfi = map->fldrv_priv; struct cfi_pri_intelext *cfip = cfi->cmdset_priv; @@ -917,7 +1114,7 @@ static int __xipram xip_wait_for_operation( flstate_t oldstate, newstate; start = xip_currtime(); - usec = chip_op_time * 8; + usec = chip_op_time_max; if (usec == 0) usec = 500000; done = 0; @@ -971,10 +1168,10 @@ static int __xipram xip_wait_for_operation( chip->state = newstate; map_write(map, CMD(0xff), adr); (void) map_read(map, adr); - asm volatile (".rep 8; nop; .endr"); + xip_iprefetch(); local_irq_enable(); - spin_unlock(chip->mutex); - asm volatile (".rep 8; nop; .endr"); + mutex_unlock(&chip->mutex); + xip_iprefetch(); cond_resched(); /* @@ -983,15 +1180,15 @@ static int __xipram xip_wait_for_operation( * a suspended erase state. If so let's wait * until it's done. */ - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); while (chip->state != newstate) { DECLARE_WAITQUEUE(wait, current); set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&chip->wq, &wait); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); schedule(); remove_wait_queue(&chip->wq, &wait); - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); } /* Disallow XIP again */ local_irq_disable(); @@ -1027,8 +1224,8 @@ static int __xipram xip_wait_for_operation( #define XIP_INVAL_CACHED_RANGE(map, from, size) \ INVALIDATE_CACHED_RANGE(map, from, size) -#define INVAL_CACHE_AND_WAIT(map, chip, cmd_adr, inval_adr, inval_len, usec) \ - xip_wait_for_operation(map, chip, cmd_adr, usec) +#define INVAL_CACHE_AND_WAIT(map, chip, cmd_adr, inval_adr, inval_len, usec, usec_max) \ + xip_wait_for_operation(map, chip, cmd_adr, usec_max) #else @@ -1040,29 +1237,51 @@ static int __xipram xip_wait_for_operation( static int inval_cache_and_wait_for_operation( struct map_info *map, struct flchip *chip, unsigned long cmd_adr, unsigned long inval_adr, int inval_len, - unsigned int chip_op_time) + unsigned int chip_op_time, unsigned int chip_op_time_max) { struct cfi_private *cfi = map->fldrv_priv; map_word status, status_OK = CMD(0x80); int chip_state = chip->state; - unsigned int timeo, sleep_time; + unsigned int timeo, sleep_time, reset_timeo; - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); if (inval_len) INVALIDATE_CACHED_RANGE(map, inval_adr, inval_len); - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); - /* set our timeout to 8 times the expected delay */ - timeo = chip_op_time * 8; + timeo = chip_op_time_max; if (!timeo) timeo = 500000; + reset_timeo = timeo; sleep_time = chip_op_time / 2; for (;;) { + if (chip->state != chip_state) { + /* Someone's suspended the operation: sleep */ + DECLARE_WAITQUEUE(wait, current); + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + mutex_lock(&chip->mutex); + continue; + } + status = map_read(map, cmd_adr); if (map_word_andequal(map, status, status_OK, status_OK)) break; + if (chip->erase_suspended && chip_state == FL_ERASING) { + /* Erase suspend occurred while sleep: reset timeout */ + timeo = reset_timeo; + chip->erase_suspended = 0; + } + if (chip->write_suspended && chip_state == FL_WRITING) { + /* Write suspend occurred while sleep: reset timeout */ + timeo = reset_timeo; + chip->write_suspended = 0; + } if (!timeo) { map_write(map, CMD(0x70), cmd_adr); chip->state = FL_STATUS; @@ -1070,7 +1289,7 @@ static int inval_cache_and_wait_for_operation( } /* OK Still waiting. Drop the lock, wait a while and retry. */ - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); if (sleep_time >= 1000000/HZ) { /* * Half of the normal delay still remaining @@ -1085,18 +1304,7 @@ static int inval_cache_and_wait_for_operation( cond_resched(); timeo--; } - spin_lock(chip->mutex); - - if (chip->state != chip_state) { - /* Someone's suspended the operation: sleep */ - DECLARE_WAITQUEUE(wait, current); - set_current_state(TASK_UNINTERRUPTIBLE); - add_wait_queue(&chip->wq, &wait); - spin_unlock(chip->mutex); - schedule(); - remove_wait_queue(&chip->wq, &wait); - spin_lock(chip->mutex); - } + mutex_lock(&chip->mutex); } /* Done and happy. */ @@ -1106,8 +1314,8 @@ static int inval_cache_and_wait_for_operation( #endif -#define WAIT_TIMEOUT(map, chip, adr, udelay) \ - INVAL_CACHE_AND_WAIT(map, chip, adr, 0, 0, udelay); +#define WAIT_TIMEOUT(map, chip, adr, udelay, udelay_max) \ + INVAL_CACHE_AND_WAIT(map, chip, adr, 0, 0, udelay, udelay_max); static int do_point_onechip (struct map_info *map, struct flchip *chip, loff_t adr, size_t len) @@ -1121,7 +1329,7 @@ static int do_point_onechip (struct map_info *map, struct flchip *chip, loff_t a /* Ensure cmd read/writes are aligned. */ cmd_addr = adr & ~(map_bankwidth(map)-1); - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); ret = get_chip(map, chip, cmd_addr, FL_POINT); @@ -1132,37 +1340,45 @@ static int do_point_onechip (struct map_info *map, struct flchip *chip, loff_t a chip->state = FL_POINT; chip->ref_point_counter++; } - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return ret; } -static int cfi_intelext_point (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf) +static int cfi_intelext_point(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, void **virt, resource_size_t *phys) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; - unsigned long ofs; + unsigned long ofs, last_end = 0; int chipnum; int ret = 0; - if (!map->virt || (from + len > mtd->size)) + if (!map->virt) return -EINVAL; - *mtdbuf = (void *)map->virt + from; - *retlen = 0; - /* Now lock the chip(s) to POINT state */ /* ofs: offset within the first chip that the first read should start */ chipnum = (from >> cfi->chipshift); ofs = from - (chipnum << cfi->chipshift); + *virt = map->virt + cfi->chips[chipnum].start + ofs; + if (phys) + *phys = map->phys + cfi->chips[chipnum].start + ofs; + while (len) { unsigned long thislen; if (chipnum >= cfi->numchips) break; + /* We cannot point across chips that are virtually disjoint */ + if (!last_end) + last_end = cfi->chips[chipnum].start; + else if (cfi->chips[chipnum].start != last_end) + break; + if ((len + ofs -1) >> cfi->chipshift) thislen = (1<<cfi->chipshift) - ofs; else @@ -1176,17 +1392,18 @@ static int cfi_intelext_point (struct mtd_info *mtd, loff_t from, size_t len, si len -= thislen; ofs = 0; + last_end += 1 << cfi->chipshift; chipnum++; } return 0; } -static void cfi_intelext_unpoint (struct mtd_info *mtd, u_char *addr, loff_t from, size_t len) +static int cfi_intelext_unpoint(struct mtd_info *mtd, loff_t from, size_t len) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; unsigned long ofs; - int chipnum; + int chipnum, err = 0; /* Now unlock the chip(s) POINT state */ @@ -1194,7 +1411,7 @@ static void cfi_intelext_unpoint (struct mtd_info *mtd, u_char *addr, loff_t fro chipnum = (from >> cfi->chipshift); ofs = from - (chipnum << cfi->chipshift); - while (len) { + while (len && !err) { unsigned long thislen; struct flchip *chip; @@ -1207,21 +1424,25 @@ static void cfi_intelext_unpoint (struct mtd_info *mtd, u_char *addr, loff_t fro else thislen = len; - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); if (chip->state == FL_POINT) { chip->ref_point_counter--; if(chip->ref_point_counter == 0) chip->state = FL_READY; - } else - printk(KERN_ERR "%s: Warning: unpoint called on non pointed region\n", map->name); /* Should this give an error? */ + } else { + printk(KERN_ERR "%s: Error: unpoint called on non pointed region\n", map->name); + err = -EINVAL; + } put_chip(map, chip, chip->start); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); len -= thislen; ofs = 0; chipnum++; } + + return err; } static inline int do_read_onechip(struct map_info *map, struct flchip *chip, loff_t adr, size_t len, u_char *buf) @@ -1235,10 +1456,10 @@ static inline int do_read_onechip(struct map_info *map, struct flchip *chip, lof /* Ensure cmd read/writes are aligned. */ cmd_addr = adr & ~(map_bankwidth(map)-1); - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); ret = get_chip(map, chip, cmd_addr, FL_READY); if (ret) { - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return ret; } @@ -1252,7 +1473,7 @@ static inline int do_read_onechip(struct map_info *map, struct flchip *chip, lof put_chip(map, chip, cmd_addr); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return 0; } @@ -1268,8 +1489,6 @@ static int cfi_intelext_read (struct mtd_info *mtd, loff_t from, size_t len, siz chipnum = (from >> cfi->chipshift); ofs = from - (chipnum << cfi->chipshift); - *retlen = 0; - while (len) { unsigned long thislen; @@ -1306,7 +1525,7 @@ static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip, switch (mode) { case FL_WRITING: - write_cmd = (cfi->cfiq->P_ID != 0x0200) ? CMD(0x40) : CMD(0x41); + write_cmd = (cfi->cfiq->P_ID != P_ID_INTEL_PERFORMANCE) ? CMD(0x40) : CMD(0x41); break; case FL_OTP_WRITE: write_cmd = CMD(0xc0); @@ -1315,10 +1534,10 @@ static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip, return -EINVAL; } - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); ret = get_chip(map, chip, adr, mode); if (ret) { - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return ret; } @@ -1331,7 +1550,8 @@ static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip, ret = INVAL_CACHE_AND_WAIT(map, chip, adr, adr, map_bankwidth(map), - chip->word_write_time); + chip->word_write_time, + chip->word_write_time_max); if (ret) { xip_enable(map, chip, adr); printk(KERN_ERR "%s: word write error (status timeout)\n", map->name); @@ -1362,8 +1582,9 @@ static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip, } xip_enable(map, chip, adr); - out: put_chip(map, chip, adr); - spin_unlock(chip->mutex); + out: DISABLE_VPP(map); + put_chip(map, chip, adr); + mutex_unlock(&chip->mutex); return ret; } @@ -1376,10 +1597,6 @@ static int cfi_intelext_write_words (struct mtd_info *mtd, loff_t to , size_t le int chipnum; unsigned long ofs; - *retlen = 0; - if (!len) - return 0; - chipnum = to >> cfi->chipshift; ofs = to - (chipnum << cfi->chipshift); @@ -1461,22 +1678,31 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, int ret, wbufsize, word_gap, words; const struct kvec *vec; unsigned long vec_seek; + unsigned long initial_adr; + int initial_len = len; wbufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize; adr += chip->start; + initial_adr = adr; cmd_adr = adr & ~(wbufsize-1); + /* Sharp LH28F640BF chips need the first address for the + * Page Buffer Program command. See Table 5 of + * LH28F320BF, LH28F640BF, LH28F128BF Series (Appendix FUM00701) */ + if (is_LH28F640BF(cfi)) + cmd_adr = adr; + /* Let's determine this according to the interleave only once */ - write_cmd = (cfi->cfiq->P_ID != 0x0200) ? CMD(0xe8) : CMD(0xe9); + write_cmd = (cfi->cfiq->P_ID != P_ID_INTEL_PERFORMANCE) ? CMD(0xe8) : CMD(0xe9); - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); ret = get_chip(map, chip, cmd_adr, FL_WRITING); if (ret) { - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return ret; } - XIP_INVAL_CACHED_RANGE(map, adr, len); + XIP_INVAL_CACHED_RANGE(map, initial_adr, initial_len); ENABLE_VPP(map); xip_disable(map, chip, cmd_adr); @@ -1499,7 +1725,7 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, chip->state = FL_WRITING_TO_BUFFER; map_write(map, write_cmd, cmd_adr); - ret = WAIT_TIMEOUT(map, chip, cmd_adr, 0); + ret = WAIT_TIMEOUT(map, chip, cmd_adr, 0, 0); if (ret) { /* Argh. Not ready for write to buffer */ map_word Xstatus = map_read(map, cmd_adr); @@ -1516,7 +1742,7 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, /* Figure out the number of words to write */ word_gap = (-adr & (map_bankwidth(map)-1)); - words = (len - word_gap + map_bankwidth(map) - 1) / map_bankwidth(map); + words = DIV_ROUND_UP(len - word_gap, map_bankwidth(map)); if (!word_gap) { words--; } else { @@ -1567,8 +1793,9 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, chip->state = FL_WRITING; ret = INVAL_CACHE_AND_WAIT(map, chip, cmd_adr, - adr, len, - chip->buffer_write_time); + initial_adr, initial_len, + chip->buffer_write_time, + chip->buffer_write_time_max); if (ret) { map_write(map, CMD(0x70), cmd_adr); chip->state = FL_STATUS; @@ -1601,8 +1828,9 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, } xip_enable(map, chip, cmd_adr); - out: put_chip(map, chip, cmd_adr); - spin_unlock(chip->mutex); + out: DISABLE_VPP(map); + put_chip(map, chip, cmd_adr); + mutex_unlock(&chip->mutex); return ret; } @@ -1620,7 +1848,6 @@ static int cfi_intelext_writev (struct mtd_info *mtd, const struct kvec *vecs, for (i = 0; i < count; i++) len += vecs[i].iov_len; - *retlen = 0; if (!len) return 0; @@ -1681,10 +1908,10 @@ static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip, adr += chip->start; retry: - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); ret = get_chip(map, chip, adr, FL_ERASING); if (ret) { - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return ret; } @@ -1703,7 +1930,8 @@ static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip, ret = INVAL_CACHE_AND_WAIT(map, chip, adr, adr, len, - chip->erase_time); + chip->erase_time, + chip->erase_time_max); if (ret) { map_write(map, CMD(0x70), adr); chip->state = FL_STATUS; @@ -1738,8 +1966,9 @@ static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip, ret = -EIO; } else if (chipstatus & 0x20 && retries--) { printk(KERN_DEBUG "block erase failed at 0x%08lx: status 0x%lx. Retrying...\n", adr, chipstatus); + DISABLE_VPP(map); put_chip(map, chip, adr); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); goto retry; } else { printk(KERN_ERR "%s: block erase failed at 0x%08lx (status 0x%lx)\n", map->name, adr, chipstatus); @@ -1750,12 +1979,13 @@ static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip, } xip_enable(map, chip, adr); - out: put_chip(map, chip, adr); - spin_unlock(chip->mutex); + out: DISABLE_VPP(map); + put_chip(map, chip, adr); + mutex_unlock(&chip->mutex); return ret; } -int cfi_intelext_erase_varsize(struct mtd_info *mtd, struct erase_info *instr) +static int cfi_intelext_erase_varsize(struct mtd_info *mtd, struct erase_info *instr) { unsigned long ofs, len; int ret; @@ -1784,7 +2014,7 @@ static void cfi_intelext_sync (struct mtd_info *mtd) for (i=0; !ret && i<cfi->numchips; i++) { chip = &cfi->chips[i]; - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); ret = get_chip(map, chip, chip->start, FL_SYNCING); if (!ret) { @@ -1795,7 +2025,7 @@ static void cfi_intelext_sync (struct mtd_info *mtd) * with the chip now anyway. */ } - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); } /* Unlock the chips again */ @@ -1803,19 +2033,18 @@ static void cfi_intelext_sync (struct mtd_info *mtd) for (i--; i >=0; i--) { chip = &cfi->chips[i]; - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); if (chip->state == FL_SYNCING) { chip->state = chip->oldstate; chip->oldstate = FL_READY; wake_up(&chip->wq); } - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); } } -#ifdef DEBUG_LOCK_BITS -static int __xipram do_printlockstatus_oneblock(struct map_info *map, +static int __xipram do_getlockstatus_oneblock(struct map_info *map, struct flchip *chip, unsigned long adr, int len, void *thunk) @@ -1829,8 +2058,17 @@ static int __xipram do_printlockstatus_oneblock(struct map_info *map, chip->state = FL_JEDEC_QUERY; status = cfi_read_query(map, adr+(2*ofs_factor)); xip_enable(map, chip, 0); + return status; +} + +#ifdef DEBUG_LOCK_BITS +static int __xipram do_printlockstatus_oneblock(struct map_info *map, + struct flchip *chip, + unsigned long adr, + int len, void *thunk) +{ printk(KERN_DEBUG "block status register for 0x%08lx is %x\n", - adr, status); + adr, do_getlockstatus_oneblock(map, chip, adr, len, thunk)); return 0; } #endif @@ -1843,15 +2081,15 @@ static int __xipram do_xxlock_oneblock(struct map_info *map, struct flchip *chip { struct cfi_private *cfi = map->fldrv_priv; struct cfi_pri_intelext *extp = cfi->cmdset_priv; - int udelay; + int mdelay; int ret; adr += chip->start; - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); ret = get_chip(map, chip, adr, FL_LOCKING); if (ret) { - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return ret; } @@ -1872,9 +2110,17 @@ static int __xipram do_xxlock_oneblock(struct map_info *map, struct flchip *chip * If Instant Individual Block Locking supported then no need * to delay. */ - udelay = (!extp || !(extp->FeatureSupport & (1 << 5))) ? 1000000/HZ : 0; + /* + * Unlocking may take up to 1.4 seconds on some Intel flashes. So + * lets use a max of 1.5 seconds (1500ms) as timeout. + * + * See "Clear Block Lock-Bits Time" on page 40 in + * "3 Volt Intel StrataFlash Memory" 28F128J3,28F640J3,28F320J3 manual + * from February 2003 + */ + mdelay = (!extp || !(extp->FeatureSupport & (1 << 5))) ? 1500 : 0; - ret = WAIT_TIMEOUT(map, chip, adr, udelay); + ret = WAIT_TIMEOUT(map, chip, adr, mdelay, mdelay * 1000); if (ret) { map_write(map, CMD(0x70), adr); chip->state = FL_STATUS; @@ -1884,20 +2130,21 @@ static int __xipram do_xxlock_oneblock(struct map_info *map, struct flchip *chip } xip_enable(map, chip, adr); -out: put_chip(map, chip, adr); - spin_unlock(chip->mutex); + out: DISABLE_VPP(map); + put_chip(map, chip, adr); + mutex_unlock(&chip->mutex); return ret; } -static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, size_t len) +static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) { int ret; #ifdef DEBUG_LOCK_BITS printk(KERN_DEBUG "%s: lock status before, ofs=0x%08llx, len=0x%08X\n", - __FUNCTION__, ofs, len); + __func__, ofs, len); cfi_varsize_frob(mtd, do_printlockstatus_oneblock, - ofs, len, 0); + ofs, len, NULL); #endif ret = cfi_varsize_frob(mtd, do_xxlock_oneblock, @@ -1905,23 +2152,23 @@ static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, size_t len) #ifdef DEBUG_LOCK_BITS printk(KERN_DEBUG "%s: lock status after, ret=%d\n", - __FUNCTION__, ret); + __func__, ret); cfi_varsize_frob(mtd, do_printlockstatus_oneblock, - ofs, len, 0); + ofs, len, NULL); #endif return ret; } -static int cfi_intelext_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) +static int cfi_intelext_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) { int ret; #ifdef DEBUG_LOCK_BITS printk(KERN_DEBUG "%s: lock status before, ofs=0x%08llx, len=0x%08X\n", - __FUNCTION__, ofs, len); + __func__, ofs, len); cfi_varsize_frob(mtd, do_printlockstatus_oneblock, - ofs, len, 0); + ofs, len, NULL); #endif ret = cfi_varsize_frob(mtd, do_xxlock_oneblock, @@ -1929,14 +2176,21 @@ static int cfi_intelext_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) #ifdef DEBUG_LOCK_BITS printk(KERN_DEBUG "%s: lock status after, ret=%d\n", - __FUNCTION__, ret); + __func__, ret); cfi_varsize_frob(mtd, do_printlockstatus_oneblock, - ofs, len, 0); + ofs, len, NULL); #endif return ret; } +static int cfi_intelext_is_locked(struct mtd_info *mtd, loff_t ofs, + uint64_t len) +{ + return cfi_varsize_frob(mtd, do_getlockstatus_oneblock, + ofs, len, NULL) ? 1 : 0; +} + #ifdef CONFIG_MTD_OTP typedef int (*otp_op_t)(struct map_info *map, struct flchip *chip, @@ -1950,10 +2204,10 @@ do_otp_read(struct map_info *map, struct flchip *chip, u_long offset, struct cfi_private *cfi = map->fldrv_priv; int ret; - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); ret = get_chip(map, chip, chip->start, FL_JEDEC_QUERY); if (ret) { - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return ret; } @@ -1972,7 +2226,7 @@ do_otp_read(struct map_info *map, struct flchip *chip, u_long offset, INVALIDATE_CACHED_RANGE(map, chip->start + offset, size); put_chip(map, chip, chip->start); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return 0; } @@ -2044,7 +2298,7 @@ static int cfi_intelext_otp_walk(struct mtd_info *mtd, loff_t from, size_t len, /* Some chips have OTP located in the _top_ partition only. For example: Intel 28F256L18T (T means top-parameter device) */ - if (cfi->mfr == MANUFACTURER_INTEL) { + if (cfi->mfr == CFI_MFR_INTEL) { switch (cfi->id) { case 0x880b: case 0x880c: @@ -2183,40 +2437,66 @@ static int cfi_intelext_lock_user_prot_reg(struct mtd_info *mtd, NULL, do_otp_lock, 1); } -static int cfi_intelext_get_fact_prot_info(struct mtd_info *mtd, - struct otp_info *buf, size_t len) -{ - size_t retlen; - int ret; +static int cfi_intelext_get_fact_prot_info(struct mtd_info *mtd, size_t len, + size_t *retlen, struct otp_info *buf) - ret = cfi_intelext_otp_walk(mtd, 0, len, &retlen, (u_char *)buf, NULL, 0); - return ret ? : retlen; +{ + return cfi_intelext_otp_walk(mtd, 0, len, retlen, (u_char *)buf, + NULL, 0); } -static int cfi_intelext_get_user_prot_info(struct mtd_info *mtd, - struct otp_info *buf, size_t len) +static int cfi_intelext_get_user_prot_info(struct mtd_info *mtd, size_t len, + size_t *retlen, struct otp_info *buf) { - size_t retlen; - int ret; - - ret = cfi_intelext_otp_walk(mtd, 0, len, &retlen, (u_char *)buf, NULL, 1); - return ret ? : retlen; + return cfi_intelext_otp_walk(mtd, 0, len, retlen, (u_char *)buf, + NULL, 1); } #endif +static void cfi_intelext_save_locks(struct mtd_info *mtd) +{ + struct mtd_erase_region_info *region; + int block, status, i; + unsigned long adr; + size_t len; + + for (i = 0; i < mtd->numeraseregions; i++) { + region = &mtd->eraseregions[i]; + if (!region->lockmap) + continue; + + for (block = 0; block < region->numblocks; block++){ + len = region->erasesize; + adr = region->offset + block * len; + + status = cfi_varsize_frob(mtd, + do_getlockstatus_oneblock, adr, len, NULL); + if (status) + set_bit(block, region->lockmap); + else + clear_bit(block, region->lockmap); + } + } +} + static int cfi_intelext_suspend(struct mtd_info *mtd) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_intelext *extp = cfi->cmdset_priv; int i; struct flchip *chip; int ret = 0; + if ((mtd->flags & MTD_POWERUP_LOCK) + && extp && (extp->FeatureSupport & (1 << 5))) + cfi_intelext_save_locks(mtd); + for (i=0; !ret && i<cfi->numchips; i++) { chip = &cfi->chips[i]; - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); switch (chip->state) { case FL_READY: @@ -2224,6 +2504,8 @@ static int cfi_intelext_suspend(struct mtd_info *mtd) case FL_CFI_QUERY: case FL_JEDEC_QUERY: if (chip->oldstate == FL_READY) { + /* place the chip in a known state before suspend */ + map_write(map, CMD(0xFF), cfi->chips[i].start); chip->oldstate = chip->state; chip->state = FL_PM_SUSPENDED; /* No need to wake_up() on this state change - @@ -2241,12 +2523,12 @@ static int cfi_intelext_suspend(struct mtd_info *mtd) allowed to. Or should we return -EAGAIN, because the upper layers ought to have already shut down anything which was using the device anyway? The latter for now. */ - printk(KERN_NOTICE "Flash device refused suspend due to active operation (state %d)\n", chip->oldstate); + printk(KERN_NOTICE "Flash device refused suspend due to active operation (state %d)\n", chip->state); ret = -EAGAIN; case FL_PM_SUSPENDED: break; } - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); } /* Unlock the chips again */ @@ -2255,7 +2537,7 @@ static int cfi_intelext_suspend(struct mtd_info *mtd) for (i--; i >=0; i--) { chip = &cfi->chips[i]; - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); if (chip->state == FL_PM_SUSPENDED) { /* No need to force it into a known state here, @@ -2265,17 +2547,38 @@ static int cfi_intelext_suspend(struct mtd_info *mtd) chip->oldstate = FL_READY; wake_up(&chip->wq); } - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); } } return ret; } +static void cfi_intelext_restore_locks(struct mtd_info *mtd) +{ + struct mtd_erase_region_info *region; + int block, i; + unsigned long adr; + size_t len; + + for (i = 0; i < mtd->numeraseregions; i++) { + region = &mtd->eraseregions[i]; + if (!region->lockmap) + continue; + + for_each_clear_bit(block, region->lockmap, region->numblocks) { + len = region->erasesize; + adr = region->offset + block * len; + cfi_intelext_unlock(mtd, adr, len); + } + } +} + static void cfi_intelext_resume(struct mtd_info *mtd) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_intelext *extp = cfi->cmdset_priv; int i; struct flchip *chip; @@ -2283,7 +2586,7 @@ static void cfi_intelext_resume(struct mtd_info *mtd) chip = &cfi->chips[i]; - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); /* Go to known state. Chip may have been power cycled */ if (chip->state == FL_PM_SUSPENDED) { @@ -2292,8 +2595,12 @@ static void cfi_intelext_resume(struct mtd_info *mtd) wake_up(&chip->wq); } - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); } + + if ((mtd->flags & MTD_POWERUP_LOCK) + && extp && (extp->FeatureSupport & (1 << 5))) + cfi_intelext_restore_locks(mtd); } static int cfi_intelext_reset(struct mtd_info *mtd) @@ -2308,13 +2615,14 @@ static int cfi_intelext_reset(struct mtd_info *mtd) /* force the completion of any ongoing operation and switch to array mode so any bootloader in flash is accessible for soft reboot. */ - spin_lock(chip->mutex); - ret = get_chip(map, chip, chip->start, FL_SYNCING); + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, chip->start, FL_SHUTDOWN); if (!ret) { map_write(map, CMD(0xff), chip->start); - chip->state = FL_READY; + chip->state = FL_SHUTDOWN; + put_chip(map, chip, chip->start); } - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); } return 0; @@ -2334,12 +2642,19 @@ static void cfi_intelext_destroy(struct mtd_info *mtd) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; + struct mtd_erase_region_info *region; + int i; cfi_intelext_reset(mtd); unregister_reboot_notifier(&mtd->reboot_notifier); kfree(cfi->cmdset_priv); kfree(cfi->cfiq); kfree(cfi->chips[0].priv); kfree(cfi); + for (i = 0; i < mtd->numeraseregions; i++) { + region = &mtd->eraseregions[i]; + if (region->lockmap) + kfree(region->lockmap); + } kfree(mtd->eraseregions); } diff --git a/drivers/mtd/chips/cfi_cmdset_0002.c b/drivers/mtd/chips/cfi_cmdset_0002.c index 702ae4cd869..e21fde9d4d7 100644 --- a/drivers/mtd/chips/cfi_cmdset_0002.c +++ b/drivers/mtd/chips/cfi_cmdset_0002.c @@ -13,19 +13,17 @@ * XIP support hooks by Vitaly Wool (based on code for Intel flash * by Nicolas Pitre) * + * 25/09/2008 Christopher Moore: TopBottom fixup for many Macronix with CFI V1.0 + * * Occasionally maintained by Thayne Harbaugh tharbaugh at lnxi dot com * * This code is GPL - * - * $Id: cfi_cmdset_0002.c,v 1.122 2005/11/07 11:14:22 gleixner Exp $ - * */ #include <linux/module.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/sched.h> -#include <linux/init.h> #include <asm/io.h> #include <asm/byteorder.h> @@ -33,7 +31,9 @@ #include <linux/slab.h> #include <linux/delay.h> #include <linux/interrupt.h> -#include <linux/mtd/compatmac.h> +#include <linux/reboot.h> +#include <linux/of.h> +#include <linux/of_platform.h> #include <linux/mtd/map.h> #include <linux/mtd/mtd.h> #include <linux/mtd/cfi.h> @@ -44,10 +44,8 @@ #define MAX_WORD_RETRIES 3 -#define MANUFACTURER_AMD 0x0001 -#define MANUFACTURER_ATMEL 0x001F -#define MANUFACTURER_SST 0x00BF #define SST49LF004B 0x0060 +#define SST49LF040B 0x0050 #define SST49LF008A 0x005a #define AT49BV6416 0x00d6 @@ -59,8 +57,12 @@ static int cfi_amdstd_erase_varsize(struct mtd_info *, struct erase_info *); static void cfi_amdstd_sync (struct mtd_info *); static int cfi_amdstd_suspend (struct mtd_info *); static void cfi_amdstd_resume (struct mtd_info *); +static int cfi_amdstd_reboot(struct notifier_block *, unsigned long, void *); static int cfi_amdstd_secsi_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *); +static int cfi_amdstd_panic_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf); + static void cfi_amdstd_destroy(struct mtd_info *); struct mtd_info *cfi_cmdset_0002(struct map_info *, int); @@ -70,8 +72,12 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr); #include "fwh_lock.h" -static int cfi_atmel_lock(struct mtd_info *mtd, loff_t ofs, size_t len); -static int cfi_atmel_unlock(struct mtd_info *mtd, loff_t ofs, size_t len); +static int cfi_atmel_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len); +static int cfi_atmel_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len); + +static int cfi_ppb_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len); +static int cfi_ppb_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len); +static int cfi_ppb_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len); static struct mtd_chip_driver cfi_amdstd_chipdrv = { .probe = NULL, /* Not usable directly */ @@ -136,7 +142,7 @@ static void cfi_tell_features(struct cfi_pri_amdstd *extp) #ifdef AMD_BOOTLOC_BUG /* Wheee. Bring me the head of someone at AMD. */ -static void fixup_amd_bootblock(struct mtd_info *mtd, void* param) +static void fixup_amd_bootblock(struct mtd_info *mtd) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; @@ -146,28 +152,57 @@ static void fixup_amd_bootblock(struct mtd_info *mtd, void* param) if (((major << 8) | minor) < 0x3131) { /* CFI version 1.0 => don't trust bootloc */ + + pr_debug("%s: JEDEC Vendor ID is 0x%02X Device ID is 0x%02X\n", + map->name, cfi->mfr, cfi->id); + + /* AFAICS all 29LV400 with a bottom boot block have a device ID + * of 0x22BA in 16-bit mode and 0xBA in 8-bit mode. + * These were badly detected as they have the 0x80 bit set + * so treat them as a special case. + */ + if (((cfi->id == 0xBA) || (cfi->id == 0x22BA)) && + + /* Macronix added CFI to their 2nd generation + * MX29LV400C B/T but AFAICS no other 29LV400 (AMD, + * Fujitsu, Spansion, EON, ESI and older Macronix) + * has CFI. + * + * Therefore also check the manufacturer. + * This reduces the risk of false detection due to + * the 8-bit device ID. + */ + (cfi->mfr == CFI_MFR_MACRONIX)) { + pr_debug("%s: Macronix MX29LV400C with bottom boot block" + " detected\n", map->name); + extp->TopBottom = 2; /* bottom boot */ + } else if (cfi->id & 0x80) { printk(KERN_WARNING "%s: JEDEC Device ID is 0x%02X. Assuming broken CFI table.\n", map->name, cfi->id); extp->TopBottom = 3; /* top boot */ } else { extp->TopBottom = 2; /* bottom boot */ } + + pr_debug("%s: AMD CFI PRI V%c.%c has no boot block field;" + " deduced %s from Device ID\n", map->name, major, minor, + extp->TopBottom == 2 ? "bottom" : "top"); } } #endif -static void fixup_use_write_buffers(struct mtd_info *mtd, void *param) +static void fixup_use_write_buffers(struct mtd_info *mtd) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; if (cfi->cfiq->BufWriteTimeoutTyp) { - DEBUG(MTD_DEBUG_LEVEL1, "Using buffer write method\n" ); - mtd->write = cfi_amdstd_write_buffers; + pr_debug("Using buffer write method\n" ); + mtd->_write = cfi_amdstd_write_buffers; } } /* Atmel chips don't use the same PRI format as AMD chips */ -static void fixup_convert_atmel_pri(struct mtd_info *mtd, void *param) +static void fixup_convert_atmel_pri(struct mtd_info *mtd) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; @@ -180,26 +215,38 @@ static void fixup_convert_atmel_pri(struct mtd_info *mtd, void *param) if (atmel_pri.Features & 0x02) extp->EraseSuspend = 2; - if (atmel_pri.BottomBoot) - extp->TopBottom = 2; - else - extp->TopBottom = 3; + /* Some chips got it backwards... */ + if (cfi->id == AT49BV6416) { + if (atmel_pri.BottomBoot) + extp->TopBottom = 3; + else + extp->TopBottom = 2; + } else { + if (atmel_pri.BottomBoot) + extp->TopBottom = 2; + else + extp->TopBottom = 3; + } + + /* burst write mode not supported */ + cfi->cfiq->BufWriteTimeoutTyp = 0; + cfi->cfiq->BufWriteTimeoutMax = 0; } -static void fixup_use_secsi(struct mtd_info *mtd, void *param) +static void fixup_use_secsi(struct mtd_info *mtd) { /* Setup for chips with a secsi area */ - mtd->read_user_prot_reg = cfi_amdstd_secsi_read; - mtd->read_fact_prot_reg = cfi_amdstd_secsi_read; + mtd->_read_user_prot_reg = cfi_amdstd_secsi_read; + mtd->_read_fact_prot_reg = cfi_amdstd_secsi_read; } -static void fixup_use_erase_chip(struct mtd_info *mtd, void *param) +static void fixup_use_erase_chip(struct mtd_info *mtd) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; if ((cfi->cfiq->NumEraseRegions == 1) && ((cfi->cfiq->EraseRegionInfo[0] & 0xffff) == 0)) { - mtd->erase = cfi_amdstd_erase_chip; + mtd->_erase = cfi_amdstd_erase_chip; } } @@ -208,33 +255,146 @@ static void fixup_use_erase_chip(struct mtd_info *mtd, void *param) * Some Atmel chips (e.g. the AT49BV6416) power-up with all sectors * locked by default. */ -static void fixup_use_atmel_lock(struct mtd_info *mtd, void *param) +static void fixup_use_atmel_lock(struct mtd_info *mtd) +{ + mtd->_lock = cfi_atmel_lock; + mtd->_unlock = cfi_atmel_unlock; + mtd->flags |= MTD_POWERUP_LOCK; +} + +static void fixup_old_sst_eraseregion(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + /* + * These flashes report two separate eraseblock regions based on the + * sector_erase-size and block_erase-size, although they both operate on the + * same memory. This is not allowed according to CFI, so we just pick the + * sector_erase-size. + */ + cfi->cfiq->NumEraseRegions = 1; +} + +static void fixup_sst39vf(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + fixup_old_sst_eraseregion(mtd); + + cfi->addr_unlock1 = 0x5555; + cfi->addr_unlock2 = 0x2AAA; +} + +static void fixup_sst39vf_rev_b(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + fixup_old_sst_eraseregion(mtd); + + cfi->addr_unlock1 = 0x555; + cfi->addr_unlock2 = 0x2AA; + + cfi->sector_erase_cmd = CMD(0x50); +} + +static void fixup_sst38vf640x_sectorsize(struct mtd_info *mtd) { - mtd->lock = cfi_atmel_lock; - mtd->unlock = cfi_atmel_unlock; - mtd->flags |= MTD_STUPID_LOCK; + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + fixup_sst39vf_rev_b(mtd); + + /* + * CFI reports 1024 sectors (0x03ff+1) of 64KBytes (0x0100*256) where + * it should report a size of 8KBytes (0x0020*256). + */ + cfi->cfiq->EraseRegionInfo[0] = 0x002003ff; + pr_warning("%s: Bad 38VF640x CFI data; adjusting sector size from 64 to 8KiB\n", mtd->name); +} + +static void fixup_s29gl064n_sectors(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + if ((cfi->cfiq->EraseRegionInfo[0] & 0xffff) == 0x003f) { + cfi->cfiq->EraseRegionInfo[0] |= 0x0040; + pr_warning("%s: Bad S29GL064N CFI data; adjust from 64 to 128 sectors\n", mtd->name); + } } +static void fixup_s29gl032n_sectors(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + if ((cfi->cfiq->EraseRegionInfo[1] & 0xffff) == 0x007e) { + cfi->cfiq->EraseRegionInfo[1] &= ~0x0040; + pr_warning("%s: Bad S29GL032N CFI data; adjust from 127 to 63 sectors\n", mtd->name); + } +} + +static void fixup_s29ns512p_sectors(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + /* + * S29NS512P flash uses more than 8bits to report number of sectors, + * which is not permitted by CFI. + */ + cfi->cfiq->EraseRegionInfo[0] = 0x020001ff; + pr_warning("%s: Bad S29NS512P CFI data; adjust to 512 sectors\n", mtd->name); +} + +/* Used to fix CFI-Tables of chips without Extended Query Tables */ +static struct cfi_fixup cfi_nopri_fixup_table[] = { + { CFI_MFR_SST, 0x234a, fixup_sst39vf }, /* SST39VF1602 */ + { CFI_MFR_SST, 0x234b, fixup_sst39vf }, /* SST39VF1601 */ + { CFI_MFR_SST, 0x235a, fixup_sst39vf }, /* SST39VF3202 */ + { CFI_MFR_SST, 0x235b, fixup_sst39vf }, /* SST39VF3201 */ + { CFI_MFR_SST, 0x235c, fixup_sst39vf_rev_b }, /* SST39VF3202B */ + { CFI_MFR_SST, 0x235d, fixup_sst39vf_rev_b }, /* SST39VF3201B */ + { CFI_MFR_SST, 0x236c, fixup_sst39vf_rev_b }, /* SST39VF6402B */ + { CFI_MFR_SST, 0x236d, fixup_sst39vf_rev_b }, /* SST39VF6401B */ + { 0, 0, NULL } +}; + static struct cfi_fixup cfi_fixup_table[] = { + { CFI_MFR_ATMEL, CFI_ID_ANY, fixup_convert_atmel_pri }, #ifdef AMD_BOOTLOC_BUG - { CFI_MFR_AMD, CFI_ID_ANY, fixup_amd_bootblock, NULL }, + { CFI_MFR_AMD, CFI_ID_ANY, fixup_amd_bootblock }, + { CFI_MFR_AMIC, CFI_ID_ANY, fixup_amd_bootblock }, + { CFI_MFR_MACRONIX, CFI_ID_ANY, fixup_amd_bootblock }, #endif - { CFI_MFR_AMD, 0x0050, fixup_use_secsi, NULL, }, - { CFI_MFR_AMD, 0x0053, fixup_use_secsi, NULL, }, - { CFI_MFR_AMD, 0x0055, fixup_use_secsi, NULL, }, - { CFI_MFR_AMD, 0x0056, fixup_use_secsi, NULL, }, - { CFI_MFR_AMD, 0x005C, fixup_use_secsi, NULL, }, - { CFI_MFR_AMD, 0x005F, fixup_use_secsi, NULL, }, + { CFI_MFR_AMD, 0x0050, fixup_use_secsi }, + { CFI_MFR_AMD, 0x0053, fixup_use_secsi }, + { CFI_MFR_AMD, 0x0055, fixup_use_secsi }, + { CFI_MFR_AMD, 0x0056, fixup_use_secsi }, + { CFI_MFR_AMD, 0x005C, fixup_use_secsi }, + { CFI_MFR_AMD, 0x005F, fixup_use_secsi }, + { CFI_MFR_AMD, 0x0c01, fixup_s29gl064n_sectors }, + { CFI_MFR_AMD, 0x1301, fixup_s29gl064n_sectors }, + { CFI_MFR_AMD, 0x1a00, fixup_s29gl032n_sectors }, + { CFI_MFR_AMD, 0x1a01, fixup_s29gl032n_sectors }, + { CFI_MFR_AMD, 0x3f00, fixup_s29ns512p_sectors }, + { CFI_MFR_SST, 0x536a, fixup_sst38vf640x_sectorsize }, /* SST38VF6402 */ + { CFI_MFR_SST, 0x536b, fixup_sst38vf640x_sectorsize }, /* SST38VF6401 */ + { CFI_MFR_SST, 0x536c, fixup_sst38vf640x_sectorsize }, /* SST38VF6404 */ + { CFI_MFR_SST, 0x536d, fixup_sst38vf640x_sectorsize }, /* SST38VF6403 */ #if !FORCE_WORD_WRITE - { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_write_buffers, NULL, }, + { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_write_buffers }, #endif - { CFI_MFR_ATMEL, CFI_ID_ANY, fixup_convert_atmel_pri, NULL }, - { 0, 0, NULL, NULL } + { 0, 0, NULL } }; static struct cfi_fixup jedec_fixup_table[] = { - { MANUFACTURER_SST, SST49LF004B, fixup_use_fwh_lock, NULL, }, - { MANUFACTURER_SST, SST49LF008A, fixup_use_fwh_lock, NULL, }, - { 0, 0, NULL, NULL } + { CFI_MFR_SST, SST49LF004B, fixup_use_fwh_lock }, + { CFI_MFR_SST, SST49LF040B, fixup_use_fwh_lock }, + { CFI_MFR_SST, SST49LF008A, fixup_use_fwh_lock }, + { 0, 0, NULL } }; static struct cfi_fixup fixup_table[] = { @@ -243,107 +403,217 @@ static struct cfi_fixup fixup_table[] = { * well. This table is to pick all cases where * we know that is the case. */ - { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_erase_chip, NULL }, - { CFI_MFR_ATMEL, AT49BV6416, fixup_use_atmel_lock, NULL }, - { 0, 0, NULL, NULL } + { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_erase_chip }, + { CFI_MFR_ATMEL, AT49BV6416, fixup_use_atmel_lock }, + { 0, 0, NULL } }; +static void cfi_fixup_major_minor(struct cfi_private *cfi, + struct cfi_pri_amdstd *extp) +{ + if (cfi->mfr == CFI_MFR_SAMSUNG) { + if ((extp->MajorVersion == '0' && extp->MinorVersion == '0') || + (extp->MajorVersion == '3' && extp->MinorVersion == '3')) { + /* + * Samsung K8P2815UQB and K8D6x16UxM chips + * report major=0 / minor=0. + * K8D3x16UxC chips report major=3 / minor=3. + */ + printk(KERN_NOTICE " Fixing Samsung's Amd/Fujitsu" + " Extended Query version to 1.%c\n", + extp->MinorVersion); + extp->MajorVersion = '1'; + } + } + + /* + * SST 38VF640x chips report major=0xFF / minor=0xFF. + */ + if (cfi->mfr == CFI_MFR_SST && (cfi->id >> 4) == 0x0536) { + extp->MajorVersion = '1'; + extp->MinorVersion = '0'; + } +} + +static int is_m29ew(struct cfi_private *cfi) +{ + if (cfi->mfr == CFI_MFR_INTEL && + ((cfi->device_type == CFI_DEVICETYPE_X8 && (cfi->id & 0xff) == 0x7e) || + (cfi->device_type == CFI_DEVICETYPE_X16 && cfi->id == 0x227e))) + return 1; + return 0; +} + +/* + * From TN-13-07: Patching the Linux Kernel and U-Boot for M29 Flash, page 20: + * Some revisions of the M29EW suffer from erase suspend hang ups. In + * particular, it can occur when the sequence + * Erase Confirm -> Suspend -> Program -> Resume + * causes a lockup due to internal timing issues. The consequence is that the + * erase cannot be resumed without inserting a dummy command after programming + * and prior to resuming. [...] The work-around is to issue a dummy write cycle + * that writes an F0 command code before the RESUME command. + */ +static void cfi_fixup_m29ew_erase_suspend(struct map_info *map, + unsigned long adr) +{ + struct cfi_private *cfi = map->fldrv_priv; + /* before resume, insert a dummy 0xF0 cycle for Micron M29EW devices */ + if (is_m29ew(cfi)) + map_write(map, CMD(0xF0), adr); +} + +/* + * From TN-13-07: Patching the Linux Kernel and U-Boot for M29 Flash, page 22: + * + * Some revisions of the M29EW (for example, A1 and A2 step revisions) + * are affected by a problem that could cause a hang up when an ERASE SUSPEND + * command is issued after an ERASE RESUME operation without waiting for a + * minimum delay. The result is that once the ERASE seems to be completed + * (no bits are toggling), the contents of the Flash memory block on which + * the erase was ongoing could be inconsistent with the expected values + * (typically, the array value is stuck to the 0xC0, 0xC4, 0x80, or 0x84 + * values), causing a consequent failure of the ERASE operation. + * The occurrence of this issue could be high, especially when file system + * operations on the Flash are intensive. As a result, it is recommended + * that a patch be applied. Intensive file system operations can cause many + * calls to the garbage routine to free Flash space (also by erasing physical + * Flash blocks) and as a result, many consecutive SUSPEND and RESUME + * commands can occur. The problem disappears when a delay is inserted after + * the RESUME command by using the udelay() function available in Linux. + * The DELAY value must be tuned based on the customer's platform. + * The maximum value that fixes the problem in all cases is 500us. + * But, in our experience, a delay of 30 µs to 50 µs is sufficient + * in most cases. + * We have chosen 500µs because this latency is acceptable. + */ +static void cfi_fixup_m29ew_delay_after_resume(struct cfi_private *cfi) +{ + /* + * Resolving the Delay After Resume Issue see Micron TN-13-07 + * Worst case delay must be 500µs but 30-50µs should be ok as well + */ + if (is_m29ew(cfi)) + cfi_udelay(500); +} + struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary) { struct cfi_private *cfi = map->fldrv_priv; + struct device_node __maybe_unused *np = map->device_node; struct mtd_info *mtd; int i; - mtd = kmalloc(sizeof(*mtd), GFP_KERNEL); - if (!mtd) { - printk(KERN_WARNING "Failed to allocate memory for MTD device\n"); + mtd = kzalloc(sizeof(*mtd), GFP_KERNEL); + if (!mtd) return NULL; - } - memset(mtd, 0, sizeof(*mtd)); mtd->priv = map; mtd->type = MTD_NORFLASH; /* Fill in the default mtd operations */ - mtd->erase = cfi_amdstd_erase_varsize; - mtd->write = cfi_amdstd_write_words; - mtd->read = cfi_amdstd_read; - mtd->sync = cfi_amdstd_sync; - mtd->suspend = cfi_amdstd_suspend; - mtd->resume = cfi_amdstd_resume; + mtd->_erase = cfi_amdstd_erase_varsize; + mtd->_write = cfi_amdstd_write_words; + mtd->_read = cfi_amdstd_read; + mtd->_sync = cfi_amdstd_sync; + mtd->_suspend = cfi_amdstd_suspend; + mtd->_resume = cfi_amdstd_resume; mtd->flags = MTD_CAP_NORFLASH; mtd->name = map->name; mtd->writesize = 1; + mtd->writebufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize; + + pr_debug("MTD %s(): write buffer size %d\n", __func__, + mtd->writebufsize); + + mtd->_panic_write = cfi_amdstd_panic_write; + mtd->reboot_notifier.notifier_call = cfi_amdstd_reboot; if (cfi->cfi_mode==CFI_MODE_CFI){ unsigned char bootloc; - /* - * It's a real CFI chip, not one for which the probe - * routine faked a CFI structure. So we read the feature - * table from it. - */ __u16 adr = primary?cfi->cfiq->P_ADR:cfi->cfiq->A_ADR; struct cfi_pri_amdstd *extp; extp = (struct cfi_pri_amdstd*)cfi_read_pri(map, adr, sizeof(*extp), "Amd/Fujitsu"); - if (!extp) { - kfree(mtd); - return NULL; - } + if (extp) { + /* + * It's a real CFI chip, not one for which the probe + * routine faked a CFI structure. + */ + cfi_fixup_major_minor(cfi, extp); - if (extp->MajorVersion != '1' || - (extp->MinorVersion < '0' || extp->MinorVersion > '4')) { - printk(KERN_ERR " Unknown Amd/Fujitsu Extended Query " - "version %c.%c.\n", extp->MajorVersion, - extp->MinorVersion); - kfree(extp); - kfree(mtd); - return NULL; - } + /* + * Valid primary extension versions are: 1.0, 1.1, 1.2, 1.3, 1.4, 1.5 + * see: http://cs.ozerki.net/zap/pub/axim-x5/docs/cfi_r20.pdf, page 19 + * http://www.spansion.com/Support/AppNotes/cfi_100_20011201.pdf + * http://www.spansion.com/Support/Datasheets/s29ws-p_00_a12_e.pdf + * http://www.spansion.com/Support/Datasheets/S29GL_128S_01GS_00_02_e.pdf + */ + if (extp->MajorVersion != '1' || + (extp->MajorVersion == '1' && (extp->MinorVersion < '0' || extp->MinorVersion > '5'))) { + printk(KERN_ERR " Unknown Amd/Fujitsu Extended Query " + "version %c.%c (%#02x/%#02x).\n", + extp->MajorVersion, extp->MinorVersion, + extp->MajorVersion, extp->MinorVersion); + kfree(extp); + kfree(mtd); + return NULL; + } + + printk(KERN_INFO " Amd/Fujitsu Extended Query version %c.%c.\n", + extp->MajorVersion, extp->MinorVersion); - /* Install our own private info structure */ - cfi->cmdset_priv = extp; + /* Install our own private info structure */ + cfi->cmdset_priv = extp; - /* Apply cfi device specific fixups */ - cfi_fixup(mtd, cfi_fixup_table); + /* Apply cfi device specific fixups */ + cfi_fixup(mtd, cfi_fixup_table); #ifdef DEBUG_CFI_FEATURES - /* Tell the user about it in lots of lovely detail */ - cfi_tell_features(extp); + /* Tell the user about it in lots of lovely detail */ + cfi_tell_features(extp); #endif - bootloc = extp->TopBottom; - if ((bootloc != 2) && (bootloc != 3)) { - printk(KERN_WARNING "%s: CFI does not contain boot " - "bank location. Assuming top.\n", map->name); - bootloc = 2; - } +#ifdef CONFIG_OF + if (np && of_property_read_bool( + np, "use-advanced-sector-protection") + && extp->BlkProtUnprot == 8) { + printk(KERN_INFO " Advanced Sector Protection (PPB Locking) supported\n"); + mtd->_lock = cfi_ppb_lock; + mtd->_unlock = cfi_ppb_unlock; + mtd->_is_locked = cfi_ppb_is_locked; + } +#endif + + bootloc = extp->TopBottom; + if ((bootloc < 2) || (bootloc > 5)) { + printk(KERN_WARNING "%s: CFI contains unrecognised boot " + "bank location (%d). Assuming bottom.\n", + map->name, bootloc); + bootloc = 2; + } - if (bootloc == 3 && cfi->cfiq->NumEraseRegions > 1) { - printk(KERN_WARNING "%s: Swapping erase regions for broken CFI table.\n", map->name); + if (bootloc == 3 && cfi->cfiq->NumEraseRegions > 1) { + printk(KERN_WARNING "%s: Swapping erase regions for top-boot CFI table.\n", map->name); - for (i=0; i<cfi->cfiq->NumEraseRegions / 2; i++) { - int j = (cfi->cfiq->NumEraseRegions-1)-i; - __u32 swap; + for (i=0; i<cfi->cfiq->NumEraseRegions / 2; i++) { + int j = (cfi->cfiq->NumEraseRegions-1)-i; + __u32 swap; - swap = cfi->cfiq->EraseRegionInfo[i]; - cfi->cfiq->EraseRegionInfo[i] = cfi->cfiq->EraseRegionInfo[j]; - cfi->cfiq->EraseRegionInfo[j] = swap; + swap = cfi->cfiq->EraseRegionInfo[i]; + cfi->cfiq->EraseRegionInfo[i] = cfi->cfiq->EraseRegionInfo[j]; + cfi->cfiq->EraseRegionInfo[j] = swap; + } } + /* Set the default CFI lock/unlock addresses */ + cfi->addr_unlock1 = 0x555; + cfi->addr_unlock2 = 0x2aa; } - /* Set the default CFI lock/unlock addresses */ - cfi->addr_unlock1 = 0x555; - cfi->addr_unlock2 = 0x2aa; - /* Modify the unlock address if we are in compatibility mode */ - if ( /* x16 in x8 mode */ - ((cfi->device_type == CFI_DEVICETYPE_X8) && - (cfi->cfiq->InterfaceDesc == 2)) || - /* x32 in x16 mode */ - ((cfi->device_type == CFI_DEVICETYPE_X16) && - (cfi->cfiq->InterfaceDesc == 4))) - { - cfi->addr_unlock1 = 0xaaa; - cfi->addr_unlock2 = 0x555; + cfi_fixup(mtd, cfi_nopri_fixup_table); + + if (!cfi->addr_unlock1 || !cfi->addr_unlock2) { + kfree(mtd); + return NULL; } } /* CFI mode */ @@ -358,13 +628,19 @@ struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary) cfi->chips[i].word_write_time = 1<<cfi->cfiq->WordWriteTimeoutTyp; cfi->chips[i].buffer_write_time = 1<<cfi->cfiq->BufWriteTimeoutTyp; cfi->chips[i].erase_time = 1<<cfi->cfiq->BlockEraseTimeoutTyp; + cfi->chips[i].ref_point_counter = 0; + init_waitqueue_head(&(cfi->chips[i].wq)); } map->fldrv = &cfi_amdstd_chipdrv; return cfi_amdstd_setup(mtd); } +struct mtd_info *cfi_cmdset_0006(struct map_info *map, int primary) __attribute__((alias("cfi_cmdset_0002"))); +struct mtd_info *cfi_cmdset_0701(struct map_info *map, int primary) __attribute__((alias("cfi_cmdset_0002"))); EXPORT_SYMBOL_GPL(cfi_cmdset_0002); +EXPORT_SYMBOL_GPL(cfi_cmdset_0006); +EXPORT_SYMBOL_GPL(cfi_cmdset_0701); static struct mtd_info *cfi_amdstd_setup(struct mtd_info *mtd) { @@ -382,10 +658,8 @@ static struct mtd_info *cfi_amdstd_setup(struct mtd_info *mtd) mtd->numeraseregions = cfi->cfiq->NumEraseRegions * cfi->numchips; mtd->eraseregions = kmalloc(sizeof(struct mtd_erase_region_info) * mtd->numeraseregions, GFP_KERNEL); - if (!mtd->eraseregions) { - printk(KERN_WARNING "Failed to allocate memory for MTD erase region info\n"); + if (!mtd->eraseregions) goto setup_err; - } for (i=0; i<cfi->cfiq->NumEraseRegions; i++) { unsigned long ernum, ersize; @@ -407,28 +681,14 @@ static struct mtd_info *cfi_amdstd_setup(struct mtd_info *mtd) printk(KERN_WARNING "Sum of regions (%lx) != total size of set of interleaved chips (%lx)\n", offset, devsize); goto setup_err; } -#if 0 - // debug - for (i=0; i<mtd->numeraseregions;i++){ - printk("%d: offset=0x%x,size=0x%x,blocks=%d\n", - i,mtd->eraseregions[i].offset, - mtd->eraseregions[i].erasesize, - mtd->eraseregions[i].numblocks); - } -#endif - - /* FIXME: erase-suspend-program is broken. See - http://lists.infradead.org/pipermail/linux-mtd/2003-December/009001.html */ - printk(KERN_NOTICE "cfi_cmdset_0002: Disabling erase-suspend-program due to code brokenness.\n"); __module_get(THIS_MODULE); + register_reboot_notifier(&mtd->reboot_notifier); return mtd; setup_err: - if(mtd) { - kfree(mtd->eraseregions); - kfree(mtd); - } + kfree(mtd->eraseregions); + kfree(mtd); kfree(cfi->cmdset_priv); kfree(cfi->cfiq); return NULL; @@ -442,8 +702,8 @@ static struct mtd_info *cfi_amdstd_setup(struct mtd_info *mtd) * * Note that anything more complicated than checking if no bits are toggling * (including checking DQ5 for an error status) is tricky to get working - * correctly and is therefore not done (particulary with interleaved chips - * as each chip must be checked independantly of the others). + * correctly and is therefore not done (particularly with interleaved chips + * as each chip must be checked independently of the others). */ static int __xipram chip_ready(struct map_info *map, unsigned long addr) { @@ -466,8 +726,8 @@ static int __xipram chip_ready(struct map_info *map, unsigned long addr) * * Note that anything more complicated than checking if no bits are toggling * (including checking DQ5 for an error status) is tricky to get working - * correctly and is therefore not done (particulary with interleaved chips - * as each chip must be checked independantly of the others). + * correctly and is therefore not done (particularly with interleaved chips + * as each chip must be checked independently of the others). * */ static int __xipram chip_good(struct map_info *map, unsigned long addr, map_word expected) @@ -500,12 +760,11 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr if (time_after(jiffies, timeo)) { printk(KERN_ERR "Waiting for chip to be ready timed out.\n"); - spin_unlock(chip->mutex); return -EIO; } - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); cfi_udelay(1); - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); /* Someone else might have been playing with it. */ goto retry; } @@ -516,13 +775,9 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr return 0; case FL_ERASING: - if (mode == FL_WRITING) /* FIXME: Erase-suspend-program appears broken. */ - goto sleep; - - if (!(mode == FL_READY || mode == FL_POINT - || !cfip - || (mode == FL_WRITING && (cfip->EraseSuspend & 0x2)) - || (mode == FL_WRITING && (cfip->EraseSuspend & 0x1)))) + if (!cfip || !(cfip->EraseSuspend & (0x1|0x2)) || + !(mode == FL_READY || mode == FL_POINT || + (mode == FL_WRITING && (cfip->EraseSuspend & 0x2)))) goto sleep; /* We could check to see if we're trying to access the sector @@ -546,16 +801,14 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr * there was an error (so leave the erase * routine to recover from it) or we trying to * use the erase-in-progress sector. */ - map_write(map, CMD(0x30), chip->in_progress_block_addr); - chip->state = FL_ERASING; - chip->oldstate = FL_READY; + put_chip(map, chip, adr); printk(KERN_ERR "MTD %s(): chip not ready after erase suspend\n", __func__); return -EIO; } - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); cfi_udelay(1); - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); /* Nobody will touch it while it's in state FL_ERASE_SUSPENDING. So we can just loop here. */ } @@ -570,6 +823,10 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr chip->state = FL_READY; return 0; + case FL_SHUTDOWN: + /* The machine is rebooting */ + return -EIO; + case FL_POINT: /* Only if there's no operation suspended... */ if (mode == FL_READY && chip->oldstate == FL_READY) @@ -579,10 +836,10 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr sleep: set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&chip->wq, &wait); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); schedule(); remove_wait_queue(&chip->wq, &wait); - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); goto resettime; } } @@ -594,8 +851,10 @@ static void put_chip(struct map_info *map, struct flchip *chip, unsigned long ad switch(chip->oldstate) { case FL_ERASING: - chip->state = chip->oldstate; - map_write(map, CMD(0x30), chip->in_progress_block_addr); + cfi_fixup_m29ew_erase_suspend(map, + chip->in_progress_block_addr); + map_write(map, cfi->sector_erase_cmd, chip->in_progress_block_addr); + cfi_fixup_m29ew_delay_after_resume(cfi); chip->oldstate = FL_READY; chip->state = FL_ERASING; break; @@ -607,8 +866,6 @@ static void put_chip(struct map_info *map, struct flchip *chip, unsigned long ad case FL_READY: case FL_STATUS: - /* We should really make set_vpp() count, rather than doing this */ - DISABLE_VPP(map); break; default: printk(KERN_ERR "MTD: put_chip() called with oldstate %d!!\n", chip->oldstate); @@ -712,10 +969,10 @@ static void __xipram xip_udelay(struct map_info *map, struct flchip *chip, chip->erase_suspended = 1; map_write(map, CMD(0xf0), adr); (void) map_read(map, adr); - asm volatile (".rep 8; nop; .endr"); + xip_iprefetch(); local_irq_enable(); - spin_unlock(chip->mutex); - asm volatile (".rep 8; nop; .endr"); + mutex_unlock(&chip->mutex); + xip_iprefetch(); cond_resched(); /* @@ -724,21 +981,23 @@ static void __xipram xip_udelay(struct map_info *map, struct flchip *chip, * a suspended erase state. If so let's wait * until it's done. */ - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); while (chip->state != FL_XIP_WHILE_ERASING) { DECLARE_WAITQUEUE(wait, current); set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&chip->wq, &wait); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); schedule(); remove_wait_queue(&chip->wq, &wait); - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); } /* Disallow XIP again */ local_irq_disable(); + /* Correct Erase Suspend Hangups for M29EW */ + cfi_fixup_m29ew_erase_suspend(map, adr); /* Resume the write or erase operation */ - map_write(map, CMD(0x30), adr); + map_write(map, cfi->sector_erase_cmd, adr); chip->state = oldstate; start = xip_currtime(); } else if (usec >= 1000000/HZ) { @@ -794,17 +1053,17 @@ static void __xipram xip_udelay(struct map_info *map, struct flchip *chip, #define UDELAY(map, chip, adr, usec) \ do { \ - spin_unlock(chip->mutex); \ + mutex_unlock(&chip->mutex); \ cfi_udelay(usec); \ - spin_lock(chip->mutex); \ + mutex_lock(&chip->mutex); \ } while (0) #define INVALIDATE_CACHE_UDELAY(map, chip, adr, len, usec) \ do { \ - spin_unlock(chip->mutex); \ + mutex_unlock(&chip->mutex); \ INVALIDATE_CACHED_RANGE(map, adr, len); \ cfi_udelay(usec); \ - spin_lock(chip->mutex); \ + mutex_lock(&chip->mutex); \ } while (0) #endif @@ -820,10 +1079,10 @@ static inline int do_read_onechip(struct map_info *map, struct flchip *chip, lof /* Ensure cmd read/writes are aligned. */ cmd_addr = adr & ~(map_bankwidth(map)-1); - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); ret = get_chip(map, chip, cmd_addr, FL_READY); if (ret) { - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return ret; } @@ -836,7 +1095,7 @@ static inline int do_read_onechip(struct map_info *map, struct flchip *chip, lof put_chip(map, chip, cmd_addr); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return 0; } @@ -850,13 +1109,9 @@ static int cfi_amdstd_read (struct mtd_info *mtd, loff_t from, size_t len, size_ int ret = 0; /* ofs: offset within the first chip that the first read should start */ - chipnum = (from >> cfi->chipshift); ofs = from - (chipnum << cfi->chipshift); - - *retlen = 0; - while (len) { unsigned long thislen; @@ -890,23 +1145,16 @@ static inline int do_read_secsi_onechip(struct map_info *map, struct flchip *chi struct cfi_private *cfi = map->fldrv_priv; retry: - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); if (chip->state != FL_READY){ -#if 0 - printk(KERN_DEBUG "Waiting for chip to read, status = %d\n", chip->state); -#endif set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&chip->wq, &wait); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); schedule(); remove_wait_queue(&chip->wq, &wait); -#if 0 - if(signal_pending(current)) - return -EINTR; -#endif timeo = jiffies + HZ; goto retry; @@ -928,7 +1176,7 @@ static inline int do_read_secsi_onechip(struct map_info *map, struct flchip *chi cfi_send_gen_cmd(0x00, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); wake_up(&chip->wq); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return 0; } @@ -941,16 +1189,11 @@ static int cfi_amdstd_secsi_read (struct mtd_info *mtd, loff_t from, size_t len, int chipnum; int ret = 0; - /* ofs: offset within the first chip that the first read should start */ - /* 8 secsi bytes per chip */ chipnum=from>>3; ofs=from & 7; - - *retlen = 0; - while (len) { unsigned long thislen; @@ -997,14 +1240,14 @@ static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip, adr += chip->start; - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); ret = get_chip(map, chip, adr, FL_WRITING); if (ret) { - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return ret; } - DEBUG( MTD_DEBUG_LEVEL3, "MTD %s(): WRITE 0x%.8lx(0x%.8lx)\n", + pr_debug("MTD %s(): WRITE 0x%.8lx(0x%.8lx)\n", __func__, adr, datum.x[0] ); /* @@ -1015,7 +1258,7 @@ static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip, */ oldd = map_read(map, adr); if (map_word_equal(map, oldd, datum)) { - DEBUG( MTD_DEBUG_LEVEL3, "MTD %s(): NOP\n", + pr_debug("MTD %s(): NOP\n", __func__); goto op_done; } @@ -1043,11 +1286,11 @@ static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip, set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&chip->wq, &wait); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); schedule(); remove_wait_queue(&chip->wq, &wait); timeo = jiffies + (HZ / 2); /* FIXME */ - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); continue; } @@ -1078,8 +1321,9 @@ static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip, xip_enable(map, chip, adr); op_done: chip->state = FL_READY; + DISABLE_VPP(map); put_chip(map, chip, adr); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return ret; } @@ -1095,10 +1339,6 @@ static int cfi_amdstd_write_words(struct mtd_info *mtd, loff_t to, size_t len, unsigned long ofs, chipstart; DECLARE_WAITQUEUE(wait, current); - *retlen = 0; - if (!len) - return 0; - chipnum = to >> cfi->chipshift; ofs = to - (chipnum << cfi->chipshift); chipstart = cfi->chips[chipnum].start; @@ -1111,30 +1351,23 @@ static int cfi_amdstd_write_words(struct mtd_info *mtd, loff_t to, size_t len, map_word tmp_buf; retry: - spin_lock(cfi->chips[chipnum].mutex); + mutex_lock(&cfi->chips[chipnum].mutex); if (cfi->chips[chipnum].state != FL_READY) { -#if 0 - printk(KERN_DEBUG "Waiting for chip to write, status = %d\n", cfi->chips[chipnum].state); -#endif set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&cfi->chips[chipnum].wq, &wait); - spin_unlock(cfi->chips[chipnum].mutex); + mutex_unlock(&cfi->chips[chipnum].mutex); schedule(); remove_wait_queue(&cfi->chips[chipnum].wq, &wait); -#if 0 - if(signal_pending(current)) - return -EINTR; -#endif goto retry; } /* Load 'tmp_buf' with old contents of flash */ tmp_buf = map_read(map, bus_ofs+chipstart); - spin_unlock(cfi->chips[chipnum].mutex); + mutex_unlock(&cfi->chips[chipnum].mutex); /* Number of bytes to copy from buffer */ n = min_t(int, len, map_bankwidth(map)-i); @@ -1189,29 +1422,22 @@ static int cfi_amdstd_write_words(struct mtd_info *mtd, loff_t to, size_t len, map_word tmp_buf; retry1: - spin_lock(cfi->chips[chipnum].mutex); + mutex_lock(&cfi->chips[chipnum].mutex); if (cfi->chips[chipnum].state != FL_READY) { -#if 0 - printk(KERN_DEBUG "Waiting for chip to write, status = %d\n", cfi->chips[chipnum].state); -#endif set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&cfi->chips[chipnum].wq, &wait); - spin_unlock(cfi->chips[chipnum].mutex); + mutex_unlock(&cfi->chips[chipnum].mutex); schedule(); remove_wait_queue(&cfi->chips[chipnum].wq, &wait); -#if 0 - if(signal_pending(current)) - return -EINTR; -#endif goto retry1; } tmp_buf = map_read(map, ofs + chipstart); - spin_unlock(cfi->chips[chipnum].mutex); + mutex_unlock(&cfi->chips[chipnum].mutex); tmp_buf = map_word_load_partial(map, tmp_buf, buf, 0, len); @@ -1246,16 +1472,16 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, adr += chip->start; cmd_adr = adr; - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); ret = get_chip(map, chip, adr, FL_WRITING); if (ret) { - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return ret; } datum = map_word_load(map, buf); - DEBUG( MTD_DEBUG_LEVEL3, "MTD %s(): WRITE 0x%.8lx(0x%.8lx)\n", + pr_debug("MTD %s(): WRITE 0x%.8lx(0x%.8lx)\n", __func__, adr, datum.x[0] ); XIP_INVAL_CACHED_RANGE(map, adr, len); @@ -1264,7 +1490,6 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL); - //cfi_send_gen_cmd(0xA0, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); /* Write Buffer Load */ map_write(map, CMD(0x25), cmd_adr); @@ -1304,11 +1529,11 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&chip->wq, &wait); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); schedule(); remove_wait_queue(&chip->wq, &wait); timeo = jiffies + (HZ / 2); /* FIXME */ - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); continue; } @@ -1324,19 +1549,32 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, UDELAY(map, chip, adr, 1); } - /* reset on all failures. */ - map_write( map, CMD(0xF0), chip->start ); + /* + * Recovery from write-buffer programming failures requires + * the write-to-buffer-reset sequence. Since the last part + * of the sequence also works as a normal reset, we can run + * the same commands regardless of why we are here. + * See e.g. + * http://www.spansion.com/Support/Application%20Notes/MirrorBit_Write_Buffer_Prog_Page_Buffer_Read_AN.pdf + */ + cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, + cfi->device_type, NULL); + cfi_send_gen_cmd(0xF0, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); xip_enable(map, chip, adr); /* FIXME - should have reset delay before continuing */ - printk(KERN_WARNING "MTD %s(): software timeout\n", - __func__ ); + printk(KERN_WARNING "MTD %s(): software timeout, address:0x%.8lx.\n", + __func__, adr); ret = -EIO; op_done: chip->state = FL_READY; + DISABLE_VPP(map); put_chip(map, chip, adr); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return ret; } @@ -1352,10 +1590,6 @@ static int cfi_amdstd_write_buffers(struct mtd_info *mtd, loff_t to, size_t len, int chipnum; unsigned long ofs; - *retlen = 0; - if (!len) - return 0; - chipnum = to >> cfi->chipshift; ofs = to - (chipnum << cfi->chipshift); @@ -1421,6 +1655,238 @@ static int cfi_amdstd_write_buffers(struct mtd_info *mtd, loff_t to, size_t len, return 0; } +/* + * Wait for the flash chip to become ready to write data + * + * This is only called during the panic_write() path. When panic_write() + * is called, the kernel is in the process of a panic, and will soon be + * dead. Therefore we don't take any locks, and attempt to get access + * to the chip as soon as possible. + */ +static int cfi_amdstd_panic_wait(struct map_info *map, struct flchip *chip, + unsigned long adr) +{ + struct cfi_private *cfi = map->fldrv_priv; + int retries = 10; + int i; + + /* + * If the driver thinks the chip is idle, and no toggle bits + * are changing, then the chip is actually idle for sure. + */ + if (chip->state == FL_READY && chip_ready(map, adr)) + return 0; + + /* + * Try several times to reset the chip and then wait for it + * to become idle. The upper limit of a few milliseconds of + * delay isn't a big problem: the kernel is dying anyway. It + * is more important to save the messages. + */ + while (retries > 0) { + const unsigned long timeo = (HZ / 1000) + 1; + + /* send the reset command */ + map_write(map, CMD(0xF0), chip->start); + + /* wait for the chip to become ready */ + for (i = 0; i < jiffies_to_usecs(timeo); i++) { + if (chip_ready(map, adr)) + return 0; + + udelay(1); + } + } + + /* the chip never became ready */ + return -EBUSY; +} + +/* + * Write out one word of data to a single flash chip during a kernel panic + * + * This is only called during the panic_write() path. When panic_write() + * is called, the kernel is in the process of a panic, and will soon be + * dead. Therefore we don't take any locks, and attempt to get access + * to the chip as soon as possible. + * + * The implementation of this routine is intentionally similar to + * do_write_oneword(), in order to ease code maintenance. + */ +static int do_panic_write_oneword(struct map_info *map, struct flchip *chip, + unsigned long adr, map_word datum) +{ + const unsigned long uWriteTimeout = (HZ / 1000) + 1; + struct cfi_private *cfi = map->fldrv_priv; + int retry_cnt = 0; + map_word oldd; + int ret = 0; + int i; + + adr += chip->start; + + ret = cfi_amdstd_panic_wait(map, chip, adr); + if (ret) + return ret; + + pr_debug("MTD %s(): PANIC WRITE 0x%.8lx(0x%.8lx)\n", + __func__, adr, datum.x[0]); + + /* + * Check for a NOP for the case when the datum to write is already + * present - it saves time and works around buggy chips that corrupt + * data at other locations when 0xff is written to a location that + * already contains 0xff. + */ + oldd = map_read(map, adr); + if (map_word_equal(map, oldd, datum)) { + pr_debug("MTD %s(): NOP\n", __func__); + goto op_done; + } + + ENABLE_VPP(map); + +retry: + cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0xA0, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); + map_write(map, datum, adr); + + for (i = 0; i < jiffies_to_usecs(uWriteTimeout); i++) { + if (chip_ready(map, adr)) + break; + + udelay(1); + } + + if (!chip_good(map, adr, datum)) { + /* reset on all failures. */ + map_write(map, CMD(0xF0), chip->start); + /* FIXME - should have reset delay before continuing */ + + if (++retry_cnt <= MAX_WORD_RETRIES) + goto retry; + + ret = -EIO; + } + +op_done: + DISABLE_VPP(map); + return ret; +} + +/* + * Write out some data during a kernel panic + * + * This is used by the mtdoops driver to save the dying messages from a + * kernel which has panic'd. + * + * This routine ignores all of the locking used throughout the rest of the + * driver, in order to ensure that the data gets written out no matter what + * state this driver (and the flash chip itself) was in when the kernel crashed. + * + * The implementation of this routine is intentionally similar to + * cfi_amdstd_write_words(), in order to ease code maintenance. + */ +static int cfi_amdstd_panic_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + unsigned long ofs, chipstart; + int ret = 0; + int chipnum; + + chipnum = to >> cfi->chipshift; + ofs = to - (chipnum << cfi->chipshift); + chipstart = cfi->chips[chipnum].start; + + /* If it's not bus aligned, do the first byte write */ + if (ofs & (map_bankwidth(map) - 1)) { + unsigned long bus_ofs = ofs & ~(map_bankwidth(map) - 1); + int i = ofs - bus_ofs; + int n = 0; + map_word tmp_buf; + + ret = cfi_amdstd_panic_wait(map, &cfi->chips[chipnum], bus_ofs); + if (ret) + return ret; + + /* Load 'tmp_buf' with old contents of flash */ + tmp_buf = map_read(map, bus_ofs + chipstart); + + /* Number of bytes to copy from buffer */ + n = min_t(int, len, map_bankwidth(map) - i); + + tmp_buf = map_word_load_partial(map, tmp_buf, buf, i, n); + + ret = do_panic_write_oneword(map, &cfi->chips[chipnum], + bus_ofs, tmp_buf); + if (ret) + return ret; + + ofs += n; + buf += n; + (*retlen) += n; + len -= n; + + if (ofs >> cfi->chipshift) { + chipnum++; + ofs = 0; + if (chipnum == cfi->numchips) + return 0; + } + } + + /* We are now aligned, write as much as possible */ + while (len >= map_bankwidth(map)) { + map_word datum; + + datum = map_word_load(map, buf); + + ret = do_panic_write_oneword(map, &cfi->chips[chipnum], + ofs, datum); + if (ret) + return ret; + + ofs += map_bankwidth(map); + buf += map_bankwidth(map); + (*retlen) += map_bankwidth(map); + len -= map_bankwidth(map); + + if (ofs >> cfi->chipshift) { + chipnum++; + ofs = 0; + if (chipnum == cfi->numchips) + return 0; + + chipstart = cfi->chips[chipnum].start; + } + } + + /* Write the trailing bytes if any */ + if (len & (map_bankwidth(map) - 1)) { + map_word tmp_buf; + + ret = cfi_amdstd_panic_wait(map, &cfi->chips[chipnum], ofs); + if (ret) + return ret; + + tmp_buf = map_read(map, ofs + chipstart); + + tmp_buf = map_word_load_partial(map, tmp_buf, buf, 0, len); + + ret = do_panic_write_oneword(map, &cfi->chips[chipnum], + ofs, tmp_buf); + if (ret) + return ret; + + (*retlen) += len; + } + + return 0; +} + /* * Handle devices with one erase region, that only implement @@ -1436,14 +1902,14 @@ static int __xipram do_erase_chip(struct map_info *map, struct flchip *chip) adr = cfi->addr_unlock1; - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); ret = get_chip(map, chip, adr, FL_WRITING); if (ret) { - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return ret; } - DEBUG( MTD_DEBUG_LEVEL3, "MTD %s(): ERASE 0x%.8lx\n", + pr_debug("MTD %s(): ERASE 0x%.8lx\n", __func__, chip->start ); XIP_INVAL_CACHED_RANGE(map, adr, map->size); @@ -1472,10 +1938,10 @@ static int __xipram do_erase_chip(struct map_info *map, struct flchip *chip) /* Someone's suspended the erase. Sleep */ set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&chip->wq, &wait); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); schedule(); remove_wait_queue(&chip->wq, &wait); - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); continue; } if (chip->erase_suspended) { @@ -1508,8 +1974,9 @@ static int __xipram do_erase_chip(struct map_info *map, struct flchip *chip) chip->state = FL_READY; xip_enable(map, chip, adr); + DISABLE_VPP(map); put_chip(map, chip, adr); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return ret; } @@ -1524,14 +1991,14 @@ static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip, adr += chip->start; - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); ret = get_chip(map, chip, adr, FL_ERASING); if (ret) { - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return ret; } - DEBUG( MTD_DEBUG_LEVEL3, "MTD %s(): ERASE 0x%.8lx\n", + pr_debug("MTD %s(): ERASE 0x%.8lx\n", __func__, adr ); XIP_INVAL_CACHED_RANGE(map, adr, len); @@ -1543,7 +2010,7 @@ static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip, cfi_send_gen_cmd(0x80, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL); - map_write(map, CMD(0x30), adr); + map_write(map, cfi->sector_erase_cmd, adr); chip->state = FL_ERASING; chip->erase_suspended = 0; @@ -1560,10 +2027,10 @@ static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip, /* Someone's suspended the erase. Sleep */ set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&chip->wq, &wait); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); schedule(); remove_wait_queue(&chip->wq, &wait); - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); continue; } if (chip->erase_suspended) { @@ -1598,13 +2065,14 @@ static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip, } chip->state = FL_READY; + DISABLE_VPP(map); put_chip(map, chip, adr); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return ret; } -int cfi_amdstd_erase_varsize(struct mtd_info *mtd, struct erase_info *instr) +static int cfi_amdstd_erase_varsize(struct mtd_info *mtd, struct erase_info *instr) { unsigned long ofs, len; int ret; @@ -1651,14 +2119,13 @@ static int do_atmel_lock(struct map_info *map, struct flchip *chip, struct cfi_private *cfi = map->fldrv_priv; int ret; - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); ret = get_chip(map, chip, adr + chip->start, FL_LOCKING); if (ret) goto out_unlock; chip->state = FL_LOCKING; - DEBUG(MTD_DEBUG_LEVEL3, "MTD %s(): LOCK 0x%08lx len %d\n", - __func__, adr, len); + pr_debug("MTD %s(): LOCK 0x%08lx len %d\n", __func__, adr, len); cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); @@ -1677,7 +2144,7 @@ static int do_atmel_lock(struct map_info *map, struct flchip *chip, ret = 0; out_unlock: - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return ret; } @@ -1687,14 +2154,13 @@ static int do_atmel_unlock(struct map_info *map, struct flchip *chip, struct cfi_private *cfi = map->fldrv_priv; int ret; - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); ret = get_chip(map, chip, adr + chip->start, FL_UNLOCKING); if (ret) goto out_unlock; chip->state = FL_UNLOCKING; - DEBUG(MTD_DEBUG_LEVEL3, "MTD %s(): LOCK 0x%08lx len %d\n", - __func__, adr, len); + pr_debug("MTD %s(): LOCK 0x%08lx len %d\n", __func__, adr, len); cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); @@ -1705,20 +2171,219 @@ static int do_atmel_unlock(struct map_info *map, struct flchip *chip, ret = 0; out_unlock: - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return ret; } -static int cfi_atmel_lock(struct mtd_info *mtd, loff_t ofs, size_t len) +static int cfi_atmel_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) { return cfi_varsize_frob(mtd, do_atmel_lock, ofs, len, NULL); } -static int cfi_atmel_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) +static int cfi_atmel_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) { return cfi_varsize_frob(mtd, do_atmel_unlock, ofs, len, NULL); } +/* + * Advanced Sector Protection - PPB (Persistent Protection Bit) locking + */ + +struct ppb_lock { + struct flchip *chip; + loff_t offset; + int locked; +}; + +#define MAX_SECTORS 512 + +#define DO_XXLOCK_ONEBLOCK_LOCK ((void *)1) +#define DO_XXLOCK_ONEBLOCK_UNLOCK ((void *)2) +#define DO_XXLOCK_ONEBLOCK_GETLOCK ((void *)3) + +static int __maybe_unused do_ppb_xxlock(struct map_info *map, + struct flchip *chip, + unsigned long adr, int len, void *thunk) +{ + struct cfi_private *cfi = map->fldrv_priv; + unsigned long timeo; + int ret; + + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, adr + chip->start, FL_LOCKING); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + + pr_debug("MTD %s(): XXLOCK 0x%08lx len %d\n", __func__, adr, len); + + cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, + cfi->device_type, NULL); + /* PPB entry command */ + cfi_send_gen_cmd(0xC0, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); + + if (thunk == DO_XXLOCK_ONEBLOCK_LOCK) { + chip->state = FL_LOCKING; + map_write(map, CMD(0xA0), chip->start + adr); + map_write(map, CMD(0x00), chip->start + adr); + } else if (thunk == DO_XXLOCK_ONEBLOCK_UNLOCK) { + /* + * Unlocking of one specific sector is not supported, so we + * have to unlock all sectors of this device instead + */ + chip->state = FL_UNLOCKING; + map_write(map, CMD(0x80), chip->start); + map_write(map, CMD(0x30), chip->start); + } else if (thunk == DO_XXLOCK_ONEBLOCK_GETLOCK) { + chip->state = FL_JEDEC_QUERY; + /* Return locked status: 0->locked, 1->unlocked */ + ret = !cfi_read_query(map, adr); + } else + BUG(); + + /* + * Wait for some time as unlocking of all sectors takes quite long + */ + timeo = jiffies + msecs_to_jiffies(2000); /* 2s max (un)locking */ + for (;;) { + if (chip_ready(map, adr)) + break; + + if (time_after(jiffies, timeo)) { + printk(KERN_ERR "Waiting for chip to be ready timed out.\n"); + ret = -EIO; + break; + } + + UDELAY(map, chip, adr, 1); + } + + /* Exit BC commands */ + map_write(map, CMD(0x90), chip->start); + map_write(map, CMD(0x00), chip->start); + + chip->state = FL_READY; + put_chip(map, chip, adr + chip->start); + mutex_unlock(&chip->mutex); + + return ret; +} + +static int __maybe_unused cfi_ppb_lock(struct mtd_info *mtd, loff_t ofs, + uint64_t len) +{ + return cfi_varsize_frob(mtd, do_ppb_xxlock, ofs, len, + DO_XXLOCK_ONEBLOCK_LOCK); +} + +static int __maybe_unused cfi_ppb_unlock(struct mtd_info *mtd, loff_t ofs, + uint64_t len) +{ + struct mtd_erase_region_info *regions = mtd->eraseregions; + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + struct ppb_lock *sect; + unsigned long adr; + loff_t offset; + uint64_t length; + int chipnum; + int i; + int sectors; + int ret; + + /* + * PPB unlocking always unlocks all sectors of the flash chip. + * We need to re-lock all previously locked sectors. So lets + * first check the locking status of all sectors and save + * it for future use. + */ + sect = kzalloc(MAX_SECTORS * sizeof(struct ppb_lock), GFP_KERNEL); + if (!sect) + return -ENOMEM; + + /* + * This code to walk all sectors is a slightly modified version + * of the cfi_varsize_frob() code. + */ + i = 0; + chipnum = 0; + adr = 0; + sectors = 0; + offset = 0; + length = mtd->size; + + while (length) { + int size = regions[i].erasesize; + + /* + * Only test sectors that shall not be unlocked. The other + * sectors shall be unlocked, so lets keep their locking + * status at "unlocked" (locked=0) for the final re-locking. + */ + if ((adr < ofs) || (adr >= (ofs + len))) { + sect[sectors].chip = &cfi->chips[chipnum]; + sect[sectors].offset = offset; + sect[sectors].locked = do_ppb_xxlock( + map, &cfi->chips[chipnum], adr, 0, + DO_XXLOCK_ONEBLOCK_GETLOCK); + } + + adr += size; + offset += size; + length -= size; + + if (offset == regions[i].offset + size * regions[i].numblocks) + i++; + + if (adr >> cfi->chipshift) { + adr = 0; + chipnum++; + + if (chipnum >= cfi->numchips) + break; + } + + sectors++; + if (sectors >= MAX_SECTORS) { + printk(KERN_ERR "Only %d sectors for PPB locking supported!\n", + MAX_SECTORS); + kfree(sect); + return -EINVAL; + } + } + + /* Now unlock the whole chip */ + ret = cfi_varsize_frob(mtd, do_ppb_xxlock, ofs, len, + DO_XXLOCK_ONEBLOCK_UNLOCK); + if (ret) { + kfree(sect); + return ret; + } + + /* + * PPB unlocking always unlocks all sectors of the flash chip. + * We need to re-lock all previously locked sectors. + */ + for (i = 0; i < sectors; i++) { + if (sect[i].locked) + do_ppb_xxlock(map, sect[i].chip, sect[i].offset, 0, + DO_XXLOCK_ONEBLOCK_LOCK); + } + + kfree(sect); + return ret; +} + +static int __maybe_unused cfi_ppb_is_locked(struct mtd_info *mtd, loff_t ofs, + uint64_t len) +{ + return cfi_varsize_frob(mtd, do_ppb_xxlock, ofs, len, + DO_XXLOCK_ONEBLOCK_GETLOCK) ? 1 : 0; +} static void cfi_amdstd_sync (struct mtd_info *mtd) { @@ -1733,7 +2398,7 @@ static void cfi_amdstd_sync (struct mtd_info *mtd) chip = &cfi->chips[i]; retry: - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); switch(chip->state) { case FL_READY: @@ -1747,14 +2412,15 @@ static void cfi_amdstd_sync (struct mtd_info *mtd) * with the chip now anyway. */ case FL_SYNCING: - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); break; default: /* Not an idle state */ + set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&chip->wq, &wait); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); schedule(); @@ -1769,13 +2435,13 @@ static void cfi_amdstd_sync (struct mtd_info *mtd) for (i--; i >=0; i--) { chip = &cfi->chips[i]; - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); if (chip->state == FL_SYNCING) { chip->state = chip->oldstate; wake_up(&chip->wq); } - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); } } @@ -1791,7 +2457,7 @@ static int cfi_amdstd_suspend(struct mtd_info *mtd) for (i=0; !ret && i<cfi->numchips; i++) { chip = &cfi->chips[i]; - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); switch(chip->state) { case FL_READY: @@ -1811,7 +2477,7 @@ static int cfi_amdstd_suspend(struct mtd_info *mtd) ret = -EAGAIN; break; } - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); } /* Unlock the chips again */ @@ -1820,13 +2486,13 @@ static int cfi_amdstd_suspend(struct mtd_info *mtd) for (i--; i >=0; i--) { chip = &cfi->chips[i]; - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); if (chip->state == FL_PM_SUSPENDED) { chip->state = chip->oldstate; wake_up(&chip->wq); } - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); } } @@ -1845,7 +2511,7 @@ static void cfi_amdstd_resume(struct mtd_info *mtd) chip = &cfi->chips[i]; - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); if (chip->state == FL_PM_SUSPENDED) { chip->state = FL_READY; @@ -1855,15 +2521,62 @@ static void cfi_amdstd_resume(struct mtd_info *mtd) else printk(KERN_ERR "Argh. Chip not in PM_SUSPENDED state upon resume()\n"); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); } } + +/* + * Ensure that the flash device is put back into read array mode before + * unloading the driver or rebooting. On some systems, rebooting while + * the flash is in query/program/erase mode will prevent the CPU from + * fetching the bootloader code, requiring a hard reset or power cycle. + */ +static int cfi_amdstd_reset(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + int i, ret; + struct flchip *chip; + + for (i = 0; i < cfi->numchips; i++) { + + chip = &cfi->chips[i]; + + mutex_lock(&chip->mutex); + + ret = get_chip(map, chip, chip->start, FL_SHUTDOWN); + if (!ret) { + map_write(map, CMD(0xF0), chip->start); + chip->state = FL_SHUTDOWN; + put_chip(map, chip, chip->start); + } + + mutex_unlock(&chip->mutex); + } + + return 0; +} + + +static int cfi_amdstd_reboot(struct notifier_block *nb, unsigned long val, + void *v) +{ + struct mtd_info *mtd; + + mtd = container_of(nb, struct mtd_info, reboot_notifier); + cfi_amdstd_reset(mtd); + return NOTIFY_DONE; +} + + static void cfi_amdstd_destroy(struct mtd_info *mtd) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; + cfi_amdstd_reset(mtd); + unregister_reboot_notifier(&mtd->reboot_notifier); kfree(cfi->cmdset_priv); kfree(cfi->cfiq); kfree(cfi); @@ -1873,3 +2586,5 @@ static void cfi_amdstd_destroy(struct mtd_info *mtd) MODULE_LICENSE("GPL"); MODULE_AUTHOR("Crossnet Co. <info@crossnet.co.jp> et al."); MODULE_DESCRIPTION("MTD chip driver for AMD/Fujitsu flash chips"); +MODULE_ALIAS("cfi_cmdset_0006"); +MODULE_ALIAS("cfi_cmdset_0701"); diff --git a/drivers/mtd/chips/cfi_cmdset_0020.c b/drivers/mtd/chips/cfi_cmdset_0020.c index fae70a5db54..423666b51ef 100644 --- a/drivers/mtd/chips/cfi_cmdset_0020.c +++ b/drivers/mtd/chips/cfi_cmdset_0020.c @@ -4,9 +4,7 @@ * * (C) 2000 Red Hat. GPL'd * - * $Id: cfi_cmdset_0020.c,v 1.22 2005/11/07 11:14:22 gleixner Exp $ - * - * 10/10/2000 Nicolas Pitre <nico@cam.org> + * 10/10/2000 Nicolas Pitre <nico@fluxnic.net> * - completely revamped method functions so they are aware and * independent of the flash geometry (buswidth, interleave, etc.) * - scalability vs code size is completely set at compile-time @@ -24,7 +22,6 @@ #include <linux/types.h> #include <linux/kernel.h> #include <linux/sched.h> -#include <linux/init.h> #include <asm/io.h> #include <asm/byteorder.h> @@ -35,7 +32,6 @@ #include <linux/mtd/map.h> #include <linux/mtd/cfi.h> #include <linux/mtd/mtd.h> -#include <linux/mtd/compatmac.h> static int cfi_staa_read(struct mtd_info *, loff_t, size_t, size_t *, u_char *); @@ -44,8 +40,8 @@ static int cfi_staa_writev(struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen); static int cfi_staa_erase_varsize(struct mtd_info *, struct erase_info *); static void cfi_staa_sync (struct mtd_info *); -static int cfi_staa_lock(struct mtd_info *mtd, loff_t ofs, size_t len); -static int cfi_staa_unlock(struct mtd_info *mtd, loff_t ofs, size_t len); +static int cfi_staa_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len); +static int cfi_staa_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len); static int cfi_staa_suspend (struct mtd_info *); static void cfi_staa_resume (struct mtd_info *); @@ -142,8 +138,9 @@ struct mtd_info *cfi_cmdset_0020(struct map_info *map, int primary) } /* Do some byteswapping if necessary */ - extp->FeatureSupport = cfi32_to_cpu(extp->FeatureSupport); - extp->BlkStatusRegMask = cfi32_to_cpu(extp->BlkStatusRegMask); + extp->FeatureSupport = cfi32_to_cpu(map, extp->FeatureSupport); + extp->BlkStatusRegMask = cfi32_to_cpu(map, + extp->BlkStatusRegMask); #ifdef DEBUG_CFI_FEATURES /* Tell the user about it in lots of lovely detail */ @@ -158,6 +155,8 @@ struct mtd_info *cfi_cmdset_0020(struct map_info *map, int primary) cfi->chips[i].word_write_time = 128; cfi->chips[i].buffer_write_time = 128; cfi->chips[i].erase_time = 1024; + cfi->chips[i].ref_point_counter = 0; + init_waitqueue_head(&(cfi->chips[i].wq)); } return cfi_staa_setup(map); @@ -172,16 +171,14 @@ static struct mtd_info *cfi_staa_setup(struct map_info *map) int i,j; unsigned long devsize = (1<<cfi->cfiq->DevSize) * cfi->interleave; - mtd = kmalloc(sizeof(*mtd), GFP_KERNEL); + mtd = kzalloc(sizeof(*mtd), GFP_KERNEL); //printk(KERN_DEBUG "number of CFI chips: %d\n", cfi->numchips); if (!mtd) { - printk(KERN_ERR "Failed to allocate memory for MTD device\n"); kfree(cfi->cmdset_priv); return NULL; } - memset(mtd, 0, sizeof(*mtd)); mtd->priv = map; mtd->type = MTD_NORFLASH; mtd->size = devsize * cfi->numchips; @@ -190,7 +187,6 @@ static struct mtd_info *cfi_staa_setup(struct map_info *map) mtd->eraseregions = kmalloc(sizeof(struct mtd_erase_region_info) * mtd->numeraseregions, GFP_KERNEL); if (!mtd->eraseregions) { - printk(KERN_ERR "Failed to allocate memory for MTD erase region info\n"); kfree(cfi->cmdset_priv); kfree(mtd); return NULL; @@ -222,24 +218,25 @@ static struct mtd_info *cfi_staa_setup(struct map_info *map) } for (i=0; i<mtd->numeraseregions;i++){ - printk(KERN_DEBUG "%d: offset=0x%x,size=0x%x,blocks=%d\n", - i,mtd->eraseregions[i].offset, + printk(KERN_DEBUG "%d: offset=0x%llx,size=0x%x,blocks=%d\n", + i, (unsigned long long)mtd->eraseregions[i].offset, mtd->eraseregions[i].erasesize, mtd->eraseregions[i].numblocks); } /* Also select the correct geometry setup too */ - mtd->erase = cfi_staa_erase_varsize; - mtd->read = cfi_staa_read; - mtd->write = cfi_staa_write_buffers; - mtd->writev = cfi_staa_writev; - mtd->sync = cfi_staa_sync; - mtd->lock = cfi_staa_lock; - mtd->unlock = cfi_staa_unlock; - mtd->suspend = cfi_staa_suspend; - mtd->resume = cfi_staa_resume; + mtd->_erase = cfi_staa_erase_varsize; + mtd->_read = cfi_staa_read; + mtd->_write = cfi_staa_write_buffers; + mtd->_writev = cfi_staa_writev; + mtd->_sync = cfi_staa_sync; + mtd->_lock = cfi_staa_lock; + mtd->_unlock = cfi_staa_unlock; + mtd->_suspend = cfi_staa_suspend; + mtd->_resume = cfi_staa_resume; mtd->flags = MTD_CAP_NORFLASH & ~MTD_BIT_WRITEABLE; mtd->writesize = 8; /* FIXME: Should be 0 for STMicro flashes w/out ECC */ + mtd->writebufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize; map->fldrv = &cfi_staa_chipdrv; __module_get(THIS_MODULE); mtd->name = map->name; @@ -266,7 +263,7 @@ static inline int do_read_onechip(struct map_info *map, struct flchip *chip, lof timeo = jiffies + HZ; retry: - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); /* Check that the chip's ready to talk to us. * If it's in FL_ERASING state, suspend it and make it talk now. @@ -297,15 +294,16 @@ static inline int do_read_onechip(struct map_info *map, struct flchip *chip, lof /* make sure we're in 'read status' mode */ map_write(map, CMD(0x70), cmd_addr); chip->state = FL_ERASING; - spin_unlock_bh(chip->mutex); + wake_up(&chip->wq); + mutex_unlock(&chip->mutex); printk(KERN_ERR "Chip not ready after erase " "suspended: status = 0x%lx\n", status.x[0]); return -EIO; } - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); cfi_udelay(1); - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); } suspended = 1; @@ -336,13 +334,13 @@ static inline int do_read_onechip(struct map_info *map, struct flchip *chip, lof /* Urgh. Chip not yet ready to talk to us. */ if (time_after(jiffies, timeo)) { - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); printk(KERN_ERR "waiting for chip to be ready timed out in read. WSM status = %lx\n", status.x[0]); return -EIO; } /* Latency issues. Drop the lock, wait a while and retry */ - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); cfi_udelay(1); goto retry; @@ -352,7 +350,7 @@ static inline int do_read_onechip(struct map_info *map, struct flchip *chip, lof someone changes the status */ set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&chip->wq, &wait); - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); schedule(); remove_wait_queue(&chip->wq, &wait); timeo = jiffies + HZ; @@ -377,7 +375,7 @@ static inline int do_read_onechip(struct map_info *map, struct flchip *chip, lof } wake_up(&chip->wq); - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); return 0; } @@ -393,8 +391,6 @@ static int cfi_staa_read (struct mtd_info *mtd, loff_t from, size_t len, size_t chipnum = (from >> cfi->chipshift); ofs = from - (chipnum << cfi->chipshift); - *retlen = 0; - while (len) { unsigned long thislen; @@ -444,9 +440,9 @@ static inline int do_write_buffer(struct map_info *map, struct flchip *chip, retry: #ifdef DEBUG_CFI_FEATURES - printk("%s: chip->state[%d]\n", __FUNCTION__, chip->state); + printk("%s: chip->state[%d]\n", __func__, chip->state); #endif - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); /* Check that the chip's ready to talk to us. * Later, we can actually think about interrupting it @@ -462,7 +458,7 @@ static inline int do_write_buffer(struct map_info *map, struct flchip *chip, map_write(map, CMD(0x70), cmd_adr); chip->state = FL_STATUS; #ifdef DEBUG_CFI_FEATURES - printk("%s: 1 status[%x]\n", __FUNCTION__, map_read(map, cmd_adr)); + printk("%s: 1 status[%x]\n", __func__, map_read(map, cmd_adr)); #endif case FL_STATUS: @@ -471,14 +467,14 @@ static inline int do_write_buffer(struct map_info *map, struct flchip *chip, break; /* Urgh. Chip not yet ready to talk to us. */ if (time_after(jiffies, timeo)) { - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); printk(KERN_ERR "waiting for chip to be ready timed out in buffer write Xstatus = %lx, status = %lx\n", status.x[0], map_read(map, cmd_adr).x[0]); return -EIO; } /* Latency issues. Drop the lock, wait a while and retry */ - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); cfi_udelay(1); goto retry; @@ -487,7 +483,7 @@ static inline int do_write_buffer(struct map_info *map, struct flchip *chip, someone changes the status */ set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&chip->wq, &wait); - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); schedule(); remove_wait_queue(&chip->wq, &wait); timeo = jiffies + HZ; @@ -504,16 +500,16 @@ static inline int do_write_buffer(struct map_info *map, struct flchip *chip, if (map_word_andequal(map, status, status_OK, status_OK)) break; - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); cfi_udelay(1); - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); if (++z > 100) { /* Argh. Not ready for write to buffer */ DISABLE_VPP(map); map_write(map, CMD(0x70), cmd_adr); chip->state = FL_STATUS; - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); printk(KERN_ERR "Chip not ready for buffer write. Xstatus = %lx\n", status.x[0]); return -EIO; } @@ -533,9 +529,9 @@ static inline int do_write_buffer(struct map_info *map, struct flchip *chip, map_write(map, CMD(0xd0), cmd_adr); chip->state = FL_WRITING; - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); cfi_udelay(chip->buffer_write_time); - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); timeo = jiffies + (HZ/2); z = 0; @@ -544,11 +540,11 @@ static inline int do_write_buffer(struct map_info *map, struct flchip *chip, /* Someone's suspended the write. Sleep */ set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&chip->wq, &wait); - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); schedule(); remove_wait_queue(&chip->wq, &wait); timeo = jiffies + (HZ / 2); /* FIXME */ - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); continue; } @@ -564,16 +560,16 @@ static inline int do_write_buffer(struct map_info *map, struct flchip *chip, map_write(map, CMD(0x70), adr); chip->state = FL_STATUS; DISABLE_VPP(map); - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); printk(KERN_ERR "waiting for chip to be ready timed out in bufwrite\n"); return -EIO; } /* Latency issues. Drop the lock, wait a while and retry */ - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); cfi_udelay(1); z++; - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); } if (!z) { chip->buffer_write_time--; @@ -590,18 +586,18 @@ static inline int do_write_buffer(struct map_info *map, struct flchip *chip, /* check for errors: 'lock bit', 'VPP', 'dead cell'/'unerased cell' or 'incorrect cmd' -- saw */ if (map_word_bitsset(map, status, CMD(0x3a))) { #ifdef DEBUG_CFI_FEATURES - printk("%s: 2 status[%lx]\n", __FUNCTION__, status.x[0]); + printk("%s: 2 status[%lx]\n", __func__, status.x[0]); #endif /* clear status */ map_write(map, CMD(0x50), cmd_adr); /* put back into read status register mode */ map_write(map, CMD(0x70), adr); wake_up(&chip->wq); - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); return map_word_bitsset(map, status, CMD(0x02)) ? -EROFS : -EIO; } wake_up(&chip->wq); - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); return 0; } @@ -616,17 +612,13 @@ static int cfi_staa_write_buffers (struct mtd_info *mtd, loff_t to, int chipnum; unsigned long ofs; - *retlen = 0; - if (!len) - return 0; - chipnum = to >> cfi->chipshift; ofs = to - (chipnum << cfi->chipshift); #ifdef DEBUG_CFI_FEATURES - printk("%s: map_bankwidth(map)[%x]\n", __FUNCTION__, map_bankwidth(map)); - printk("%s: chipnum[%x] wbufsize[%x]\n", __FUNCTION__, chipnum, wbufsize); - printk("%s: ofs[%x] len[%x]\n", __FUNCTION__, ofs, len); + printk("%s: map_bankwidth(map)[%x]\n", __func__, map_bankwidth(map)); + printk("%s: chipnum[%x] wbufsize[%x]\n", __func__, chipnum, wbufsize); + printk("%s: ofs[%x] len[%x]\n", __func__, ofs, len); #endif /* Write buffer is worth it only if more than one word to write... */ @@ -663,7 +655,7 @@ static int cfi_staa_write_buffers (struct mtd_info *mtd, loff_t to, * a small buffer for this. * XXX: If the buffer size is not a multiple of 2, this will break */ -#define ECCBUF_SIZE (mtd->eccsize) +#define ECCBUF_SIZE (mtd->writesize) #define ECCBUF_DIV(x) ((x) & ~(ECCBUF_SIZE - 1)) #define ECCBUF_MOD(x) ((x) & (ECCBUF_SIZE - 1)) static int @@ -698,7 +690,8 @@ cfi_staa_writev(struct mtd_info *mtd, const struct kvec *vecs, continue; } memcpy(buffer+buflen, elem_base, ECCBUF_SIZE-buflen); - ret = mtd->write(mtd, to, ECCBUF_SIZE, &thislen, buffer); + ret = mtd_write(mtd, to, ECCBUF_SIZE, &thislen, + buffer); totlen += thislen; if (ret || thislen != ECCBUF_SIZE) goto write_error; @@ -707,7 +700,8 @@ cfi_staa_writev(struct mtd_info *mtd, const struct kvec *vecs, to += ECCBUF_SIZE; } if (ECCBUF_DIV(elem_len)) { /* write clean aligned data */ - ret = mtd->write(mtd, to, ECCBUF_DIV(elem_len), &thislen, elem_base); + ret = mtd_write(mtd, to, ECCBUF_DIV(elem_len), + &thislen, elem_base); totlen += thislen; if (ret || thislen != ECCBUF_DIV(elem_len)) goto write_error; @@ -721,7 +715,7 @@ cfi_staa_writev(struct mtd_info *mtd, const struct kvec *vecs, } if (buflen) { /* flush last page, even if not full */ /* This is sometimes intended behaviour, really */ - ret = mtd->write(mtd, to, buflen, &thislen, buffer); + ret = mtd_write(mtd, to, buflen, &thislen, buffer); totlen += thislen; if (ret || thislen != ECCBUF_SIZE) goto write_error; @@ -750,7 +744,7 @@ static inline int do_erase_oneblock(struct map_info *map, struct flchip *chip, u timeo = jiffies + HZ; retry: - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); /* Check that the chip's ready to talk to us. */ switch (chip->state) { @@ -767,13 +761,13 @@ retry: /* Urgh. Chip not yet ready to talk to us. */ if (time_after(jiffies, timeo)) { - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); printk(KERN_ERR "waiting for chip to be ready timed out in erase\n"); return -EIO; } /* Latency issues. Drop the lock, wait a while and retry */ - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); cfi_udelay(1); goto retry; @@ -782,7 +776,7 @@ retry: someone changes the status */ set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&chip->wq, &wait); - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); schedule(); remove_wait_queue(&chip->wq, &wait); timeo = jiffies + HZ; @@ -798,9 +792,9 @@ retry: map_write(map, CMD(0xD0), adr); chip->state = FL_ERASING; - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); msleep(1000); - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); /* FIXME. Use a timer to check this, and return immediately. */ /* Once the state machine's known to be working I'll do that */ @@ -811,11 +805,11 @@ retry: /* Someone's suspended the erase. Sleep */ set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&chip->wq, &wait); - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); schedule(); remove_wait_queue(&chip->wq, &wait); timeo = jiffies + (HZ*20); /* FIXME */ - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); continue; } @@ -829,14 +823,14 @@ retry: chip->state = FL_STATUS; printk(KERN_ERR "waiting for erase to complete timed out. Xstatus = %lx, status = %lx.\n", status.x[0], map_read(map, adr).x[0]); DISABLE_VPP(map); - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); return -EIO; } /* Latency issues. Drop the lock, wait a while and retry */ - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); cfi_udelay(1); - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); } DISABLE_VPP(map); @@ -879,7 +873,7 @@ retry: printk(KERN_DEBUG "Chip erase failed at 0x%08lx: status 0x%x. Retrying...\n", adr, chipstatus); timeo = jiffies + HZ; chip->state = FL_STATUS; - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); goto retry; } printk(KERN_DEBUG "Chip erase failed at 0x%08lx: status 0x%x\n", adr, chipstatus); @@ -888,11 +882,12 @@ retry: } wake_up(&chip->wq); - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); return ret; } -int cfi_staa_erase_varsize(struct mtd_info *mtd, struct erase_info *instr) +static int cfi_staa_erase_varsize(struct mtd_info *mtd, + struct erase_info *instr) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; unsigned long adr, len; @@ -900,12 +895,6 @@ int cfi_staa_erase_varsize(struct mtd_info *mtd, struct erase_info *instr) int i, first; struct mtd_erase_region_info *regions = mtd->eraseregions; - if (instr->addr > mtd->size) - return -EINVAL; - - if ((instr->len + instr->addr) > mtd->size) - return -EINVAL; - /* Check that both start and end of the requested erase are * aligned with the erasesize at the appropriate addresses. */ @@ -964,7 +953,7 @@ int cfi_staa_erase_varsize(struct mtd_info *mtd, struct erase_info *instr) adr += regions[i].erasesize; len -= regions[i].erasesize; - if (adr % (1<< cfi->chipshift) == ((regions[i].offset + (regions[i].erasesize * regions[i].numblocks)) %( 1<< cfi->chipshift))) + if (adr % (1<< cfi->chipshift) == (((unsigned long)regions[i].offset + (regions[i].erasesize * regions[i].numblocks)) %( 1<< cfi->chipshift))) i++; if (adr >> cfi->chipshift) { @@ -972,7 +961,7 @@ int cfi_staa_erase_varsize(struct mtd_info *mtd, struct erase_info *instr) chipnum++; if (chipnum >= cfi->numchips) - break; + break; } } @@ -995,7 +984,7 @@ static void cfi_staa_sync (struct mtd_info *mtd) chip = &cfi->chips[i]; retry: - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); switch(chip->state) { case FL_READY: @@ -1009,14 +998,15 @@ static void cfi_staa_sync (struct mtd_info *mtd) * with the chip now anyway. */ case FL_SYNCING: - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); break; default: /* Not an idle state */ + set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&chip->wq, &wait); - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); schedule(); remove_wait_queue(&chip->wq, &wait); @@ -1029,13 +1019,13 @@ static void cfi_staa_sync (struct mtd_info *mtd) for (i--; i >=0; i--) { chip = &cfi->chips[i]; - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); if (chip->state == FL_SYNCING) { chip->state = chip->oldstate; wake_up(&chip->wq); } - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); } } @@ -1053,7 +1043,7 @@ static inline int do_lock_oneblock(struct map_info *map, struct flchip *chip, un timeo = jiffies + HZ; retry: - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); /* Check that the chip's ready to talk to us. */ switch (chip->state) { @@ -1070,13 +1060,13 @@ retry: /* Urgh. Chip not yet ready to talk to us. */ if (time_after(jiffies, timeo)) { - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); printk(KERN_ERR "waiting for chip to be ready timed out in lock\n"); return -EIO; } /* Latency issues. Drop the lock, wait a while and retry */ - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); cfi_udelay(1); goto retry; @@ -1085,7 +1075,7 @@ retry: someone changes the status */ set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&chip->wq, &wait); - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); schedule(); remove_wait_queue(&chip->wq, &wait); timeo = jiffies + HZ; @@ -1097,9 +1087,9 @@ retry: map_write(map, CMD(0x01), adr); chip->state = FL_LOCKING; - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); msleep(1000); - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); /* FIXME. Use a timer to check this, and return immediately. */ /* Once the state machine's known to be working I'll do that */ @@ -1117,24 +1107,24 @@ retry: chip->state = FL_STATUS; printk(KERN_ERR "waiting for lock to complete timed out. Xstatus = %lx, status = %lx.\n", status.x[0], map_read(map, adr).x[0]); DISABLE_VPP(map); - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); return -EIO; } /* Latency issues. Drop the lock, wait a while and retry */ - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); cfi_udelay(1); - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); } /* Done and happy. */ chip->state = FL_STATUS; DISABLE_VPP(map); wake_up(&chip->wq); - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); return 0; } -static int cfi_staa_lock(struct mtd_info *mtd, loff_t ofs, size_t len) +static int cfi_staa_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; @@ -1150,9 +1140,6 @@ static int cfi_staa_lock(struct mtd_info *mtd, loff_t ofs, size_t len) if (len & (mtd->erasesize -1)) return -EINVAL; - if ((len + ofs) > mtd->size) - return -EINVAL; - chipnum = ofs >> cfi->chipshift; adr = ofs - (chipnum << cfi->chipshift); @@ -1183,7 +1170,7 @@ static int cfi_staa_lock(struct mtd_info *mtd, loff_t ofs, size_t len) chipnum++; if (chipnum >= cfi->numchips) - break; + break; } } return 0; @@ -1202,7 +1189,7 @@ static inline int do_unlock_oneblock(struct map_info *map, struct flchip *chip, timeo = jiffies + HZ; retry: - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); /* Check that the chip's ready to talk to us. */ switch (chip->state) { @@ -1219,13 +1206,13 @@ retry: /* Urgh. Chip not yet ready to talk to us. */ if (time_after(jiffies, timeo)) { - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); printk(KERN_ERR "waiting for chip to be ready timed out in unlock\n"); return -EIO; } /* Latency issues. Drop the lock, wait a while and retry */ - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); cfi_udelay(1); goto retry; @@ -1234,7 +1221,7 @@ retry: someone changes the status */ set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&chip->wq, &wait); - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); schedule(); remove_wait_queue(&chip->wq, &wait); timeo = jiffies + HZ; @@ -1246,9 +1233,9 @@ retry: map_write(map, CMD(0xD0), adr); chip->state = FL_UNLOCKING; - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); msleep(1000); - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); /* FIXME. Use a timer to check this, and return immediately. */ /* Once the state machine's known to be working I'll do that */ @@ -1266,24 +1253,24 @@ retry: chip->state = FL_STATUS; printk(KERN_ERR "waiting for unlock to complete timed out. Xstatus = %lx, status = %lx.\n", status.x[0], map_read(map, adr).x[0]); DISABLE_VPP(map); - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); return -EIO; } /* Latency issues. Drop the unlock, wait a while and retry */ - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); cfi_udelay(1); - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); } /* Done and happy. */ chip->state = FL_STATUS; DISABLE_VPP(map); wake_up(&chip->wq); - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); return 0; } -static int cfi_staa_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) +static int cfi_staa_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; @@ -1333,7 +1320,7 @@ static int cfi_staa_suspend(struct mtd_info *mtd) for (i=0; !ret && i<cfi->numchips; i++) { chip = &cfi->chips[i]; - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); switch(chip->state) { case FL_READY: @@ -1353,7 +1340,7 @@ static int cfi_staa_suspend(struct mtd_info *mtd) ret = -EAGAIN; break; } - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); } /* Unlock the chips again */ @@ -1362,7 +1349,7 @@ static int cfi_staa_suspend(struct mtd_info *mtd) for (i--; i >=0; i--) { chip = &cfi->chips[i]; - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); if (chip->state == FL_PM_SUSPENDED) { /* No need to force it into a known state here, @@ -1371,7 +1358,7 @@ static int cfi_staa_suspend(struct mtd_info *mtd) chip->state = chip->oldstate; wake_up(&chip->wq); } - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); } } @@ -1389,7 +1376,7 @@ static void cfi_staa_resume(struct mtd_info *mtd) chip = &cfi->chips[i]; - spin_lock_bh(chip->mutex); + mutex_lock(&chip->mutex); /* Go to known state. Chip may have been power cycled */ if (chip->state == FL_PM_SUSPENDED) { @@ -1398,7 +1385,7 @@ static void cfi_staa_resume(struct mtd_info *mtd) wake_up(&chip->wq); } - spin_unlock_bh(chip->mutex); + mutex_unlock(&chip->mutex); } } diff --git a/drivers/mtd/chips/cfi_probe.c b/drivers/mtd/chips/cfi_probe.c index 60e11a0ada9..e8d0164498b 100644 --- a/drivers/mtd/chips/cfi_probe.c +++ b/drivers/mtd/chips/cfi_probe.c @@ -1,7 +1,6 @@ /* Common Flash Interface probe code. (C) 2000 Red Hat. GPL'd. - $Id: cfi_probe.c,v 1.86 2005/11/29 14:48:31 gleixner Exp $ */ #include <linux/module.h> @@ -39,23 +38,20 @@ struct mtd_info *cfi_probe(struct map_info *map); #define xip_allowed(base, map) \ do { \ (void) map_read(map, base); \ - asm volatile (".rep 8; nop; .endr"); \ + xip_iprefetch(); \ local_irq_enable(); \ } while (0) #define xip_enable(base, map, cfi) \ do { \ - cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); \ - cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL); \ + cfi_qry_mode_off(base, map, cfi); \ xip_allowed(base, map); \ } while (0) #define xip_disable_qry(base, map, cfi) \ do { \ xip_disable(); \ - cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); \ - cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL); \ - cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL); \ + cfi_qry_mode_on(base, map, cfi); \ } while (0) #else @@ -71,32 +67,6 @@ do { \ in: interleave,type,mode ret: table index, <0 for error */ -static int __xipram qry_present(struct map_info *map, __u32 base, - struct cfi_private *cfi) -{ - int osf = cfi->interleave * cfi->device_type; // scale factor - map_word val[3]; - map_word qry[3]; - - qry[0] = cfi_build_cmd('Q', map, cfi); - qry[1] = cfi_build_cmd('R', map, cfi); - qry[2] = cfi_build_cmd('Y', map, cfi); - - val[0] = map_read(map, base + osf*0x10); - val[1] = map_read(map, base + osf*0x11); - val[2] = map_read(map, base + osf*0x12); - - if (!map_word_equal(map, qry[0], val[0])) - return 0; - - if (!map_word_equal(map, qry[1], val[1])) - return 0; - - if (!map_word_equal(map, qry[2], val[2])) - return 0; - - return 1; // "QRY" found -} static int __xipram cfi_probe_chip(struct map_info *map, __u32 base, unsigned long *chip_map, struct cfi_private *cfi) @@ -117,11 +87,7 @@ static int __xipram cfi_probe_chip(struct map_info *map, __u32 base, } xip_disable(); - cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); - cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL); - cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL); - - if (!qry_present(map,base,cfi)) { + if (!cfi_qry_mode_on(base, map, cfi)) { xip_enable(base, map, cfi); return 0; } @@ -142,14 +108,13 @@ static int __xipram cfi_probe_chip(struct map_info *map, __u32 base, start = i << cfi->chipshift; /* This chip should be in read mode if it's one we've already touched. */ - if (qry_present(map, start, cfi)) { + if (cfi_qry_present(map, start, cfi)) { /* Eep. This chip also had the QRY marker. * Is it an alias for the new one? */ - cfi_send_gen_cmd(0xF0, 0, start, map, cfi, cfi->device_type, NULL); - cfi_send_gen_cmd(0xFF, 0, start, map, cfi, cfi->device_type, NULL); + cfi_qry_mode_off(start, map, cfi); /* If the QRY marker goes away, it's an alias */ - if (!qry_present(map, start, cfi)) { + if (!cfi_qry_present(map, start, cfi)) { xip_allowed(base, map); printk(KERN_DEBUG "%s: Found an alias at 0x%x for the chip at 0x%lx\n", map->name, base, start); @@ -159,10 +124,9 @@ static int __xipram cfi_probe_chip(struct map_info *map, __u32 base, * unfortunate. Stick the new chip in read mode * too and if it's the same, assume it's an alias. */ /* FIXME: Use other modes to do a proper check */ - cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); - cfi_send_gen_cmd(0xFF, 0, start, map, cfi, cfi->device_type, NULL); + cfi_qry_mode_off(base, map, cfi); - if (qry_present(map, base, cfi)) { + if (cfi_qry_present(map, base, cfi)) { xip_allowed(base, map); printk(KERN_DEBUG "%s: Found an alias at 0x%x for the chip at 0x%lx\n", map->name, base, start); @@ -177,8 +141,7 @@ static int __xipram cfi_probe_chip(struct map_info *map, __u32 base, cfi->numchips++; /* Put it back into Read Mode */ - cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); - cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL); + cfi_qry_mode_off(base, map, cfi); xip_allowed(base, map); printk(KERN_INFO "%s: Found %d x%d devices at 0x%x in %d-bit bank\n", @@ -195,6 +158,7 @@ static int __xipram cfi_chip_setup(struct map_info *map, __u32 base = 0; int num_erase_regions = cfi_read_query(map, base + (0x10 + 28)*ofs_factor); int i; + int addr_unlock1 = 0x555, addr_unlock2 = 0x2AA; xip_enable(base, map, cfi); #ifdef DEBUG_CFI @@ -204,40 +168,20 @@ static int __xipram cfi_chip_setup(struct map_info *map, return 0; cfi->cfiq = kmalloc(sizeof(struct cfi_ident) + num_erase_regions * 4, GFP_KERNEL); - if (!cfi->cfiq) { - printk(KERN_WARNING "%s: kmalloc failed for CFI ident structure\n", map->name); + if (!cfi->cfiq) return 0; - } memset(cfi->cfiq,0,sizeof(struct cfi_ident)); cfi->cfi_mode = CFI_MODE_CFI; + cfi->sector_erase_cmd = CMD(0x30); + /* Read the CFI info structure */ xip_disable_qry(base, map, cfi); for (i=0; i<(sizeof(struct cfi_ident) + num_erase_regions * 4); i++) ((unsigned char *)cfi->cfiq)[i] = cfi_read_query(map,base + (0x10 + i)*ofs_factor); - /* Note we put the device back into Read Mode BEFORE going into Auto - * Select Mode, as some devices support nesting of modes, others - * don't. This way should always work. - * On cmdset 0001 the writes of 0xaa and 0x55 are not needed, and - * so should be treated as nops or illegal (and so put the device - * back into Read Mode, which is a nop in this case). - */ - cfi_send_gen_cmd(0xf0, 0, base, map, cfi, cfi->device_type, NULL); - cfi_send_gen_cmd(0xaa, 0x555, base, map, cfi, cfi->device_type, NULL); - cfi_send_gen_cmd(0x55, 0x2aa, base, map, cfi, cfi->device_type, NULL); - cfi_send_gen_cmd(0x90, 0x555, base, map, cfi, cfi->device_type, NULL); - cfi->mfr = cfi_read_query16(map, base); - cfi->id = cfi_read_query16(map, base + ofs_factor); - - /* Put it back into Read Mode */ - cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); - /* ... even if it's an Intel chip */ - cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL); - xip_allowed(base, map); - /* Do any necessary byteswapping */ cfi->cfiq->P_ID = le16_to_cpu(cfi->cfiq->P_ID); @@ -262,9 +206,38 @@ static int __xipram cfi_chip_setup(struct map_info *map, #endif } - printk(KERN_INFO "%s: Found %d x%d devices at 0x%x in %d-bit bank\n", + if (cfi->cfiq->P_ID == P_ID_SST_OLD) { + addr_unlock1 = 0x5555; + addr_unlock2 = 0x2AAA; + } + + /* + * Note we put the device back into Read Mode BEFORE going into Auto + * Select Mode, as some devices support nesting of modes, others + * don't. This way should always work. + * On cmdset 0001 the writes of 0xaa and 0x55 are not needed, and + * so should be treated as nops or illegal (and so put the device + * back into Read Mode, which is a nop in this case). + */ + cfi_send_gen_cmd(0xf0, 0, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0xaa, addr_unlock1, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, addr_unlock2, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x90, addr_unlock1, base, map, cfi, cfi->device_type, NULL); + cfi->mfr = cfi_read_query16(map, base); + cfi->id = cfi_read_query16(map, base + ofs_factor); + + /* Get AMD/Spansion extended JEDEC ID */ + if (cfi->mfr == CFI_MFR_AMD && (cfi->id & 0xff) == 0x7e) + cfi->id = cfi_read_query(map, base + 0xe * ofs_factor) << 8 | + cfi_read_query(map, base + 0xf * ofs_factor); + + /* Put it back into Read Mode */ + cfi_qry_mode_off(base, map, cfi); + xip_allowed(base, map); + + printk(KERN_INFO "%s: Found %d x%d devices at 0x%x in %d-bit bank. Manufacturer ID %#08x Chip ID %#08x\n", map->name, cfi->interleave, cfi->device_type*8, base, - map->bankwidth*8); + map->bankwidth*8, cfi->mfr, cfi->id); return 1; } @@ -303,6 +276,9 @@ static char *vendorname(__u16 vendor) case P_ID_SST_PAGE: return "SST Page Write"; + case P_ID_SST_OLD: + return "SST 39VF160x/39VF320x"; + case P_ID_INTEL_PERFORMANCE: return "Intel Performance Code"; @@ -370,27 +346,27 @@ static void print_cfi_ident(struct cfi_ident *cfip) printk("Device size: 0x%X bytes (%d MiB)\n", 1 << cfip->DevSize, 1<< (cfip->DevSize - 20)); printk("Flash Device Interface description: 0x%4.4X\n", cfip->InterfaceDesc); switch(cfip->InterfaceDesc) { - case 0: + case CFI_INTERFACE_X8_ASYNC: printk(" - x8-only asynchronous interface\n"); break; - case 1: + case CFI_INTERFACE_X16_ASYNC: printk(" - x16-only asynchronous interface\n"); break; - case 2: + case CFI_INTERFACE_X8_BY_X16_ASYNC: printk(" - supports x8 and x16 via BYTE# with asynchronous interface\n"); break; - case 3: + case CFI_INTERFACE_X32_ASYNC: printk(" - x32-only asynchronous interface\n"); break; - case 4: + case CFI_INTERFACE_X16_BY_X32_ASYNC: printk(" - supports x16 and x32 via Word# with asynchronous interface\n"); break; - case 65535: + case CFI_INTERFACE_NOT_ALLOWED: printk(" - Not Allowed / Reserved\n"); break; diff --git a/drivers/mtd/chips/cfi_util.c b/drivers/mtd/chips/cfi_util.c index d8e7a026ba5..09c79bd0b4f 100644 --- a/drivers/mtd/chips/cfi_util.c +++ b/drivers/mtd/chips/cfi_util.c @@ -1,20 +1,16 @@ /* * Common Flash Interface support: - * Generic utility functions not dependant on command set + * Generic utility functions not dependent on command set * * Copyright (C) 2002 Red Hat * Copyright (C) 2003 STMicroelectronics Limited * * This code is covered by the GPL. - * - * $Id: cfi_util.c,v 1.10 2005/11/07 11:14:23 gleixner Exp $ - * */ #include <linux/module.h> #include <linux/types.h> #include <linux/kernel.h> -#include <linux/sched.h> #include <asm/io.h> #include <asm/byteorder.h> @@ -26,7 +22,84 @@ #include <linux/mtd/mtd.h> #include <linux/mtd/map.h> #include <linux/mtd/cfi.h> -#include <linux/mtd/compatmac.h> + +int __xipram cfi_qry_present(struct map_info *map, __u32 base, + struct cfi_private *cfi) +{ + int osf = cfi->interleave * cfi->device_type; /* scale factor */ + map_word val[3]; + map_word qry[3]; + + qry[0] = cfi_build_cmd('Q', map, cfi); + qry[1] = cfi_build_cmd('R', map, cfi); + qry[2] = cfi_build_cmd('Y', map, cfi); + + val[0] = map_read(map, base + osf*0x10); + val[1] = map_read(map, base + osf*0x11); + val[2] = map_read(map, base + osf*0x12); + + if (!map_word_equal(map, qry[0], val[0])) + return 0; + + if (!map_word_equal(map, qry[1], val[1])) + return 0; + + if (!map_word_equal(map, qry[2], val[2])) + return 0; + + return 1; /* "QRY" found */ +} +EXPORT_SYMBOL_GPL(cfi_qry_present); + +int __xipram cfi_qry_mode_on(uint32_t base, struct map_info *map, + struct cfi_private *cfi) +{ + cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL); + if (cfi_qry_present(map, base, cfi)) + return 1; + /* QRY not found probably we deal with some odd CFI chips */ + /* Some revisions of some old Intel chips? */ + cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL); + if (cfi_qry_present(map, base, cfi)) + return 1; + /* ST M29DW chips */ + cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x98, 0x555, base, map, cfi, cfi->device_type, NULL); + if (cfi_qry_present(map, base, cfi)) + return 1; + /* some old SST chips, e.g. 39VF160x/39VF320x */ + cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0xAA, 0x5555, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, 0x2AAA, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x98, 0x5555, base, map, cfi, cfi->device_type, NULL); + if (cfi_qry_present(map, base, cfi)) + return 1; + /* SST 39VF640xB */ + cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0xAA, 0x555, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, 0x2AA, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x98, 0x555, base, map, cfi, cfi->device_type, NULL); + if (cfi_qry_present(map, base, cfi)) + return 1; + /* QRY not found */ + return 0; +} +EXPORT_SYMBOL_GPL(cfi_qry_mode_on); + +void __xipram cfi_qry_mode_off(uint32_t base, struct map_info *map, + struct cfi_private *cfi) +{ + cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL); + /* M29W128G flashes require an additional reset command + when exit qry mode */ + if ((cfi->mfr == CFI_MFR_ST) && (cfi->id == 0x227E || cfi->id == 0x7E)) + cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); +} +EXPORT_SYMBOL_GPL(cfi_qry_mode_off); struct cfi_extquery * __xipram cfi_read_pri(struct map_info *map, __u16 adr, __u16 size, const char* name) @@ -37,23 +110,21 @@ __xipram cfi_read_pri(struct map_info *map, __u16 adr, __u16 size, const char* n int i; struct cfi_extquery *extp = NULL; - printk(" %s Extended Query Table at 0x%4.4X\n", name, adr); if (!adr) goto out; + printk(KERN_INFO "%s Extended Query Table at 0x%4.4X\n", name, adr); + extp = kmalloc(size, GFP_KERNEL); - if (!extp) { - printk(KERN_ERR "Failed to allocate memory\n"); + if (!extp) goto out; - } #ifdef CONFIG_MTD_XIP local_irq_disable(); #endif /* Switch it into Query Mode */ - cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL); - + cfi_qry_mode_on(base, map, cfi); /* Read in the Extended Query Table */ for (i=0; i<size; i++) { ((unsigned char *)extp)[i] = @@ -61,12 +132,11 @@ __xipram cfi_read_pri(struct map_info *map, __u16 adr, __u16 size, const char* n } /* Make sure it returns to read mode */ - cfi_send_gen_cmd(0xf0, 0, base, map, cfi, cfi->device_type, NULL); - cfi_send_gen_cmd(0xff, 0, base, map, cfi, cfi->device_type, NULL); + cfi_qry_mode_off(base, map, cfi); #ifdef CONFIG_MTD_XIP (void) map_read(map, base); - asm volatile (".rep 8; nop; .endr"); + xip_iprefetch(); local_irq_enable(); #endif @@ -84,7 +154,7 @@ void cfi_fixup(struct mtd_info *mtd, struct cfi_fixup *fixups) for (f=fixups; f->fixup; f++) { if (((f->mfr == CFI_MFR_ANY) || (f->mfr == cfi->mfr)) && ((f->id == CFI_ID_ANY) || (f->id == cfi->id))) { - f->fixup(mtd, f->param); + f->fixup(mtd); } } } @@ -101,12 +171,6 @@ int cfi_varsize_frob(struct mtd_info *mtd, varsize_frob_t frob, int i, first; struct mtd_erase_region_info *regions = mtd->eraseregions; - if (ofs > mtd->size) - return -EINVAL; - - if ((len + ofs) > mtd->size) - return -EINVAL; - /* Check that both start and end of the requested erase are * aligned with the erasesize at the appropriate addresses. */ @@ -175,7 +239,7 @@ int cfi_varsize_frob(struct mtd_info *mtd, varsize_frob_t frob, chipnum++; if (chipnum >= cfi->numchips) - break; + break; } } diff --git a/drivers/mtd/chips/chipreg.c b/drivers/mtd/chips/chipreg.c index 2174c97549f..0bbc61ba952 100644 --- a/drivers/mtd/chips/chipreg.c +++ b/drivers/mtd/chips/chipreg.c @@ -1,6 +1,4 @@ /* - * $Id: chipreg.c,v 1.17 2004/11/16 18:29:00 dwmw2 Exp $ - * * Registration for chip drivers * */ @@ -12,7 +10,6 @@ #include <linux/slab.h> #include <linux/mtd/map.h> #include <linux/mtd/mtd.h> -#include <linux/mtd/compatmac.h> static DEFINE_SPINLOCK(chip_drvs_lock); static LIST_HEAD(chip_drvs_list); @@ -79,10 +76,7 @@ struct mtd_info *do_map_probe(const char *name, struct map_info *map) */ module_put(drv->module); - if (ret) - return ret; - - return NULL; + return ret; } /* * Destroy an MTD device which was created for a map device. diff --git a/drivers/mtd/chips/fwh_lock.h b/drivers/mtd/chips/fwh_lock.h index 77303ce5dcf..800b0e853e8 100644 --- a/drivers/mtd/chips/fwh_lock.h +++ b/drivers/mtd/chips/fwh_lock.h @@ -34,8 +34,7 @@ static int fwh_xxlock_oneblock(struct map_info *map, struct flchip *chip, /* Refuse the operation if the we cannot look behind the chip */ if (chip->start < 0x400000) { - DEBUG( MTD_DEBUG_LEVEL3, - "MTD %s(): chip->start: %lx wanted >= 0x400000\n", + pr_debug( "MTD %s(): chip->start: %lx wanted >= 0x400000\n", __func__, chip->start ); return -EIO; } @@ -58,25 +57,26 @@ static int fwh_xxlock_oneblock(struct map_info *map, struct flchip *chip, * to flash memory - that means that we don't have to check status * and timeout. */ - spin_lock(chip->mutex); + mutex_lock(&chip->mutex); ret = get_chip(map, chip, adr, FL_LOCKING); if (ret) { - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return ret; } + chip->oldstate = chip->state; chip->state = xxlt->state; map_write(map, CMD(xxlt->val), adr); /* Done and happy. */ - chip->state = FL_READY; + chip->state = chip->oldstate; put_chip(map, chip, adr); - spin_unlock(chip->mutex); + mutex_unlock(&chip->mutex); return 0; } -static int fwh_lock_varsize(struct mtd_info *mtd, loff_t ofs, size_t len) +static int fwh_lock_varsize(struct mtd_info *mtd, loff_t ofs, uint64_t len) { int ret; @@ -87,7 +87,7 @@ static int fwh_lock_varsize(struct mtd_info *mtd, loff_t ofs, size_t len) } -static int fwh_unlock_varsize(struct mtd_info *mtd, loff_t ofs, size_t len) +static int fwh_unlock_varsize(struct mtd_info *mtd, loff_t ofs, uint64_t len) { int ret; @@ -97,11 +97,11 @@ static int fwh_unlock_varsize(struct mtd_info *mtd, loff_t ofs, size_t len) return ret; } -static void fixup_use_fwh_lock(struct mtd_info *mtd, void *param) +static void fixup_use_fwh_lock(struct mtd_info *mtd) { printk(KERN_NOTICE "using fwh lock/unlock method\n"); /* Setup for the chips with the fwh lock method */ - mtd->lock = fwh_lock_varsize; - mtd->unlock = fwh_unlock_varsize; + mtd->_lock = fwh_lock_varsize; + mtd->_unlock = fwh_unlock_varsize; } #endif /* FWH_LOCK_H */ diff --git a/drivers/mtd/chips/gen_probe.c b/drivers/mtd/chips/gen_probe.c index cdb0f590b40..b57ceea2151 100644 --- a/drivers/mtd/chips/gen_probe.c +++ b/drivers/mtd/chips/gen_probe.c @@ -2,7 +2,6 @@ * Routines common to all CFI-type probes. * (C) 2001-2003 Red Hat, Inc. * GPL'd - * $Id: gen_probe.c,v 1.24 2005/11/07 11:14:23 gleixner Exp $ */ #include <linux/kernel.h> @@ -40,7 +39,7 @@ struct mtd_info *mtd_do_chip_probe(struct map_info *map, struct chip_probe *cp) if (mtd) { if (mtd->size > map->size) { printk(KERN_WARNING "Reducing visibility of %ldKiB chip to %ldKiB\n", - (unsigned long)mtd->size >> 10, + (unsigned long)mtd->size >> 10, (unsigned long)map->size >> 10); mtd->size = map->size; } @@ -71,8 +70,8 @@ static struct cfi_private *genprobe_ident_chips(struct map_info *map, struct chi interleave and device type, etc. */ if (!genprobe_new_chip(map, cp, &cfi)) { /* The probe didn't like it */ - printk(KERN_DEBUG "%s: Found no %s device at location zero\n", - cp->name, map->name); + pr_debug("%s: Found no %s device at location zero\n", + cp->name, map->name); return NULL; } @@ -112,14 +111,12 @@ static struct cfi_private *genprobe_ident_chips(struct map_info *map, struct chi max_chips = 1; } - mapsize = (max_chips + BITS_PER_LONG-1) / BITS_PER_LONG; - chip_map = kmalloc(mapsize, GFP_KERNEL); + mapsize = sizeof(long) * DIV_ROUND_UP(max_chips, BITS_PER_LONG); + chip_map = kzalloc(mapsize, GFP_KERNEL); if (!chip_map) { - printk(KERN_WARNING "%s: kmalloc failed for CFI chip map\n", map->name); kfree(cfi.cfiq); return NULL; } - memset (chip_map, 0, mapsize); set_bit(0, chip_map); /* Mark first chip valid */ @@ -141,7 +138,6 @@ static struct cfi_private *genprobe_ident_chips(struct map_info *map, struct chi retcfi = kmalloc(sizeof(struct cfi_private) + cfi.numchips * sizeof(struct flchip), GFP_KERNEL); if (!retcfi) { - printk(KERN_WARNING "%s: kmalloc failed for CFI private structure\n", map->name); kfree(cfi.cfiq); kfree(chip_map); return NULL; @@ -157,8 +153,7 @@ static struct cfi_private *genprobe_ident_chips(struct map_info *map, struct chi pchip->start = (i << cfi.chipshift); pchip->state = FL_READY; init_waitqueue_head(&pchip->wq); - spin_lock_init(&pchip->_spinlock); - pchip->mutex = &pchip->_spinlock; + mutex_init(&pchip->mutex); } } @@ -207,14 +202,14 @@ static inline struct mtd_info *cfi_cmdset_unknown(struct map_info *map, struct cfi_private *cfi = map->fldrv_priv; __u16 type = primary?cfi->cfiq->P_ID:cfi->cfiq->A_ID; #ifdef CONFIG_MODULES - char probename[16+sizeof(MODULE_SYMBOL_PREFIX)]; + char probename[sizeof(VMLINUX_SYMBOL_STR(cfi_cmdset_%4.4X))]; cfi_cmdset_fn_t *probe_function; - sprintf(probename, MODULE_SYMBOL_PREFIX "cfi_cmdset_%4.4X", type); + sprintf(probename, VMLINUX_SYMBOL_STR(cfi_cmdset_%4.4X), type); probe_function = __symbol_get(probename); if (!probe_function) { - request_module(probename + sizeof(MODULE_SYMBOL_PREFIX) - 1); + request_module("cfi_cmdset_%4.4X", type); probe_function = __symbol_get(probename); } @@ -244,17 +239,19 @@ static struct mtd_info *check_cmd_set(struct map_info *map, int primary) /* We need these for the !CONFIG_MODULES case, because symbol_get() doesn't work there */ #ifdef CONFIG_MTD_CFI_INTELEXT - case 0x0001: - case 0x0003: - case 0x0200: + case P_ID_INTEL_EXT: + case P_ID_INTEL_STD: + case P_ID_INTEL_PERFORMANCE: return cfi_cmdset_0001(map, primary); #endif #ifdef CONFIG_MTD_CFI_AMDSTD - case 0x0002: + case P_ID_AMD_STD: + case P_ID_SST_OLD: + case P_ID_WINBOND: return cfi_cmdset_0002(map, primary); #endif #ifdef CONFIG_MTD_CFI_STAA - case 0x0020: + case P_ID_ST_ADV: return cfi_cmdset_0020(map, primary); #endif default: diff --git a/drivers/mtd/chips/jedec.c b/drivers/mtd/chips/jedec.c deleted file mode 100644 index 2c3f019197c..00000000000 --- a/drivers/mtd/chips/jedec.c +++ /dev/null @@ -1,936 +0,0 @@ - -/* JEDEC Flash Interface. - * This is an older type of interface for self programming flash. It is - * commonly use in older AMD chips and is obsolete compared with CFI. - * It is called JEDEC because the JEDEC association distributes the ID codes - * for the chips. - * - * See the AMD flash databook for information on how to operate the interface. - * - * This code does not support anything wider than 8 bit flash chips, I am - * not going to guess how to send commands to them, plus I expect they will - * all speak CFI.. - * - * $Id: jedec.c,v 1.22 2005/01/05 18:05:11 dwmw2 Exp $ - */ - -#include <linux/init.h> -#include <linux/module.h> -#include <linux/kernel.h> -#include <linux/slab.h> -#include <linux/mtd/jedec.h> -#include <linux/mtd/map.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/compatmac.h> - -static struct mtd_info *jedec_probe(struct map_info *); -static int jedec_probe8(struct map_info *map,unsigned long base, - struct jedec_private *priv); -static int jedec_probe16(struct map_info *map,unsigned long base, - struct jedec_private *priv); -static int jedec_probe32(struct map_info *map,unsigned long base, - struct jedec_private *priv); -static void jedec_flash_chip_scan(struct jedec_private *priv,unsigned long start, - unsigned long len); -static int flash_erase(struct mtd_info *mtd, struct erase_info *instr); -static int flash_write(struct mtd_info *mtd, loff_t start, size_t len, - size_t *retlen, const u_char *buf); - -static unsigned long my_bank_size; - -/* Listing of parts and sizes. We need this table to learn the sector - size of the chip and the total length */ -static const struct JEDECTable JEDEC_table[] = { - { - .jedec = 0x013D, - .name = "AMD Am29F017D", - .size = 2*1024*1024, - .sectorsize = 64*1024, - .capabilities = MTD_CAP_NORFLASH - }, - { - .jedec = 0x01AD, - .name = "AMD Am29F016", - .size = 2*1024*1024, - .sectorsize = 64*1024, - .capabilities = MTD_CAP_NORFLASH - }, - { - .jedec = 0x01D5, - .name = "AMD Am29F080", - .size = 1*1024*1024, - .sectorsize = 64*1024, - .capabilities = MTD_CAP_NORFLASH - }, - { - .jedec = 0x01A4, - .name = "AMD Am29F040", - .size = 512*1024, - .sectorsize = 64*1024, - .capabilities = MTD_CAP_NORFLASH - }, - { - .jedec = 0x20E3, - .name = "AMD Am29W040B", - .size = 512*1024, - .sectorsize = 64*1024, - .capabilities = MTD_CAP_NORFLASH - }, - { - .jedec = 0xC2AD, - .name = "Macronix MX29F016", - .size = 2*1024*1024, - .sectorsize = 64*1024, - .capabilities = MTD_CAP_NORFLASH - }, - { .jedec = 0x0 } -}; - -static const struct JEDECTable *jedec_idtoinf(__u8 mfr,__u8 id); -static void jedec_sync(struct mtd_info *mtd) {}; -static int jedec_read(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf); -static int jedec_read_banked(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf); - -static struct mtd_info *jedec_probe(struct map_info *map); - - - -static struct mtd_chip_driver jedec_chipdrv = { - .probe = jedec_probe, - .name = "jedec", - .module = THIS_MODULE -}; - -/* Probe entry point */ - -static struct mtd_info *jedec_probe(struct map_info *map) -{ - struct mtd_info *MTD; - struct jedec_private *priv; - unsigned long Base; - unsigned long SectorSize; - unsigned count; - unsigned I,Uniq; - char Part[200]; - memset(&priv,0,sizeof(priv)); - - MTD = kmalloc(sizeof(struct mtd_info) + sizeof(struct jedec_private), GFP_KERNEL); - if (!MTD) - return NULL; - - memset(MTD, 0, sizeof(struct mtd_info) + sizeof(struct jedec_private)); - priv = (struct jedec_private *)&MTD[1]; - - my_bank_size = map->size; - - if (map->size/my_bank_size > MAX_JEDEC_CHIPS) - { - printk("mtd: Increase MAX_JEDEC_CHIPS, too many banks.\n"); - kfree(MTD); - return NULL; - } - - for (Base = 0; Base < map->size; Base += my_bank_size) - { - // Perhaps zero could designate all tests? - if (map->buswidth == 0) - map->buswidth = 1; - - if (map->buswidth == 1){ - if (jedec_probe8(map,Base,priv) == 0) { - printk("did recognize jedec chip\n"); - kfree(MTD); - return NULL; - } - } - if (map->buswidth == 2) - jedec_probe16(map,Base,priv); - if (map->buswidth == 4) - jedec_probe32(map,Base,priv); - } - - // Get the biggest sector size - SectorSize = 0; - for (I = 0; priv->chips[I].jedec != 0 && I < MAX_JEDEC_CHIPS; I++) - { - // printk("priv->chips[%d].jedec is %x\n",I,priv->chips[I].jedec); - // printk("priv->chips[%d].sectorsize is %lx\n",I,priv->chips[I].sectorsize); - if (priv->chips[I].sectorsize > SectorSize) - SectorSize = priv->chips[I].sectorsize; - } - - // Quickly ensure that the other sector sizes are factors of the largest - for (I = 0; priv->chips[I].jedec != 0 && I < MAX_JEDEC_CHIPS; I++) - { - if ((SectorSize/priv->chips[I].sectorsize)*priv->chips[I].sectorsize != SectorSize) - { - printk("mtd: Failed. Device has incompatible mixed sector sizes\n"); - kfree(MTD); - return NULL; - } - } - - /* Generate a part name that includes the number of different chips and - other configuration information */ - count = 1; - strlcpy(Part,map->name,sizeof(Part)-10); - strcat(Part," "); - Uniq = 0; - for (I = 0; priv->chips[I].jedec != 0 && I < MAX_JEDEC_CHIPS; I++) - { - const struct JEDECTable *JEDEC; - - if (priv->chips[I+1].jedec == priv->chips[I].jedec) - { - count++; - continue; - } - - // Locate the chip in the jedec table - JEDEC = jedec_idtoinf(priv->chips[I].jedec >> 8,priv->chips[I].jedec); - if (JEDEC == 0) - { - printk("mtd: Internal Error, JEDEC not set\n"); - kfree(MTD); - return NULL; - } - - if (Uniq != 0) - strcat(Part,","); - Uniq++; - - if (count != 1) - sprintf(Part+strlen(Part),"%x*[%s]",count,JEDEC->name); - else - sprintf(Part+strlen(Part),"%s",JEDEC->name); - if (strlen(Part) > sizeof(Part)*2/3) - break; - count = 1; - } - - /* Determine if the chips are organized in a linear fashion, or if there - are empty banks. Note, the last bank does not count here, only the - first banks are important. Holes on non-bank boundaries can not exist - due to the way the detection algorithm works. */ - if (priv->size < my_bank_size) - my_bank_size = priv->size; - priv->is_banked = 0; - //printk("priv->size is %x, my_bank_size is %x\n",priv->size,my_bank_size); - //printk("priv->bank_fill[0] is %x\n",priv->bank_fill[0]); - if (!priv->size) { - printk("priv->size is zero\n"); - kfree(MTD); - return NULL; - } - if (priv->size/my_bank_size) { - if (priv->size/my_bank_size == 1) { - priv->size = my_bank_size; - } - else { - for (I = 0; I != priv->size/my_bank_size - 1; I++) - { - if (priv->bank_fill[I] != my_bank_size) - priv->is_banked = 1; - - /* This even could be eliminated, but new de-optimized read/write - functions have to be written */ - printk("priv->bank_fill[%d] is %lx, priv->bank_fill[0] is %lx\n",I,priv->bank_fill[I],priv->bank_fill[0]); - if (priv->bank_fill[I] != priv->bank_fill[0]) - { - printk("mtd: Failed. Cannot handle unsymmetric banking\n"); - kfree(MTD); - return NULL; - } - } - } - } - if (priv->is_banked == 1) - strcat(Part,", banked"); - - // printk("Part: '%s'\n",Part); - - memset(MTD,0,sizeof(*MTD)); - // strlcpy(MTD->name,Part,sizeof(MTD->name)); - MTD->name = map->name; - MTD->type = MTD_NORFLASH; - MTD->flags = MTD_CAP_NORFLASH; - MTD->writesize = 1; - MTD->erasesize = SectorSize*(map->buswidth); - // printk("MTD->erasesize is %x\n",(unsigned int)MTD->erasesize); - MTD->size = priv->size; - // printk("MTD->size is %x\n",(unsigned int)MTD->size); - //MTD->module = THIS_MODULE; // ? Maybe this should be the low level module? - MTD->erase = flash_erase; - if (priv->is_banked == 1) - MTD->read = jedec_read_banked; - else - MTD->read = jedec_read; - MTD->write = flash_write; - MTD->sync = jedec_sync; - MTD->priv = map; - map->fldrv_priv = priv; - map->fldrv = &jedec_chipdrv; - __module_get(THIS_MODULE); - return MTD; -} - -/* Helper for the JEDEC function, JEDEC numbers all have odd parity */ -static int checkparity(u_char C) -{ - u_char parity = 0; - while (C != 0) - { - parity ^= C & 1; - C >>= 1; - } - - return parity == 1; -} - - -/* Take an array of JEDEC numbers that represent interleved flash chips - and process them. Check to make sure they are good JEDEC numbers, look - them up and then add them to the chip list */ -static int handle_jedecs(struct map_info *map,__u8 *Mfg,__u8 *Id,unsigned Count, - unsigned long base,struct jedec_private *priv) -{ - unsigned I,J; - unsigned long Size; - unsigned long SectorSize; - const struct JEDECTable *JEDEC; - - // Test #2 JEDEC numbers exhibit odd parity - for (I = 0; I != Count; I++) - { - if (checkparity(Mfg[I]) == 0 || checkparity(Id[I]) == 0) - return 0; - } - - // Finally, just make sure all the chip sizes are the same - JEDEC = jedec_idtoinf(Mfg[0],Id[0]); - - if (JEDEC == 0) - { - printk("mtd: Found JEDEC flash chip, but do not have a table entry for %x:%x\n",Mfg[0],Mfg[1]); - return 0; - } - - Size = JEDEC->size; - SectorSize = JEDEC->sectorsize; - for (I = 0; I != Count; I++) - { - JEDEC = jedec_idtoinf(Mfg[0],Id[0]); - if (JEDEC == 0) - { - printk("mtd: Found JEDEC flash chip, but do not have a table entry for %x:%x\n",Mfg[0],Mfg[1]); - return 0; - } - - if (Size != JEDEC->size || SectorSize != JEDEC->sectorsize) - { - printk("mtd: Failed. Interleved flash does not have matching characteristics\n"); - return 0; - } - } - - // Load the Chips - for (I = 0; I != MAX_JEDEC_CHIPS; I++) - { - if (priv->chips[I].jedec == 0) - break; - } - - if (I + Count > MAX_JEDEC_CHIPS) - { - printk("mtd: Device has too many chips. Increase MAX_JEDEC_CHIPS\n"); - return 0; - } - - // Add them to the table - for (J = 0; J != Count; J++) - { - unsigned long Bank; - - JEDEC = jedec_idtoinf(Mfg[J],Id[J]); - priv->chips[I].jedec = (Mfg[J] << 8) | Id[J]; - priv->chips[I].size = JEDEC->size; - priv->chips[I].sectorsize = JEDEC->sectorsize; - priv->chips[I].base = base + J; - priv->chips[I].datashift = J*8; - priv->chips[I].capabilities = JEDEC->capabilities; - priv->chips[I].offset = priv->size + J; - - // log2 n :| - priv->chips[I].addrshift = 0; - for (Bank = Count; Bank != 1; Bank >>= 1, priv->chips[I].addrshift++); - - // Determine how filled this bank is. - Bank = base & (~(my_bank_size-1)); - if (priv->bank_fill[Bank/my_bank_size] < base + - (JEDEC->size << priv->chips[I].addrshift) - Bank) - priv->bank_fill[Bank/my_bank_size] = base + (JEDEC->size << priv->chips[I].addrshift) - Bank; - I++; - } - - priv->size += priv->chips[I-1].size*Count; - - return priv->chips[I-1].size; -} - -/* Lookup the chip information from the JEDEC ID table. */ -static const struct JEDECTable *jedec_idtoinf(__u8 mfr,__u8 id) -{ - __u16 Id = (mfr << 8) | id; - unsigned long I = 0; - for (I = 0; JEDEC_table[I].jedec != 0; I++) - if (JEDEC_table[I].jedec == Id) - return JEDEC_table + I; - return NULL; -} - -// Look for flash using an 8 bit bus interface -static int jedec_probe8(struct map_info *map,unsigned long base, - struct jedec_private *priv) -{ - #define flread(x) map_read8(map,base+x) - #define flwrite(v,x) map_write8(map,v,base+x) - - const unsigned long AutoSel1 = 0xAA; - const unsigned long AutoSel2 = 0x55; - const unsigned long AutoSel3 = 0x90; - const unsigned long Reset = 0xF0; - __u32 OldVal; - __u8 Mfg[1]; - __u8 Id[1]; - unsigned I; - unsigned long Size; - - // Wait for any write/erase operation to settle - OldVal = flread(base); - for (I = 0; OldVal != flread(base) && I < 10000; I++) - OldVal = flread(base); - - // Reset the chip - flwrite(Reset,0x555); - - // Send the sequence - flwrite(AutoSel1,0x555); - flwrite(AutoSel2,0x2AA); - flwrite(AutoSel3,0x555); - - // Get the JEDEC numbers - Mfg[0] = flread(0); - Id[0] = flread(1); - // printk("Mfg is %x, Id is %x\n",Mfg[0],Id[0]); - - Size = handle_jedecs(map,Mfg,Id,1,base,priv); - // printk("handle_jedecs Size is %x\n",(unsigned int)Size); - if (Size == 0) - { - flwrite(Reset,0x555); - return 0; - } - - - // Reset. - flwrite(Reset,0x555); - - return 1; - - #undef flread - #undef flwrite -} - -// Look for flash using a 16 bit bus interface (ie 2 8-bit chips) -static int jedec_probe16(struct map_info *map,unsigned long base, - struct jedec_private *priv) -{ - return 0; -} - -// Look for flash using a 32 bit bus interface (ie 4 8-bit chips) -static int jedec_probe32(struct map_info *map,unsigned long base, - struct jedec_private *priv) -{ - #define flread(x) map_read32(map,base+((x)<<2)) - #define flwrite(v,x) map_write32(map,v,base+((x)<<2)) - - const unsigned long AutoSel1 = 0xAAAAAAAA; - const unsigned long AutoSel2 = 0x55555555; - const unsigned long AutoSel3 = 0x90909090; - const unsigned long Reset = 0xF0F0F0F0; - __u32 OldVal; - __u8 Mfg[4]; - __u8 Id[4]; - unsigned I; - unsigned long Size; - - // Wait for any write/erase operation to settle - OldVal = flread(base); - for (I = 0; OldVal != flread(base) && I < 10000; I++) - OldVal = flread(base); - - // Reset the chip - flwrite(Reset,0x555); - - // Send the sequence - flwrite(AutoSel1,0x555); - flwrite(AutoSel2,0x2AA); - flwrite(AutoSel3,0x555); - - // Test #1, JEDEC numbers are readable from 0x??00/0x??01 - if (flread(0) != flread(0x100) || - flread(1) != flread(0x101)) - { - flwrite(Reset,0x555); - return 0; - } - - // Split up the JEDEC numbers - OldVal = flread(0); - for (I = 0; I != 4; I++) - Mfg[I] = (OldVal >> (I*8)); - OldVal = flread(1); - for (I = 0; I != 4; I++) - Id[I] = (OldVal >> (I*8)); - - Size = handle_jedecs(map,Mfg,Id,4,base,priv); - if (Size == 0) - { - flwrite(Reset,0x555); - return 0; - } - - /* Check if there is address wrap around within a single bank, if this - returns JEDEC numbers then we assume that it is wrap around. Notice - we call this routine with the JEDEC return still enabled, if two or - more flashes have a truncated address space the probe test will still - work */ - if (base + (Size<<2)+0x555 < map->size && - base + (Size<<2)+0x555 < (base & (~(my_bank_size-1))) + my_bank_size) - { - if (flread(base+Size) != flread(base+Size + 0x100) || - flread(base+Size + 1) != flread(base+Size + 0x101)) - { - jedec_probe32(map,base+Size,priv); - } - } - - // Reset. - flwrite(0xF0F0F0F0,0x555); - - return 1; - - #undef flread - #undef flwrite -} - -/* Linear read. */ -static int jedec_read(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf) -{ - struct map_info *map = mtd->priv; - - map_copy_from(map, buf, from, len); - *retlen = len; - return 0; -} - -/* Banked read. Take special care to jump past the holes in the bank - mapping. This version assumes symetry in the holes.. */ -static int jedec_read_banked(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf) -{ - struct map_info *map = mtd->priv; - struct jedec_private *priv = map->fldrv_priv; - - *retlen = 0; - while (len > 0) - { - // Determine what bank and offset into that bank the first byte is - unsigned long bank = from & (~(priv->bank_fill[0]-1)); - unsigned long offset = from & (priv->bank_fill[0]-1); - unsigned long get = len; - if (priv->bank_fill[0] - offset < len) - get = priv->bank_fill[0] - offset; - - bank /= priv->bank_fill[0]; - map_copy_from(map,buf + *retlen,bank*my_bank_size + offset,get); - - len -= get; - *retlen += get; - from += get; - } - return 0; -} - -/* Pass the flags value that the flash return before it re-entered read - mode. */ -static void jedec_flash_failed(unsigned char code) -{ - /* Bit 5 being high indicates that there was an internal device - failure, erasure time limits exceeded or something */ - if ((code & (1 << 5)) != 0) - { - printk("mtd: Internal Flash failure\n"); - return; - } - printk("mtd: Programming didn't take\n"); -} - -/* This uses the erasure function described in the AMD Flash Handbook, - it will work for flashes with a fixed sector size only. Flashes with - a selection of sector sizes (ie the AMD Am29F800B) will need a different - routine. This routine tries to parallize erasing multiple chips/sectors - where possible */ -static int flash_erase(struct mtd_info *mtd, struct erase_info *instr) -{ - // Does IO to the currently selected chip - #define flread(x) map_read8(map,chip->base+((x)<<chip->addrshift)) - #define flwrite(v,x) map_write8(map,v,chip->base+((x)<<chip->addrshift)) - - unsigned long Time = 0; - unsigned long NoTime = 0; - unsigned long start = instr->addr, len = instr->len; - unsigned int I; - struct map_info *map = mtd->priv; - struct jedec_private *priv = map->fldrv_priv; - - // Verify the arguments.. - if (start + len > mtd->size || - (start % mtd->erasesize) != 0 || - (len % mtd->erasesize) != 0 || - (len/mtd->erasesize) == 0) - return -EINVAL; - - jedec_flash_chip_scan(priv,start,len); - - // Start the erase sequence on each chip - for (I = 0; priv->chips[I].jedec != 0 && I < MAX_JEDEC_CHIPS; I++) - { - unsigned long off; - struct jedec_flash_chip *chip = priv->chips + I; - - if (chip->length == 0) - continue; - - if (chip->start + chip->length > chip->size) - { - printk("DIE\n"); - return -EIO; - } - - flwrite(0xF0,chip->start + 0x555); - flwrite(0xAA,chip->start + 0x555); - flwrite(0x55,chip->start + 0x2AA); - flwrite(0x80,chip->start + 0x555); - flwrite(0xAA,chip->start + 0x555); - flwrite(0x55,chip->start + 0x2AA); - - /* Once we start selecting the erase sectors the delay between each - command must not exceed 50us or it will immediately start erasing - and ignore the other sectors */ - for (off = 0; off < len; off += chip->sectorsize) - { - // Check to make sure we didn't timeout - flwrite(0x30,chip->start + off); - if (off == 0) - continue; - if ((flread(chip->start + off) & (1 << 3)) != 0) - { - printk("mtd: Ack! We timed out the erase timer!\n"); - return -EIO; - } - } - } - - /* We could split this into a timer routine and return early, performing - background erasure.. Maybe later if the need warrents */ - - /* Poll the flash for erasure completion, specs say this can take as long - as 480 seconds to do all the sectors (for a 2 meg flash). - Erasure time is dependent on chip age, temp and wear.. */ - - /* This being a generic routine assumes a 32 bit bus. It does read32s - and bundles interleved chips into the same grouping. This will work - for all bus widths */ - Time = 0; - NoTime = 0; - for (I = 0; priv->chips[I].jedec != 0 && I < MAX_JEDEC_CHIPS; I++) - { - struct jedec_flash_chip *chip = priv->chips + I; - unsigned long off = 0; - unsigned todo[4] = {0,0,0,0}; - unsigned todo_left = 0; - unsigned J; - - if (chip->length == 0) - continue; - - /* Find all chips in this data line, realistically this is all - or nothing up to the interleve count */ - for (J = 0; priv->chips[J].jedec != 0 && J < MAX_JEDEC_CHIPS; J++) - { - if ((priv->chips[J].base & (~((1<<chip->addrshift)-1))) == - (chip->base & (~((1<<chip->addrshift)-1)))) - { - todo_left++; - todo[priv->chips[J].base & ((1<<chip->addrshift)-1)] = 1; - } - } - - /* printk("todo: %x %x %x %x\n",(short)todo[0],(short)todo[1], - (short)todo[2],(short)todo[3]); - */ - while (1) - { - __u32 Last[4]; - unsigned long Count = 0; - - /* During erase bit 7 is held low and bit 6 toggles, we watch this, - should it stop toggling or go high then the erase is completed, - or this is not really flash ;> */ - switch (map->buswidth) { - case 1: - Last[0] = map_read8(map,(chip->base >> chip->addrshift) + chip->start + off); - Last[1] = map_read8(map,(chip->base >> chip->addrshift) + chip->start + off); - Last[2] = map_read8(map,(chip->base >> chip->addrshift) + chip->start + off); - break; - case 2: - Last[0] = map_read16(map,(chip->base >> chip->addrshift) + chip->start + off); - Last[1] = map_read16(map,(chip->base >> chip->addrshift) + chip->start + off); - Last[2] = map_read16(map,(chip->base >> chip->addrshift) + chip->start + off); - break; - case 3: - Last[0] = map_read32(map,(chip->base >> chip->addrshift) + chip->start + off); - Last[1] = map_read32(map,(chip->base >> chip->addrshift) + chip->start + off); - Last[2] = map_read32(map,(chip->base >> chip->addrshift) + chip->start + off); - break; - } - Count = 3; - while (todo_left != 0) - { - for (J = 0; J != 4; J++) - { - __u8 Byte1 = (Last[(Count-1)%4] >> (J*8)) & 0xFF; - __u8 Byte2 = (Last[(Count-2)%4] >> (J*8)) & 0xFF; - __u8 Byte3 = (Last[(Count-3)%4] >> (J*8)) & 0xFF; - if (todo[J] == 0) - continue; - - if ((Byte1 & (1 << 7)) == 0 && Byte1 != Byte2) - { -// printk("Check %x %x %x\n",(short)J,(short)Byte1,(short)Byte2); - continue; - } - - if (Byte1 == Byte2) - { - jedec_flash_failed(Byte3); - return -EIO; - } - - todo[J] = 0; - todo_left--; - } - -/* if (NoTime == 0) - Time += HZ/10 - schedule_timeout(HZ/10);*/ - NoTime = 0; - - switch (map->buswidth) { - case 1: - Last[Count % 4] = map_read8(map,(chip->base >> chip->addrshift) + chip->start + off); - break; - case 2: - Last[Count % 4] = map_read16(map,(chip->base >> chip->addrshift) + chip->start + off); - break; - case 4: - Last[Count % 4] = map_read32(map,(chip->base >> chip->addrshift) + chip->start + off); - break; - } - Count++; - -/* // Count time, max of 15s per sector (according to AMD) - if (Time > 15*len/mtd->erasesize*HZ) - { - printk("mtd: Flash Erase Timed out\n"); - return -EIO; - } */ - } - - // Skip to the next chip if we used chip erase - if (chip->length == chip->size) - off = chip->size; - else - off += chip->sectorsize; - - if (off >= chip->length) - break; - NoTime = 1; - } - - for (J = 0; priv->chips[J].jedec != 0 && J < MAX_JEDEC_CHIPS; J++) - { - if ((priv->chips[J].base & (~((1<<chip->addrshift)-1))) == - (chip->base & (~((1<<chip->addrshift)-1)))) - priv->chips[J].length = 0; - } - } - - //printk("done\n"); - instr->state = MTD_ERASE_DONE; - mtd_erase_callback(instr); - return 0; - - #undef flread - #undef flwrite -} - -/* This is the simple flash writing function. It writes to every byte, in - sequence. It takes care of how to properly address the flash if - the flash is interleved. It can only be used if all the chips in the - array are identical!*/ -static int flash_write(struct mtd_info *mtd, loff_t start, size_t len, - size_t *retlen, const u_char *buf) -{ - /* Does IO to the currently selected chip. It takes the bank addressing - base (which is divisible by the chip size) adds the necessary lower bits - of addrshift (interleave index) and then adds the control register index. */ - #define flread(x) map_read8(map,base+(off&((1<<chip->addrshift)-1))+((x)<<chip->addrshift)) - #define flwrite(v,x) map_write8(map,v,base+(off&((1<<chip->addrshift)-1))+((x)<<chip->addrshift)) - - struct map_info *map = mtd->priv; - struct jedec_private *priv = map->fldrv_priv; - unsigned long base; - unsigned long off; - size_t save_len = len; - - if (start + len > mtd->size) - return -EIO; - - //printk("Here"); - - //printk("flash_write: start is %x, len is %x\n",start,(unsigned long)len); - while (len != 0) - { - struct jedec_flash_chip *chip = priv->chips; - unsigned long bank; - unsigned long boffset; - - // Compute the base of the flash. - off = ((unsigned long)start) % (chip->size << chip->addrshift); - base = start - off; - - // Perform banked addressing translation. - bank = base & (~(priv->bank_fill[0]-1)); - boffset = base & (priv->bank_fill[0]-1); - bank = (bank/priv->bank_fill[0])*my_bank_size; - base = bank + boffset; - - // printk("Flasing %X %X %X\n",base,chip->size,len); - // printk("off is %x, compare with %x\n",off,chip->size << chip->addrshift); - - // Loop over this page - for (; off != (chip->size << chip->addrshift) && len != 0; start++, len--, off++,buf++) - { - unsigned char oldbyte = map_read8(map,base+off); - unsigned char Last[4]; - unsigned long Count = 0; - - if (oldbyte == *buf) { - // printk("oldbyte and *buf is %x,len is %x\n",oldbyte,len); - continue; - } - if (((~oldbyte) & *buf) != 0) - printk("mtd: warn: Trying to set a 0 to a 1\n"); - - // Write - flwrite(0xAA,0x555); - flwrite(0x55,0x2AA); - flwrite(0xA0,0x555); - map_write8(map,*buf,base + off); - Last[0] = map_read8(map,base + off); - Last[1] = map_read8(map,base + off); - Last[2] = map_read8(map,base + off); - - /* Wait for the flash to finish the operation. We store the last 4 - status bytes that have been retrieved so we can determine why - it failed. The toggle bits keep toggling when there is a - failure */ - for (Count = 3; Last[(Count - 1) % 4] != Last[(Count - 2) % 4] && - Count < 10000; Count++) - Last[Count % 4] = map_read8(map,base + off); - if (Last[(Count - 1) % 4] != *buf) - { - jedec_flash_failed(Last[(Count - 3) % 4]); - return -EIO; - } - } - } - *retlen = save_len; - return 0; -} - -/* This is used to enhance the speed of the erase routine, - when things are being done to multiple chips it is possible to - parallize the operations, particularly full memory erases of multi - chip memories benifit */ -static void jedec_flash_chip_scan(struct jedec_private *priv,unsigned long start, - unsigned long len) -{ - unsigned int I; - - // Zero the records - for (I = 0; priv->chips[I].jedec != 0 && I < MAX_JEDEC_CHIPS; I++) - priv->chips[I].start = priv->chips[I].length = 0; - - // Intersect the region with each chip - for (I = 0; priv->chips[I].jedec != 0 && I < MAX_JEDEC_CHIPS; I++) - { - struct jedec_flash_chip *chip = priv->chips + I; - unsigned long ByteStart; - unsigned long ChipEndByte = chip->offset + (chip->size << chip->addrshift); - - // End is before this chip or the start is after it - if (start+len < chip->offset || - ChipEndByte - (1 << chip->addrshift) < start) - continue; - - if (start < chip->offset) - { - ByteStart = chip->offset; - chip->start = 0; - } - else - { - chip->start = (start - chip->offset + (1 << chip->addrshift)-1) >> chip->addrshift; - ByteStart = start; - } - - if (start + len >= ChipEndByte) - chip->length = (ChipEndByte - ByteStart) >> chip->addrshift; - else - chip->length = (start + len - ByteStart + (1 << chip->addrshift)-1) >> chip->addrshift; - } -} - -int __init jedec_init(void) -{ - register_mtd_chip_driver(&jedec_chipdrv); - return 0; -} - -static void __exit jedec_exit(void) -{ - unregister_mtd_chip_driver(&jedec_chipdrv); -} - -module_init(jedec_init); -module_exit(jedec_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Jason Gunthorpe <jgg@deltatee.com> et al."); -MODULE_DESCRIPTION("Old MTD chip driver for JEDEC-compliant flash chips"); diff --git a/drivers/mtd/chips/jedec_probe.c b/drivers/mtd/chips/jedec_probe.c index 1154dac715a..7c0b27d132b 100644 --- a/drivers/mtd/chips/jedec_probe.c +++ b/drivers/mtd/chips/jedec_probe.c @@ -1,7 +1,6 @@ /* Common Flash Interface probe code. (C) 2000 Red Hat. GPL'd. - $Id: jedec_probe.c,v 1.66 2005/11/07 11:14:23 gleixner Exp $ See JEDEC (http://www.jedec.org/) standard JESD21C (section 3.5) for the standard this probe goes back to. @@ -17,31 +16,14 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/interrupt.h> -#include <linux/init.h> #include <linux/mtd/mtd.h> #include <linux/mtd/map.h> #include <linux/mtd/cfi.h> #include <linux/mtd/gen_probe.h> -/* Manufacturers */ -#define MANUFACTURER_AMD 0x0001 -#define MANUFACTURER_ATMEL 0x001f -#define MANUFACTURER_FUJITSU 0x0004 -#define MANUFACTURER_HYUNDAI 0x00AD -#define MANUFACTURER_INTEL 0x0089 -#define MANUFACTURER_MACRONIX 0x00C2 -#define MANUFACTURER_NEC 0x0010 -#define MANUFACTURER_PMC 0x009D -#define MANUFACTURER_SHARP 0x00b0 -#define MANUFACTURER_SST 0x00BF -#define MANUFACTURER_ST 0x0020 -#define MANUFACTURER_TOSHIBA 0x0098 -#define MANUFACTURER_WINBOND 0x00da - - /* AMD */ -#define AM29DL800BB 0x22C8 +#define AM29DL800BB 0x22CB #define AM29DL800BT 0x224A #define AM29F800BB 0x2258 @@ -59,6 +41,8 @@ #define AM29LV040B 0x004F #define AM29F032B 0x0041 #define AM29F002T 0x00B0 +#define AM29SL800DB 0x226B +#define AM29SL800DT 0x22EA /* Atmel */ #define AT49BV512 0x0003 @@ -68,8 +52,13 @@ #define AT49BV32X 0x00C8 #define AT49BV32XT 0x00C9 +/* Eon */ +#define EN29SL800BB 0x226B +#define EN29SL800BT 0x22EA + /* Fujitsu */ #define MBM29F040C 0x00A4 +#define MBM29F800BA 0x2258 #define MBM29LV650UE 0x22D7 #define MBM29LV320TE 0x22F6 #define MBM29LV320BE 0x22F9 @@ -104,6 +93,11 @@ #define I28F320B3B 0x8897 #define I28F640B3T 0x8898 #define I28F640B3B 0x8899 +#define I28F640C3B 0x88CD +#define I28F160F3T 0x88F3 +#define I28F160F3B 0x88F4 +#define I28F160C3T 0x88C2 +#define I28F160C3B 0x88C3 #define I82802AB 0x00ad #define I82802AC 0x00ac @@ -126,11 +120,14 @@ #define PM49FL008 0x006A /* Sharp */ -#define LH28F640BF 0x00b0 +#define LH28F640BF 0x00B0 /* ST - www.st.com */ -#define M29W800DT 0x00D7 -#define M29W800DB 0x005B +#define M29F800AB 0x0058 +#define M29W800DT 0x22D7 +#define M29W800DB 0x225B +#define M29W400DT 0x00EE +#define M29W400DB 0x00EF #define M29W160DT 0x22C4 #define M29W160DB 0x2249 #define M29W040B 0x00E3 @@ -138,6 +135,9 @@ #define M50FW080 0x002D #define M50FW016 0x002E #define M50LPW080 0x002F +#define M50FLW080A 0x0080 +#define M50FLW080B 0x0081 +#define PSD4256G6V 0x00e9 /* SST */ #define SST29EE020 0x0010 @@ -147,17 +147,23 @@ #define SST39LF800 0x2781 #define SST39LF160 0x2782 #define SST39VF1601 0x234b +#define SST39VF3201 0x235b +#define SST39WF1601 0x274b +#define SST39WF1602 0x274a #define SST39LF512 0x00D4 #define SST39LF010 0x00D5 #define SST39LF020 0x00D6 #define SST39LF040 0x00D7 #define SST39SF010A 0x00B5 #define SST39SF020A 0x00B6 +#define SST39SF040 0x00B7 #define SST49LF004B 0x0060 +#define SST49LF040B 0x0050 #define SST49LF008A 0x005a #define SST49LF030A 0x001C #define SST49LF040A 0x0051 #define SST49LF080A 0x005B +#define SST36VF3203 0x7354 /* Toshiba */ #define TC58FVT160 0x00C2 @@ -185,15 +191,17 @@ enum uaddr { MTD_UADDR_0x0555_0x02AA, MTD_UADDR_0x0555_0x0AAA, MTD_UADDR_0x5555_0x2AAA, + MTD_UADDR_0x0AAA_0x0554, MTD_UADDR_0x0AAA_0x0555, + MTD_UADDR_0xAAAA_0x5555, MTD_UADDR_DONT_CARE, /* Requires an arbitrary address */ MTD_UADDR_UNNECESSARY, /* Does not require any address */ }; struct unlock_addr { - u32 addr1; - u32 addr2; + uint32_t addr1; + uint32_t addr2; }; @@ -202,7 +210,7 @@ struct unlock_addr { * exists, but is for MTD_UADDR_NOT_SUPPORTED - and, therefore, * should not be used. The problem is that structures with * initializers have extra fields initialized to 0. It is _very_ - * desireable to have the unlock address entries for unsupported + * desirable to have the unlock address entries for unsupported * data widths automatically initialized - that means that * MTD_UADDR_NOT_SUPPORTED must be 0 and the first entry here * must go unused. @@ -228,11 +236,21 @@ static const struct unlock_addr unlock_addrs[] = { .addr2 = 0x2aaa }, + [MTD_UADDR_0x0AAA_0x0554] = { + .addr1 = 0x0AAA, + .addr2 = 0x0554 + }, + [MTD_UADDR_0x0AAA_0x0555] = { .addr1 = 0x0AAA, .addr2 = 0x0555 }, + [MTD_UADDR_0xAAAA_0x5555] = { + .addr1 = 0xaaaa, + .addr2 = 0x5555 + }, + [MTD_UADDR_DONT_CARE] = { .addr1 = 0x0000, /* Doesn't matter which address */ .addr2 = 0x0000 /* is used - must be last entry */ @@ -244,16 +262,16 @@ static const struct unlock_addr unlock_addrs[] = { } }; - struct amd_flash_info { - const __u16 mfr_id; - const __u16 dev_id; const char *name; - const int DevSize; - const int NumEraseRegions; - const int CmdSet; - const __u8 uaddr[4]; /* unlock addrs for 8, 16, 32, 64 */ - const ulong regions[6]; + const uint16_t mfr_id; + const uint16_t dev_id; + const uint8_t dev_size; + const uint8_t nr_regions; + const uint16_t cmd_set; + const uint32_t regions[6]; + const uint8_t devtypes; /* Bitmask for x8, x16 etc. */ + const uint8_t uaddr; /* unlock addrs for 8, 16, 32, 64 */ }; #define ERASEINFO(size,blocks) (size<<8)|(blocks-1) @@ -275,29 +293,26 @@ struct amd_flash_info { */ static const struct amd_flash_info jedec_table[] = { { - .mfr_id = MANUFACTURER_AMD, + .mfr_id = CFI_MFR_AMD, .dev_id = AM29F032B, .name = "AMD AM29F032B", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x02AA /* x8 */ - }, - .DevSize = SIZE_4MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .devtypes = CFI_DEVICETYPE_X8, + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,64) } }, { - .mfr_id = MANUFACTURER_AMD, + .mfr_id = CFI_MFR_AMD, .dev_id = AM29LV160DT, .name = "AMD AM29LV160DT", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA /* x16 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x10000,31), ERASEINFO(0x08000,1), @@ -305,16 +320,14 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x04000,1) } }, { - .mfr_id = MANUFACTURER_AMD, + .mfr_id = CFI_MFR_AMD, .dev_id = AM29LV160DB, .name = "AMD AM29LV160DB", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA /* x16 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x04000,1), ERASEINFO(0x02000,2), @@ -322,16 +335,14 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x10000,31) } }, { - .mfr_id = MANUFACTURER_AMD, + .mfr_id = CFI_MFR_AMD, .dev_id = AM29LV400BB, .name = "AMD AM29LV400BB", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x04000,1), ERASEINFO(0x02000,2), @@ -339,16 +350,14 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x10000,7) } }, { - .mfr_id = MANUFACTURER_AMD, + .mfr_id = CFI_MFR_AMD, .dev_id = AM29LV400BT, .name = "AMD AM29LV400BT", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x10000,7), ERASEINFO(0x08000,1), @@ -356,16 +365,14 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x04000,1) } }, { - .mfr_id = MANUFACTURER_AMD, + .mfr_id = CFI_MFR_AMD, .dev_id = AM29LV800BB, .name = "AMD AM29LV800BB", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x04000,1), ERASEINFO(0x02000,2), @@ -374,16 +381,14 @@ static const struct amd_flash_info jedec_table[] = { } }, { /* add DL */ - .mfr_id = MANUFACTURER_AMD, + .mfr_id = CFI_MFR_AMD, .dev_id = AM29DL800BB, .name = "AMD AM29DL800BB", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 6, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 6, .regions = { ERASEINFO(0x04000,1), ERASEINFO(0x08000,1), @@ -393,16 +398,14 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x10000,14) } }, { - .mfr_id = MANUFACTURER_AMD, + .mfr_id = CFI_MFR_AMD, .dev_id = AM29DL800BT, .name = "AMD AM29DL800BT", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 6, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 6, .regions = { ERASEINFO(0x10000,14), ERASEINFO(0x04000,1), @@ -412,16 +415,14 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x04000,1) } }, { - .mfr_id = MANUFACTURER_AMD, + .mfr_id = CFI_MFR_AMD, .dev_id = AM29F800BB, .name = "AMD AM29F800BB", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x04000,1), ERASEINFO(0x02000,2), @@ -429,16 +430,14 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x10000,15), } }, { - .mfr_id = MANUFACTURER_AMD, + .mfr_id = CFI_MFR_AMD, .dev_id = AM29LV800BT, .name = "AMD AM29LV800BT", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x10000,15), ERASEINFO(0x08000,1), @@ -446,16 +445,14 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x04000,1) } }, { - .mfr_id = MANUFACTURER_AMD, + .mfr_id = CFI_MFR_AMD, .dev_id = AM29F800BT, .name = "AMD AM29F800BT", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x10000,15), ERASEINFO(0x08000,1), @@ -463,80 +460,74 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x04000,1) } }, { - .mfr_id = MANUFACTURER_AMD, + .mfr_id = CFI_MFR_AMD, .dev_id = AM29F017D, .name = "AMD AM29F017D", - .uaddr = { - [0] = MTD_UADDR_DONT_CARE /* x8 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_DONT_CARE, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,32), } }, { - .mfr_id = MANUFACTURER_AMD, + .mfr_id = CFI_MFR_AMD, .dev_id = AM29F016D, .name = "AMD AM29F016D", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x02AA /* x8 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,32), } }, { - .mfr_id = MANUFACTURER_AMD, + .mfr_id = CFI_MFR_AMD, .dev_id = AM29F080, .name = "AMD AM29F080", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x02AA /* x8 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,16), } }, { - .mfr_id = MANUFACTURER_AMD, + .mfr_id = CFI_MFR_AMD, .dev_id = AM29F040, .name = "AMD AM29F040", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x02AA /* x8 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,8), } }, { - .mfr_id = MANUFACTURER_AMD, + .mfr_id = CFI_MFR_AMD, .dev_id = AM29LV040B, .name = "AMD AM29LV040B", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x02AA /* x8 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,8), } }, { - .mfr_id = MANUFACTURER_AMD, + .mfr_id = CFI_MFR_AMD, .dev_id = AM29F002T, .name = "AMD AM29F002T", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x02AA /* x8 */ - }, - .DevSize = SIZE_256KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_256KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x10000,3), ERASEINFO(0x08000,1), @@ -544,159 +535,216 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x04000,1), } }, { - .mfr_id = MANUFACTURER_ATMEL, + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29SL800DT, + .name = "AMD AM29SL800DT", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000,15), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x04000,1), + } + }, { + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29SL800DB, + .name = "AMD AM29SL800DB", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,15), + } + }, { + .mfr_id = CFI_MFR_ATMEL, .dev_id = AT49BV512, .name = "Atmel AT49BV512", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ - }, - .DevSize = SIZE_64KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_64KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,1) } }, { - .mfr_id = MANUFACTURER_ATMEL, + .mfr_id = CFI_MFR_ATMEL, .dev_id = AT29LV512, .name = "Atmel AT29LV512", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ - }, - .DevSize = SIZE_64KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_64KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x80,256), ERASEINFO(0x80,256) } }, { - .mfr_id = MANUFACTURER_ATMEL, + .mfr_id = CFI_MFR_ATMEL, .dev_id = AT49BV16X, .name = "Atmel AT49BV16X", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x0AAA, /* x8 */ - [1] = MTD_UADDR_0x0555_0x0AAA /* x16 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x0AAA, /* ???? */ + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x02000,8), ERASEINFO(0x10000,31) } }, { - .mfr_id = MANUFACTURER_ATMEL, + .mfr_id = CFI_MFR_ATMEL, .dev_id = AT49BV16XT, .name = "Atmel AT49BV16XT", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x0AAA, /* x8 */ - [1] = MTD_UADDR_0x0555_0x0AAA /* x16 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x0AAA, /* ???? */ + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x10000,31), ERASEINFO(0x02000,8) } }, { - .mfr_id = MANUFACTURER_ATMEL, + .mfr_id = CFI_MFR_ATMEL, .dev_id = AT49BV32X, .name = "Atmel AT49BV32X", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x0AAA, /* x8 */ - [1] = MTD_UADDR_0x0555_0x0AAA /* x16 */ - }, - .DevSize = SIZE_4MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x0AAA, /* ???? */ + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x02000,8), ERASEINFO(0x10000,63) } }, { - .mfr_id = MANUFACTURER_ATMEL, + .mfr_id = CFI_MFR_ATMEL, .dev_id = AT49BV32XT, .name = "Atmel AT49BV32XT", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x0AAA, /* x8 */ - [1] = MTD_UADDR_0x0555_0x0AAA /* x16 */ - }, - .DevSize = SIZE_4MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x0AAA, /* ???? */ + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x10000,63), ERASEINFO(0x02000,8) } }, { - .mfr_id = MANUFACTURER_FUJITSU, + .mfr_id = CFI_MFR_EON, + .dev_id = EN29SL800BT, + .name = "Eon EN29SL800BT", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000,15), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x04000,1), + } + }, { + .mfr_id = CFI_MFR_EON, + .dev_id = EN29SL800BB, + .name = "Eon EN29SL800BB", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,15), + } + }, { + .mfr_id = CFI_MFR_FUJITSU, .dev_id = MBM29F040C, .name = "Fujitsu MBM29F040C", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,8) } }, { - .mfr_id = MANUFACTURER_FUJITSU, + .mfr_id = CFI_MFR_FUJITSU, + .dev_id = MBM29F800BA, + .name = "Fujitsu MBM29F800BA", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,15), + } + }, { + .mfr_id = CFI_MFR_FUJITSU, .dev_id = MBM29LV650UE, .name = "Fujitsu MBM29LV650UE", - .uaddr = { - [0] = MTD_UADDR_DONT_CARE /* x16 */ - }, - .DevSize = SIZE_8MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_DONT_CARE, + .dev_size = SIZE_8MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,128) } }, { - .mfr_id = MANUFACTURER_FUJITSU, + .mfr_id = CFI_MFR_FUJITSU, .dev_id = MBM29LV320TE, .name = "Fujitsu MBM29LV320TE", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_4MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x10000,63), ERASEINFO(0x02000,8) } }, { - .mfr_id = MANUFACTURER_FUJITSU, + .mfr_id = CFI_MFR_FUJITSU, .dev_id = MBM29LV320BE, .name = "Fujitsu MBM29LV320BE", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_4MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x02000,8), ERASEINFO(0x10000,63) } }, { - .mfr_id = MANUFACTURER_FUJITSU, + .mfr_id = CFI_MFR_FUJITSU, .dev_id = MBM29LV160TE, .name = "Fujitsu MBM29LV160TE", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x10000,31), ERASEINFO(0x08000,1), @@ -704,16 +752,14 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x04000,1) } }, { - .mfr_id = MANUFACTURER_FUJITSU, + .mfr_id = CFI_MFR_FUJITSU, .dev_id = MBM29LV160BE, .name = "Fujitsu MBM29LV160BE", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x04000,1), ERASEINFO(0x02000,2), @@ -721,16 +767,14 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x10000,31) } }, { - .mfr_id = MANUFACTURER_FUJITSU, + .mfr_id = CFI_MFR_FUJITSU, .dev_id = MBM29LV800BA, .name = "Fujitsu MBM29LV800BA", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x04000,1), ERASEINFO(0x02000,2), @@ -738,16 +782,14 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x10000,15) } }, { - .mfr_id = MANUFACTURER_FUJITSU, + .mfr_id = CFI_MFR_FUJITSU, .dev_id = MBM29LV800TA, .name = "Fujitsu MBM29LV800TA", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x10000,15), ERASEINFO(0x08000,1), @@ -755,16 +797,14 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x04000,1) } }, { - .mfr_id = MANUFACTURER_FUJITSU, + .mfr_id = CFI_MFR_FUJITSU, .dev_id = MBM29LV400BC, .name = "Fujitsu MBM29LV400BC", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x04000,1), ERASEINFO(0x02000,2), @@ -772,16 +812,14 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x10000,7) } }, { - .mfr_id = MANUFACTURER_FUJITSU, + .mfr_id = CFI_MFR_FUJITSU, .dev_id = MBM29LV400TC, .name = "Fujitsu MBM29LV400TC", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x10000,7), ERASEINFO(0x08000,1), @@ -789,15 +827,14 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x04000,1) } }, { - .mfr_id = MANUFACTURER_HYUNDAI, + .mfr_id = CFI_MFR_HYUNDAI, .dev_id = HY29F002T, .name = "Hyundai HY29F002T", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x02AA /* x8 */ - }, - .DevSize = SIZE_256KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_256KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x10000,3), ERASEINFO(0x08000,1), @@ -805,333 +842,319 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x04000,1), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I28F004B3B, .name = "Intel 28F004B3B", - .uaddr = { - [0] = MTD_UADDR_UNNECESSARY, /* x8 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_INTEL_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x02000, 8), ERASEINFO(0x10000, 7), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I28F004B3T, .name = "Intel 28F004B3T", - .uaddr = { - [0] = MTD_UADDR_UNNECESSARY, /* x8 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_INTEL_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x10000, 7), ERASEINFO(0x02000, 8), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I28F400B3B, .name = "Intel 28F400B3B", - .uaddr = { - [0] = MTD_UADDR_UNNECESSARY, /* x8 */ - [1] = MTD_UADDR_UNNECESSARY, /* x16 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_INTEL_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x02000, 8), ERASEINFO(0x10000, 7), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I28F400B3T, .name = "Intel 28F400B3T", - .uaddr = { - [0] = MTD_UADDR_UNNECESSARY, /* x8 */ - [1] = MTD_UADDR_UNNECESSARY, /* x16 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_INTEL_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x10000, 7), ERASEINFO(0x02000, 8), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I28F008B3B, .name = "Intel 28F008B3B", - .uaddr = { - [0] = MTD_UADDR_UNNECESSARY, /* x8 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_INTEL_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x02000, 8), ERASEINFO(0x10000, 15), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I28F008B3T, .name = "Intel 28F008B3T", - .uaddr = { - [0] = MTD_UADDR_UNNECESSARY, /* x8 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_INTEL_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x10000, 15), ERASEINFO(0x02000, 8), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I28F008S5, .name = "Intel 28F008S5", - .uaddr = { - [0] = MTD_UADDR_UNNECESSARY, /* x8 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_INTEL_EXT, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,16), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I28F016S5, .name = "Intel 28F016S5", - .uaddr = { - [0] = MTD_UADDR_UNNECESSARY, /* x8 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_INTEL_EXT, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,32), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I28F008SA, .name = "Intel 28F008SA", - .uaddr = { - [0] = MTD_UADDR_UNNECESSARY, /* x8 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_INTEL_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x10000, 16), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I28F800B3B, .name = "Intel 28F800B3B", - .uaddr = { - [1] = MTD_UADDR_UNNECESSARY, /* x16 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_INTEL_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x02000, 8), ERASEINFO(0x10000, 15), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I28F800B3T, .name = "Intel 28F800B3T", - .uaddr = { - [1] = MTD_UADDR_UNNECESSARY, /* x16 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_INTEL_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x10000, 15), ERASEINFO(0x02000, 8), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I28F016B3B, .name = "Intel 28F016B3B", - .uaddr = { - [0] = MTD_UADDR_UNNECESSARY, /* x8 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_INTEL_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x02000, 8), ERASEINFO(0x10000, 31), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I28F016S3, .name = "Intel I28F016S3", - .uaddr = { - [0] = MTD_UADDR_UNNECESSARY, /* x8 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_INTEL_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x10000, 32), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I28F016B3T, .name = "Intel 28F016B3T", - .uaddr = { - [0] = MTD_UADDR_UNNECESSARY, /* x8 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_INTEL_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x10000, 31), ERASEINFO(0x02000, 8), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I28F160B3B, .name = "Intel 28F160B3B", - .uaddr = { - [1] = MTD_UADDR_UNNECESSARY, /* x16 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_INTEL_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x02000, 8), ERASEINFO(0x10000, 31), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I28F160B3T, .name = "Intel 28F160B3T", - .uaddr = { - [1] = MTD_UADDR_UNNECESSARY, /* x16 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_INTEL_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x10000, 31), ERASEINFO(0x02000, 8), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I28F320B3B, .name = "Intel 28F320B3B", - .uaddr = { - [1] = MTD_UADDR_UNNECESSARY, /* x16 */ - }, - .DevSize = SIZE_4MiB, - .CmdSet = P_ID_INTEL_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x02000, 8), ERASEINFO(0x10000, 63), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I28F320B3T, .name = "Intel 28F320B3T", - .uaddr = { - [1] = MTD_UADDR_UNNECESSARY, /* x16 */ - }, - .DevSize = SIZE_4MiB, - .CmdSet = P_ID_INTEL_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x10000, 63), ERASEINFO(0x02000, 8), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I28F640B3B, .name = "Intel 28F640B3B", - .uaddr = { - [1] = MTD_UADDR_UNNECESSARY, /* x16 */ - }, - .DevSize = SIZE_8MiB, - .CmdSet = P_ID_INTEL_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_8MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x02000, 8), ERASEINFO(0x10000, 127), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I28F640B3T, .name = "Intel 28F640B3T", - .uaddr = { - [1] = MTD_UADDR_UNNECESSARY, /* x16 */ - }, - .DevSize = SIZE_8MiB, - .CmdSet = P_ID_INTEL_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_8MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x10000, 127), ERASEINFO(0x02000, 8), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F640C3B, + .name = "Intel 28F640C3B", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_8MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x02000, 8), + ERASEINFO(0x10000, 127), + } + }, { + .mfr_id = CFI_MFR_INTEL, .dev_id = I82802AB, .name = "Intel 82802AB", - .uaddr = { - [0] = MTD_UADDR_UNNECESSARY, /* x8 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_INTEL_EXT, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,8), } }, { - .mfr_id = MANUFACTURER_INTEL, + .mfr_id = CFI_MFR_INTEL, .dev_id = I82802AC, .name = "Intel 82802AC", - .uaddr = { - [0] = MTD_UADDR_UNNECESSARY, /* x8 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_INTEL_EXT, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,16), } }, { - .mfr_id = MANUFACTURER_MACRONIX, + .mfr_id = CFI_MFR_MACRONIX, .dev_id = MX29LV040C, .name = "Macronix MX29LV040C", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x02AA, /* x8 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,8), } }, { - .mfr_id = MANUFACTURER_MACRONIX, + .mfr_id = CFI_MFR_MACRONIX, .dev_id = MX29LV160T, .name = "MXIC MX29LV160T", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x10000,31), ERASEINFO(0x08000,1), @@ -1139,32 +1162,28 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x04000,1) } }, { - .mfr_id = MANUFACTURER_NEC, + .mfr_id = CFI_MFR_NEC, .dev_id = UPD29F064115, .name = "NEC uPD29F064115", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x02AA, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_8MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 3, + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_0xAAAA_0x5555, + .dev_size = SIZE_8MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 3, .regions = { ERASEINFO(0x2000,8), ERASEINFO(0x10000,126), ERASEINFO(0x2000,8), } }, { - .mfr_id = MANUFACTURER_MACRONIX, + .mfr_id = CFI_MFR_MACRONIX, .dev_id = MX29LV160B, .name = "MXIC MX29LV160B", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x04000,1), ERASEINFO(0x02000,2), @@ -1172,57 +1191,53 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x10000,31) } }, { - .mfr_id = MANUFACTURER_MACRONIX, + .mfr_id = CFI_MFR_MACRONIX, .dev_id = MX29F040, .name = "Macronix MX29F040", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x02AA /* x8 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,8), } - }, { - .mfr_id = MANUFACTURER_MACRONIX, + }, { + .mfr_id = CFI_MFR_MACRONIX, .dev_id = MX29F016, .name = "Macronix MX29F016", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x02AA /* x8 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,32), } - }, { - .mfr_id = MANUFACTURER_MACRONIX, + }, { + .mfr_id = CFI_MFR_MACRONIX, .dev_id = MX29F004T, .name = "Macronix MX29F004T", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x02AA /* x8 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x10000,7), ERASEINFO(0x08000,1), ERASEINFO(0x02000,2), ERASEINFO(0x04000,1), } - }, { - .mfr_id = MANUFACTURER_MACRONIX, + }, { + .mfr_id = CFI_MFR_MACRONIX, .dev_id = MX29F004B, .name = "Macronix MX29F004B", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x02AA /* x8 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x04000,1), ERASEINFO(0x02000,2), @@ -1230,15 +1245,14 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x10000,7), } }, { - .mfr_id = MANUFACTURER_MACRONIX, + .mfr_id = CFI_MFR_MACRONIX, .dev_id = MX29F002T, .name = "Macronix MX29F002T", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x02AA /* x8 */ - }, - .DevSize = SIZE_256KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_256KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x10000,3), ERASEINFO(0x08000,1), @@ -1246,266 +1260,338 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x04000,1), } }, { - .mfr_id = MANUFACTURER_PMC, + .mfr_id = CFI_MFR_PMC, .dev_id = PM49FL002, .name = "PMC Pm49FL002", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ - }, - .DevSize = SIZE_256KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_256KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO( 0x01000, 64 ) } }, { - .mfr_id = MANUFACTURER_PMC, + .mfr_id = CFI_MFR_PMC, .dev_id = PM49FL004, .name = "PMC Pm49FL004", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO( 0x01000, 128 ) } }, { - .mfr_id = MANUFACTURER_PMC, + .mfr_id = CFI_MFR_PMC, .dev_id = PM49FL008, .name = "PMC Pm49FL008", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO( 0x01000, 256 ) } }, { - .mfr_id = MANUFACTURER_SHARP, + .mfr_id = CFI_MFR_SHARP, .dev_id = LH28F640BF, .name = "LH28F640BF", - .uaddr = { - [0] = MTD_UADDR_UNNECESSARY, /* x8 */ - }, - .DevSize = SIZE_4MiB, - .CmdSet = P_ID_INTEL_STD, - .NumEraseRegions= 1, - .regions = { - ERASEINFO(0x40000,16), + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_8MiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 2, + .regions = { + ERASEINFO(0x10000, 127), + ERASEINFO(0x02000, 8), } - }, { - .mfr_id = MANUFACTURER_SST, + }, { + .mfr_id = CFI_MFR_SST, .dev_id = SST39LF512, .name = "SST 39LF512", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ - }, - .DevSize = SIZE_64KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_64KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x01000,16), } - }, { - .mfr_id = MANUFACTURER_SST, + }, { + .mfr_id = CFI_MFR_SST, .dev_id = SST39LF010, .name = "SST 39LF010", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ - }, - .DevSize = SIZE_128KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_128KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x01000,32), } - }, { - .mfr_id = MANUFACTURER_SST, - .dev_id = SST29EE020, + }, { + .mfr_id = CFI_MFR_SST, + .dev_id = SST29EE020, .name = "SST 29EE020", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ - }, - .DevSize = SIZE_256KiB, - .CmdSet = P_ID_SST_PAGE, - .NumEraseRegions= 1, - .regions = {ERASEINFO(0x01000,64), - } - }, { - .mfr_id = MANUFACTURER_SST, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_256KiB, + .cmd_set = P_ID_SST_PAGE, + .nr_regions = 1, + .regions = {ERASEINFO(0x01000,64), + } + }, { + .mfr_id = CFI_MFR_SST, .dev_id = SST29LE020, - .name = "SST 29LE020", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ - }, - .DevSize = SIZE_256KiB, - .CmdSet = P_ID_SST_PAGE, - .NumEraseRegions= 1, - .regions = {ERASEINFO(0x01000,64), - } + .name = "SST 29LE020", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_256KiB, + .cmd_set = P_ID_SST_PAGE, + .nr_regions = 1, + .regions = {ERASEINFO(0x01000,64), + } }, { - .mfr_id = MANUFACTURER_SST, + .mfr_id = CFI_MFR_SST, .dev_id = SST39LF020, .name = "SST 39LF020", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ - }, - .DevSize = SIZE_256KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_256KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x01000,64), } - }, { - .mfr_id = MANUFACTURER_SST, + }, { + .mfr_id = CFI_MFR_SST, .dev_id = SST39LF040, .name = "SST 39LF040", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x01000,128), } - }, { - .mfr_id = MANUFACTURER_SST, + }, { + .mfr_id = CFI_MFR_SST, .dev_id = SST39SF010A, .name = "SST 39SF010A", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ - }, - .DevSize = SIZE_128KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_128KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x01000,32), } - }, { - .mfr_id = MANUFACTURER_SST, + }, { + .mfr_id = CFI_MFR_SST, .dev_id = SST39SF020A, .name = "SST 39SF020A", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ - }, - .DevSize = SIZE_256KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_256KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x01000,64), } }, { - .mfr_id = MANUFACTURER_SST, + .mfr_id = CFI_MFR_SST, + .dev_id = SST39SF040, + .name = "SST 39SF040", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x01000,128), + } + }, { + .mfr_id = CFI_MFR_SST, + .dev_id = SST49LF040B, + .name = "SST 49LF040B", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x01000,128), + } + }, { + + .mfr_id = CFI_MFR_SST, .dev_id = SST49LF004B, .name = "SST 49LF004B", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x01000,128), } }, { - .mfr_id = MANUFACTURER_SST, + .mfr_id = CFI_MFR_SST, .dev_id = SST49LF008A, .name = "SST 49LF008A", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x01000,256), } }, { - .mfr_id = MANUFACTURER_SST, + .mfr_id = CFI_MFR_SST, .dev_id = SST49LF030A, .name = "SST 49LF030A", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x01000,96), } }, { - .mfr_id = MANUFACTURER_SST, + .mfr_id = CFI_MFR_SST, .dev_id = SST49LF040A, .name = "SST 49LF040A", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x01000,128), } }, { - .mfr_id = MANUFACTURER_SST, + .mfr_id = CFI_MFR_SST, .dev_id = SST49LF080A, .name = "SST 49LF080A", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x01000,256), } }, { - .mfr_id = MANUFACTURER_SST, /* should be CFI */ - .dev_id = SST39LF160, - .name = "SST 39LF160", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA, /* x8 */ - [1] = MTD_UADDR_0x5555_0x2AAA /* x16 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 2, - .regions = { - ERASEINFO(0x1000,256), - ERASEINFO(0x1000,256) - } - }, { - .mfr_id = MANUFACTURER_SST, /* should be CFI */ - .dev_id = SST39VF1601, - .name = "SST 39VF1601", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA, /* x8 */ - [1] = MTD_UADDR_0x5555_0x2AAA /* x16 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 2, - .regions = { - ERASEINFO(0x1000,256), - ERASEINFO(0x1000,256) - } - - }, { - .mfr_id = MANUFACTURER_ST, /* FIXME - CFI device? */ + .mfr_id = CFI_MFR_SST, /* should be CFI */ + .dev_id = SST39LF160, + .name = "SST 39LF160", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_0xAAAA_0x5555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x1000,256), + ERASEINFO(0x1000,256) + } + }, { + .mfr_id = CFI_MFR_SST, /* should be CFI */ + .dev_id = SST39VF1601, + .name = "SST 39VF1601", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_0xAAAA_0x5555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x1000,256), + ERASEINFO(0x1000,256) + } + }, { + /* CFI is broken: reports AMD_STD, but needs custom uaddr */ + .mfr_id = CFI_MFR_SST, + .dev_id = SST39WF1601, + .name = "SST 39WF1601", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_0xAAAA_0x5555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x1000,256), + ERASEINFO(0x1000,256) + } + }, { + /* CFI is broken: reports AMD_STD, but needs custom uaddr */ + .mfr_id = CFI_MFR_SST, + .dev_id = SST39WF1602, + .name = "SST 39WF1602", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_0xAAAA_0x5555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x1000,256), + ERASEINFO(0x1000,256) + } + }, { + .mfr_id = CFI_MFR_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 = CFI_MFR_SST, + .dev_id = SST36VF3203, + .name = "SST 36VF3203", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,64), + } + }, { + .mfr_id = CFI_MFR_ST, + .dev_id = M29F800AB, + .name = "ST M29F800AB", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,15), + } + }, { + .mfr_id = CFI_MFR_ST, /* FIXME - CFI device? */ .dev_id = M29W800DT, .name = "ST M29W800DT", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA, /* x8 */ - [1] = MTD_UADDR_0x5555_0x2AAA /* x16 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x10000,15), ERASEINFO(0x08000,1), @@ -1513,33 +1599,59 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x04000,1) } }, { - .mfr_id = MANUFACTURER_ST, /* FIXME - CFI device? */ + .mfr_id = CFI_MFR_ST, /* FIXME - CFI device? */ .dev_id = M29W800DB, .name = "ST M29W800DB", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA, /* x8 */ - [1] = MTD_UADDR_0x5555_0x2AAA /* x16 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x04000,1), ERASEINFO(0x02000,2), ERASEINFO(0x08000,1), ERASEINFO(0x10000,15) } + }, { + .mfr_id = CFI_MFR_ST, + .dev_id = M29W400DT, + .name = "ST M29W400DT", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,7), + ERASEINFO(0x02000,1), + ERASEINFO(0x08000,2), + ERASEINFO(0x10000,1) + } }, { - .mfr_id = MANUFACTURER_ST, /* FIXME - CFI device? */ + .mfr_id = CFI_MFR_ST, + .dev_id = M29W400DB, + .name = "ST M29W400DB", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,7) + } + }, { + .mfr_id = CFI_MFR_ST, /* FIXME - CFI device? */ .dev_id = M29W160DT, .name = "ST M29W160DT", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x02AA, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, /* ???? */ + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x10000,31), ERASEINFO(0x08000,1), @@ -1547,98 +1659,131 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x04000,1) } }, { - .mfr_id = MANUFACTURER_ST, /* FIXME - CFI device? */ + .mfr_id = CFI_MFR_ST, /* FIXME - CFI device? */ .dev_id = M29W160DB, .name = "ST M29W160DB", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x02AA, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, /* ???? */ + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x04000,1), ERASEINFO(0x02000,2), ERASEINFO(0x08000,1), ERASEINFO(0x10000,31) } - }, { - .mfr_id = MANUFACTURER_ST, + }, { + .mfr_id = CFI_MFR_ST, .dev_id = M29W040B, .name = "ST M29W040B", - .uaddr = { - [0] = MTD_UADDR_0x0555_0x02AA /* x8 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,8), } - }, { - .mfr_id = MANUFACTURER_ST, + }, { + .mfr_id = CFI_MFR_ST, .dev_id = M50FW040, .name = "ST M50FW040", - .uaddr = { - [0] = MTD_UADDR_UNNECESSARY, /* x8 */ - }, - .DevSize = SIZE_512KiB, - .CmdSet = P_ID_INTEL_EXT, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,8), } - }, { - .mfr_id = MANUFACTURER_ST, + }, { + .mfr_id = CFI_MFR_ST, .dev_id = M50FW080, .name = "ST M50FW080", - .uaddr = { - [0] = MTD_UADDR_UNNECESSARY, /* x8 */ - }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_INTEL_EXT, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,16), } - }, { - .mfr_id = MANUFACTURER_ST, + }, { + .mfr_id = CFI_MFR_ST, .dev_id = M50FW016, .name = "ST M50FW016", - .uaddr = { - [0] = MTD_UADDR_UNNECESSARY, /* x8 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_INTEL_EXT, - .NumEraseRegions= 1, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,32), } }, { - .mfr_id = MANUFACTURER_ST, + .mfr_id = CFI_MFR_ST, .dev_id = M50LPW080, .name = "ST M50LPW080", - .uaddr = { - [0] = MTD_UADDR_UNNECESSARY, /* x8 */ + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,16), }, - .DevSize = SIZE_1MiB, - .CmdSet = P_ID_INTEL_EXT, - .NumEraseRegions= 1, + }, { + .mfr_id = CFI_MFR_ST, + .dev_id = M50FLW080A, + .name = "ST M50FLW080A", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 4, + .regions = { + ERASEINFO(0x1000,16), + ERASEINFO(0x10000,13), + ERASEINFO(0x1000,16), + ERASEINFO(0x1000,16), + } + }, { + .mfr_id = CFI_MFR_ST, + .dev_id = M50FLW080B, + .name = "ST M50FLW080B", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 4, + .regions = { + ERASEINFO(0x1000,16), + ERASEINFO(0x1000,16), + ERASEINFO(0x10000,13), + ERASEINFO(0x1000,16), + } + }, { + .mfr_id = 0xff00 | CFI_MFR_ST, + .dev_id = 0xff00 | PSD4256G6V, + .name = "ST PSD4256G6V", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_0x0AAA_0x0554, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, .regions = { ERASEINFO(0x10000,16), } }, { - .mfr_id = MANUFACTURER_TOSHIBA, + .mfr_id = CFI_MFR_TOSHIBA, .dev_id = TC58FVT160, .name = "Toshiba TC58FVT160", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA /* x16 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x10000,31), ERASEINFO(0x08000,1), @@ -1646,16 +1791,14 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x04000,1) } }, { - .mfr_id = MANUFACTURER_TOSHIBA, + .mfr_id = CFI_MFR_TOSHIBA, .dev_id = TC58FVB160, .name = "Toshiba TC58FVB160", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA /* x16 */ - }, - .DevSize = SIZE_2MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x04000,1), ERASEINFO(0x02000,2), @@ -1663,75 +1806,66 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x10000,31) } }, { - .mfr_id = MANUFACTURER_TOSHIBA, + .mfr_id = CFI_MFR_TOSHIBA, .dev_id = TC58FVB321, .name = "Toshiba TC58FVB321", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA /* x16 */ - }, - .DevSize = SIZE_4MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x02000,8), ERASEINFO(0x10000,63) } }, { - .mfr_id = MANUFACTURER_TOSHIBA, + .mfr_id = CFI_MFR_TOSHIBA, .dev_id = TC58FVT321, .name = "Toshiba TC58FVT321", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA /* x16 */ - }, - .DevSize = SIZE_4MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x10000,63), ERASEINFO(0x02000,8) } }, { - .mfr_id = MANUFACTURER_TOSHIBA, + .mfr_id = CFI_MFR_TOSHIBA, .dev_id = TC58FVB641, .name = "Toshiba TC58FVB641", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_8MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_8MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x02000,8), ERASEINFO(0x10000,127) } }, { - .mfr_id = MANUFACTURER_TOSHIBA, + .mfr_id = CFI_MFR_TOSHIBA, .dev_id = TC58FVT641, .name = "Toshiba TC58FVT641", - .uaddr = { - [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */ - [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ - }, - .DevSize = SIZE_8MiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 2, + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_8MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, .regions = { ERASEINFO(0x10000,127), ERASEINFO(0x02000,8) } }, { - .mfr_id = MANUFACTURER_WINBOND, + .mfr_id = CFI_MFR_WINBOND, .dev_id = W49V002A, .name = "Winbond W49V002A", - .uaddr = { - [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */ - }, - .DevSize = SIZE_256KiB, - .CmdSet = P_ID_AMD_STD, - .NumEraseRegions= 4, + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_256KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, .regions = { ERASEINFO(0x10000, 3), ERASEINFO(0x08000, 1), @@ -1741,38 +1875,39 @@ static const struct amd_flash_info jedec_table[] = { } }; - -static int cfi_jedec_setup(struct cfi_private *p_cfi, int index); - -static int jedec_probe_chip(struct map_info *map, __u32 base, - unsigned long *chip_map, struct cfi_private *cfi); - -static struct mtd_info *jedec_probe(struct map_info *map); - -static inline u32 jedec_read_mfr(struct map_info *map, __u32 base, +static inline u32 jedec_read_mfr(struct map_info *map, uint32_t base, struct cfi_private *cfi) { map_word result; unsigned long mask; - u32 ofs = cfi_build_cmd_addr(0, cfi_interleave(cfi), cfi->device_type); - mask = (1 << (cfi->device_type * 8)) -1; - result = map_read(map, base + ofs); + int bank = 0; + + /* According to JEDEC "Standard Manufacturer's Identification Code" + * (http://www.jedec.org/download/search/jep106W.pdf) + * several first banks can contain 0x7f instead of actual ID + */ + do { + uint32_t ofs = cfi_build_cmd_addr(0 + (bank << 8), map, cfi); + mask = (1 << (cfi->device_type * 8)) - 1; + result = map_read(map, base + ofs); + bank++; + } while ((result.x[0] & mask) == CFI_MFR_CONTINUATION); + return result.x[0] & mask; } -static inline u32 jedec_read_id(struct map_info *map, __u32 base, +static inline u32 jedec_read_id(struct map_info *map, uint32_t base, struct cfi_private *cfi) { map_word result; unsigned long mask; - u32 ofs = cfi_build_cmd_addr(1, cfi_interleave(cfi), cfi->device_type); + u32 ofs = cfi_build_cmd_addr(1, map, cfi); mask = (1 << (cfi->device_type * 8)) -1; result = map_read(map, base + ofs); return result.x[0] & mask; } -static inline void jedec_reset(u32 base, struct map_info *map, - struct cfi_private *cfi) +static void jedec_reset(u32 base, struct map_info *map, struct cfi_private *cfi) { /* Reset */ @@ -1780,18 +1915,17 @@ static inline void jedec_reset(u32 base, struct map_info *map, * (oh and incidentaly the jedec spec - 3.5.3.3) the reset * sequence is *supposed* to be 0xaa at 0x5555, 0x55 at * 0x2aaa, 0xF0 at 0x5555 this will not affect the AMD chips - * as they will ignore the writes and dont care what address + * as they will ignore the writes and don't care what address * the F0 is written to */ - if(cfi->addr_unlock1) { - DEBUG( MTD_DEBUG_LEVEL3, - "reset unlock called %x %x \n", + if (cfi->addr_unlock1) { + pr_debug( "reset unlock called %x %x \n", cfi->addr_unlock1,cfi->addr_unlock2); cfi_send_gen_cmd(0xaa, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL); cfi_send_gen_cmd(0x55, cfi->addr_unlock2, base, map, cfi, cfi->device_type, NULL); } cfi_send_gen_cmd(0xF0, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL); - /* Some misdesigned intel chips do not respond for 0xF0 for a reset, + /* Some misdesigned Intel chips do not respond for 0xF0 for a reset, * so ensure we're in read mode. Send both the Intel and the AMD command * for this. Intel uses 0xff for this, AMD uses 0xff for NOP, so * this should be safe. @@ -1801,93 +1935,72 @@ static inline void jedec_reset(u32 base, struct map_info *map, } -static inline __u8 finfo_uaddr(const struct amd_flash_info *finfo, int device_type) +static int cfi_jedec_setup(struct map_info *map, struct cfi_private *cfi, int index) { - int uaddr_idx; - __u8 uaddr = MTD_UADDR_NOT_SUPPORTED; - - switch ( device_type ) { - case CFI_DEVICETYPE_X8: uaddr_idx = 0; break; - case CFI_DEVICETYPE_X16: uaddr_idx = 1; break; - case CFI_DEVICETYPE_X32: uaddr_idx = 2; break; - default: - printk(KERN_NOTICE "MTD: %s(): unknown device_type %d\n", - __func__, device_type); - goto uaddr_done; - } - - uaddr = finfo->uaddr[uaddr_idx]; + int i,num_erase_regions; + uint8_t uaddr; - if (uaddr != MTD_UADDR_NOT_SUPPORTED ) { - /* ASSERT("The unlock addresses for non-8-bit mode - are bollocks. We don't really need an array."); */ - uaddr = finfo->uaddr[0]; + if (!(jedec_table[index].devtypes & cfi->device_type)) { + pr_debug("Rejecting potential %s with incompatible %d-bit device type\n", + jedec_table[index].name, 4 * (1<<cfi->device_type)); + return 0; } - uaddr_done: - return uaddr; -} - - -static int cfi_jedec_setup(struct cfi_private *p_cfi, int index) -{ - int i,num_erase_regions; - __u8 uaddr; - - printk("Found: %s\n",jedec_table[index].name); + printk(KERN_INFO "Found: %s\n",jedec_table[index].name); - num_erase_regions = jedec_table[index].NumEraseRegions; + num_erase_regions = jedec_table[index].nr_regions; - p_cfi->cfiq = kmalloc(sizeof(struct cfi_ident) + num_erase_regions * 4, GFP_KERNEL); - if (!p_cfi->cfiq) { + cfi->cfiq = kmalloc(sizeof(struct cfi_ident) + num_erase_regions * 4, GFP_KERNEL); + if (!cfi->cfiq) { //xx printk(KERN_WARNING "%s: kmalloc failed for CFI ident structure\n", map->name); return 0; } - memset(p_cfi->cfiq,0,sizeof(struct cfi_ident)); + memset(cfi->cfiq, 0, sizeof(struct cfi_ident)); - p_cfi->cfiq->P_ID = jedec_table[index].CmdSet; - p_cfi->cfiq->NumEraseRegions = jedec_table[index].NumEraseRegions; - p_cfi->cfiq->DevSize = jedec_table[index].DevSize; - p_cfi->cfi_mode = CFI_MODE_JEDEC; + cfi->cfiq->P_ID = jedec_table[index].cmd_set; + cfi->cfiq->NumEraseRegions = jedec_table[index].nr_regions; + cfi->cfiq->DevSize = jedec_table[index].dev_size; + cfi->cfi_mode = CFI_MODE_JEDEC; + cfi->sector_erase_cmd = CMD(0x30); for (i=0; i<num_erase_regions; i++){ - p_cfi->cfiq->EraseRegionInfo[i] = jedec_table[index].regions[i]; + cfi->cfiq->EraseRegionInfo[i] = jedec_table[index].regions[i]; } - p_cfi->cmdset_priv = NULL; + cfi->cmdset_priv = NULL; /* This may be redundant for some cases, but it doesn't hurt */ - p_cfi->mfr = jedec_table[index].mfr_id; - p_cfi->id = jedec_table[index].dev_id; + cfi->mfr = jedec_table[index].mfr_id; + cfi->id = jedec_table[index].dev_id; - uaddr = finfo_uaddr(&jedec_table[index], p_cfi->device_type); - if ( uaddr == MTD_UADDR_NOT_SUPPORTED ) { - kfree( p_cfi->cfiq ); - return 0; - } + uaddr = jedec_table[index].uaddr; - p_cfi->addr_unlock1 = unlock_addrs[uaddr].addr1; - p_cfi->addr_unlock2 = unlock_addrs[uaddr].addr2; + /* The table has unlock addresses in _bytes_, and we try not to let + our brains explode when we see the datasheets talking about address + lines numbered from A-1 to A18. The CFI table has unlock addresses + in device-words according to the mode the device is connected in */ + cfi->addr_unlock1 = unlock_addrs[uaddr].addr1 / cfi->device_type; + cfi->addr_unlock2 = unlock_addrs[uaddr].addr2 / cfi->device_type; - return 1; /* ok */ + return 1; /* ok */ } /* - * There is a BIG problem properly ID'ing the JEDEC devic and guaranteeing + * There is a BIG problem properly ID'ing the JEDEC device and guaranteeing * the mapped address, unlock addresses, and proper chip ID. This function * attempts to minimize errors. It is doubtfull that this probe will ever * be perfect - consequently there should be some module parameters that * could be manually specified to force the chip info. */ -static inline int jedec_match( __u32 base, +static inline int jedec_match( uint32_t base, struct map_info *map, struct cfi_private *cfi, const struct amd_flash_info *finfo ) { int rc = 0; /* failure until all tests pass */ u32 mfr, id; - __u8 uaddr; + uint8_t uaddr; /* * The IDs must match. For X16 and X32 devices operating in @@ -1900,26 +2013,26 @@ static inline int jedec_match( __u32 base, */ switch (cfi->device_type) { case CFI_DEVICETYPE_X8: - mfr = (__u8)finfo->mfr_id; - id = (__u8)finfo->dev_id; + mfr = (uint8_t)finfo->mfr_id; + id = (uint8_t)finfo->dev_id; /* bjd: it seems that if we do this, we can end up * detecting 16bit flashes as an 8bit device, even though * there aren't. */ if (finfo->dev_id > 0xff) { - DEBUG( MTD_DEBUG_LEVEL3, "%s(): ID is not 8bit\n", + pr_debug("%s(): ID is not 8bit\n", __func__); goto match_done; } break; case CFI_DEVICETYPE_X16: - mfr = (__u16)finfo->mfr_id; - id = (__u16)finfo->dev_id; + mfr = (uint16_t)finfo->mfr_id; + id = (uint16_t)finfo->dev_id; break; case CFI_DEVICETYPE_X32: - mfr = (__u16)finfo->mfr_id; - id = (__u32)finfo->dev_id; + mfr = (uint16_t)finfo->mfr_id; + id = (uint32_t)finfo->dev_id; break; default: printk(KERN_WARNING @@ -1932,29 +2045,26 @@ static inline int jedec_match( __u32 base, } /* the part size must fit in the memory window */ - DEBUG( MTD_DEBUG_LEVEL3, - "MTD %s(): Check fit 0x%.8x + 0x%.8x = 0x%.8x\n", - __func__, base, 1 << finfo->DevSize, base + (1 << finfo->DevSize) ); - if ( base + cfi_interleave(cfi) * ( 1 << finfo->DevSize ) > map->size ) { - DEBUG( MTD_DEBUG_LEVEL3, - "MTD %s(): 0x%.4x 0x%.4x %dKiB doesn't fit\n", + pr_debug("MTD %s(): Check fit 0x%.8x + 0x%.8x = 0x%.8x\n", + __func__, base, 1 << finfo->dev_size, base + (1 << finfo->dev_size) ); + if ( base + cfi_interleave(cfi) * ( 1 << finfo->dev_size ) > map->size ) { + pr_debug("MTD %s(): 0x%.4x 0x%.4x %dKiB doesn't fit\n", __func__, finfo->mfr_id, finfo->dev_id, - 1 << finfo->DevSize ); + 1 << finfo->dev_size ); goto match_done; } - uaddr = finfo_uaddr(finfo, cfi->device_type); - if ( uaddr == MTD_UADDR_NOT_SUPPORTED ) { + if (! (finfo->devtypes & cfi->device_type)) goto match_done; - } - DEBUG( MTD_DEBUG_LEVEL3, "MTD %s(): check unlock addrs 0x%.4x 0x%.4x\n", + uaddr = finfo->uaddr; + + pr_debug("MTD %s(): check unlock addrs 0x%.4x 0x%.4x\n", __func__, cfi->addr_unlock1, cfi->addr_unlock2 ); if ( MTD_UADDR_UNNECESSARY != uaddr && MTD_UADDR_DONT_CARE != uaddr - && ( unlock_addrs[uaddr].addr1 != cfi->addr_unlock1 || - unlock_addrs[uaddr].addr2 != cfi->addr_unlock2 ) ) { - DEBUG( MTD_DEBUG_LEVEL3, - "MTD %s(): 0x%.4x 0x%.4x did not match\n", + && ( unlock_addrs[uaddr].addr1 / cfi->device_type != cfi->addr_unlock1 || + unlock_addrs[uaddr].addr2 / cfi->device_type != cfi->addr_unlock2 ) ) { + pr_debug("MTD %s(): 0x%.4x 0x%.4x did not match\n", __func__, unlock_addrs[uaddr].addr1, unlock_addrs[uaddr].addr2); @@ -1962,7 +2072,7 @@ static inline int jedec_match( __u32 base, } /* - * Make sure the ID's dissappear when the device is taken out of + * Make sure the ID's disappear when the device is taken out of * ID mode. The only time this should fail when it should succeed * is when the ID's are written as data to the same * addresses. For this rare and unfortunate case the chip @@ -1970,15 +2080,13 @@ static inline int jedec_match( __u32 base, * FIXME - write a driver that takes all of the chip info as * module parameters, doesn't probe but forces a load. */ - DEBUG( MTD_DEBUG_LEVEL3, - "MTD %s(): check ID's disappear when not in ID mode\n", + pr_debug("MTD %s(): check ID's disappear when not in ID mode\n", __func__ ); jedec_reset( base, map, cfi ); mfr = jedec_read_mfr( map, base, cfi ); id = jedec_read_id( map, base, cfi ); if ( mfr == cfi->mfr && id == cfi->id ) { - DEBUG( MTD_DEBUG_LEVEL3, - "MTD %s(): ID 0x%.2x:0x%.2x did not change after reset:\n" + pr_debug("MTD %s(): ID 0x%.2x:0x%.2x did not change after reset:\n" "You might need to manually specify JEDEC parameters.\n", __func__, cfi->mfr, cfi->id ); goto match_done; @@ -1991,8 +2099,8 @@ static inline int jedec_match( __u32 base, * Put the device back in ID mode - only need to do this if we * were truly frobbing a real device. */ - DEBUG( MTD_DEBUG_LEVEL3, "MTD %s(): return to ID mode\n", __func__ ); - if(cfi->addr_unlock1) { + pr_debug("MTD %s(): return to ID mode\n", __func__ ); + if (cfi->addr_unlock1) { cfi_send_gen_cmd(0xaa, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL); cfi_send_gen_cmd(0x55, cfi->addr_unlock2, base, map, cfi, cfi->device_type, NULL); } @@ -2018,8 +2126,8 @@ static int jedec_probe_chip(struct map_info *map, __u32 base, if (MTD_UADDR_UNNECESSARY == uaddr_idx) return 0; - cfi->addr_unlock1 = unlock_addrs[uaddr_idx].addr1; - cfi->addr_unlock2 = unlock_addrs[uaddr_idx].addr2; + cfi->addr_unlock1 = unlock_addrs[uaddr_idx].addr1 / cfi->device_type; + cfi->addr_unlock2 = unlock_addrs[uaddr_idx].addr2 / cfi->device_type; } /* Make certain we aren't probing past the end of map */ @@ -2031,19 +2139,11 @@ static int jedec_probe_chip(struct map_info *map, __u32 base, } /* Ensure the unlock addresses we try stay inside the map */ - probe_offset1 = cfi_build_cmd_addr( - cfi->addr_unlock1, - cfi_interleave(cfi), - cfi->device_type); - probe_offset2 = cfi_build_cmd_addr( - cfi->addr_unlock1, - cfi_interleave(cfi), - cfi->device_type); + probe_offset1 = cfi_build_cmd_addr(cfi->addr_unlock1, map, cfi); + probe_offset2 = cfi_build_cmd_addr(cfi->addr_unlock2, map, cfi); if ( ((base + probe_offset1 + map_bankwidth(map)) >= map->size) || ((base + probe_offset2 + map_bankwidth(map)) >= map->size)) - { goto retry; - } /* Reset */ jedec_reset(base, map, cfi); @@ -2062,24 +2162,22 @@ static int jedec_probe_chip(struct map_info *map, __u32 base, cfi->mfr = jedec_read_mfr(map, base, cfi); cfi->id = jedec_read_id(map, base, cfi); - DEBUG(MTD_DEBUG_LEVEL3, - "Search for id:(%02x %02x) interleave(%d) type(%d)\n", + pr_debug("Search for id:(%02x %02x) interleave(%d) type(%d)\n", cfi->mfr, cfi->id, cfi_interleave(cfi), cfi->device_type); for (i = 0; i < ARRAY_SIZE(jedec_table); i++) { if ( jedec_match( base, map, cfi, &jedec_table[i] ) ) { - DEBUG( MTD_DEBUG_LEVEL3, - "MTD %s(): matched device 0x%x,0x%x unlock_addrs: 0x%.4x 0x%.4x\n", + pr_debug("MTD %s(): matched device 0x%x,0x%x unlock_addrs: 0x%.4x 0x%.4x\n", __func__, cfi->mfr, cfi->id, cfi->addr_unlock1, cfi->addr_unlock2 ); - if (!cfi_jedec_setup(cfi, i)) + if (!cfi_jedec_setup(map, cfi, i)) return 0; goto ok_out; } } goto retry; } else { - __u16 mfr; - __u16 id; + uint16_t mfr; + uint16_t id; /* Make sure it is a chip of the same manufacturer and id */ mfr = jedec_read_mfr(map, base, cfi); diff --git a/drivers/mtd/chips/map_absent.c b/drivers/mtd/chips/map_absent.c index ac01a949b68..f7a5bca92ae 100644 --- a/drivers/mtd/chips/map_absent.c +++ b/drivers/mtd/chips/map_absent.c @@ -1,7 +1,6 @@ /* * Common code to handle absent "placeholder" devices * Copyright 2001 Resilience Corporation <ebrower@resilience.com> - * $Id: map_absent.c,v 1.6 2005/11/07 11:14:23 gleixner Exp $ * * This map driver is used to allocate "placeholder" MTD * devices on systems that have socketed/removable media. @@ -26,7 +25,6 @@ #include <linux/init.h> #include <linux/mtd/mtd.h> #include <linux/mtd/map.h> -#include <linux/mtd/compatmac.h> static int map_absent_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *); static int map_absent_write (struct mtd_info *, loff_t, size_t, size_t *, const u_char *); @@ -47,22 +45,20 @@ static struct mtd_info *map_absent_probe(struct map_info *map) { struct mtd_info *mtd; - mtd = kmalloc(sizeof(*mtd), GFP_KERNEL); + mtd = kzalloc(sizeof(*mtd), GFP_KERNEL); if (!mtd) { return NULL; } - memset(mtd, 0, sizeof(*mtd)); - map->fldrv = &map_absent_chipdrv; mtd->priv = map; mtd->name = map->name; mtd->type = MTD_ABSENT; mtd->size = map->size; - mtd->erase = map_absent_erase; - mtd->read = map_absent_read; - mtd->write = map_absent_write; - mtd->sync = map_absent_sync; + mtd->_erase = map_absent_erase; + mtd->_read = map_absent_read; + mtd->_write = map_absent_write; + mtd->_sync = map_absent_sync; mtd->flags = 0; mtd->erasesize = PAGE_SIZE; mtd->writesize = 1; @@ -74,13 +70,11 @@ static struct mtd_info *map_absent_probe(struct map_info *map) static int map_absent_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { - *retlen = 0; return -ENODEV; } static int map_absent_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { - *retlen = 0; return -ENODEV; } diff --git a/drivers/mtd/chips/map_ram.c b/drivers/mtd/chips/map_ram.c index 3a66680abfd..991c2a1c05d 100644 --- a/drivers/mtd/chips/map_ram.c +++ b/drivers/mtd/chips/map_ram.c @@ -1,7 +1,6 @@ /* * Common code to handle map devices which are simple RAM * (C) 2000 Red Hat. GPL'd. - * $Id: map_ram.c,v 1.22 2005/01/05 18:05:12 dwmw2 Exp $ */ #include <linux/module.h> @@ -14,7 +13,6 @@ #include <linux/init.h> #include <linux/mtd/mtd.h> #include <linux/mtd/map.h> -#include <linux/mtd/compatmac.h> static int mapram_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *); @@ -22,6 +20,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 = { @@ -55,21 +55,20 @@ static struct mtd_info *map_ram_probe(struct map_info *map) #endif /* OK. It seems to be RAM. */ - mtd = kmalloc(sizeof(*mtd), GFP_KERNEL); + mtd = kzalloc(sizeof(*mtd), GFP_KERNEL); if (!mtd) return NULL; - memset(mtd, 0, sizeof(*mtd)); - map->fldrv = &mapram_chipdrv; mtd->priv = map; mtd->name = map->name; mtd->type = MTD_RAM; mtd->size = map->size; - mtd->erase = mapram_erase; - mtd->read = mapram_read; - mtd->write = mapram_write; - mtd->sync = mapram_nop; + mtd->_erase = mapram_erase; + mtd->_get_unmapped_area = mapram_unmapped_area; + mtd->_read = mapram_read; + mtd->_write = mapram_write; + mtd->_sync = mapram_nop; mtd->flags = MTD_CAP_RAM; mtd->writesize = 1; @@ -82,6 +81,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; @@ -109,14 +122,10 @@ static int mapram_erase (struct mtd_info *mtd, struct erase_info *instr) unsigned long i; allff = map_word_ff(map); - for (i=0; i<instr->len; i += map_bankwidth(map)) map_write(map, allff, instr->addr + i); - instr->state = MTD_ERASE_DONE; - mtd_erase_callback(instr); - return 0; } diff --git a/drivers/mtd/chips/map_rom.c b/drivers/mtd/chips/map_rom.c index 1b328b1378f..47a43cf7e5c 100644 --- a/drivers/mtd/chips/map_rom.c +++ b/drivers/mtd/chips/map_rom.c @@ -1,7 +1,6 @@ /* * Common code to handle map devices which are simple ROM * (C) 2000 Red Hat. GPL'd. - * $Id: map_rom.c,v 1.23 2005/01/05 18:05:12 dwmw2 Exp $ */ #include <linux/module.h> @@ -14,12 +13,14 @@ #include <linux/init.h> #include <linux/mtd/mtd.h> #include <linux/mtd/map.h> -#include <linux/mtd/compatmac.h> static int maprom_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *); static int maprom_write (struct mtd_info *, loff_t, size_t, size_t *, const u_char *); 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, @@ -31,20 +32,20 @@ static struct mtd_info *map_rom_probe(struct map_info *map) { struct mtd_info *mtd; - mtd = kmalloc(sizeof(*mtd), GFP_KERNEL); + mtd = kzalloc(sizeof(*mtd), GFP_KERNEL); if (!mtd) return NULL; - memset(mtd, 0, sizeof(*mtd)); - map->fldrv = &maprom_chipdrv; mtd->priv = map; mtd->name = map->name; mtd->type = MTD_ROM; mtd->size = map->size; - mtd->read = maprom_read; - mtd->write = maprom_write; - mtd->sync = maprom_nop; + mtd->_get_unmapped_area = maprom_unmapped_area; + mtd->_read = maprom_read; + mtd->_write = maprom_write; + mtd->_sync = maprom_nop; + mtd->_erase = maprom_erase; mtd->flags = MTD_CAP_ROM; mtd->erasesize = map->size; mtd->writesize = 1; @@ -54,6 +55,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; @@ -70,8 +85,13 @@ static void maprom_nop(struct mtd_info *mtd) static int maprom_write (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { - printk(KERN_NOTICE "maprom_write called\n"); - return -EIO; + return -EROFS; +} + +static int maprom_erase (struct mtd_info *mtd, struct erase_info *info) +{ + /* We do our best 8) */ + return -EROFS; } static int __init map_rom_init(void) diff --git a/drivers/mtd/chips/sharp.c b/drivers/mtd/chips/sharp.c deleted file mode 100644 index 967abbecdff..00000000000 --- a/drivers/mtd/chips/sharp.c +++ /dev/null @@ -1,604 +0,0 @@ -/* - * MTD chip driver for pre-CFI Sharp flash chips - * - * Copyright 2000,2001 David A. Schleef <ds@schleef.org> - * 2000,2001 Lineo, Inc. - * - * $Id: sharp.c,v 1.17 2005/11/29 14:28:28 gleixner Exp $ - * - * Devices supported: - * LH28F016SCT Symmetrical block flash memory, 2Mx8 - * LH28F008SCT Symmetrical block flash memory, 1Mx8 - * - * Documentation: - * http://www.sharpmeg.com/datasheets/memic/flashcmp/ - * http://www.sharpmeg.com/datasheets/memic/flashcmp/01symf/16m/016sctl9.pdf - * 016sctl9.pdf - * - * Limitations: - * This driver only supports 4x1 arrangement of chips. - * Not tested on anything but PowerPC. - */ - -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/types.h> -#include <linux/sched.h> -#include <linux/errno.h> -#include <linux/interrupt.h> -#include <linux/mtd/map.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/cfi.h> -#include <linux/delay.h> -#include <linux/init.h> -#include <linux/slab.h> - -#define CMD_RESET 0xffffffff -#define CMD_READ_ID 0x90909090 -#define CMD_READ_STATUS 0x70707070 -#define CMD_CLEAR_STATUS 0x50505050 -#define CMD_BLOCK_ERASE_1 0x20202020 -#define CMD_BLOCK_ERASE_2 0xd0d0d0d0 -#define CMD_BYTE_WRITE 0x40404040 -#define CMD_SUSPEND 0xb0b0b0b0 -#define CMD_RESUME 0xd0d0d0d0 -#define CMD_SET_BLOCK_LOCK_1 0x60606060 -#define CMD_SET_BLOCK_LOCK_2 0x01010101 -#define CMD_SET_MASTER_LOCK_1 0x60606060 -#define CMD_SET_MASTER_LOCK_2 0xf1f1f1f1 -#define CMD_CLEAR_BLOCK_LOCKS_1 0x60606060 -#define CMD_CLEAR_BLOCK_LOCKS_2 0xd0d0d0d0 - -#define SR_READY 0x80808080 // 1 = ready -#define SR_ERASE_SUSPEND 0x40404040 // 1 = block erase suspended -#define SR_ERROR_ERASE 0x20202020 // 1 = error in block erase or clear lock bits -#define SR_ERROR_WRITE 0x10101010 // 1 = error in byte write or set lock bit -#define SR_VPP 0x08080808 // 1 = Vpp is low -#define SR_WRITE_SUSPEND 0x04040404 // 1 = byte write suspended -#define SR_PROTECT 0x02020202 // 1 = lock bit set -#define SR_RESERVED 0x01010101 - -#define SR_ERRORS (SR_ERROR_ERASE|SR_ERROR_WRITE|SR_VPP|SR_PROTECT) - -/* Configuration options */ - -#undef AUTOUNLOCK /* automatically unlocks blocks before erasing */ - -static struct mtd_info *sharp_probe(struct map_info *); - -static int sharp_probe_map(struct map_info *map,struct mtd_info *mtd); - -static int sharp_read(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf); -static int sharp_write(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, const u_char *buf); -static int sharp_erase(struct mtd_info *mtd, struct erase_info *instr); -static void sharp_sync(struct mtd_info *mtd); -static int sharp_suspend(struct mtd_info *mtd); -static void sharp_resume(struct mtd_info *mtd); -static void sharp_destroy(struct mtd_info *mtd); - -static int sharp_write_oneword(struct map_info *map, struct flchip *chip, - unsigned long adr, __u32 datum); -static int sharp_erase_oneblock(struct map_info *map, struct flchip *chip, - unsigned long adr); -#ifdef AUTOUNLOCK -static void sharp_unlock_oneblock(struct map_info *map, struct flchip *chip, - unsigned long adr); -#endif - - -struct sharp_info{ - struct flchip *chip; - int bogus; - int chipshift; - int numchips; - struct flchip chips[1]; -}; - -static void sharp_destroy(struct mtd_info *mtd); - -static struct mtd_chip_driver sharp_chipdrv = { - .probe = sharp_probe, - .destroy = sharp_destroy, - .name = "sharp", - .module = THIS_MODULE -}; - - -static struct mtd_info *sharp_probe(struct map_info *map) -{ - struct mtd_info *mtd = NULL; - struct sharp_info *sharp = NULL; - int width; - - mtd = kmalloc(sizeof(*mtd), GFP_KERNEL); - if(!mtd) - return NULL; - - sharp = kmalloc(sizeof(*sharp), GFP_KERNEL); - if(!sharp) { - kfree(mtd); - return NULL; - } - - memset(mtd, 0, sizeof(*mtd)); - - width = sharp_probe_map(map,mtd); - if(!width){ - kfree(mtd); - kfree(sharp); - return NULL; - } - - mtd->priv = map; - mtd->type = MTD_NORFLASH; - mtd->erase = sharp_erase; - mtd->read = sharp_read; - mtd->write = sharp_write; - mtd->sync = sharp_sync; - mtd->suspend = sharp_suspend; - mtd->resume = sharp_resume; - mtd->flags = MTD_CAP_NORFLASH; - mtd->writesize = 1; - mtd->name = map->name; - - memset(sharp, 0, sizeof(*sharp)); - sharp->chipshift = 23; - sharp->numchips = 1; - sharp->chips[0].start = 0; - sharp->chips[0].state = FL_READY; - sharp->chips[0].mutex = &sharp->chips[0]._spinlock; - sharp->chips[0].word_write_time = 0; - init_waitqueue_head(&sharp->chips[0].wq); - spin_lock_init(&sharp->chips[0]._spinlock); - - map->fldrv = &sharp_chipdrv; - map->fldrv_priv = sharp; - - __module_get(THIS_MODULE); - return mtd; -} - -static inline void sharp_send_cmd(struct map_info *map, unsigned long cmd, unsigned long adr) -{ - map_word map_cmd; - map_cmd.x[0] = cmd; - map_write(map, map_cmd, adr); -} - -static int sharp_probe_map(struct map_info *map,struct mtd_info *mtd) -{ - map_word tmp, read0, read4; - unsigned long base = 0; - int width = 4; - - tmp = map_read(map, base+0); - - sharp_send_cmd(map, CMD_READ_ID, base+0); - - read0 = map_read(map, base+0); - read4 = map_read(map, base+4); - if(read0.x[0] == 0x89898989){ - printk("Looks like sharp flash\n"); - switch(read4.x[0]){ - case 0xaaaaaaaa: - case 0xa0a0a0a0: - /* aa - LH28F016SCT-L95 2Mx8, 32 64k blocks*/ - /* a0 - LH28F016SCT-Z4 2Mx8, 32 64k blocks*/ - mtd->erasesize = 0x10000 * width; - mtd->size = 0x200000 * width; - return width; - case 0xa6a6a6a6: - /* a6 - LH28F008SCT-L12 1Mx8, 16 64k blocks*/ - /* a6 - LH28F008SCR-L85 1Mx8, 16 64k blocks*/ - mtd->erasesize = 0x10000 * width; - mtd->size = 0x100000 * width; - return width; -#if 0 - case 0x00000000: /* unknown */ - /* XX - LH28F004SCT 512kx8, 8 64k blocks*/ - mtd->erasesize = 0x10000 * width; - mtd->size = 0x80000 * width; - return width; -#endif - default: - printk("Sort-of looks like sharp flash, 0x%08lx 0x%08lx\n", - read0.x[0], read4.x[0]); - } - }else if((map_read(map, base+0).x[0] == CMD_READ_ID)){ - /* RAM, probably */ - printk("Looks like RAM\n"); - map_write(map, tmp, base+0); - }else{ - printk("Doesn't look like sharp flash, 0x%08lx 0x%08lx\n", - read0.x[0], read4.x[0]); - } - - return 0; -} - -/* This function returns with the chip->mutex lock held. */ -static int sharp_wait(struct map_info *map, struct flchip *chip) -{ - int i; - map_word status; - unsigned long timeo = jiffies + HZ; - DECLARE_WAITQUEUE(wait, current); - int adr = 0; - -retry: - spin_lock_bh(chip->mutex); - - switch(chip->state){ - case FL_READY: - sharp_send_cmd(map, CMD_READ_STATUS, adr); - chip->state = FL_STATUS; - case FL_STATUS: - for(i=0;i<100;i++){ - status = map_read(map, adr); - if((status.x[0] & SR_READY)==SR_READY) - break; - udelay(1); - } - break; - default: - printk("Waiting for chip\n"); - - set_current_state(TASK_INTERRUPTIBLE); - add_wait_queue(&chip->wq, &wait); - - spin_unlock_bh(chip->mutex); - - schedule(); - remove_wait_queue(&chip->wq, &wait); - - if(signal_pending(current)) - return -EINTR; - - timeo = jiffies + HZ; - - goto retry; - } - - sharp_send_cmd(map, CMD_RESET, adr); - - chip->state = FL_READY; - - return 0; -} - -static void sharp_release(struct flchip *chip) -{ - wake_up(&chip->wq); - spin_unlock_bh(chip->mutex); -} - -static int sharp_read(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf) -{ - struct map_info *map = mtd->priv; - struct sharp_info *sharp = map->fldrv_priv; - int chipnum; - int ret = 0; - int ofs = 0; - - chipnum = (from >> sharp->chipshift); - ofs = from & ((1 << sharp->chipshift)-1); - - *retlen = 0; - - while(len){ - unsigned long thislen; - - if(chipnum>=sharp->numchips) - break; - - thislen = len; - if(ofs+thislen >= (1<<sharp->chipshift)) - thislen = (1<<sharp->chipshift) - ofs; - - ret = sharp_wait(map,&sharp->chips[chipnum]); - if(ret<0) - break; - - map_copy_from(map,buf,ofs,thislen); - - sharp_release(&sharp->chips[chipnum]); - - *retlen += thislen; - len -= thislen; - buf += thislen; - - ofs = 0; - chipnum++; - } - return ret; -} - -static int sharp_write(struct mtd_info *mtd, loff_t to, size_t len, - size_t *retlen, const u_char *buf) -{ - struct map_info *map = mtd->priv; - struct sharp_info *sharp = map->fldrv_priv; - int ret = 0; - int i,j; - int chipnum; - unsigned long ofs; - union { u32 l; unsigned char uc[4]; } tbuf; - - *retlen = 0; - - while(len){ - tbuf.l = 0xffffffff; - chipnum = to >> sharp->chipshift; - ofs = to & ((1<<sharp->chipshift)-1); - - j=0; - for(i=ofs&3;i<4 && len;i++){ - tbuf.uc[i] = *buf; - buf++; - to++; - len--; - j++; - } - sharp_write_oneword(map, &sharp->chips[chipnum], ofs&~3, tbuf.l); - if(ret<0) - return ret; - (*retlen)+=j; - } - - return 0; -} - -static int sharp_write_oneword(struct map_info *map, struct flchip *chip, - unsigned long adr, __u32 datum) -{ - int ret; - int timeo; - int try; - int i; - map_word data, status; - - status.x[0] = 0; - ret = sharp_wait(map,chip); - - for(try=0;try<10;try++){ - sharp_send_cmd(map, CMD_BYTE_WRITE, adr); - /* cpu_to_le32 -> hack to fix the writel be->le conversion */ - data.x[0] = cpu_to_le32(datum); - map_write(map, data, adr); - - chip->state = FL_WRITING; - - timeo = jiffies + (HZ/2); - - sharp_send_cmd(map, CMD_READ_STATUS, adr); - for(i=0;i<100;i++){ - status = map_read(map, adr); - if((status.x[0] & SR_READY) == SR_READY) - break; - } - if(i==100){ - printk("sharp: timed out writing\n"); - } - - if(!(status.x[0] & SR_ERRORS)) - break; - - printk("sharp: error writing byte at addr=%08lx status=%08lx\n", adr, status.x[0]); - - sharp_send_cmd(map, CMD_CLEAR_STATUS, adr); - } - sharp_send_cmd(map, CMD_RESET, adr); - chip->state = FL_READY; - - wake_up(&chip->wq); - spin_unlock_bh(chip->mutex); - - return 0; -} - -static int sharp_erase(struct mtd_info *mtd, struct erase_info *instr) -{ - struct map_info *map = mtd->priv; - struct sharp_info *sharp = map->fldrv_priv; - unsigned long adr,len; - int chipnum, ret=0; - -//printk("sharp_erase()\n"); - if(instr->addr & (mtd->erasesize - 1)) - return -EINVAL; - if(instr->len & (mtd->erasesize - 1)) - return -EINVAL; - if(instr->len + instr->addr > mtd->size) - return -EINVAL; - - chipnum = instr->addr >> sharp->chipshift; - adr = instr->addr & ((1<<sharp->chipshift)-1); - len = instr->len; - - while(len){ - ret = sharp_erase_oneblock(map, &sharp->chips[chipnum], adr); - if(ret)return ret; - - adr += mtd->erasesize; - len -= mtd->erasesize; - if(adr >> sharp->chipshift){ - adr = 0; - chipnum++; - if(chipnum>=sharp->numchips) - break; - } - } - - instr->state = MTD_ERASE_DONE; - mtd_erase_callback(instr); - - return 0; -} - -static int sharp_do_wait_for_ready(struct map_info *map, struct flchip *chip, - unsigned long adr) -{ - int ret; - unsigned long timeo; - map_word status; - DECLARE_WAITQUEUE(wait, current); - - sharp_send_cmd(map, CMD_READ_STATUS, adr); - status = map_read(map, adr); - - timeo = jiffies + HZ; - - while(time_before(jiffies, timeo)){ - sharp_send_cmd(map, CMD_READ_STATUS, adr); - status = map_read(map, adr); - if((status.x[0] & SR_READY)==SR_READY){ - ret = 0; - goto out; - } - set_current_state(TASK_INTERRUPTIBLE); - add_wait_queue(&chip->wq, &wait); - - //spin_unlock_bh(chip->mutex); - - schedule_timeout(1); - schedule(); - remove_wait_queue(&chip->wq, &wait); - - //spin_lock_bh(chip->mutex); - - if (signal_pending(current)){ - ret = -EINTR; - goto out; - } - - } - ret = -ETIME; -out: - return ret; -} - -static int sharp_erase_oneblock(struct map_info *map, struct flchip *chip, - unsigned long adr) -{ - int ret; - //int timeo; - map_word status; - //int i; - -//printk("sharp_erase_oneblock()\n"); - -#ifdef AUTOUNLOCK - /* This seems like a good place to do an unlock */ - sharp_unlock_oneblock(map,chip,adr); -#endif - - sharp_send_cmd(map, CMD_BLOCK_ERASE_1, adr); - sharp_send_cmd(map, CMD_BLOCK_ERASE_2, adr); - - chip->state = FL_ERASING; - - ret = sharp_do_wait_for_ready(map,chip,adr); - if(ret<0)return ret; - - sharp_send_cmd(map, CMD_READ_STATUS, adr); - status = map_read(map, adr); - - if(!(status.x[0] & SR_ERRORS)){ - sharp_send_cmd(map, CMD_RESET, adr); - chip->state = FL_READY; - //spin_unlock_bh(chip->mutex); - return 0; - } - - printk("sharp: error erasing block at addr=%08lx status=%08lx\n", adr, status.x[0]); - sharp_send_cmd(map, CMD_CLEAR_STATUS, adr); - - //spin_unlock_bh(chip->mutex); - - return -EIO; -} - -#ifdef AUTOUNLOCK -static void sharp_unlock_oneblock(struct map_info *map, struct flchip *chip, - unsigned long adr) -{ - int i; - map_word status; - - sharp_send_cmd(map, CMD_CLEAR_BLOCK_LOCKS_1, adr); - sharp_send_cmd(map, CMD_CLEAR_BLOCK_LOCKS_2, adr); - - udelay(100); - - status = map_read(map, adr); - printk("status=%08lx\n", status.x[0]); - - for(i=0;i<1000;i++){ - //sharp_send_cmd(map, CMD_READ_STATUS, adr); - status = map_read(map, adr); - if((status.x[0] & SR_READY) == SR_READY) - break; - udelay(100); - } - if(i==1000){ - printk("sharp: timed out unlocking block\n"); - } - - if(!(status.x[0] & SR_ERRORS)){ - sharp_send_cmd(map, CMD_RESET, adr); - chip->state = FL_READY; - return; - } - - printk("sharp: error unlocking block at addr=%08lx status=%08lx\n", adr, status.x[0]); - sharp_send_cmd(map, CMD_CLEAR_STATUS, adr); -} -#endif - -static void sharp_sync(struct mtd_info *mtd) -{ - //printk("sharp_sync()\n"); -} - -static int sharp_suspend(struct mtd_info *mtd) -{ - printk("sharp_suspend()\n"); - return -EINVAL; -} - -static void sharp_resume(struct mtd_info *mtd) -{ - printk("sharp_resume()\n"); - -} - -static void sharp_destroy(struct mtd_info *mtd) -{ - printk("sharp_destroy()\n"); - -} - -static int __init sharp_probe_init(void) -{ - printk("MTD Sharp chip driver <ds@lineo.com>\n"); - - register_mtd_chip_driver(&sharp_chipdrv); - - return 0; -} - -static void __exit sharp_probe_exit(void) -{ - unregister_mtd_chip_driver(&sharp_chipdrv); -} - -module_init(sharp_probe_init); -module_exit(sharp_probe_exit); - - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("David Schleef <ds@schleef.org>"); -MODULE_DESCRIPTION("Old MTD chip driver for pre-CFI Sharp flash chips"); diff --git a/drivers/mtd/cmdlinepart.c b/drivers/mtd/cmdlinepart.c index a7a7bfe3387..3e829b37af8 100644 --- a/drivers/mtd/cmdlinepart.c +++ b/drivers/mtd/cmdlinepart.c @@ -1,18 +1,43 @@ /* - * $Id: cmdlinepart.c,v 1.19 2005/11/07 11:14:19 gleixner Exp $ - * * Read flash partition table from command line * - * Copyright 2002 SYSGO Real-Time Solutions GmbH + * Copyright © 2002 SYSGO Real-Time Solutions GmbH + * Copyright © 2002-2010 David Woodhouse <dwmw2@infradead.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * The format for the command line is as follows: * * mtdparts=<mtddef>[;<mtddef] * <mtddef> := <mtd-id>:<partdef>[,<partdef>] - * <partdef> := <size>[@offset][<name>][ro] + * <partdef> := <size>[@<offset>][<name>][ro][lk] * <mtd-id> := unique name used in mapping driver/device (mtd->name) * <size> := standard linux memsize OR "-" to denote all remaining space + * size is automatically truncated at end of device + * if specified or trucated size is 0 the part is skipped + * <offset> := standard linux memsize + * if omitted the part will immediately follow the previous part + * or 0 if the first part * <name> := '(' NAME ')' + * NAME will appear in /proc/mtd + * + * <size> and <offset> can be specified such that the parts are out of order + * in physical memory and may even overlap. + * + * The parts are assigned MTD numbers in the order they are specified in the + * command line regardless of their order in physical memory. * * Examples: * @@ -25,10 +50,10 @@ #include <linux/kernel.h> #include <linux/slab.h> - #include <linux/mtd/mtd.h> #include <linux/mtd/partitions.h> -#include <linux/bootmem.h> +#include <linux/module.h> +#include <linux/err.h> /* error message prefix */ #define ERRP "mtd: " @@ -42,8 +67,8 @@ /* special size referring to all the remaining space in a partition */ -#define SIZE_REMAINING UINT_MAX -#define OFFSET_CONTINUOUS UINT_MAX +#define SIZE_REMAINING ULLONG_MAX +#define OFFSET_CONTINUOUS ULLONG_MAX struct cmdline_mtd_partition { struct cmdline_mtd_partition *next; @@ -55,9 +80,10 @@ struct cmdline_mtd_partition { /* mtdpart_setup() parses into here */ static struct cmdline_mtd_partition *partitions; -/* the command line passed to mtdpart_setupd() */ +/* the command line passed to mtdpart_setup() */ +static char *mtdparts; static char *cmdline; -static int cmdline_parsed = 0; +static int cmdline_parsed; /* * Parse one partition definition for an MTD. Since there can be many @@ -68,15 +94,14 @@ static int cmdline_parsed = 0; * syntax has been verified ok. */ static struct mtd_partition * newpart(char *s, - char **retptr, - int *num_parts, - int this_part, - unsigned char **extra_mem_ptr, - int extra_mem_size) + char **retptr, + int *num_parts, + int this_part, + unsigned char **extra_mem_ptr, + int extra_mem_size) { struct mtd_partition *parts; - unsigned long size; - unsigned long offset = OFFSET_CONTINUOUS; + unsigned long long size, offset = OFFSET_CONTINUOUS; char *name; int name_len; unsigned char *extra_mem; @@ -84,119 +109,107 @@ static struct mtd_partition * newpart(char *s, unsigned int mask_flags; /* fetch the partition size */ - if (*s == '-') - { /* assign all remaining space to this partition */ + if (*s == '-') { + /* assign all remaining space to this partition */ size = SIZE_REMAINING; s++; - } - else - { + } else { size = memparse(s, &s); - if (size < PAGE_SIZE) - { - printk(KERN_ERR ERRP "partition size too small (%lx)\n", size); - return NULL; + if (size < PAGE_SIZE) { + printk(KERN_ERR ERRP "partition size too small (%llx)\n", + size); + return ERR_PTR(-EINVAL); } } /* fetch partition name and flags */ mask_flags = 0; /* this is going to be a regular partition */ delim = 0; - /* check for offset */ - if (*s == '@') - { - s++; - offset = memparse(s, &s); - } - /* now look for name */ + + /* check for offset */ + if (*s == '@') { + s++; + offset = memparse(s, &s); + } + + /* now look for name */ if (*s == '(') - { delim = ')'; - } - if (delim) - { + if (delim) { char *p; - name = ++s; - if ((p = strchr(name, delim)) == 0) - { + name = ++s; + p = strchr(name, delim); + if (!p) { printk(KERN_ERR ERRP "no closing %c found in partition name\n", delim); - return NULL; + return ERR_PTR(-EINVAL); } name_len = p - name; s = p + 1; - } - else - { - name = NULL; + } else { + name = NULL; name_len = 13; /* Partition_000 */ } /* record name length for memory allocation later */ extra_mem_size += name_len + 1; - /* test for options */ - if (strncmp(s, "ro", 2) == 0) - { + /* test for options */ + if (strncmp(s, "ro", 2) == 0) { mask_flags |= MTD_WRITEABLE; s += 2; - } + } + + /* if lk is found do NOT unlock the MTD partition*/ + if (strncmp(s, "lk", 2) == 0) { + mask_flags |= MTD_POWERUP_LOCK; + s += 2; + } /* test if more partitions are following */ - if (*s == ',') - { - if (size == SIZE_REMAINING) - { + if (*s == ',') { + if (size == SIZE_REMAINING) { printk(KERN_ERR ERRP "no partitions allowed after a fill-up partition\n"); - return NULL; + return ERR_PTR(-EINVAL); } /* more partitions follow, parse them */ - if ((parts = newpart(s + 1, &s, num_parts, - this_part + 1, &extra_mem, extra_mem_size)) == 0) - return NULL; - } - else - { /* this is the last partition: allocate space for all */ + parts = newpart(s + 1, &s, num_parts, this_part + 1, + &extra_mem, extra_mem_size); + if (IS_ERR(parts)) + return parts; + } else { + /* this is the last partition: allocate space for all */ int alloc_size; *num_parts = this_part + 1; alloc_size = *num_parts * sizeof(struct mtd_partition) + extra_mem_size; - parts = kmalloc(alloc_size, GFP_KERNEL); + + parts = kzalloc(alloc_size, GFP_KERNEL); if (!parts) - { - printk(KERN_ERR ERRP "out of memory\n"); - return NULL; - } - memset(parts, 0, alloc_size); + return ERR_PTR(-ENOMEM); extra_mem = (unsigned char *)(parts + *num_parts); } + /* enter this partition (offset will be calculated later if it is zero at this point) */ parts[this_part].size = size; parts[this_part].offset = offset; parts[this_part].mask_flags = mask_flags; if (name) - { strlcpy(extra_mem, name, name_len + 1); - } else - { sprintf(extra_mem, "Partition_%03d", this_part); - } parts[this_part].name = extra_mem; extra_mem += name_len + 1; - dbg(("partition %d: name <%s>, offset %x, size %x, mask flags %x\n", - this_part, - parts[this_part].name, - parts[this_part].offset, - parts[this_part].size, - parts[this_part].mask_flags)); + dbg(("partition %d: name <%s>, offset %llx, size %llx, mask flags %x\n", + this_part, parts[this_part].name, parts[this_part].offset, + parts[this_part].size, parts[this_part].mask_flags)); /* return (updated) pointer to extra_mem memory */ if (extra_mem_ptr) - *extra_mem_ptr = extra_mem; + *extra_mem_ptr = extra_mem; /* return (updated) pointer command line string */ *retptr = s; @@ -216,16 +229,16 @@ static int mtdpart_setup_real(char *s) { struct cmdline_mtd_partition *this_mtd; struct mtd_partition *parts; - int mtd_id_len; - int num_parts; + int mtd_id_len, num_parts; char *p, *mtd_id; - mtd_id = s; + mtd_id = s; + /* fetch <mtd-id> */ - if (!(p = strchr(s, ':'))) - { + p = strchr(s, ':'); + if (!p) { printk(KERN_ERR ERRP "no mtd-id\n"); - return 0; + return -EINVAL; } mtd_id_len = p - mtd_id; @@ -242,8 +255,7 @@ static int mtdpart_setup_real(char *s) (unsigned char**)&this_mtd, /* out: extra mem */ mtd_id_len + 1 + sizeof(*this_mtd) + sizeof(void*)-1 /*alignment*/); - if(!parts) - { + if (IS_ERR(parts)) { /* * An error occurred. We're either: * a) out of memory, or @@ -251,12 +263,12 @@ static int mtdpart_setup_real(char *s) * Either way, this mtd is hosed and we're * unlikely to succeed in parsing any more */ - return 0; + return PTR_ERR(parts); } /* align this_mtd */ this_mtd = (struct cmdline_mtd_partition *) - ALIGN((unsigned long)this_mtd, sizeof(void*)); + ALIGN((unsigned long)this_mtd, sizeof(void *)); /* enter results */ this_mtd->parts = parts; this_mtd->num_parts = num_parts; @@ -276,14 +288,14 @@ static int mtdpart_setup_real(char *s) break; /* does another spec follow? */ - if (*s != ';') - { + if (*s != ';') { printk(KERN_ERR ERRP "bad character after partition (%c)\n", *s); - return 0; + return -EINVAL; } s++; } - return 1; + + return 0; } /* @@ -294,48 +306,67 @@ static int mtdpart_setup_real(char *s) * the first one in the chain if a NULL mtd_id is passed in. */ static int parse_cmdline_partitions(struct mtd_info *master, - struct mtd_partition **pparts, - unsigned long origin) + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) { - unsigned long offset; - int i; + unsigned long long offset; + int i, err; struct cmdline_mtd_partition *part; - char *mtd_id = master->name; - - if(!cmdline) - return -EINVAL; + const char *mtd_id = master->name; /* parse command line */ - if (!cmdline_parsed) - mtdpart_setup_real(cmdline); + if (!cmdline_parsed) { + err = mtdpart_setup_real(cmdline); + if (err) + return err; + } - for(part = partitions; part; part = part->next) - { + /* + * Search for the partition definition matching master->name. + * If master->name is not set, stop at first partition definition. + */ + for (part = partitions; part; part = part->next) { if ((!mtd_id) || (!strcmp(part->mtd_id, mtd_id))) - { - for(i = 0, offset = 0; i < part->num_parts; i++) - { - if (part->parts[i].offset == OFFSET_CONTINUOUS) - part->parts[i].offset = offset; - else - offset = part->parts[i].offset; - if (part->parts[i].size == SIZE_REMAINING) - part->parts[i].size = master->size - offset; - if (offset + part->parts[i].size > master->size) - { - printk(KERN_WARNING ERRP - "%s: partitioning exceeds flash size, truncating\n", - part->mtd_id); - part->parts[i].size = master->size - offset; - part->num_parts = i; - } - offset += part->parts[i].size; - } - *pparts = part->parts; - return part->num_parts; + break; + } + + if (!part) + return 0; + + for (i = 0, offset = 0; i < part->num_parts; i++) { + if (part->parts[i].offset == OFFSET_CONTINUOUS) + part->parts[i].offset = offset; + else + offset = part->parts[i].offset; + + if (part->parts[i].size == SIZE_REMAINING) + part->parts[i].size = master->size - offset; + + if (offset + part->parts[i].size > master->size) { + printk(KERN_WARNING ERRP + "%s: partitioning exceeds flash size, truncating\n", + part->mtd_id); + part->parts[i].size = master->size - offset; + } + offset += part->parts[i].size; + + if (part->parts[i].size == 0) { + printk(KERN_WARNING ERRP + "%s: skipping zero sized partition\n", + part->mtd_id); + part->num_parts--; + memmove(&part->parts[i], &part->parts[i + 1], + sizeof(*part->parts) * (part->num_parts - i)); + i--; } } - return -EINVAL; + + *pparts = kmemdup(part->parts, sizeof(*part->parts) * part->num_parts, + GFP_KERNEL); + if (!*pparts) + return -ENOMEM; + + return part->num_parts; } @@ -346,7 +377,7 @@ static int parse_cmdline_partitions(struct mtd_info *master, * * This function needs to be visible for bootloaders. */ -int mtdpart_setup(char *s) +static int __init mtdpart_setup(char *s) { cmdline = s; return 1; @@ -362,10 +393,22 @@ static struct mtd_part_parser cmdline_parser = { static int __init cmdline_parser_init(void) { - return register_mtd_parser(&cmdline_parser); + if (mtdparts) + mtdpart_setup(mtdparts); + register_mtd_parser(&cmdline_parser); + return 0; +} + +static void __exit cmdline_parser_exit(void) +{ + deregister_mtd_parser(&cmdline_parser); } module_init(cmdline_parser_init); +module_exit(cmdline_parser_exit); + +MODULE_PARM_DESC(mtdparts, "Partitioning specification"); +module_param(mtdparts, charp, 0); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Marius Groeger <mag@sysgo.de>"); diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig index 16c02b5ccf7..c49d0b127fe 100644 --- a/drivers/mtd/devices/Kconfig +++ b/drivers/mtd/devices/Kconfig @@ -1,12 +1,10 @@ -# drivers/mtd/maps/Kconfig -# $Id: Kconfig,v 1.18 2005/11/07 11:14:24 gleixner Exp $ - menu "Self-contained MTD device drivers" depends on MTD!=n + depends on HAS_IOMEM config MTD_PMC551 tristate "Ramix PMC551 PCI Mezzanine RAM card support" - depends on MTD && PCI + depends on PCI ---help--- This provides a MTD device driver for the Ramix PMC551 RAM PCI card from Ramix Inc. <http://www.ramix.com/products/memory/pmc551.html>. @@ -40,7 +38,7 @@ config MTD_PMC551_DEBUG config MTD_MS02NV tristate "DEC MS02-NV NVRAM module support" - depends on MTD && MACH_DECSTATION + depends on MACH_DECSTATION help This is an MTD driver for the DEC's MS02-NV (54-20948-01) battery backed-up NVRAM module. The module was originally meant as an NFS @@ -49,28 +47,81 @@ config MTD_MS02NV If you want to compile this driver as a module ( = code which can be inserted in and removed from the running kernel whenever you want), - say M here and read <file:Documentation/modules.txt>. The module will - be called ms02-nv.o. + say M here and read <file:Documentation/kbuild/modules.txt>. + The module will be called ms02-nv. config MTD_DATAFLASH tristate "Support for AT45xxx DataFlash" - depends on MTD && SPI_MASTER && EXPERIMENTAL + depends on SPI_MASTER help This enables access to AT45xxx DataFlash chips, using SPI. Sometimes DataFlash chips are packaged inside MMC-format cards; at this writing, the MMC stack won't handle those. +config MTD_DATAFLASH_WRITE_VERIFY + bool "Verify DataFlash page writes" + depends on MTD_DATAFLASH + help + This adds an extra check when data is written to the flash. + It may help if you are verifying chip setup (timings etc) on + your board. There is a rare possibility that even though the + device thinks the write was successful, a bit could have been + flipped accidentally due to device wear or something else. + +config MTD_DATAFLASH_OTP + bool "DataFlash OTP support (Security Register)" + depends on MTD_DATAFLASH + help + Newer DataFlash chips (revisions C and D) support 128 bytes of + one-time-programmable (OTP) data. The first half may be written + (once) with up to 64 bytes of data, such as a serial number or + other key product data. The second half is programmed with a + unique-to-each-chip bit pattern at the factory. + config MTD_M25P80 - tristate "Support for M25 SPI Flash" - depends on MTD && SPI_MASTER && EXPERIMENTAL + tristate "Support most SPI Flash chips (AT26DF, M25P, W25X, ...)" + depends on SPI_MASTER && MTD_SPI_NOR + help + This enables access to most modern SPI flash chips, used for + program and data storage. Series supported include Atmel AT26DF, + Spansion S25SL, SST 25VF, ST M25P, and Winbond W25X. Other chips + are supported as well. See the driver source for the current list, + or to add other chips. + + Note that the original DataFlash chips (AT45 series, not AT26DF), + need an entirely different driver. + + Set up your spi devices with the right board-specific platform data, + if you want to specify device partitioning or to use a device which + doesn't support the JEDEC ID instruction. + +config MTD_SPEAR_SMI + tristate "SPEAR MTD NOR Support through SMI controller" + depends on PLAT_SPEAR + default y + help + This enable SNOR support on SPEAR platforms using SMI controller + +config MTD_SST25L + tristate "Support SST25L (non JEDEC) SPI Flash chips" + depends on SPI_MASTER + help + This enables access to the non JEDEC SST25L SPI flash chips, used + for program and data storage. + + Set up your spi devices with the right board-specific platform data, + if you want to specify device partitioning. + +config MTD_BCM47XXSFLASH + tristate "R/O support for serial flash on BCMA bus" + depends on BCMA_SFLASH help - This enables access to ST M25P80 and similar SPI flash chips, - used for program and data storage. Set up your spi devices - with the right board-specific platform data. + BCMA bus can have various flash memories attached, they are + registered by bcma as platform devices. This enables driver for + serial flash memories (only read-only mode is implemented). config MTD_SLRAM tristate "Uncached system RAM" - depends on MTD help If your CPU cannot cache all of the physical memory in your machine, you can still use it for storage or swap by using this driver to @@ -78,7 +129,6 @@ config MTD_SLRAM config MTD_PHRAM tristate "Physical system RAM" - depends on MTD help This is a re-implementation of the slram driver above. @@ -88,7 +138,7 @@ config MTD_PHRAM config MTD_LART tristate "28F160xx flash driver for LART" - depends on SA1100_LART && MTD + depends on SA1100_LART help This enables the flash driver for LART. Please note that you do not need any mapping/chip driver for LART. This one does it all @@ -96,7 +146,6 @@ config MTD_LART config MTD_MTDRAM tristate "Test driver using RAM" - depends on MTD help This enables a test MTD device driver which uses vmalloc() to provide storage. You probably want to say 'N' unless you're @@ -136,7 +185,7 @@ config MTDRAM_ABS_POS config MTD_BLOCK2MTD tristate "MTD using block device" - depends on MTD + depends on BLOCK help This driver allows a block device to appear as an MTD. It would generally be used in the following cases: @@ -148,124 +197,32 @@ config MTD_BLOCK2MTD comment "Disk-On-Chip Device Drivers" -config MTD_DOC2000 - tristate "M-Systems Disk-On-Chip 2000 and Millennium (DEPRECATED)" - depends on MTD - select MTD_DOCPROBE - select MTD_NAND_IDS - ---help--- - This provides an MTD device driver for the M-Systems DiskOnChip - 2000 and Millennium devices. Originally designed for the DiskOnChip - 2000, it also now includes support for the DiskOnChip Millennium. - If you have problems with this driver and the DiskOnChip Millennium, - you may wish to try the alternative Millennium driver below. To use - the alternative driver, you will need to undefine DOC_SINGLE_DRIVER - in the <file:drivers/mtd/devices/docprobe.c> source code. - - If you use this device, you probably also want to enable the NFTL - 'NAND Flash Translation Layer' option below, which is used to - emulate a block device by using a kind of file system on the flash - chips. - - NOTE: This driver is deprecated and will probably be removed soon. - Please try the new DiskOnChip driver under "NAND Flash Device - Drivers". - -config MTD_DOC2001 - tristate "M-Systems Disk-On-Chip Millennium-only alternative driver (DEPRECATED)" - depends on MTD - select MTD_DOCPROBE - select MTD_NAND_IDS - ---help--- - This provides an alternative MTD device driver for the M-Systems - DiskOnChip Millennium devices. Use this if you have problems with - the combined DiskOnChip 2000 and Millennium driver above. To get - the DiskOnChip probe code to load and use this driver instead of - the other one, you will need to undefine DOC_SINGLE_DRIVER near - the beginning of <file:drivers/mtd/devices/docprobe.c>. - - If you use this device, you probably also want to enable the NFTL - 'NAND Flash Translation Layer' option below, which is used to - emulate a block device by using a kind of file system on the flash - chips. - - NOTE: This driver is deprecated and will probably be removed soon. - Please try the new DiskOnChip driver under "NAND Flash Device - Drivers". - -config MTD_DOC2001PLUS - tristate "M-Systems Disk-On-Chip Millennium Plus" - depends on MTD - select MTD_DOCPROBE - select MTD_NAND_IDS +config MTD_DOCG3 + tristate "M-Systems Disk-On-Chip G3" + select BCH + select BCH_CONST_PARAMS + select BITREVERSE ---help--- This provides an MTD device driver for the M-Systems DiskOnChip - Millennium Plus devices. - - If you use this device, you probably also want to enable the INFTL - 'Inverse NAND Flash Translation Layer' option below, which is used - to emulate a block device by using a kind of file system on the - flash chips. - - NOTE: This driver will soon be replaced by the new DiskOnChip driver - under "NAND Flash Device Drivers" (currently that driver does not - support all Millennium Plus devices). - -config MTD_DOCPROBE - tristate - select MTD_DOCECC + G3 devices. -config MTD_DOCECC - tristate + The driver provides access to G3 DiskOnChip, distributed by + M-Systems and now Sandisk. The support is very experimental, + and doesn't give access to any write operations. -config MTD_DOCPROBE_ADVANCED - bool "Advanced detection options for DiskOnChip" - depends on MTD_DOCPROBE +config MTD_ST_SPI_FSM + tristate "ST Microelectronics SPI FSM Serial Flash Controller" + depends on ARCH_STI help - This option allows you to specify nonstandard address at which to - probe for a DiskOnChip, or to change the detection options. You - are unlikely to need any of this unless you are using LinuxBIOS. - Say 'N'. - -config MTD_DOCPROBE_ADDRESS - hex "Physical address of DiskOnChip" if MTD_DOCPROBE_ADVANCED - depends on MTD_DOCPROBE - default "0x0000" if MTD_DOCPROBE_ADVANCED - default "0" if !MTD_DOCPROBE_ADVANCED - ---help--- - By default, the probe for DiskOnChip devices will look for a - DiskOnChip at every multiple of 0x2000 between 0xC8000 and 0xEE000. - This option allows you to specify a single address at which to probe - for the device, which is useful if you have other devices in that - range which get upset when they are probed. - - (Note that on PowerPC, the normal probe will only check at - 0xE4000000.) - - Normally, you should leave this set to zero, to allow the probe at - the normal addresses. + This provides an MTD device driver for the ST Microelectronics + SPI Fast Sequence Mode (FSM) Serial Flash Controller and support + for a subset of connected Serial Flash devices. -config MTD_DOCPROBE_HIGH - bool "Probe high addresses" - depends on MTD_DOCPROBE_ADVANCED - help - By default, the probe for DiskOnChip devices will look for a - DiskOnChip at every multiple of 0x2000 between 0xC8000 and 0xEE000. - This option changes to make it probe between 0xFFFC8000 and - 0xFFFEE000. Unless you are using LinuxBIOS, this is unlikely to be - useful to you. Say 'N'. - -config MTD_DOCPROBE_55AA - bool "Probe for 0x55 0xAA BIOS Extension Signature" - depends on MTD_DOCPROBE_ADVANCED - help - Check for the 0x55 0xAA signature of a DiskOnChip, and do not - continue with probing if it is absent. The signature will always be - present for a DiskOnChip 2000 or a normal DiskOnChip Millennium. - Only if you have overwritten the first block of a DiskOnChip - Millennium will it be absent. Enable this option if you are using - LinuxBIOS or if you need to recover a DiskOnChip Millennium on which - you have managed to wipe the first block. +if MTD_DOCG3 +config BCH_CONST_M + default 14 +config BCH_CONST_T + default 4 +endif endmenu - diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile index 0f788d5c4bf..c68868f6058 100644 --- a/drivers/mtd/devices/Makefile +++ b/drivers/mtd/devices/Makefile @@ -1,13 +1,8 @@ # -# linux/drivers/devices/Makefile +# linux/drivers/mtd/devices/Makefile # -# $Id: Makefile.common,v 1.7 2004/12/22 17:51:15 joern Exp $ -obj-$(CONFIG_MTD_DOC2000) += doc2000.o -obj-$(CONFIG_MTD_DOC2001) += doc2001.o -obj-$(CONFIG_MTD_DOC2001PLUS) += doc2001plus.o -obj-$(CONFIG_MTD_DOCPROBE) += docprobe.o -obj-$(CONFIG_MTD_DOCECC) += docecc.o +obj-$(CONFIG_MTD_DOCG3) += docg3.o obj-$(CONFIG_MTD_SLRAM) += slram.o obj-$(CONFIG_MTD_PHRAM) += phram.o obj-$(CONFIG_MTD_PMC551) += pmc551.o @@ -17,3 +12,11 @@ obj-$(CONFIG_MTD_LART) += lart.o obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o obj-$(CONFIG_MTD_M25P80) += m25p80.o +obj-$(CONFIG_MTD_NAND_OMAP_BCH) += elm.o +obj-$(CONFIG_MTD_SPEAR_SMI) += spear_smi.o +obj-$(CONFIG_MTD_SST25L) += sst25l.o +obj-$(CONFIG_MTD_BCM47XXSFLASH) += bcm47xxsflash.o +obj-$(CONFIG_MTD_ST_SPI_FSM) += st_spi_fsm.o + + +CFLAGS_docg3.o += -I$(src) diff --git a/drivers/mtd/devices/bcm47xxsflash.c b/drivers/mtd/devices/bcm47xxsflash.c new file mode 100644 index 00000000000..77de29bc02b --- /dev/null +++ b/drivers/mtd/devices/bcm47xxsflash.c @@ -0,0 +1,340 @@ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/mtd/mtd.h> +#include <linux/platform_device.h> +#include <linux/bcma/bcma.h> + +#include "bcm47xxsflash.h" + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Serial flash driver for BCMA bus"); + +static const char * const probes[] = { "bcm47xxpart", NULL }; + +/************************************************** + * Various helpers + **************************************************/ + +static void bcm47xxsflash_cmd(struct bcm47xxsflash *b47s, u32 opcode) +{ + int i; + + b47s->cc_write(b47s, BCMA_CC_FLASHCTL, BCMA_CC_FLASHCTL_START | opcode); + for (i = 0; i < 1000; i++) { + if (!(b47s->cc_read(b47s, BCMA_CC_FLASHCTL) & + BCMA_CC_FLASHCTL_BUSY)) + return; + cpu_relax(); + } + pr_err("Control command failed (timeout)!\n"); +} + +static int bcm47xxsflash_poll(struct bcm47xxsflash *b47s, int timeout) +{ + unsigned long deadline = jiffies + timeout; + + do { + switch (b47s->type) { + case BCM47XXSFLASH_TYPE_ST: + bcm47xxsflash_cmd(b47s, OPCODE_ST_RDSR); + if (!(b47s->cc_read(b47s, BCMA_CC_FLASHDATA) & + SR_ST_WIP)) + return 0; + break; + case BCM47XXSFLASH_TYPE_ATMEL: + bcm47xxsflash_cmd(b47s, OPCODE_AT_STATUS); + if (b47s->cc_read(b47s, BCMA_CC_FLASHDATA) & + SR_AT_READY) + return 0; + break; + } + + cpu_relax(); + udelay(1); + } while (!time_after_eq(jiffies, deadline)); + + pr_err("Timeout waiting for flash to be ready!\n"); + + return -EBUSY; +} + +/************************************************** + * MTD ops + **************************************************/ + +static int bcm47xxsflash_erase(struct mtd_info *mtd, struct erase_info *erase) +{ + struct bcm47xxsflash *b47s = mtd->priv; + int err; + + switch (b47s->type) { + case BCM47XXSFLASH_TYPE_ST: + bcm47xxsflash_cmd(b47s, OPCODE_ST_WREN); + b47s->cc_write(b47s, BCMA_CC_FLASHADDR, erase->addr); + /* Newer flashes have "sub-sectors" which can be erased + * independently with a new command: ST_SSE. The ST_SE command + * erases 64KB just as before. + */ + if (b47s->blocksize < (64 * 1024)) + bcm47xxsflash_cmd(b47s, OPCODE_ST_SSE); + else + bcm47xxsflash_cmd(b47s, OPCODE_ST_SE); + break; + case BCM47XXSFLASH_TYPE_ATMEL: + b47s->cc_write(b47s, BCMA_CC_FLASHADDR, erase->addr << 1); + bcm47xxsflash_cmd(b47s, OPCODE_AT_PAGE_ERASE); + break; + } + + err = bcm47xxsflash_poll(b47s, HZ); + if (err) + erase->state = MTD_ERASE_FAILED; + else + erase->state = MTD_ERASE_DONE; + + if (erase->callback) + erase->callback(erase); + + return err; +} + +static int bcm47xxsflash_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct bcm47xxsflash *b47s = mtd->priv; + + /* Check address range */ + if ((from + len) > mtd->size) + return -EINVAL; + + memcpy_fromio(buf, (void __iomem *)KSEG0ADDR(b47s->window + from), + len); + *retlen = len; + + return len; +} + +static int bcm47xxsflash_write_st(struct mtd_info *mtd, u32 offset, size_t len, + const u_char *buf) +{ + struct bcm47xxsflash *b47s = mtd->priv; + int written = 0; + + /* Enable writes */ + bcm47xxsflash_cmd(b47s, OPCODE_ST_WREN); + + /* Write first byte */ + b47s->cc_write(b47s, BCMA_CC_FLASHADDR, offset); + b47s->cc_write(b47s, BCMA_CC_FLASHDATA, *buf++); + + /* Program page */ + if (b47s->bcma_cc->core->id.rev < 20) { + bcm47xxsflash_cmd(b47s, OPCODE_ST_PP); + return 1; /* 1B written */ + } + + /* Program page and set CSA (on newer chips we can continue writing) */ + bcm47xxsflash_cmd(b47s, OPCODE_ST_CSA | OPCODE_ST_PP); + offset++; + len--; + written++; + + while (len > 0) { + /* Page boundary, another function call is needed */ + if ((offset & 0xFF) == 0) + break; + + bcm47xxsflash_cmd(b47s, OPCODE_ST_CSA | *buf++); + offset++; + len--; + written++; + } + + /* All done, drop CSA & poll */ + b47s->cc_write(b47s, BCMA_CC_FLASHCTL, 0); + udelay(1); + if (bcm47xxsflash_poll(b47s, HZ / 10)) + pr_err("Flash rejected dropping CSA\n"); + + return written; +} + +static int bcm47xxsflash_write_at(struct mtd_info *mtd, u32 offset, size_t len, + const u_char *buf) +{ + struct bcm47xxsflash *b47s = mtd->priv; + u32 mask = b47s->blocksize - 1; + u32 page = (offset & ~mask) << 1; + u32 byte = offset & mask; + int written = 0; + + /* If we don't overwrite whole page, read it to the buffer first */ + if (byte || (len < b47s->blocksize)) { + int err; + + b47s->cc_write(b47s, BCMA_CC_FLASHADDR, page); + bcm47xxsflash_cmd(b47s, OPCODE_AT_BUF1_LOAD); + /* 250 us for AT45DB321B */ + err = bcm47xxsflash_poll(b47s, HZ / 1000); + if (err) { + pr_err("Timeout reading page 0x%X info buffer\n", page); + return err; + } + } + + /* Change buffer content with our data */ + while (len > 0) { + /* Page boundary, another function call is needed */ + if (byte == b47s->blocksize) + break; + + b47s->cc_write(b47s, BCMA_CC_FLASHADDR, byte++); + b47s->cc_write(b47s, BCMA_CC_FLASHDATA, *buf++); + bcm47xxsflash_cmd(b47s, OPCODE_AT_BUF1_WRITE); + len--; + written++; + } + + /* Program page with the buffer content */ + b47s->cc_write(b47s, BCMA_CC_FLASHADDR, page); + bcm47xxsflash_cmd(b47s, OPCODE_AT_BUF1_PROGRAM); + + return written; +} + +static int bcm47xxsflash_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct bcm47xxsflash *b47s = mtd->priv; + int written; + + /* Writing functions can return without writing all passed data, for + * example when the hardware is too old or when we git page boundary. + */ + while (len > 0) { + switch (b47s->type) { + case BCM47XXSFLASH_TYPE_ST: + written = bcm47xxsflash_write_st(mtd, to, len, buf); + break; + case BCM47XXSFLASH_TYPE_ATMEL: + written = bcm47xxsflash_write_at(mtd, to, len, buf); + break; + default: + BUG_ON(1); + } + if (written < 0) { + pr_err("Error writing at offset 0x%llX\n", to); + return written; + } + to += (loff_t)written; + len -= written; + *retlen += written; + buf += written; + } + + return 0; +} + +static void bcm47xxsflash_fill_mtd(struct bcm47xxsflash *b47s) +{ + struct mtd_info *mtd = &b47s->mtd; + + mtd->priv = b47s; + mtd->name = "bcm47xxsflash"; + mtd->owner = THIS_MODULE; + + mtd->type = MTD_NORFLASH; + mtd->flags = MTD_CAP_NORFLASH; + mtd->size = b47s->size; + mtd->erasesize = b47s->blocksize; + mtd->writesize = 1; + mtd->writebufsize = 1; + + mtd->_erase = bcm47xxsflash_erase; + mtd->_read = bcm47xxsflash_read; + mtd->_write = bcm47xxsflash_write; +} + +/************************************************** + * BCMA + **************************************************/ + +static int bcm47xxsflash_bcma_cc_read(struct bcm47xxsflash *b47s, u16 offset) +{ + return bcma_cc_read32(b47s->bcma_cc, offset); +} + +static void bcm47xxsflash_bcma_cc_write(struct bcm47xxsflash *b47s, u16 offset, + u32 value) +{ + bcma_cc_write32(b47s->bcma_cc, offset, value); +} + +static int bcm47xxsflash_bcma_probe(struct platform_device *pdev) +{ + struct bcma_sflash *sflash = dev_get_platdata(&pdev->dev); + struct bcm47xxsflash *b47s; + int err; + + b47s = devm_kzalloc(&pdev->dev, sizeof(*b47s), GFP_KERNEL); + if (!b47s) + return -ENOMEM; + sflash->priv = b47s; + + b47s->bcma_cc = container_of(sflash, struct bcma_drv_cc, sflash); + b47s->cc_read = bcm47xxsflash_bcma_cc_read; + b47s->cc_write = bcm47xxsflash_bcma_cc_write; + + switch (b47s->bcma_cc->capabilities & BCMA_CC_CAP_FLASHT) { + case BCMA_CC_FLASHT_STSER: + b47s->type = BCM47XXSFLASH_TYPE_ST; + break; + case BCMA_CC_FLASHT_ATSER: + b47s->type = BCM47XXSFLASH_TYPE_ATMEL; + break; + } + + b47s->window = sflash->window; + b47s->blocksize = sflash->blocksize; + b47s->numblocks = sflash->numblocks; + b47s->size = sflash->size; + bcm47xxsflash_fill_mtd(b47s); + + err = mtd_device_parse_register(&b47s->mtd, probes, NULL, NULL, 0); + if (err) { + pr_err("Failed to register MTD device: %d\n", err); + return err; + } + + if (bcm47xxsflash_poll(b47s, HZ / 10)) + pr_warn("Serial flash busy\n"); + + return 0; +} + +static int bcm47xxsflash_bcma_remove(struct platform_device *pdev) +{ + struct bcma_sflash *sflash = dev_get_platdata(&pdev->dev); + struct bcm47xxsflash *b47s = sflash->priv; + + mtd_device_unregister(&b47s->mtd); + + return 0; +} + +static struct platform_driver bcma_sflash_driver = { + .probe = bcm47xxsflash_bcma_probe, + .remove = bcm47xxsflash_bcma_remove, + .driver = { + .name = "bcma_sflash", + .owner = THIS_MODULE, + }, +}; + +/************************************************** + * Init + **************************************************/ + +module_platform_driver(bcma_sflash_driver); diff --git a/drivers/mtd/devices/bcm47xxsflash.h b/drivers/mtd/devices/bcm47xxsflash.h new file mode 100644 index 00000000000..fe93daf4f48 --- /dev/null +++ b/drivers/mtd/devices/bcm47xxsflash.h @@ -0,0 +1,76 @@ +#ifndef __BCM47XXSFLASH_H +#define __BCM47XXSFLASH_H + +#include <linux/mtd/mtd.h> + +/* Used for ST flashes only. */ +#define OPCODE_ST_WREN 0x0006 /* Write Enable */ +#define OPCODE_ST_WRDIS 0x0004 /* Write Disable */ +#define OPCODE_ST_RDSR 0x0105 /* Read Status Register */ +#define OPCODE_ST_WRSR 0x0101 /* Write Status Register */ +#define OPCODE_ST_READ 0x0303 /* Read Data Bytes */ +#define OPCODE_ST_PP 0x0302 /* Page Program */ +#define OPCODE_ST_SE 0x02d8 /* Sector Erase */ +#define OPCODE_ST_BE 0x00c7 /* Bulk Erase */ +#define OPCODE_ST_DP 0x00b9 /* Deep Power-down */ +#define OPCODE_ST_RES 0x03ab /* Read Electronic Signature */ +#define OPCODE_ST_CSA 0x1000 /* Keep chip select asserted */ +#define OPCODE_ST_SSE 0x0220 /* Sub-sector Erase */ + +/* Used for Atmel flashes only. */ +#define OPCODE_AT_READ 0x07e8 +#define OPCODE_AT_PAGE_READ 0x07d2 +#define OPCODE_AT_STATUS 0x01d7 +#define OPCODE_AT_BUF1_WRITE 0x0384 +#define OPCODE_AT_BUF2_WRITE 0x0387 +#define OPCODE_AT_BUF1_ERASE_PROGRAM 0x0283 +#define OPCODE_AT_BUF2_ERASE_PROGRAM 0x0286 +#define OPCODE_AT_BUF1_PROGRAM 0x0288 +#define OPCODE_AT_BUF2_PROGRAM 0x0289 +#define OPCODE_AT_PAGE_ERASE 0x0281 +#define OPCODE_AT_BLOCK_ERASE 0x0250 +#define OPCODE_AT_BUF1_WRITE_ERASE_PROGRAM 0x0382 +#define OPCODE_AT_BUF2_WRITE_ERASE_PROGRAM 0x0385 +#define OPCODE_AT_BUF1_LOAD 0x0253 +#define OPCODE_AT_BUF2_LOAD 0x0255 +#define OPCODE_AT_BUF1_COMPARE 0x0260 +#define OPCODE_AT_BUF2_COMPARE 0x0261 +#define OPCODE_AT_BUF1_REPROGRAM 0x0258 +#define OPCODE_AT_BUF2_REPROGRAM 0x0259 + +/* Status register bits for ST flashes */ +#define SR_ST_WIP 0x01 /* Write In Progress */ +#define SR_ST_WEL 0x02 /* Write Enable Latch */ +#define SR_ST_BP_MASK 0x1c /* Block Protect */ +#define SR_ST_BP_SHIFT 2 +#define SR_ST_SRWD 0x80 /* Status Register Write Disable */ + +/* Status register bits for Atmel flashes */ +#define SR_AT_READY 0x80 +#define SR_AT_MISMATCH 0x40 +#define SR_AT_ID_MASK 0x38 +#define SR_AT_ID_SHIFT 3 + +struct bcma_drv_cc; + +enum bcm47xxsflash_type { + BCM47XXSFLASH_TYPE_ATMEL, + BCM47XXSFLASH_TYPE_ST, +}; + +struct bcm47xxsflash { + struct bcma_drv_cc *bcma_cc; + int (*cc_read)(struct bcm47xxsflash *b47s, u16 offset); + void (*cc_write)(struct bcm47xxsflash *b47s, u16 offset, u32 value); + + enum bcm47xxsflash_type type; + + u32 window; + u32 blocksize; + u16 numblocks; + u32 size; + + struct mtd_info mtd; +}; + +#endif /* BCM47XXSFLASH */ diff --git a/drivers/mtd/devices/block2mtd.c b/drivers/mtd/devices/block2mtd.c index 401c6a294ba..66f0405f7e5 100644 --- a/drivers/mtd/devices/block2mtd.c +++ b/drivers/mtd/devices/block2mtd.c @@ -1,13 +1,14 @@ /* - * $Id: block2mtd.c,v 1.30 2005/11/29 14:48:32 gleixner Exp $ - * * block2mtd.c - create an mtd from a block device * * Copyright (C) 2001,2002 Simon Evans <spse@secret.org.uk> - * Copyright (C) 2004-2006 Jörn Engel <joern@wh.fh-wedel.de> + * Copyright (C) 2004-2006 Joern Engel <joern@wh.fh-wedel.de> * * Licence: GPL */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/fs.h> #include <linux/blkdev.h> @@ -16,16 +17,10 @@ #include <linux/list.h> #include <linux/init.h> #include <linux/mtd/mtd.h> -#include <linux/buffer_head.h> #include <linux/mutex.h> #include <linux/mount.h> - -#define VERSION "$Revision: 1.30 $" - - -#define ERROR(fmt, args...) printk(KERN_ERR "block2mtd: " fmt "\n" , ## args) -#define INFO(fmt, args...) printk(KERN_INFO "block2mtd: " fmt "\n" , ## args) - +#include <linux/slab.h> +#include <linux/major.h> /* Info for the block device */ struct block2mtd_dev { @@ -40,60 +35,11 @@ struct block2mtd_dev { static LIST_HEAD(blkmtd_device_list); -#define PAGE_READAHEAD 64 -static void cache_readahead(struct address_space *mapping, int index) -{ - filler_t *filler = (filler_t*)mapping->a_ops->readpage; - int i, pagei; - unsigned ret = 0; - unsigned long end_index; - struct page *page; - LIST_HEAD(page_pool); - struct inode *inode = mapping->host; - loff_t isize = i_size_read(inode); - - if (!isize) { - INFO("iSize=0 in cache_readahead\n"); - return; - } - - end_index = ((isize - 1) >> PAGE_CACHE_SHIFT); - - read_lock_irq(&mapping->tree_lock); - for (i = 0; i < PAGE_READAHEAD; i++) { - pagei = index + i; - if (pagei > end_index) { - INFO("Overrun end of disk in cache readahead\n"); - break; - } - page = radix_tree_lookup(&mapping->page_tree, pagei); - if (page && (!i)) - break; - if (page) - continue; - read_unlock_irq(&mapping->tree_lock); - page = page_cache_alloc_cold(mapping); - read_lock_irq(&mapping->tree_lock); - if (!page) - break; - page->index = pagei; - list_add(&page->lru, &page_pool); - ret++; - } - read_unlock_irq(&mapping->tree_lock); - if (ret) - read_cache_pages(mapping, &page_pool, filler, NULL); -} - - -static struct page* page_readahead(struct address_space *mapping, int index) +static struct page *page_read(struct address_space *mapping, int index) { - filler_t *filler = (filler_t*)mapping->a_ops->readpage; - cache_readahead(mapping, index); - return read_cache_page(mapping, index, filler, NULL); + return read_mapping_page(mapping, index, NULL); } - /* erase a specified part of the device */ static int _block2mtd_erase(struct block2mtd_dev *dev, loff_t to, size_t len) { @@ -105,19 +51,18 @@ static int _block2mtd_erase(struct block2mtd_dev *dev, loff_t to, size_t len) u_long *max; while (pages) { - page = page_readahead(mapping, index); - if (!page) - return -ENOMEM; + page = page_read(mapping, index); if (IS_ERR(page)) return PTR_ERR(page); - max = (u_long*)page_address(page) + PAGE_SIZE; - for (p=(u_long*)page_address(page); p<max; p++) + max = page_address(page) + PAGE_SIZE; + for (p=page_address(page); p<max; p++) if (*p != -1UL) { lock_page(page); memset(page_address(page), 0xff, PAGE_SIZE); set_page_dirty(page); unlock_page(page); + balance_dirty_pages_ratelimited(mapping); break; } @@ -139,12 +84,11 @@ static int block2mtd_erase(struct mtd_info *mtd, struct erase_info *instr) err = _block2mtd_erase(dev, from, len); mutex_unlock(&dev->write_mutex); if (err) { - ERROR("erase failed err = %d", err); + pr_err("erase failed err = %d\n", err); instr->state = MTD_ERASE_FAILED; } else instr->state = MTD_ERASE_DONE; - instr->state = MTD_ERASE_DONE; mtd_erase_callback(instr); return err; } @@ -159,14 +103,6 @@ static int block2mtd_read(struct mtd_info *mtd, loff_t from, size_t len, int offset = from & (PAGE_SIZE-1); int cpylen; - if (from > mtd->size) - return -EINVAL; - if (from + len > mtd->size) - len = mtd->size - from; - - if (retlen) - *retlen = 0; - while (len) { if ((offset + len) > PAGE_SIZE) cpylen = PAGE_SIZE - offset; // multiple pages @@ -174,10 +110,7 @@ static int block2mtd_read(struct mtd_info *mtd, loff_t from, size_t len, cpylen = len; // this page len = len - cpylen; - // Get page - page = page_readahead(dev->blkdev->bd_inode->i_mapping, index); - if (!page) - return -ENOMEM; + page = page_read(dev->blkdev->bd_inode->i_mapping, index); if (IS_ERR(page)) return PTR_ERR(page); @@ -204,8 +137,6 @@ static int _block2mtd_write(struct block2mtd_dev *dev, const u_char *buf, int offset = to & ~PAGE_MASK; // page offset int cpylen; - if (retlen) - *retlen = 0; while (len) { if ((offset+len) > PAGE_SIZE) cpylen = PAGE_SIZE - offset; // multiple pages @@ -213,10 +144,7 @@ static int _block2mtd_write(struct block2mtd_dev *dev, const u_char *buf, cpylen = len; // this page len = len - cpylen; - // Get page - page = page_readahead(mapping, index); - if (!page) - return -ENOMEM; + page = page_read(mapping, index); if (IS_ERR(page)) return PTR_ERR(page); @@ -225,6 +153,7 @@ static int _block2mtd_write(struct block2mtd_dev *dev, const u_char *buf, memcpy(page_address(page) + offset, buf, cpylen); set_page_dirty(page); unlock_page(page); + balance_dirty_pages_ratelimited(mapping); } page_cache_release(page); @@ -245,13 +174,6 @@ static int block2mtd_write(struct mtd_info *mtd, loff_t to, size_t len, struct block2mtd_dev *dev = mtd->priv; int err; - if (!len) - return 0; - if (to >= mtd->size) - return -ENOSPC; - if (to + len > mtd->size) - len = mtd->size - to; - mutex_lock(&dev->write_mutex); err = _block2mtd_write(dev, buf, to, len, retlen); mutex_unlock(&dev->write_mutex); @@ -278,89 +200,96 @@ static void block2mtd_free_device(struct block2mtd_dev *dev) kfree(dev->mtd.name); if (dev->blkdev) { - invalidate_inode_pages(dev->blkdev->bd_inode->i_mapping); - close_bdev_excl(dev->blkdev); + invalidate_mapping_pages(dev->blkdev->bd_inode->i_mapping, + 0, -1); + blkdev_put(dev->blkdev, FMODE_READ|FMODE_WRITE|FMODE_EXCL); } kfree(dev); } -/* FIXME: ensure that mtd->size % erase_size == 0 */ static struct block2mtd_dev *add_device(char *devname, int erase_size) { + const fmode_t mode = FMODE_READ | FMODE_WRITE | FMODE_EXCL; struct block_device *bdev; struct block2mtd_dev *dev; + char *name; if (!devname) return NULL; - dev = kmalloc(sizeof(struct block2mtd_dev), GFP_KERNEL); + dev = kzalloc(sizeof(struct block2mtd_dev), GFP_KERNEL); if (!dev) return NULL; - memset(dev, 0, sizeof(*dev)); /* Get a handle on the device */ - bdev = open_bdev_excl(devname, O_RDWR, NULL); + bdev = blkdev_get_by_path(devname, mode, dev); #ifndef MODULE if (IS_ERR(bdev)) { /* We might not have rootfs mounted at this point. Try to resolve the device name by other means. */ - dev_t dev = name_to_dev_t(devname); - if (dev != 0) { - bdev = open_by_devnum(dev, FMODE_WRITE | FMODE_READ); - } + dev_t devt = name_to_dev_t(devname); + if (devt) + bdev = blkdev_get_by_dev(devt, mode, dev); } #endif if (IS_ERR(bdev)) { - ERROR("error: cannot open device %s", devname); - goto devinit_err; + pr_err("error: cannot open device %s\n", devname); + goto err_free_block2mtd; } dev->blkdev = bdev; if (MAJOR(bdev->bd_dev) == MTD_BLOCK_MAJOR) { - ERROR("attempting to use an MTD device as a block device"); - goto devinit_err; + pr_err("attempting to use an MTD device as a block device\n"); + goto err_free_block2mtd; + } + + if ((long)dev->blkdev->bd_inode->i_size % erase_size) { + pr_err("erasesize must be a divisor of device size\n"); + goto err_free_block2mtd; } mutex_init(&dev->write_mutex); /* Setup the MTD structure */ /* make the name contain the block device in */ - dev->mtd.name = kmalloc(sizeof("block2mtd: ") + strlen(devname), - GFP_KERNEL); - if (!dev->mtd.name) - goto devinit_err; + name = kasprintf(GFP_KERNEL, "block2mtd: %s", devname); + if (!name) + goto err_destroy_mutex; - sprintf(dev->mtd.name, "block2mtd: %s", devname); + dev->mtd.name = name; dev->mtd.size = dev->blkdev->bd_inode->i_size & PAGE_MASK; dev->mtd.erasesize = erase_size; dev->mtd.writesize = 1; + dev->mtd.writebufsize = PAGE_SIZE; dev->mtd.type = MTD_RAM; dev->mtd.flags = MTD_CAP_RAM; - dev->mtd.erase = block2mtd_erase; - dev->mtd.write = block2mtd_write; - dev->mtd.writev = default_mtd_writev; - dev->mtd.sync = block2mtd_sync; - dev->mtd.read = block2mtd_read; + dev->mtd._erase = block2mtd_erase; + dev->mtd._write = block2mtd_write; + dev->mtd._sync = block2mtd_sync; + dev->mtd._read = block2mtd_read; dev->mtd.priv = dev; dev->mtd.owner = THIS_MODULE; - if (add_mtd_device(&dev->mtd)) { - /* Device didnt get added, so free the entry */ - goto devinit_err; + if (mtd_device_register(&dev->mtd, NULL, 0)) { + /* Device didn't get added, so free the entry */ + goto err_destroy_mutex; } list_add(&dev->list, &blkmtd_device_list); - INFO("mtd%d: [%s] erase_size = %dKiB [%d]", dev->mtd.index, - dev->mtd.name + strlen("blkmtd: "), - dev->mtd.erasesize >> 10, dev->mtd.erasesize); + pr_info("mtd%d: [%s] erase_size = %dKiB [%d]\n", + dev->mtd.index, + dev->mtd.name + strlen("block2mtd: "), + dev->mtd.erasesize >> 10, dev->mtd.erasesize); return dev; -devinit_err: +err_destroy_mutex: + mutex_destroy(&dev->write_mutex); +err_free_block2mtd: block2mtd_free_device(dev); return NULL; } @@ -417,17 +346,11 @@ static inline void kill_final_newline(char *str) } -#define parse_err(fmt, args...) do { \ - ERROR("block2mtd: " fmt "\n", ## args); \ - return 0; \ -} while (0) - #ifndef MODULE static int block2mtd_init_called = 0; -static __initdata char block2mtd_paramline[80 + 12]; /* 80 for device, 12 for erase size */ +static char block2mtd_paramline[80 + 12]; /* 80 for device, 12 for erase size */ #endif - static int block2mtd_setup2(const char *val) { char buf[80 + 12]; /* 80 for device, 12 for erase size */ @@ -437,8 +360,10 @@ static int block2mtd_setup2(const char *val) size_t erase_size = PAGE_SIZE; int i, ret; - if (strnlen(val, sizeof(buf)) >= sizeof(buf)) - parse_err("parameter too long"); + if (strnlen(val, sizeof(buf)) >= sizeof(buf)) { + pr_err("parameter too long\n"); + return 0; + } strcpy(str, val); kill_final_newline(str); @@ -446,21 +371,27 @@ static int block2mtd_setup2(const char *val) for (i = 0; i < 2; i++) token[i] = strsep(&str, ","); - if (str) - parse_err("too many arguments"); + if (str) { + pr_err("too many arguments\n"); + return 0; + } - if (!token[0]) - parse_err("no argument"); + if (!token[0]) { + pr_err("no argument\n"); + return 0; + } name = token[0]; - if (strlen(name) + 1 > 80) - parse_err("device name too long"); + if (strlen(name) + 1 > 80) { + pr_err("device name too long\n"); + return 0; + } if (token[1]) { ret = parse_num(&erase_size, token[1]); if (ret) { - kfree(name); - parse_err("illegal erase size"); + pr_err("illegal erase size\n"); + return 0; } } @@ -503,7 +434,6 @@ MODULE_PARM_DESC(block2mtd, "Device to use. \"block2mtd=<dev>[,<erasesize>]\""); static int __init block2mtd_init(void) { int ret = 0; - INFO("version " VERSION); #ifndef MODULE if (strlen(block2mtd_paramline)) @@ -515,7 +445,7 @@ static int __init block2mtd_init(void) } -static void __devexit block2mtd_exit(void) +static void block2mtd_exit(void) { struct list_head *pos, *next; @@ -523,9 +453,11 @@ static void __devexit block2mtd_exit(void) list_for_each_safe(pos, next, &blkmtd_device_list) { struct block2mtd_dev *dev = list_entry(pos, typeof(*dev), list); block2mtd_sync(&dev->mtd); - del_mtd_device(&dev->mtd); - INFO("mtd%d: [%s] removed", dev->mtd.index, - dev->mtd.name + strlen("blkmtd: ")); + mtd_device_unregister(&dev->mtd); + mutex_destroy(&dev->write_mutex); + pr_info("mtd%d: [%s] removed\n", + dev->mtd.index, + dev->mtd.name + strlen("block2mtd: ")); list_del(&dev->list); block2mtd_free_device(dev); } @@ -536,5 +468,5 @@ module_init(block2mtd_init); module_exit(block2mtd_exit); MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Simon Evans <spse@secret.org.uk> and others"); +MODULE_AUTHOR("Joern Engel <joern@lazybastard.org>"); MODULE_DESCRIPTION("Emulate an MTD using a block device"); diff --git a/drivers/mtd/devices/doc2000.c b/drivers/mtd/devices/doc2000.c deleted file mode 100644 index 603a7951ac9..00000000000 --- a/drivers/mtd/devices/doc2000.c +++ /dev/null @@ -1,1206 +0,0 @@ - -/* - * Linux driver for Disk-On-Chip 2000 and Millennium - * (c) 1999 Machine Vision Holdings, Inc. - * (c) 1999, 2000 David Woodhouse <dwmw2@infradead.org> - * - * $Id: doc2000.c,v 1.67 2005/11/07 11:14:24 gleixner Exp $ - */ - -#include <linux/kernel.h> -#include <linux/module.h> -#include <asm/errno.h> -#include <asm/io.h> -#include <asm/uaccess.h> -#include <linux/miscdevice.h> -#include <linux/pci.h> -#include <linux/delay.h> -#include <linux/slab.h> -#include <linux/sched.h> -#include <linux/init.h> -#include <linux/types.h> -#include <linux/bitops.h> -#include <linux/mutex.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/nand.h> -#include <linux/mtd/doc2000.h> - -#define DOC_SUPPORT_2000 -#define DOC_SUPPORT_2000TSOP -#define DOC_SUPPORT_MILLENNIUM - -#ifdef DOC_SUPPORT_2000 -#define DoC_is_2000(doc) (doc->ChipID == DOC_ChipID_Doc2k) -#else -#define DoC_is_2000(doc) (0) -#endif - -#if defined(DOC_SUPPORT_2000TSOP) || defined(DOC_SUPPORT_MILLENNIUM) -#define DoC_is_Millennium(doc) (doc->ChipID == DOC_ChipID_DocMil) -#else -#define DoC_is_Millennium(doc) (0) -#endif - -/* #define ECC_DEBUG */ - -/* I have no idea why some DoC chips can not use memcpy_from|to_io(). - * This may be due to the different revisions of the ASIC controller built-in or - * simplily a QA/Bug issue. Who knows ?? If you have trouble, please uncomment - * this: - #undef USE_MEMCPY -*/ - -static int doc_read(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf); -static int doc_write(struct mtd_info *mtd, loff_t to, size_t len, - size_t *retlen, const u_char *buf); -static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, - struct mtd_oob_ops *ops); -static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, - struct mtd_oob_ops *ops); -static int doc_write_oob_nolock(struct mtd_info *mtd, loff_t ofs, size_t len, - size_t *retlen, const u_char *buf); -static int doc_erase (struct mtd_info *mtd, struct erase_info *instr); - -static struct mtd_info *doc2klist = NULL; - -/* Perform the required delay cycles by reading from the appropriate register */ -static void DoC_Delay(struct DiskOnChip *doc, unsigned short cycles) -{ - volatile char dummy; - int i; - - for (i = 0; i < cycles; i++) { - if (DoC_is_Millennium(doc)) - dummy = ReadDOC(doc->virtadr, NOP); - else - dummy = ReadDOC(doc->virtadr, DOCStatus); - } - -} - -/* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */ -static int _DoC_WaitReady(struct DiskOnChip *doc) -{ - void __iomem *docptr = doc->virtadr; - unsigned long timeo = jiffies + (HZ * 10); - - DEBUG(MTD_DEBUG_LEVEL3, - "_DoC_WaitReady called for out-of-line wait\n"); - - /* Out-of-line routine to wait for chip response */ - while (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B)) { - /* issue 2 read from NOP register after reading from CDSNControl register - see Software Requirement 11.4 item 2. */ - DoC_Delay(doc, 2); - - if (time_after(jiffies, timeo)) { - DEBUG(MTD_DEBUG_LEVEL2, "_DoC_WaitReady timed out.\n"); - return -EIO; - } - udelay(1); - cond_resched(); - } - - return 0; -} - -static inline int DoC_WaitReady(struct DiskOnChip *doc) -{ - void __iomem *docptr = doc->virtadr; - - /* This is inline, to optimise the common case, where it's ready instantly */ - int ret = 0; - - /* 4 read form NOP register should be issued in prior to the read from CDSNControl - see Software Requirement 11.4 item 2. */ - DoC_Delay(doc, 4); - - if (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B)) - /* Call the out-of-line routine to wait */ - ret = _DoC_WaitReady(doc); - - /* issue 2 read from NOP register after reading from CDSNControl register - see Software Requirement 11.4 item 2. */ - DoC_Delay(doc, 2); - - return ret; -} - -/* DoC_Command: Send a flash command to the flash chip through the CDSN Slow IO register to - bypass the internal pipeline. Each of 4 delay cycles (read from the NOP register) is - required after writing to CDSN Control register, see Software Requirement 11.4 item 3. */ - -static int DoC_Command(struct DiskOnChip *doc, unsigned char command, - unsigned char xtraflags) -{ - void __iomem *docptr = doc->virtadr; - - if (DoC_is_2000(doc)) - xtraflags |= CDSN_CTRL_FLASH_IO; - - /* Assert the CLE (Command Latch Enable) line to the flash chip */ - WriteDOC(xtraflags | CDSN_CTRL_CLE | CDSN_CTRL_CE, docptr, CDSNControl); - DoC_Delay(doc, 4); /* Software requirement 11.4.3 for Millennium */ - - if (DoC_is_Millennium(doc)) - WriteDOC(command, docptr, CDSNSlowIO); - - /* Send the command */ - WriteDOC_(command, docptr, doc->ioreg); - if (DoC_is_Millennium(doc)) - WriteDOC(command, docptr, WritePipeTerm); - - /* Lower the CLE line */ - WriteDOC(xtraflags | CDSN_CTRL_CE, docptr, CDSNControl); - DoC_Delay(doc, 4); /* Software requirement 11.4.3 for Millennium */ - - /* Wait for the chip to respond - Software requirement 11.4.1 (extended for any command) */ - return DoC_WaitReady(doc); -} - -/* DoC_Address: Set the current address for the flash chip through the CDSN Slow IO register to - bypass the internal pipeline. Each of 4 delay cycles (read from the NOP register) is - required after writing to CDSN Control register, see Software Requirement 11.4 item 3. */ - -static int DoC_Address(struct DiskOnChip *doc, int numbytes, unsigned long ofs, - unsigned char xtraflags1, unsigned char xtraflags2) -{ - int i; - void __iomem *docptr = doc->virtadr; - - if (DoC_is_2000(doc)) - xtraflags1 |= CDSN_CTRL_FLASH_IO; - - /* Assert the ALE (Address Latch Enable) line to the flash chip */ - WriteDOC(xtraflags1 | CDSN_CTRL_ALE | CDSN_CTRL_CE, docptr, CDSNControl); - - DoC_Delay(doc, 4); /* Software requirement 11.4.3 for Millennium */ - - /* Send the address */ - /* Devices with 256-byte page are addressed as: - Column (bits 0-7), Page (bits 8-15, 16-23, 24-31) - * there is no device on the market with page256 - and more than 24 bits. - Devices with 512-byte page are addressed as: - Column (bits 0-7), Page (bits 9-16, 17-24, 25-31) - * 25-31 is sent only if the chip support it. - * bit 8 changes the read command to be sent - (NAND_CMD_READ0 or NAND_CMD_READ1). - */ - - if (numbytes == ADDR_COLUMN || numbytes == ADDR_COLUMN_PAGE) { - if (DoC_is_Millennium(doc)) - WriteDOC(ofs & 0xff, docptr, CDSNSlowIO); - WriteDOC_(ofs & 0xff, docptr, doc->ioreg); - } - - if (doc->page256) { - ofs = ofs >> 8; - } else { - ofs = ofs >> 9; - } - - if (numbytes == ADDR_PAGE || numbytes == ADDR_COLUMN_PAGE) { - for (i = 0; i < doc->pageadrlen; i++, ofs = ofs >> 8) { - if (DoC_is_Millennium(doc)) - WriteDOC(ofs & 0xff, docptr, CDSNSlowIO); - WriteDOC_(ofs & 0xff, docptr, doc->ioreg); - } - } - - if (DoC_is_Millennium(doc)) - WriteDOC(ofs & 0xff, docptr, WritePipeTerm); - - DoC_Delay(doc, 2); /* Needed for some slow flash chips. mf. */ - - /* FIXME: The SlowIO's for millennium could be replaced by - a single WritePipeTerm here. mf. */ - - /* Lower the ALE line */ - WriteDOC(xtraflags1 | xtraflags2 | CDSN_CTRL_CE, docptr, - CDSNControl); - - DoC_Delay(doc, 4); /* Software requirement 11.4.3 for Millennium */ - - /* Wait for the chip to respond - Software requirement 11.4.1 */ - return DoC_WaitReady(doc); -} - -/* Read a buffer from DoC, taking care of Millennium odditys */ -static void DoC_ReadBuf(struct DiskOnChip *doc, u_char * buf, int len) -{ - volatile int dummy; - int modulus = 0xffff; - void __iomem *docptr = doc->virtadr; - int i; - - if (len <= 0) - return; - - if (DoC_is_Millennium(doc)) { - /* Read the data via the internal pipeline through CDSN IO register, - see Pipelined Read Operations 11.3 */ - dummy = ReadDOC(docptr, ReadPipeInit); - - /* Millennium should use the LastDataRead register - Pipeline Reads */ - len--; - - /* This is needed for correctly ECC calculation */ - modulus = 0xff; - } - - for (i = 0; i < len; i++) - buf[i] = ReadDOC_(docptr, doc->ioreg + (i & modulus)); - - if (DoC_is_Millennium(doc)) { - buf[i] = ReadDOC(docptr, LastDataRead); - } -} - -/* Write a buffer to DoC, taking care of Millennium odditys */ -static void DoC_WriteBuf(struct DiskOnChip *doc, const u_char * buf, int len) -{ - void __iomem *docptr = doc->virtadr; - int i; - - if (len <= 0) - return; - - for (i = 0; i < len; i++) - WriteDOC_(buf[i], docptr, doc->ioreg + i); - - if (DoC_is_Millennium(doc)) { - WriteDOC(0x00, docptr, WritePipeTerm); - } -} - - -/* DoC_SelectChip: Select a given flash chip within the current floor */ - -static inline int DoC_SelectChip(struct DiskOnChip *doc, int chip) -{ - void __iomem *docptr = doc->virtadr; - - /* Software requirement 11.4.4 before writing DeviceSelect */ - /* Deassert the CE line to eliminate glitches on the FCE# outputs */ - WriteDOC(CDSN_CTRL_WP, docptr, CDSNControl); - DoC_Delay(doc, 4); /* Software requirement 11.4.3 for Millennium */ - - /* Select the individual flash chip requested */ - WriteDOC(chip, docptr, CDSNDeviceSelect); - DoC_Delay(doc, 4); - - /* Reassert the CE line */ - WriteDOC(CDSN_CTRL_CE | CDSN_CTRL_FLASH_IO | CDSN_CTRL_WP, docptr, - CDSNControl); - DoC_Delay(doc, 4); /* Software requirement 11.4.3 for Millennium */ - - /* Wait for it to be ready */ - return DoC_WaitReady(doc); -} - -/* DoC_SelectFloor: Select a given floor (bank of flash chips) */ - -static inline int DoC_SelectFloor(struct DiskOnChip *doc, int floor) -{ - void __iomem *docptr = doc->virtadr; - - /* Select the floor (bank) of chips required */ - WriteDOC(floor, docptr, FloorSelect); - - /* Wait for the chip to be ready */ - return DoC_WaitReady(doc); -} - -/* DoC_IdentChip: Identify a given NAND chip given {floor,chip} */ - -static int DoC_IdentChip(struct DiskOnChip *doc, int floor, int chip) -{ - int mfr, id, i, j; - volatile char dummy; - - /* Page in the required floor/chip */ - DoC_SelectFloor(doc, floor); - DoC_SelectChip(doc, chip); - - /* Reset the chip */ - if (DoC_Command(doc, NAND_CMD_RESET, CDSN_CTRL_WP)) { - DEBUG(MTD_DEBUG_LEVEL2, - "DoC_Command (reset) for %d,%d returned true\n", - floor, chip); - return 0; - } - - - /* Read the NAND chip ID: 1. Send ReadID command */ - if (DoC_Command(doc, NAND_CMD_READID, CDSN_CTRL_WP)) { - DEBUG(MTD_DEBUG_LEVEL2, - "DoC_Command (ReadID) for %d,%d returned true\n", - floor, chip); - return 0; - } - - /* Read the NAND chip ID: 2. Send address byte zero */ - DoC_Address(doc, ADDR_COLUMN, 0, CDSN_CTRL_WP, 0); - - /* Read the manufacturer and device id codes from the device */ - - if (DoC_is_Millennium(doc)) { - DoC_Delay(doc, 2); - dummy = ReadDOC(doc->virtadr, ReadPipeInit); - mfr = ReadDOC(doc->virtadr, LastDataRead); - - DoC_Delay(doc, 2); - dummy = ReadDOC(doc->virtadr, ReadPipeInit); - id = ReadDOC(doc->virtadr, LastDataRead); - } else { - /* CDSN Slow IO register see Software Req 11.4 item 5. */ - dummy = ReadDOC(doc->virtadr, CDSNSlowIO); - DoC_Delay(doc, 2); - mfr = ReadDOC_(doc->virtadr, doc->ioreg); - - /* CDSN Slow IO register see Software Req 11.4 item 5. */ - dummy = ReadDOC(doc->virtadr, CDSNSlowIO); - DoC_Delay(doc, 2); - id = ReadDOC_(doc->virtadr, doc->ioreg); - } - - /* No response - return failure */ - if (mfr == 0xff || mfr == 0) - return 0; - - /* Check it's the same as the first chip we identified. - * M-Systems say that any given DiskOnChip device should only - * contain _one_ type of flash part, although that's not a - * hardware restriction. */ - if (doc->mfr) { - if (doc->mfr == mfr && doc->id == id) - return 1; /* This is another the same the first */ - else - printk(KERN_WARNING - "Flash chip at floor %d, chip %d is different:\n", - floor, chip); - } - - /* Print and store the manufacturer and ID codes. */ - for (i = 0; nand_flash_ids[i].name != NULL; i++) { - if (id == nand_flash_ids[i].id) { - /* Try to identify manufacturer */ - for (j = 0; nand_manuf_ids[j].id != 0x0; j++) { - if (nand_manuf_ids[j].id == mfr) - break; - } - printk(KERN_INFO - "Flash chip found: Manufacturer ID: %2.2X, " - "Chip ID: %2.2X (%s:%s)\n", mfr, id, - nand_manuf_ids[j].name, nand_flash_ids[i].name); - if (!doc->mfr) { - doc->mfr = mfr; - doc->id = id; - doc->chipshift = - ffs((nand_flash_ids[i].chipsize << 20)) - 1; - doc->page256 = (nand_flash_ids[i].pagesize == 256) ? 1 : 0; - doc->pageadrlen = doc->chipshift > 25 ? 3 : 2; - doc->erasesize = - nand_flash_ids[i].erasesize; - return 1; - } - return 0; - } - } - - - /* We haven't fully identified the chip. Print as much as we know. */ - printk(KERN_WARNING "Unknown flash chip found: %2.2X %2.2X\n", - id, mfr); - - printk(KERN_WARNING "Please report to dwmw2@infradead.org\n"); - return 0; -} - -/* DoC_ScanChips: Find all NAND chips present in a DiskOnChip, and identify them */ - -static void DoC_ScanChips(struct DiskOnChip *this, int maxchips) -{ - int floor, chip; - int numchips[MAX_FLOORS]; - int ret = 1; - - this->numchips = 0; - this->mfr = 0; - this->id = 0; - - /* For each floor, find the number of valid chips it contains */ - for (floor = 0; floor < MAX_FLOORS; floor++) { - ret = 1; - numchips[floor] = 0; - for (chip = 0; chip < maxchips && ret != 0; chip++) { - - ret = DoC_IdentChip(this, floor, chip); - if (ret) { - numchips[floor]++; - this->numchips++; - } - } - } - - /* If there are none at all that we recognise, bail */ - if (!this->numchips) { - printk(KERN_NOTICE "No flash chips recognised.\n"); - return; - } - - /* Allocate an array to hold the information for each chip */ - this->chips = kmalloc(sizeof(struct Nand) * this->numchips, GFP_KERNEL); - if (!this->chips) { - printk(KERN_NOTICE "No memory for allocating chip info structures\n"); - return; - } - - ret = 0; - - /* Fill out the chip array with {floor, chipno} for each - * detected chip in the device. */ - for (floor = 0; floor < MAX_FLOORS; floor++) { - for (chip = 0; chip < numchips[floor]; chip++) { - this->chips[ret].floor = floor; - this->chips[ret].chip = chip; - this->chips[ret].curadr = 0; - this->chips[ret].curmode = 0x50; - ret++; - } - } - - /* Calculate and print the total size of the device */ - this->totlen = this->numchips * (1 << this->chipshift); - - printk(KERN_INFO "%d flash chips found. Total DiskOnChip size: %ld MiB\n", - this->numchips, this->totlen >> 20); -} - -static int DoC2k_is_alias(struct DiskOnChip *doc1, struct DiskOnChip *doc2) -{ - int tmp1, tmp2, retval; - if (doc1->physadr == doc2->physadr) - return 1; - - /* Use the alias resolution register which was set aside for this - * purpose. If it's value is the same on both chips, they might - * be the same chip, and we write to one and check for a change in - * the other. It's unclear if this register is usuable in the - * DoC 2000 (it's in the Millennium docs), but it seems to work. */ - tmp1 = ReadDOC(doc1->virtadr, AliasResolution); - tmp2 = ReadDOC(doc2->virtadr, AliasResolution); - if (tmp1 != tmp2) - return 0; - - WriteDOC((tmp1 + 1) % 0xff, doc1->virtadr, AliasResolution); - tmp2 = ReadDOC(doc2->virtadr, AliasResolution); - if (tmp2 == (tmp1 + 1) % 0xff) - retval = 1; - else - retval = 0; - - /* Restore register contents. May not be necessary, but do it just to - * be safe. */ - WriteDOC(tmp1, doc1->virtadr, AliasResolution); - - return retval; -} - -/* This routine is found from the docprobe code by symbol_get(), - * which will bump the use count of this module. */ -void DoC2k_init(struct mtd_info *mtd) -{ - struct DiskOnChip *this = mtd->priv; - struct DiskOnChip *old = NULL; - int maxchips; - - /* We must avoid being called twice for the same device. */ - - if (doc2klist) - old = doc2klist->priv; - - while (old) { - if (DoC2k_is_alias(old, this)) { - printk(KERN_NOTICE - "Ignoring DiskOnChip 2000 at 0x%lX - already configured\n", - this->physadr); - iounmap(this->virtadr); - kfree(mtd); - return; - } - if (old->nextdoc) - old = old->nextdoc->priv; - else - old = NULL; - } - - - switch (this->ChipID) { - case DOC_ChipID_Doc2kTSOP: - mtd->name = "DiskOnChip 2000 TSOP"; - this->ioreg = DoC_Mil_CDSN_IO; - /* Pretend it's a Millennium */ - this->ChipID = DOC_ChipID_DocMil; - maxchips = MAX_CHIPS; - break; - case DOC_ChipID_Doc2k: - mtd->name = "DiskOnChip 2000"; - this->ioreg = DoC_2k_CDSN_IO; - maxchips = MAX_CHIPS; - break; - case DOC_ChipID_DocMil: - mtd->name = "DiskOnChip Millennium"; - this->ioreg = DoC_Mil_CDSN_IO; - maxchips = MAX_CHIPS_MIL; - break; - default: - printk("Unknown ChipID 0x%02x\n", this->ChipID); - kfree(mtd); - iounmap(this->virtadr); - return; - } - - printk(KERN_NOTICE "%s found at address 0x%lX\n", mtd->name, - this->physadr); - - mtd->type = MTD_NANDFLASH; - mtd->flags = MTD_CAP_NANDFLASH; - mtd->ecctype = MTD_ECC_RS_DiskOnChip; - mtd->size = 0; - mtd->erasesize = 0; - mtd->writesize = 512; - mtd->oobsize = 16; - mtd->owner = THIS_MODULE; - mtd->erase = doc_erase; - mtd->point = NULL; - mtd->unpoint = NULL; - mtd->read = doc_read; - mtd->write = doc_write; - mtd->read_oob = doc_read_oob; - mtd->write_oob = doc_write_oob; - mtd->sync = NULL; - - this->totlen = 0; - this->numchips = 0; - - this->curfloor = -1; - this->curchip = -1; - mutex_init(&this->lock); - - /* Ident all the chips present. */ - DoC_ScanChips(this, maxchips); - - if (!this->totlen) { - kfree(mtd); - iounmap(this->virtadr); - } else { - this->nextdoc = doc2klist; - doc2klist = mtd; - mtd->size = this->totlen; - mtd->erasesize = this->erasesize; - add_mtd_device(mtd); - return; - } -} -EXPORT_SYMBOL_GPL(DoC2k_init); - -static int doc_read(struct mtd_info *mtd, loff_t from, size_t len, - size_t * retlen, u_char * buf) -{ - struct DiskOnChip *this = mtd->priv; - void __iomem *docptr = this->virtadr; - struct Nand *mychip; - unsigned char syndrome[6], eccbuf[6]; - volatile char dummy; - int i, len256 = 0, ret=0; - size_t left = len; - - /* Don't allow read past end of device */ - if (from >= this->totlen) - return -EINVAL; - - mutex_lock(&this->lock); - - *retlen = 0; - while (left) { - len = left; - - /* Don't allow a single read to cross a 512-byte block boundary */ - if (from + len > ((from | 0x1ff) + 1)) - len = ((from | 0x1ff) + 1) - from; - - /* The ECC will not be calculated correctly if less than 512 is read */ - if (len != 0x200 && eccbuf) - printk(KERN_WARNING - "ECC needs a full sector read (adr: %lx size %lx)\n", - (long) from, (long) len); - - /* printk("DoC_Read (adr: %lx size %lx)\n", (long) from, (long) len); */ - - - /* Find the chip which is to be used and select it */ - mychip = &this->chips[from >> (this->chipshift)]; - - if (this->curfloor != mychip->floor) { - DoC_SelectFloor(this, mychip->floor); - DoC_SelectChip(this, mychip->chip); - } else if (this->curchip != mychip->chip) { - DoC_SelectChip(this, mychip->chip); - } - - this->curfloor = mychip->floor; - this->curchip = mychip->chip; - - DoC_Command(this, - (!this->page256 - && (from & 0x100)) ? NAND_CMD_READ1 : NAND_CMD_READ0, - CDSN_CTRL_WP); - DoC_Address(this, ADDR_COLUMN_PAGE, from, CDSN_CTRL_WP, - CDSN_CTRL_ECC_IO); - - /* Prime the ECC engine */ - WriteDOC(DOC_ECC_RESET, docptr, ECCConf); - WriteDOC(DOC_ECC_EN, docptr, ECCConf); - - /* treat crossing 256-byte sector for 2M x 8bits devices */ - if (this->page256 && from + len > (from | 0xff) + 1) { - len256 = (from | 0xff) + 1 - from; - DoC_ReadBuf(this, buf, len256); - - DoC_Command(this, NAND_CMD_READ0, CDSN_CTRL_WP); - DoC_Address(this, ADDR_COLUMN_PAGE, from + len256, - CDSN_CTRL_WP, CDSN_CTRL_ECC_IO); - } - - DoC_ReadBuf(this, &buf[len256], len - len256); - - /* Let the caller know we completed it */ - *retlen += len; - - /* Read the ECC data through the DiskOnChip ECC logic */ - /* Note: this will work even with 2M x 8bit devices as */ - /* they have 8 bytes of OOB per 256 page. mf. */ - DoC_ReadBuf(this, eccbuf, 6); - - /* Flush the pipeline */ - if (DoC_is_Millennium(this)) { - dummy = ReadDOC(docptr, ECCConf); - dummy = ReadDOC(docptr, ECCConf); - i = ReadDOC(docptr, ECCConf); - } else { - dummy = ReadDOC(docptr, 2k_ECCStatus); - dummy = ReadDOC(docptr, 2k_ECCStatus); - i = ReadDOC(docptr, 2k_ECCStatus); - } - - /* Check the ECC Status */ - if (i & 0x80) { - int nb_errors; - /* There was an ECC error */ -#ifdef ECC_DEBUG - printk(KERN_ERR "DiskOnChip ECC Error: Read at %lx\n", (long)from); -#endif - /* Read the ECC syndrom through the DiskOnChip ECC - logic. These syndrome will be all ZERO when there - is no error */ - for (i = 0; i < 6; i++) { - syndrome[i] = - ReadDOC(docptr, ECCSyndrome0 + i); - } - nb_errors = doc_decode_ecc(buf, syndrome); - -#ifdef ECC_DEBUG - printk(KERN_ERR "Errors corrected: %x\n", nb_errors); -#endif - if (nb_errors < 0) { - /* We return error, but have actually done the - read. Not that this can be told to - user-space, via sys_read(), but at least - MTD-aware stuff can know about it by - checking *retlen */ - ret = -EIO; - } - } - -#ifdef PSYCHO_DEBUG - printk(KERN_DEBUG "ECC DATA at %lxB: %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n", - (long)from, eccbuf[0], eccbuf[1], eccbuf[2], - eccbuf[3], eccbuf[4], eccbuf[5]); -#endif - - /* disable the ECC engine */ - WriteDOC(DOC_ECC_DIS, docptr , ECCConf); - - /* according to 11.4.1, we need to wait for the busy line - * drop if we read to the end of the page. */ - if(0 == ((from + len) & 0x1ff)) - { - DoC_WaitReady(this); - } - - from += len; - left -= len; - buf += len; - } - - mutex_unlock(&this->lock); - - return ret; -} - -static int doc_write(struct mtd_info *mtd, loff_t to, size_t len, - size_t * retlen, const u_char * buf) -{ - struct DiskOnChip *this = mtd->priv; - int di; /* Yes, DI is a hangover from when I was disassembling the binary driver */ - void __iomem *docptr = this->virtadr; - unsigned char eccbuf[6]; - volatile char dummy; - int len256 = 0; - struct Nand *mychip; - size_t left = len; - int status; - - /* Don't allow write past end of device */ - if (to >= this->totlen) - return -EINVAL; - - mutex_lock(&this->lock); - - *retlen = 0; - while (left) { - len = left; - - /* Don't allow a single write to cross a 512-byte block boundary */ - if (to + len > ((to | 0x1ff) + 1)) - len = ((to | 0x1ff) + 1) - to; - - /* The ECC will not be calculated correctly if less than 512 is written */ -/* DBB- - if (len != 0x200 && eccbuf) - printk(KERN_WARNING - "ECC needs a full sector write (adr: %lx size %lx)\n", - (long) to, (long) len); - -DBB */ - - /* printk("DoC_Write (adr: %lx size %lx)\n", (long) to, (long) len); */ - - /* Find the chip which is to be used and select it */ - mychip = &this->chips[to >> (this->chipshift)]; - - if (this->curfloor != mychip->floor) { - DoC_SelectFloor(this, mychip->floor); - DoC_SelectChip(this, mychip->chip); - } else if (this->curchip != mychip->chip) { - DoC_SelectChip(this, mychip->chip); - } - - this->curfloor = mychip->floor; - this->curchip = mychip->chip; - - /* Set device to main plane of flash */ - DoC_Command(this, NAND_CMD_RESET, CDSN_CTRL_WP); - DoC_Command(this, - (!this->page256 - && (to & 0x100)) ? NAND_CMD_READ1 : NAND_CMD_READ0, - CDSN_CTRL_WP); - - DoC_Command(this, NAND_CMD_SEQIN, 0); - DoC_Address(this, ADDR_COLUMN_PAGE, to, 0, CDSN_CTRL_ECC_IO); - - /* Prime the ECC engine */ - WriteDOC(DOC_ECC_RESET, docptr, ECCConf); - WriteDOC(DOC_ECC_EN | DOC_ECC_RW, docptr, ECCConf); - - /* treat crossing 256-byte sector for 2M x 8bits devices */ - if (this->page256 && to + len > (to | 0xff) + 1) { - len256 = (to | 0xff) + 1 - to; - DoC_WriteBuf(this, buf, len256); - - DoC_Command(this, NAND_CMD_PAGEPROG, 0); - - DoC_Command(this, NAND_CMD_STATUS, CDSN_CTRL_WP); - /* There's an implicit DoC_WaitReady() in DoC_Command */ - - dummy = ReadDOC(docptr, CDSNSlowIO); - DoC_Delay(this, 2); - - if (ReadDOC_(docptr, this->ioreg) & 1) { - printk(KERN_ERR "Error programming flash\n"); - /* Error in programming */ - *retlen = 0; - mutex_unlock(&this->lock); - return -EIO; - } - - DoC_Command(this, NAND_CMD_SEQIN, 0); - DoC_Address(this, ADDR_COLUMN_PAGE, to + len256, 0, - CDSN_CTRL_ECC_IO); - } - - DoC_WriteBuf(this, &buf[len256], len - len256); - - WriteDOC(CDSN_CTRL_ECC_IO | CDSN_CTRL_CE, docptr, CDSNControl); - - if (DoC_is_Millennium(this)) { - WriteDOC(0, docptr, NOP); - WriteDOC(0, docptr, NOP); - WriteDOC(0, docptr, NOP); - } else { - WriteDOC_(0, docptr, this->ioreg); - WriteDOC_(0, docptr, this->ioreg); - WriteDOC_(0, docptr, this->ioreg); - } - - WriteDOC(CDSN_CTRL_ECC_IO | CDSN_CTRL_FLASH_IO | CDSN_CTRL_CE, docptr, - CDSNControl); - - /* Read the ECC data through the DiskOnChip ECC logic */ - for (di = 0; di < 6; di++) { - eccbuf[di] = ReadDOC(docptr, ECCSyndrome0 + di); - } - - /* Reset the ECC engine */ - WriteDOC(DOC_ECC_DIS, docptr, ECCConf); - -#ifdef PSYCHO_DEBUG - printk - ("OOB data at %lx is %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n", - (long) to, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3], - eccbuf[4], eccbuf[5]); -#endif - DoC_Command(this, NAND_CMD_PAGEPROG, 0); - - DoC_Command(this, NAND_CMD_STATUS, CDSN_CTRL_WP); - /* There's an implicit DoC_WaitReady() in DoC_Command */ - - if (DoC_is_Millennium(this)) { - ReadDOC(docptr, ReadPipeInit); - status = ReadDOC(docptr, LastDataRead); - } else { - dummy = ReadDOC(docptr, CDSNSlowIO); - DoC_Delay(this, 2); - status = ReadDOC_(docptr, this->ioreg); - } - - if (status & 1) { - printk(KERN_ERR "Error programming flash\n"); - /* Error in programming */ - *retlen = 0; - mutex_unlock(&this->lock); - return -EIO; - } - - /* Let the caller know we completed it */ - *retlen += len; - - if (eccbuf) { - unsigned char x[8]; - size_t dummy; - int ret; - - /* Write the ECC data to flash */ - for (di=0; di<6; di++) - x[di] = eccbuf[di]; - - x[6]=0x55; - x[7]=0x55; - - ret = doc_write_oob_nolock(mtd, to, 8, &dummy, x); - if (ret) { - mutex_unlock(&this->lock); - return ret; - } - } - - to += len; - left -= len; - buf += len; - } - - mutex_unlock(&this->lock); - return 0; -} - -static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, - struct mtd_oob_ops *ops) -{ - struct DiskOnChip *this = mtd->priv; - int len256 = 0, ret; - struct Nand *mychip; - uint8_t *buf = ops->oobbuf; - size_t len = ops->len; - - BUG_ON(ops->mode != MTD_OOB_PLACE); - - ofs += ops->ooboffs; - - mutex_lock(&this->lock); - - mychip = &this->chips[ofs >> this->chipshift]; - - if (this->curfloor != mychip->floor) { - DoC_SelectFloor(this, mychip->floor); - DoC_SelectChip(this, mychip->chip); - } else if (this->curchip != mychip->chip) { - DoC_SelectChip(this, mychip->chip); - } - this->curfloor = mychip->floor; - this->curchip = mychip->chip; - - /* update address for 2M x 8bit devices. OOB starts on the second */ - /* page to maintain compatibility with doc_read_ecc. */ - if (this->page256) { - if (!(ofs & 0x8)) - ofs += 0x100; - else - ofs -= 0x8; - } - - DoC_Command(this, NAND_CMD_READOOB, CDSN_CTRL_WP); - DoC_Address(this, ADDR_COLUMN_PAGE, ofs, CDSN_CTRL_WP, 0); - - /* treat crossing 8-byte OOB data for 2M x 8bit devices */ - /* Note: datasheet says it should automaticaly wrap to the */ - /* next OOB block, but it didn't work here. mf. */ - if (this->page256 && ofs + len > (ofs | 0x7) + 1) { - len256 = (ofs | 0x7) + 1 - ofs; - DoC_ReadBuf(this, buf, len256); - - DoC_Command(this, NAND_CMD_READOOB, CDSN_CTRL_WP); - DoC_Address(this, ADDR_COLUMN_PAGE, ofs & (~0x1ff), - CDSN_CTRL_WP, 0); - } - - DoC_ReadBuf(this, &buf[len256], len - len256); - - ops->retlen = len; - /* Reading the full OOB data drops us off of the end of the page, - * causing the flash device to go into busy mode, so we need - * to wait until ready 11.4.1 and Toshiba TC58256FT docs */ - - ret = DoC_WaitReady(this); - - mutex_unlock(&this->lock); - return ret; - -} - -static int doc_write_oob_nolock(struct mtd_info *mtd, loff_t ofs, size_t len, - size_t * retlen, const u_char * buf) -{ - struct DiskOnChip *this = mtd->priv; - int len256 = 0; - void __iomem *docptr = this->virtadr; - struct Nand *mychip = &this->chips[ofs >> this->chipshift]; - volatile int dummy; - int status; - - // printk("doc_write_oob(%lx, %d): %2.2X %2.2X %2.2X %2.2X ... %2.2X %2.2X .. %2.2X %2.2X\n",(long)ofs, len, - // buf[0], buf[1], buf[2], buf[3], buf[8], buf[9], buf[14],buf[15]); - - /* Find the chip which is to be used and select it */ - if (this->curfloor != mychip->floor) { - DoC_SelectFloor(this, mychip->floor); - DoC_SelectChip(this, mychip->chip); - } else if (this->curchip != mychip->chip) { - DoC_SelectChip(this, mychip->chip); - } - this->curfloor = mychip->floor; - this->curchip = mychip->chip; - - /* disable the ECC engine */ - WriteDOC (DOC_ECC_RESET, docptr, ECCConf); - WriteDOC (DOC_ECC_DIS, docptr, ECCConf); - - /* Reset the chip, see Software Requirement 11.4 item 1. */ - DoC_Command(this, NAND_CMD_RESET, CDSN_CTRL_WP); - - /* issue the Read2 command to set the pointer to the Spare Data Area. */ - DoC_Command(this, NAND_CMD_READOOB, CDSN_CTRL_WP); - - /* update address for 2M x 8bit devices. OOB starts on the second */ - /* page to maintain compatibility with doc_read_ecc. */ - if (this->page256) { - if (!(ofs & 0x8)) - ofs += 0x100; - else - ofs -= 0x8; - } - - /* issue the Serial Data In command to initial the Page Program process */ - DoC_Command(this, NAND_CMD_SEQIN, 0); - DoC_Address(this, ADDR_COLUMN_PAGE, ofs, 0, 0); - - /* treat crossing 8-byte OOB data for 2M x 8bit devices */ - /* Note: datasheet says it should automaticaly wrap to the */ - /* next OOB block, but it didn't work here. mf. */ - if (this->page256 && ofs + len > (ofs | 0x7) + 1) { - len256 = (ofs | 0x7) + 1 - ofs; - DoC_WriteBuf(this, buf, len256); - - DoC_Command(this, NAND_CMD_PAGEPROG, 0); - DoC_Command(this, NAND_CMD_STATUS, 0); - /* DoC_WaitReady() is implicit in DoC_Command */ - - if (DoC_is_Millennium(this)) { - ReadDOC(docptr, ReadPipeInit); - status = ReadDOC(docptr, LastDataRead); - } else { - dummy = ReadDOC(docptr, CDSNSlowIO); - DoC_Delay(this, 2); - status = ReadDOC_(docptr, this->ioreg); - } - - if (status & 1) { - printk(KERN_ERR "Error programming oob data\n"); - /* There was an error */ - *retlen = 0; - return -EIO; - } - DoC_Command(this, NAND_CMD_SEQIN, 0); - DoC_Address(this, ADDR_COLUMN_PAGE, ofs & (~0x1ff), 0, 0); - } - - DoC_WriteBuf(this, &buf[len256], len - len256); - - DoC_Command(this, NAND_CMD_PAGEPROG, 0); - DoC_Command(this, NAND_CMD_STATUS, 0); - /* DoC_WaitReady() is implicit in DoC_Command */ - - if (DoC_is_Millennium(this)) { - ReadDOC(docptr, ReadPipeInit); - status = ReadDOC(docptr, LastDataRead); - } else { - dummy = ReadDOC(docptr, CDSNSlowIO); - DoC_Delay(this, 2); - status = ReadDOC_(docptr, this->ioreg); - } - - if (status & 1) { - printk(KERN_ERR "Error programming oob data\n"); - /* There was an error */ - *retlen = 0; - return -EIO; - } - - *retlen = len; - return 0; - -} - -static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, - struct mtd_oob_ops *ops) -{ - struct DiskOnChip *this = mtd->priv; - int ret; - - BUG_ON(ops->mode != MTD_OOB_PLACE); - - mutex_lock(&this->lock); - ret = doc_write_oob_nolock(mtd, ofs + ops->ooboffs, ops->len, - &ops->retlen, ops->oobbuf); - - mutex_unlock(&this->lock); - return ret; -} - -static int doc_erase(struct mtd_info *mtd, struct erase_info *instr) -{ - struct DiskOnChip *this = mtd->priv; - __u32 ofs = instr->addr; - __u32 len = instr->len; - volatile int dummy; - void __iomem *docptr = this->virtadr; - struct Nand *mychip; - int status; - - mutex_lock(&this->lock); - - if (ofs & (mtd->erasesize-1) || len & (mtd->erasesize-1)) { - mutex_unlock(&this->lock); - return -EINVAL; - } - - instr->state = MTD_ERASING; - - /* FIXME: Do this in the background. Use timers or schedule_task() */ - while(len) { - mychip = &this->chips[ofs >> this->chipshift]; - - if (this->curfloor != mychip->floor) { - DoC_SelectFloor(this, mychip->floor); - DoC_SelectChip(this, mychip->chip); - } else if (this->curchip != mychip->chip) { - DoC_SelectChip(this, mychip->chip); - } - this->curfloor = mychip->floor; - this->curchip = mychip->chip; - - DoC_Command(this, NAND_CMD_ERASE1, 0); - DoC_Address(this, ADDR_PAGE, ofs, 0, 0); - DoC_Command(this, NAND_CMD_ERASE2, 0); - - DoC_Command(this, NAND_CMD_STATUS, CDSN_CTRL_WP); - - if (DoC_is_Millennium(this)) { - ReadDOC(docptr, ReadPipeInit); - status = ReadDOC(docptr, LastDataRead); - } else { - dummy = ReadDOC(docptr, CDSNSlowIO); - DoC_Delay(this, 2); - status = ReadDOC_(docptr, this->ioreg); - } - - if (status & 1) { - printk(KERN_ERR "Error erasing at 0x%x\n", ofs); - /* There was an error */ - instr->state = MTD_ERASE_FAILED; - goto callback; - } - ofs += mtd->erasesize; - len -= mtd->erasesize; - } - instr->state = MTD_ERASE_DONE; - - callback: - mtd_erase_callback(instr); - - mutex_unlock(&this->lock); - return 0; -} - - -/**************************************************************************** - * - * Module stuff - * - ****************************************************************************/ - -static void __exit cleanup_doc2000(void) -{ - struct mtd_info *mtd; - struct DiskOnChip *this; - - while ((mtd = doc2klist)) { - this = mtd->priv; - doc2klist = this->nextdoc; - - del_mtd_device(mtd); - - iounmap(this->virtadr); - kfree(this->chips); - kfree(mtd); - } -} - -module_exit(cleanup_doc2000); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org> et al."); -MODULE_DESCRIPTION("MTD driver for DiskOnChip 2000 and Millennium"); - diff --git a/drivers/mtd/devices/doc2001.c b/drivers/mtd/devices/doc2001.c deleted file mode 100644 index 0e2a9326f71..00000000000 --- a/drivers/mtd/devices/doc2001.c +++ /dev/null @@ -1,847 +0,0 @@ - -/* - * Linux driver for Disk-On-Chip Millennium - * (c) 1999 Machine Vision Holdings, Inc. - * (c) 1999, 2000 David Woodhouse <dwmw2@infradead.org> - * - * $Id: doc2001.c,v 1.49 2005/11/07 11:14:24 gleixner Exp $ - */ - -#include <linux/kernel.h> -#include <linux/module.h> -#include <asm/errno.h> -#include <asm/io.h> -#include <asm/uaccess.h> -#include <linux/miscdevice.h> -#include <linux/pci.h> -#include <linux/delay.h> -#include <linux/slab.h> -#include <linux/sched.h> -#include <linux/init.h> -#include <linux/types.h> -#include <linux/bitops.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/nand.h> -#include <linux/mtd/doc2000.h> - -/* #define ECC_DEBUG */ - -/* I have no idea why some DoC chips can not use memcop_form|to_io(). - * This may be due to the different revisions of the ASIC controller built-in or - * simplily a QA/Bug issue. Who knows ?? If you have trouble, please uncomment - * this:*/ -#undef USE_MEMCPY - -static int doc_read(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf); -static int doc_write(struct mtd_info *mtd, loff_t to, size_t len, - size_t *retlen, const u_char *buf); -static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, - struct mtd_oob_ops *ops); -static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, - struct mtd_oob_ops *ops); -static int doc_erase (struct mtd_info *mtd, struct erase_info *instr); - -static struct mtd_info *docmillist = NULL; - -/* Perform the required delay cycles by reading from the NOP register */ -static void DoC_Delay(void __iomem * docptr, unsigned short cycles) -{ - volatile char dummy; - int i; - - for (i = 0; i < cycles; i++) - dummy = ReadDOC(docptr, NOP); -} - -/* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */ -static int _DoC_WaitReady(void __iomem * docptr) -{ - unsigned short c = 0xffff; - - DEBUG(MTD_DEBUG_LEVEL3, - "_DoC_WaitReady called for out-of-line wait\n"); - - /* Out-of-line routine to wait for chip response */ - while (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B) && --c) - ; - - if (c == 0) - DEBUG(MTD_DEBUG_LEVEL2, "_DoC_WaitReady timed out.\n"); - - return (c == 0); -} - -static inline int DoC_WaitReady(void __iomem * docptr) -{ - /* This is inline, to optimise the common case, where it's ready instantly */ - int ret = 0; - - /* 4 read form NOP register should be issued in prior to the read from CDSNControl - see Software Requirement 11.4 item 2. */ - DoC_Delay(docptr, 4); - - if (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B)) - /* Call the out-of-line routine to wait */ - ret = _DoC_WaitReady(docptr); - - /* issue 2 read from NOP register after reading from CDSNControl register - see Software Requirement 11.4 item 2. */ - DoC_Delay(docptr, 2); - - return ret; -} - -/* DoC_Command: Send a flash command to the flash chip through the CDSN IO register - with the internal pipeline. Each of 4 delay cycles (read from the NOP register) is - required after writing to CDSN Control register, see Software Requirement 11.4 item 3. */ - -static void DoC_Command(void __iomem * docptr, unsigned char command, - unsigned char xtraflags) -{ - /* Assert the CLE (Command Latch Enable) line to the flash chip */ - WriteDOC(xtraflags | CDSN_CTRL_CLE | CDSN_CTRL_CE, docptr, CDSNControl); - DoC_Delay(docptr, 4); - - /* Send the command */ - WriteDOC(command, docptr, Mil_CDSN_IO); - WriteDOC(0x00, docptr, WritePipeTerm); - - /* Lower the CLE line */ - WriteDOC(xtraflags | CDSN_CTRL_CE, docptr, CDSNControl); - DoC_Delay(docptr, 4); -} - -/* DoC_Address: Set the current address for the flash chip through the CDSN IO register - with the internal pipeline. Each of 4 delay cycles (read from the NOP register) is - required after writing to CDSN Control register, see Software Requirement 11.4 item 3. */ - -static inline void DoC_Address(void __iomem * docptr, int numbytes, unsigned long ofs, - unsigned char xtraflags1, unsigned char xtraflags2) -{ - /* Assert the ALE (Address Latch Enable) line to the flash chip */ - WriteDOC(xtraflags1 | CDSN_CTRL_ALE | CDSN_CTRL_CE, docptr, CDSNControl); - DoC_Delay(docptr, 4); - - /* Send the address */ - switch (numbytes) - { - case 1: - /* Send single byte, bits 0-7. */ - WriteDOC(ofs & 0xff, docptr, Mil_CDSN_IO); - WriteDOC(0x00, docptr, WritePipeTerm); - break; - case 2: - /* Send bits 9-16 followed by 17-23 */ - WriteDOC((ofs >> 9) & 0xff, docptr, Mil_CDSN_IO); - WriteDOC((ofs >> 17) & 0xff, docptr, Mil_CDSN_IO); - WriteDOC(0x00, docptr, WritePipeTerm); - break; - case 3: - /* Send 0-7, 9-16, then 17-23 */ - WriteDOC(ofs & 0xff, docptr, Mil_CDSN_IO); - WriteDOC((ofs >> 9) & 0xff, docptr, Mil_CDSN_IO); - WriteDOC((ofs >> 17) & 0xff, docptr, Mil_CDSN_IO); - WriteDOC(0x00, docptr, WritePipeTerm); - break; - default: - return; - } - - /* Lower the ALE line */ - WriteDOC(xtraflags1 | xtraflags2 | CDSN_CTRL_CE, docptr, CDSNControl); - DoC_Delay(docptr, 4); -} - -/* DoC_SelectChip: Select a given flash chip within the current floor */ -static int DoC_SelectChip(void __iomem * docptr, int chip) -{ - /* Select the individual flash chip requested */ - WriteDOC(chip, docptr, CDSNDeviceSelect); - DoC_Delay(docptr, 4); - - /* Wait for it to be ready */ - return DoC_WaitReady(docptr); -} - -/* DoC_SelectFloor: Select a given floor (bank of flash chips) */ -static int DoC_SelectFloor(void __iomem * docptr, int floor) -{ - /* Select the floor (bank) of chips required */ - WriteDOC(floor, docptr, FloorSelect); - - /* Wait for the chip to be ready */ - return DoC_WaitReady(docptr); -} - -/* DoC_IdentChip: Identify a given NAND chip given {floor,chip} */ -static int DoC_IdentChip(struct DiskOnChip *doc, int floor, int chip) -{ - int mfr, id, i, j; - volatile char dummy; - - /* Page in the required floor/chip - FIXME: is this supported by Millennium ?? */ - DoC_SelectFloor(doc->virtadr, floor); - DoC_SelectChip(doc->virtadr, chip); - - /* Reset the chip, see Software Requirement 11.4 item 1. */ - DoC_Command(doc->virtadr, NAND_CMD_RESET, CDSN_CTRL_WP); - DoC_WaitReady(doc->virtadr); - - /* Read the NAND chip ID: 1. Send ReadID command */ - DoC_Command(doc->virtadr, NAND_CMD_READID, CDSN_CTRL_WP); - - /* Read the NAND chip ID: 2. Send address byte zero */ - DoC_Address(doc->virtadr, 1, 0x00, CDSN_CTRL_WP, 0x00); - - /* Read the manufacturer and device id codes of the flash device through - CDSN IO register see Software Requirement 11.4 item 5.*/ - dummy = ReadDOC(doc->virtadr, ReadPipeInit); - DoC_Delay(doc->virtadr, 2); - mfr = ReadDOC(doc->virtadr, Mil_CDSN_IO); - - DoC_Delay(doc->virtadr, 2); - id = ReadDOC(doc->virtadr, Mil_CDSN_IO); - dummy = ReadDOC(doc->virtadr, LastDataRead); - - /* No response - return failure */ - if (mfr == 0xff || mfr == 0) - return 0; - - /* FIXME: to deal with multi-flash on multi-Millennium case more carefully */ - for (i = 0; nand_flash_ids[i].name != NULL; i++) { - if ( id == nand_flash_ids[i].id) { - /* Try to identify manufacturer */ - for (j = 0; nand_manuf_ids[j].id != 0x0; j++) { - if (nand_manuf_ids[j].id == mfr) - break; - } - printk(KERN_INFO "Flash chip found: Manufacturer ID: %2.2X, " - "Chip ID: %2.2X (%s:%s)\n", - mfr, id, nand_manuf_ids[j].name, nand_flash_ids[i].name); - doc->mfr = mfr; - doc->id = id; - doc->chipshift = ffs((nand_flash_ids[i].chipsize << 20)) - 1; - break; - } - } - - if (nand_flash_ids[i].name == NULL) - return 0; - else - return 1; -} - -/* DoC_ScanChips: Find all NAND chips present in a DiskOnChip, and identify them */ -static void DoC_ScanChips(struct DiskOnChip *this) -{ - int floor, chip; - int numchips[MAX_FLOORS_MIL]; - int ret; - - this->numchips = 0; - this->mfr = 0; - this->id = 0; - - /* For each floor, find the number of valid chips it contains */ - for (floor = 0,ret = 1; floor < MAX_FLOORS_MIL; floor++) { - numchips[floor] = 0; - for (chip = 0; chip < MAX_CHIPS_MIL && ret != 0; chip++) { - ret = DoC_IdentChip(this, floor, chip); - if (ret) { - numchips[floor]++; - this->numchips++; - } - } - } - /* If there are none at all that we recognise, bail */ - if (!this->numchips) { - printk("No flash chips recognised.\n"); - return; - } - - /* Allocate an array to hold the information for each chip */ - this->chips = kmalloc(sizeof(struct Nand) * this->numchips, GFP_KERNEL); - if (!this->chips){ - printk("No memory for allocating chip info structures\n"); - return; - } - - /* Fill out the chip array with {floor, chipno} for each - * detected chip in the device. */ - for (floor = 0, ret = 0; floor < MAX_FLOORS_MIL; floor++) { - for (chip = 0 ; chip < numchips[floor] ; chip++) { - this->chips[ret].floor = floor; - this->chips[ret].chip = chip; - this->chips[ret].curadr = 0; - this->chips[ret].curmode = 0x50; - ret++; - } - } - - /* Calculate and print the total size of the device */ - this->totlen = this->numchips * (1 << this->chipshift); - printk(KERN_INFO "%d flash chips found. Total DiskOnChip size: %ld MiB\n", - this->numchips ,this->totlen >> 20); -} - -static int DoCMil_is_alias(struct DiskOnChip *doc1, struct DiskOnChip *doc2) -{ - int tmp1, tmp2, retval; - - if (doc1->physadr == doc2->physadr) - return 1; - - /* Use the alias resolution register which was set aside for this - * purpose. If it's value is the same on both chips, they might - * be the same chip, and we write to one and check for a change in - * the other. It's unclear if this register is usuable in the - * DoC 2000 (it's in the Millenium docs), but it seems to work. */ - tmp1 = ReadDOC(doc1->virtadr, AliasResolution); - tmp2 = ReadDOC(doc2->virtadr, AliasResolution); - if (tmp1 != tmp2) - return 0; - - WriteDOC((tmp1+1) % 0xff, doc1->virtadr, AliasResolution); - tmp2 = ReadDOC(doc2->virtadr, AliasResolution); - if (tmp2 == (tmp1+1) % 0xff) - retval = 1; - else - retval = 0; - - /* Restore register contents. May not be necessary, but do it just to - * be safe. */ - WriteDOC(tmp1, doc1->virtadr, AliasResolution); - - return retval; -} - -/* This routine is found from the docprobe code by symbol_get(), - * which will bump the use count of this module. */ -void DoCMil_init(struct mtd_info *mtd) -{ - struct DiskOnChip *this = mtd->priv; - struct DiskOnChip *old = NULL; - - /* We must avoid being called twice for the same device. */ - if (docmillist) - old = docmillist->priv; - - while (old) { - if (DoCMil_is_alias(this, old)) { - printk(KERN_NOTICE "Ignoring DiskOnChip Millennium at " - "0x%lX - already configured\n", this->physadr); - iounmap(this->virtadr); - kfree(mtd); - return; - } - if (old->nextdoc) - old = old->nextdoc->priv; - else - old = NULL; - } - - mtd->name = "DiskOnChip Millennium"; - printk(KERN_NOTICE "DiskOnChip Millennium found at address 0x%lX\n", - this->physadr); - - mtd->type = MTD_NANDFLASH; - mtd->flags = MTD_CAP_NANDFLASH; - mtd->ecctype = MTD_ECC_RS_DiskOnChip; - mtd->size = 0; - - /* FIXME: erase size is not always 8KiB */ - mtd->erasesize = 0x2000; - - mtd->writesize = 512; - mtd->oobsize = 16; - mtd->owner = THIS_MODULE; - mtd->erase = doc_erase; - mtd->point = NULL; - mtd->unpoint = NULL; - mtd->read = doc_read; - mtd->write = doc_write; - mtd->read_oob = doc_read_oob; - mtd->write_oob = doc_write_oob; - mtd->sync = NULL; - - this->totlen = 0; - this->numchips = 0; - this->curfloor = -1; - this->curchip = -1; - - /* Ident all the chips present. */ - DoC_ScanChips(this); - - if (!this->totlen) { - kfree(mtd); - iounmap(this->virtadr); - } else { - this->nextdoc = docmillist; - docmillist = mtd; - mtd->size = this->totlen; - add_mtd_device(mtd); - return; - } -} -EXPORT_SYMBOL_GPL(DoCMil_init); - -static int doc_read (struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf) -{ - int i, ret; - volatile char dummy; - unsigned char syndrome[6], eccbuf[6]; - struct DiskOnChip *this = mtd->priv; - void __iomem *docptr = this->virtadr; - struct Nand *mychip = &this->chips[from >> (this->chipshift)]; - - /* Don't allow read past end of device */ - if (from >= this->totlen) - return -EINVAL; - - /* Don't allow a single read to cross a 512-byte block boundary */ - if (from + len > ((from | 0x1ff) + 1)) - len = ((from | 0x1ff) + 1) - from; - - /* Find the chip which is to be used and select it */ - if (this->curfloor != mychip->floor) { - DoC_SelectFloor(docptr, mychip->floor); - DoC_SelectChip(docptr, mychip->chip); - } else if (this->curchip != mychip->chip) { - DoC_SelectChip(docptr, mychip->chip); - } - this->curfloor = mychip->floor; - this->curchip = mychip->chip; - - /* issue the Read0 or Read1 command depend on which half of the page - we are accessing. Polling the Flash Ready bit after issue 3 bytes - address in Sequence Read Mode, see Software Requirement 11.4 item 1.*/ - DoC_Command(docptr, (from >> 8) & 1, CDSN_CTRL_WP); - DoC_Address(docptr, 3, from, CDSN_CTRL_WP, 0x00); - DoC_WaitReady(docptr); - - /* init the ECC engine, see Reed-Solomon EDC/ECC 11.1 .*/ - WriteDOC (DOC_ECC_RESET, docptr, ECCConf); - WriteDOC (DOC_ECC_EN, docptr, ECCConf); - - /* Read the data via the internal pipeline through CDSN IO register, - see Pipelined Read Operations 11.3 */ - dummy = ReadDOC(docptr, ReadPipeInit); -#ifndef USE_MEMCPY - for (i = 0; i < len-1; i++) { - /* N.B. you have to increase the source address in this way or the - ECC logic will not work properly */ - buf[i] = ReadDOC(docptr, Mil_CDSN_IO + (i & 0xff)); - } -#else - memcpy_fromio(buf, docptr + DoC_Mil_CDSN_IO, len - 1); -#endif - buf[len - 1] = ReadDOC(docptr, LastDataRead); - - /* Let the caller know we completed it */ - *retlen = len; - ret = 0; - - /* Read the ECC data from Spare Data Area, - see Reed-Solomon EDC/ECC 11.1 */ - dummy = ReadDOC(docptr, ReadPipeInit); -#ifndef USE_MEMCPY - for (i = 0; i < 5; i++) { - /* N.B. you have to increase the source address in this way or the - ECC logic will not work properly */ - eccbuf[i] = ReadDOC(docptr, Mil_CDSN_IO + i); - } -#else - memcpy_fromio(eccbuf, docptr + DoC_Mil_CDSN_IO, 5); -#endif - eccbuf[5] = ReadDOC(docptr, LastDataRead); - - /* Flush the pipeline */ - dummy = ReadDOC(docptr, ECCConf); - dummy = ReadDOC(docptr, ECCConf); - - /* Check the ECC Status */ - if (ReadDOC(docptr, ECCConf) & 0x80) { - int nb_errors; - /* There was an ECC error */ -#ifdef ECC_DEBUG - printk("DiskOnChip ECC Error: Read at %lx\n", (long)from); -#endif - /* Read the ECC syndrom through the DiskOnChip ECC logic. - These syndrome will be all ZERO when there is no error */ - for (i = 0; i < 6; i++) { - syndrome[i] = ReadDOC(docptr, ECCSyndrome0 + i); - } - nb_errors = doc_decode_ecc(buf, syndrome); -#ifdef ECC_DEBUG - printk("ECC Errors corrected: %x\n", nb_errors); -#endif - if (nb_errors < 0) { - /* We return error, but have actually done the read. Not that - this can be told to user-space, via sys_read(), but at least - MTD-aware stuff can know about it by checking *retlen */ - ret = -EIO; - } - } - -#ifdef PSYCHO_DEBUG - printk("ECC DATA at %lx: %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n", - (long)from, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3], - eccbuf[4], eccbuf[5]); -#endif - - /* disable the ECC engine */ - WriteDOC(DOC_ECC_DIS, docptr , ECCConf); - - return ret; -} - -static int doc_write (struct mtd_info *mtd, loff_t to, size_t len, - size_t *retlen, const u_char *buf) -{ - int i,ret = 0; - char eccbuf[6]; - volatile char dummy; - struct DiskOnChip *this = mtd->priv; - void __iomem *docptr = this->virtadr; - struct Nand *mychip = &this->chips[to >> (this->chipshift)]; - - /* Don't allow write past end of device */ - if (to >= this->totlen) - return -EINVAL; - -#if 0 - /* Don't allow a single write to cross a 512-byte block boundary */ - if (to + len > ( (to | 0x1ff) + 1)) - len = ((to | 0x1ff) + 1) - to; -#else - /* Don't allow writes which aren't exactly one block */ - if (to & 0x1ff || len != 0x200) - return -EINVAL; -#endif - - /* Find the chip which is to be used and select it */ - if (this->curfloor != mychip->floor) { - DoC_SelectFloor(docptr, mychip->floor); - DoC_SelectChip(docptr, mychip->chip); - } else if (this->curchip != mychip->chip) { - DoC_SelectChip(docptr, mychip->chip); - } - this->curfloor = mychip->floor; - this->curchip = mychip->chip; - - /* Reset the chip, see Software Requirement 11.4 item 1. */ - DoC_Command(docptr, NAND_CMD_RESET, 0x00); - DoC_WaitReady(docptr); - /* Set device to main plane of flash */ - DoC_Command(docptr, NAND_CMD_READ0, 0x00); - - /* issue the Serial Data In command to initial the Page Program process */ - DoC_Command(docptr, NAND_CMD_SEQIN, 0x00); - DoC_Address(docptr, 3, to, 0x00, 0x00); - DoC_WaitReady(docptr); - - /* init the ECC engine, see Reed-Solomon EDC/ECC 11.1 .*/ - WriteDOC (DOC_ECC_RESET, docptr, ECCConf); - WriteDOC (DOC_ECC_EN | DOC_ECC_RW, docptr, ECCConf); - - /* Write the data via the internal pipeline through CDSN IO register, - see Pipelined Write Operations 11.2 */ -#ifndef USE_MEMCPY - for (i = 0; i < len; i++) { - /* N.B. you have to increase the source address in this way or the - ECC logic will not work properly */ - WriteDOC(buf[i], docptr, Mil_CDSN_IO + i); - } -#else - memcpy_toio(docptr + DoC_Mil_CDSN_IO, buf, len); -#endif - WriteDOC(0x00, docptr, WritePipeTerm); - - /* Write ECC data to flash, the ECC info is generated by the DiskOnChip ECC logic - see Reed-Solomon EDC/ECC 11.1 */ - WriteDOC(0, docptr, NOP); - WriteDOC(0, docptr, NOP); - WriteDOC(0, docptr, NOP); - - /* Read the ECC data through the DiskOnChip ECC logic */ - for (i = 0; i < 6; i++) { - eccbuf[i] = ReadDOC(docptr, ECCSyndrome0 + i); - } - - /* ignore the ECC engine */ - WriteDOC(DOC_ECC_DIS, docptr , ECCConf); - -#ifndef USE_MEMCPY - /* Write the ECC data to flash */ - for (i = 0; i < 6; i++) { - /* N.B. you have to increase the source address in this way or the - ECC logic will not work properly */ - WriteDOC(eccbuf[i], docptr, Mil_CDSN_IO + i); - } -#else - memcpy_toio(docptr + DoC_Mil_CDSN_IO, eccbuf, 6); -#endif - - /* write the block status BLOCK_USED (0x5555) at the end of ECC data - FIXME: this is only a hack for programming the IPL area for LinuxBIOS - and should be replace with proper codes in user space utilities */ - WriteDOC(0x55, docptr, Mil_CDSN_IO); - WriteDOC(0x55, docptr, Mil_CDSN_IO + 1); - - WriteDOC(0x00, docptr, WritePipeTerm); - -#ifdef PSYCHO_DEBUG - printk("OOB data at %lx is %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n", - (long) to, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3], - eccbuf[4], eccbuf[5]); -#endif - - /* Commit the Page Program command and wait for ready - see Software Requirement 11.4 item 1.*/ - DoC_Command(docptr, NAND_CMD_PAGEPROG, 0x00); - DoC_WaitReady(docptr); - - /* Read the status of the flash device through CDSN IO register - see Software Requirement 11.4 item 5.*/ - DoC_Command(docptr, NAND_CMD_STATUS, CDSN_CTRL_WP); - dummy = ReadDOC(docptr, ReadPipeInit); - DoC_Delay(docptr, 2); - if (ReadDOC(docptr, Mil_CDSN_IO) & 1) { - printk("Error programming flash\n"); - /* Error in programming - FIXME: implement Bad Block Replacement (in nftl.c ??) */ - *retlen = 0; - ret = -EIO; - } - dummy = ReadDOC(docptr, LastDataRead); - - /* Let the caller know we completed it */ - *retlen = len; - - return ret; -} - -static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, - struct mtd_oob_ops *ops) -{ -#ifndef USE_MEMCPY - int i; -#endif - volatile char dummy; - struct DiskOnChip *this = mtd->priv; - void __iomem *docptr = this->virtadr; - struct Nand *mychip = &this->chips[ofs >> this->chipshift]; - uint8_t *buf = ops->oobbuf; - size_t len = ops->len; - - BUG_ON(ops->mode != MTD_OOB_PLACE); - - ofs += ops->ooboffs; - - /* Find the chip which is to be used and select it */ - if (this->curfloor != mychip->floor) { - DoC_SelectFloor(docptr, mychip->floor); - DoC_SelectChip(docptr, mychip->chip); - } else if (this->curchip != mychip->chip) { - DoC_SelectChip(docptr, mychip->chip); - } - this->curfloor = mychip->floor; - this->curchip = mychip->chip; - - /* disable the ECC engine */ - WriteDOC (DOC_ECC_RESET, docptr, ECCConf); - WriteDOC (DOC_ECC_DIS, docptr, ECCConf); - - /* issue the Read2 command to set the pointer to the Spare Data Area. - Polling the Flash Ready bit after issue 3 bytes address in - Sequence Read Mode, see Software Requirement 11.4 item 1.*/ - DoC_Command(docptr, NAND_CMD_READOOB, CDSN_CTRL_WP); - DoC_Address(docptr, 3, ofs, CDSN_CTRL_WP, 0x00); - DoC_WaitReady(docptr); - - /* Read the data out via the internal pipeline through CDSN IO register, - see Pipelined Read Operations 11.3 */ - dummy = ReadDOC(docptr, ReadPipeInit); -#ifndef USE_MEMCPY - for (i = 0; i < len-1; i++) { - /* N.B. you have to increase the source address in this way or the - ECC logic will not work properly */ - buf[i] = ReadDOC(docptr, Mil_CDSN_IO + i); - } -#else - memcpy_fromio(buf, docptr + DoC_Mil_CDSN_IO, len - 1); -#endif - buf[len - 1] = ReadDOC(docptr, LastDataRead); - - ops->retlen = len; - - return 0; -} - -static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, - struct mtd_oob_ops *ops) -{ -#ifndef USE_MEMCPY - int i; -#endif - volatile char dummy; - int ret = 0; - struct DiskOnChip *this = mtd->priv; - void __iomem *docptr = this->virtadr; - struct Nand *mychip = &this->chips[ofs >> this->chipshift]; - uint8_t *buf = ops->oobbuf; - size_t len = ops->len; - - BUG_ON(ops->mode != MTD_OOB_PLACE); - - ofs += ops->ooboffs; - - /* Find the chip which is to be used and select it */ - if (this->curfloor != mychip->floor) { - DoC_SelectFloor(docptr, mychip->floor); - DoC_SelectChip(docptr, mychip->chip); - } else if (this->curchip != mychip->chip) { - DoC_SelectChip(docptr, mychip->chip); - } - this->curfloor = mychip->floor; - this->curchip = mychip->chip; - - /* disable the ECC engine */ - WriteDOC (DOC_ECC_RESET, docptr, ECCConf); - WriteDOC (DOC_ECC_DIS, docptr, ECCConf); - - /* Reset the chip, see Software Requirement 11.4 item 1. */ - DoC_Command(docptr, NAND_CMD_RESET, CDSN_CTRL_WP); - DoC_WaitReady(docptr); - /* issue the Read2 command to set the pointer to the Spare Data Area. */ - DoC_Command(docptr, NAND_CMD_READOOB, CDSN_CTRL_WP); - - /* issue the Serial Data In command to initial the Page Program process */ - DoC_Command(docptr, NAND_CMD_SEQIN, 0x00); - DoC_Address(docptr, 3, ofs, 0x00, 0x00); - - /* Write the data via the internal pipeline through CDSN IO register, - see Pipelined Write Operations 11.2 */ -#ifndef USE_MEMCPY - for (i = 0; i < len; i++) { - /* N.B. you have to increase the source address in this way or the - ECC logic will not work properly */ - WriteDOC(buf[i], docptr, Mil_CDSN_IO + i); - } -#else - memcpy_toio(docptr + DoC_Mil_CDSN_IO, buf, len); -#endif - WriteDOC(0x00, docptr, WritePipeTerm); - - /* Commit the Page Program command and wait for ready - see Software Requirement 11.4 item 1.*/ - DoC_Command(docptr, NAND_CMD_PAGEPROG, 0x00); - DoC_WaitReady(docptr); - - /* Read the status of the flash device through CDSN IO register - see Software Requirement 11.4 item 5.*/ - DoC_Command(docptr, NAND_CMD_STATUS, 0x00); - dummy = ReadDOC(docptr, ReadPipeInit); - DoC_Delay(docptr, 2); - if (ReadDOC(docptr, Mil_CDSN_IO) & 1) { - printk("Error programming oob data\n"); - /* FIXME: implement Bad Block Replacement (in nftl.c ??) */ - ops->retlen = 0; - ret = -EIO; - } - dummy = ReadDOC(docptr, LastDataRead); - - ops->retlen = len; - - return ret; -} - -int doc_erase (struct mtd_info *mtd, struct erase_info *instr) -{ - volatile char dummy; - struct DiskOnChip *this = mtd->priv; - __u32 ofs = instr->addr; - __u32 len = instr->len; - void __iomem *docptr = this->virtadr; - struct Nand *mychip = &this->chips[ofs >> this->chipshift]; - - if (len != mtd->erasesize) - printk(KERN_WARNING "Erase not right size (%x != %x)n", - len, mtd->erasesize); - - /* Find the chip which is to be used and select it */ - if (this->curfloor != mychip->floor) { - DoC_SelectFloor(docptr, mychip->floor); - DoC_SelectChip(docptr, mychip->chip); - } else if (this->curchip != mychip->chip) { - DoC_SelectChip(docptr, mychip->chip); - } - this->curfloor = mychip->floor; - this->curchip = mychip->chip; - - instr->state = MTD_ERASE_PENDING; - - /* issue the Erase Setup command */ - DoC_Command(docptr, NAND_CMD_ERASE1, 0x00); - DoC_Address(docptr, 2, ofs, 0x00, 0x00); - - /* Commit the Erase Start command and wait for ready - see Software Requirement 11.4 item 1.*/ - DoC_Command(docptr, NAND_CMD_ERASE2, 0x00); - DoC_WaitReady(docptr); - - instr->state = MTD_ERASING; - - /* Read the status of the flash device through CDSN IO register - see Software Requirement 11.4 item 5. - FIXME: it seems that we are not wait long enough, some blocks are not - erased fully */ - DoC_Command(docptr, NAND_CMD_STATUS, CDSN_CTRL_WP); - dummy = ReadDOC(docptr, ReadPipeInit); - DoC_Delay(docptr, 2); - if (ReadDOC(docptr, Mil_CDSN_IO) & 1) { - printk("Error Erasing at 0x%x\n", ofs); - /* There was an error - FIXME: implement Bad Block Replacement (in nftl.c ??) */ - instr->state = MTD_ERASE_FAILED; - } else - instr->state = MTD_ERASE_DONE; - dummy = ReadDOC(docptr, LastDataRead); - - mtd_erase_callback(instr); - - return 0; -} - -/**************************************************************************** - * - * Module stuff - * - ****************************************************************************/ - -static void __exit cleanup_doc2001(void) -{ - struct mtd_info *mtd; - struct DiskOnChip *this; - - while ((mtd=docmillist)) { - this = mtd->priv; - docmillist = this->nextdoc; - - del_mtd_device(mtd); - - iounmap(this->virtadr); - kfree(this->chips); - kfree(mtd); - } -} - -module_exit(cleanup_doc2001); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org> et al."); -MODULE_DESCRIPTION("Alternative driver for DiskOnChip Millennium"); diff --git a/drivers/mtd/devices/doc2001plus.c b/drivers/mtd/devices/doc2001plus.c deleted file mode 100644 index 92dbb47f2ac..00000000000 --- a/drivers/mtd/devices/doc2001plus.c +++ /dev/null @@ -1,1112 +0,0 @@ -/* - * Linux driver for Disk-On-Chip Millennium Plus - * - * (c) 2002-2003 Greg Ungerer <gerg@snapgear.com> - * (c) 2002-2003 SnapGear Inc - * (c) 1999 Machine Vision Holdings, Inc. - * (c) 1999, 2000 David Woodhouse <dwmw2@infradead.org> - * - * $Id: doc2001plus.c,v 1.14 2005/11/07 11:14:24 gleixner Exp $ - * - * Released under GPL - */ - -#include <linux/kernel.h> -#include <linux/module.h> -#include <asm/errno.h> -#include <asm/io.h> -#include <asm/uaccess.h> -#include <linux/miscdevice.h> -#include <linux/pci.h> -#include <linux/delay.h> -#include <linux/slab.h> -#include <linux/sched.h> -#include <linux/init.h> -#include <linux/types.h> -#include <linux/bitops.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/nand.h> -#include <linux/mtd/doc2000.h> - -/* #define ECC_DEBUG */ - -/* I have no idea why some DoC chips can not use memcop_form|to_io(). - * This may be due to the different revisions of the ASIC controller built-in or - * simplily a QA/Bug issue. Who knows ?? If you have trouble, please uncomment - * this:*/ -#undef USE_MEMCPY - -static int doc_read(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf); -static int doc_write(struct mtd_info *mtd, loff_t to, size_t len, - size_t *retlen, const u_char *buf); -static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, - struct mtd_oob_ops *ops); -static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, - struct mtd_oob_ops *ops); -static int doc_erase (struct mtd_info *mtd, struct erase_info *instr); - -static struct mtd_info *docmilpluslist = NULL; - - -/* Perform the required delay cycles by writing to the NOP register */ -static void DoC_Delay(void __iomem * docptr, int cycles) -{ - int i; - - for (i = 0; (i < cycles); i++) - WriteDOC(0, docptr, Mplus_NOP); -} - -#define CDSN_CTRL_FR_B_MASK (CDSN_CTRL_FR_B0 | CDSN_CTRL_FR_B1) - -/* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */ -static int _DoC_WaitReady(void __iomem * docptr) -{ - unsigned int c = 0xffff; - - DEBUG(MTD_DEBUG_LEVEL3, - "_DoC_WaitReady called for out-of-line wait\n"); - - /* Out-of-line routine to wait for chip response */ - while (((ReadDOC(docptr, Mplus_FlashControl) & CDSN_CTRL_FR_B_MASK) != CDSN_CTRL_FR_B_MASK) && --c) - ; - - if (c == 0) - DEBUG(MTD_DEBUG_LEVEL2, "_DoC_WaitReady timed out.\n"); - - return (c == 0); -} - -static inline int DoC_WaitReady(void __iomem * docptr) -{ - /* This is inline, to optimise the common case, where it's ready instantly */ - int ret = 0; - - /* read form NOP register should be issued prior to the read from CDSNControl - see Software Requirement 11.4 item 2. */ - DoC_Delay(docptr, 4); - - if ((ReadDOC(docptr, Mplus_FlashControl) & CDSN_CTRL_FR_B_MASK) != CDSN_CTRL_FR_B_MASK) - /* Call the out-of-line routine to wait */ - ret = _DoC_WaitReady(docptr); - - return ret; -} - -/* For some reason the Millennium Plus seems to occassionally put itself - * into reset mode. For me this happens randomly, with no pattern that I - * can detect. M-systems suggest always check this on any block level - * operation and setting to normal mode if in reset mode. - */ -static inline void DoC_CheckASIC(void __iomem * docptr) -{ - /* Make sure the DoC is in normal mode */ - if ((ReadDOC(docptr, Mplus_DOCControl) & DOC_MODE_NORMAL) == 0) { - WriteDOC((DOC_MODE_NORMAL | DOC_MODE_MDWREN), docptr, Mplus_DOCControl); - WriteDOC(~(DOC_MODE_NORMAL | DOC_MODE_MDWREN), docptr, Mplus_CtrlConfirm); - } -} - -/* DoC_Command: Send a flash command to the flash chip through the Flash - * command register. Need 2 Write Pipeline Terminates to complete send. - */ -static void DoC_Command(void __iomem * docptr, unsigned char command, - unsigned char xtraflags) -{ - WriteDOC(command, docptr, Mplus_FlashCmd); - WriteDOC(command, docptr, Mplus_WritePipeTerm); - WriteDOC(command, docptr, Mplus_WritePipeTerm); -} - -/* DoC_Address: Set the current address for the flash chip through the Flash - * Address register. Need 2 Write Pipeline Terminates to complete send. - */ -static inline void DoC_Address(struct DiskOnChip *doc, int numbytes, - unsigned long ofs, unsigned char xtraflags1, - unsigned char xtraflags2) -{ - void __iomem * docptr = doc->virtadr; - - /* Allow for possible Mill Plus internal flash interleaving */ - ofs >>= doc->interleave; - - switch (numbytes) { - case 1: - /* Send single byte, bits 0-7. */ - WriteDOC(ofs & 0xff, docptr, Mplus_FlashAddress); - break; - case 2: - /* Send bits 9-16 followed by 17-23 */ - WriteDOC((ofs >> 9) & 0xff, docptr, Mplus_FlashAddress); - WriteDOC((ofs >> 17) & 0xff, docptr, Mplus_FlashAddress); - break; - case 3: - /* Send 0-7, 9-16, then 17-23 */ - WriteDOC(ofs & 0xff, docptr, Mplus_FlashAddress); - WriteDOC((ofs >> 9) & 0xff, docptr, Mplus_FlashAddress); - WriteDOC((ofs >> 17) & 0xff, docptr, Mplus_FlashAddress); - break; - default: - return; - } - - WriteDOC(0x00, docptr, Mplus_WritePipeTerm); - WriteDOC(0x00, docptr, Mplus_WritePipeTerm); -} - -/* DoC_SelectChip: Select a given flash chip within the current floor */ -static int DoC_SelectChip(void __iomem * docptr, int chip) -{ - /* No choice for flash chip on Millennium Plus */ - return 0; -} - -/* DoC_SelectFloor: Select a given floor (bank of flash chips) */ -static int DoC_SelectFloor(void __iomem * docptr, int floor) -{ - WriteDOC((floor & 0x3), docptr, Mplus_DeviceSelect); - return 0; -} - -/* - * Translate the given offset into the appropriate command and offset. - * This does the mapping using the 16bit interleave layout defined by - * M-Systems, and looks like this for a sector pair: - * +-----------+-------+-------+-------+--------------+---------+-----------+ - * | 0 --- 511 |512-517|518-519|520-521| 522 --- 1033 |1034-1039|1040 - 1055| - * +-----------+-------+-------+-------+--------------+---------+-----------+ - * | Data 0 | ECC 0 |Flags0 |Flags1 | Data 1 |ECC 1 | OOB 1 + 2 | - * +-----------+-------+-------+-------+--------------+---------+-----------+ - */ -/* FIXME: This lives in INFTL not here. Other users of flash devices - may not want it */ -static unsigned int DoC_GetDataOffset(struct mtd_info *mtd, loff_t *from) -{ - struct DiskOnChip *this = mtd->priv; - - if (this->interleave) { - unsigned int ofs = *from & 0x3ff; - unsigned int cmd; - - if (ofs < 512) { - cmd = NAND_CMD_READ0; - ofs &= 0x1ff; - } else if (ofs < 1014) { - cmd = NAND_CMD_READ1; - ofs = (ofs & 0x1ff) + 10; - } else { - cmd = NAND_CMD_READOOB; - ofs = ofs - 1014; - } - - *from = (*from & ~0x3ff) | ofs; - return cmd; - } else { - /* No interleave */ - if ((*from) & 0x100) - return NAND_CMD_READ1; - return NAND_CMD_READ0; - } -} - -static unsigned int DoC_GetECCOffset(struct mtd_info *mtd, loff_t *from) -{ - unsigned int ofs, cmd; - - if (*from & 0x200) { - cmd = NAND_CMD_READOOB; - ofs = 10 + (*from & 0xf); - } else { - cmd = NAND_CMD_READ1; - ofs = (*from & 0xf); - } - - *from = (*from & ~0x3ff) | ofs; - return cmd; -} - -static unsigned int DoC_GetFlagsOffset(struct mtd_info *mtd, loff_t *from) -{ - unsigned int ofs, cmd; - - cmd = NAND_CMD_READ1; - ofs = (*from & 0x200) ? 8 : 6; - *from = (*from & ~0x3ff) | ofs; - return cmd; -} - -static unsigned int DoC_GetHdrOffset(struct mtd_info *mtd, loff_t *from) -{ - unsigned int ofs, cmd; - - cmd = NAND_CMD_READOOB; - ofs = (*from & 0x200) ? 24 : 16; - *from = (*from & ~0x3ff) | ofs; - return cmd; -} - -static inline void MemReadDOC(void __iomem * docptr, unsigned char *buf, int len) -{ -#ifndef USE_MEMCPY - int i; - for (i = 0; i < len; i++) - buf[i] = ReadDOC(docptr, Mil_CDSN_IO + i); -#else - memcpy_fromio(buf, docptr + DoC_Mil_CDSN_IO, len); -#endif -} - -static inline void MemWriteDOC(void __iomem * docptr, unsigned char *buf, int len) -{ -#ifndef USE_MEMCPY - int i; - for (i = 0; i < len; i++) - WriteDOC(buf[i], docptr, Mil_CDSN_IO + i); -#else - memcpy_toio(docptr + DoC_Mil_CDSN_IO, buf, len); -#endif -} - -/* DoC_IdentChip: Identify a given NAND chip given {floor,chip} */ -static int DoC_IdentChip(struct DiskOnChip *doc, int floor, int chip) -{ - int mfr, id, i, j; - volatile char dummy; - void __iomem * docptr = doc->virtadr; - - /* Page in the required floor/chip */ - DoC_SelectFloor(docptr, floor); - DoC_SelectChip(docptr, chip); - - /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */ - WriteDOC((DOC_FLASH_CE | DOC_FLASH_WP), docptr, Mplus_FlashSelect); - - /* Reset the chip, see Software Requirement 11.4 item 1. */ - DoC_Command(docptr, NAND_CMD_RESET, 0); - DoC_WaitReady(docptr); - - /* Read the NAND chip ID: 1. Send ReadID command */ - DoC_Command(docptr, NAND_CMD_READID, 0); - - /* Read the NAND chip ID: 2. Send address byte zero */ - DoC_Address(doc, 1, 0x00, 0, 0x00); - - WriteDOC(0, docptr, Mplus_FlashControl); - DoC_WaitReady(docptr); - - /* Read the manufacturer and device id codes of the flash device through - CDSN IO register see Software Requirement 11.4 item 5.*/ - dummy = ReadDOC(docptr, Mplus_ReadPipeInit); - dummy = ReadDOC(docptr, Mplus_ReadPipeInit); - - mfr = ReadDOC(docptr, Mil_CDSN_IO); - if (doc->interleave) - dummy = ReadDOC(docptr, Mil_CDSN_IO); /* 2 way interleave */ - - id = ReadDOC(docptr, Mil_CDSN_IO); - if (doc->interleave) - dummy = ReadDOC(docptr, Mil_CDSN_IO); /* 2 way interleave */ - - dummy = ReadDOC(docptr, Mplus_LastDataRead); - dummy = ReadDOC(docptr, Mplus_LastDataRead); - - /* Disable flash internally */ - WriteDOC(0, docptr, Mplus_FlashSelect); - - /* No response - return failure */ - if (mfr == 0xff || mfr == 0) - return 0; - - for (i = 0; nand_flash_ids[i].name != NULL; i++) { - if (id == nand_flash_ids[i].id) { - /* Try to identify manufacturer */ - for (j = 0; nand_manuf_ids[j].id != 0x0; j++) { - if (nand_manuf_ids[j].id == mfr) - break; - } - printk(KERN_INFO "Flash chip found: Manufacturer ID: %2.2X, " - "Chip ID: %2.2X (%s:%s)\n", mfr, id, - nand_manuf_ids[j].name, nand_flash_ids[i].name); - doc->mfr = mfr; - doc->id = id; - doc->chipshift = ffs((nand_flash_ids[i].chipsize << 20)) - 1; - doc->erasesize = nand_flash_ids[i].erasesize << doc->interleave; - break; - } - } - - if (nand_flash_ids[i].name == NULL) - return 0; - return 1; -} - -/* DoC_ScanChips: Find all NAND chips present in a DiskOnChip, and identify them */ -static void DoC_ScanChips(struct DiskOnChip *this) -{ - int floor, chip; - int numchips[MAX_FLOORS_MPLUS]; - int ret; - - this->numchips = 0; - this->mfr = 0; - this->id = 0; - - /* Work out the intended interleave setting */ - this->interleave = 0; - if (this->ChipID == DOC_ChipID_DocMilPlus32) - this->interleave = 1; - - /* Check the ASIC agrees */ - if ( (this->interleave << 2) != - (ReadDOC(this->virtadr, Mplus_Configuration) & 4)) { - u_char conf = ReadDOC(this->virtadr, Mplus_Configuration); - printk(KERN_NOTICE "Setting DiskOnChip Millennium Plus interleave to %s\n", - this->interleave?"on (16-bit)":"off (8-bit)"); - conf ^= 4; - WriteDOC(conf, this->virtadr, Mplus_Configuration); - } - - /* For each floor, find the number of valid chips it contains */ - for (floor = 0,ret = 1; floor < MAX_FLOORS_MPLUS; floor++) { - numchips[floor] = 0; - for (chip = 0; chip < MAX_CHIPS_MPLUS && ret != 0; chip++) { - ret = DoC_IdentChip(this, floor, chip); - if (ret) { - numchips[floor]++; - this->numchips++; - } - } - } - /* If there are none at all that we recognise, bail */ - if (!this->numchips) { - printk("No flash chips recognised.\n"); - return; - } - - /* Allocate an array to hold the information for each chip */ - this->chips = kmalloc(sizeof(struct Nand) * this->numchips, GFP_KERNEL); - if (!this->chips){ - printk("MTD: No memory for allocating chip info structures\n"); - return; - } - - /* Fill out the chip array with {floor, chipno} for each - * detected chip in the device. */ - for (floor = 0, ret = 0; floor < MAX_FLOORS_MPLUS; floor++) { - for (chip = 0 ; chip < numchips[floor] ; chip++) { - this->chips[ret].floor = floor; - this->chips[ret].chip = chip; - this->chips[ret].curadr = 0; - this->chips[ret].curmode = 0x50; - ret++; - } - } - - /* Calculate and print the total size of the device */ - this->totlen = this->numchips * (1 << this->chipshift); - printk(KERN_INFO "%d flash chips found. Total DiskOnChip size: %ld MiB\n", - this->numchips ,this->totlen >> 20); -} - -static int DoCMilPlus_is_alias(struct DiskOnChip *doc1, struct DiskOnChip *doc2) -{ - int tmp1, tmp2, retval; - - if (doc1->physadr == doc2->physadr) - return 1; - - /* Use the alias resolution register which was set aside for this - * purpose. If it's value is the same on both chips, they might - * be the same chip, and we write to one and check for a change in - * the other. It's unclear if this register is usuable in the - * DoC 2000 (it's in the Millennium docs), but it seems to work. */ - tmp1 = ReadDOC(doc1->virtadr, Mplus_AliasResolution); - tmp2 = ReadDOC(doc2->virtadr, Mplus_AliasResolution); - if (tmp1 != tmp2) - return 0; - - WriteDOC((tmp1+1) % 0xff, doc1->virtadr, Mplus_AliasResolution); - tmp2 = ReadDOC(doc2->virtadr, Mplus_AliasResolution); - if (tmp2 == (tmp1+1) % 0xff) - retval = 1; - else - retval = 0; - - /* Restore register contents. May not be necessary, but do it just to - * be safe. */ - WriteDOC(tmp1, doc1->virtadr, Mplus_AliasResolution); - - return retval; -} - -/* This routine is found from the docprobe code by symbol_get(), - * which will bump the use count of this module. */ -void DoCMilPlus_init(struct mtd_info *mtd) -{ - struct DiskOnChip *this = mtd->priv; - struct DiskOnChip *old = NULL; - - /* We must avoid being called twice for the same device. */ - if (docmilpluslist) - old = docmilpluslist->priv; - - while (old) { - if (DoCMilPlus_is_alias(this, old)) { - printk(KERN_NOTICE "Ignoring DiskOnChip Millennium " - "Plus at 0x%lX - already configured\n", - this->physadr); - iounmap(this->virtadr); - kfree(mtd); - return; - } - if (old->nextdoc) - old = old->nextdoc->priv; - else - old = NULL; - } - - mtd->name = "DiskOnChip Millennium Plus"; - printk(KERN_NOTICE "DiskOnChip Millennium Plus found at " - "address 0x%lX\n", this->physadr); - - mtd->type = MTD_NANDFLASH; - mtd->flags = MTD_CAP_NANDFLASH; - mtd->ecctype = MTD_ECC_RS_DiskOnChip; - mtd->size = 0; - - mtd->erasesize = 0; - mtd->writesize = 512; - mtd->oobsize = 16; - mtd->owner = THIS_MODULE; - mtd->erase = doc_erase; - mtd->point = NULL; - mtd->unpoint = NULL; - mtd->read = doc_read; - mtd->write = doc_write; - mtd->read_oob = doc_read_oob; - mtd->write_oob = doc_write_oob; - mtd->sync = NULL; - - this->totlen = 0; - this->numchips = 0; - this->curfloor = -1; - this->curchip = -1; - - /* Ident all the chips present. */ - DoC_ScanChips(this); - - if (!this->totlen) { - kfree(mtd); - iounmap(this->virtadr); - } else { - this->nextdoc = docmilpluslist; - docmilpluslist = mtd; - mtd->size = this->totlen; - mtd->erasesize = this->erasesize; - add_mtd_device(mtd); - return; - } -} -EXPORT_SYMBOL_GPL(DoCMilPlus_init); - -#if 0 -static int doc_dumpblk(struct mtd_info *mtd, loff_t from) -{ - int i; - loff_t fofs; - struct DiskOnChip *this = mtd->priv; - void __iomem * docptr = this->virtadr; - struct Nand *mychip = &this->chips[from >> (this->chipshift)]; - unsigned char *bp, buf[1056]; - char c[32]; - - from &= ~0x3ff; - - /* Don't allow read past end of device */ - if (from >= this->totlen) - return -EINVAL; - - DoC_CheckASIC(docptr); - - /* Find the chip which is to be used and select it */ - if (this->curfloor != mychip->floor) { - DoC_SelectFloor(docptr, mychip->floor); - DoC_SelectChip(docptr, mychip->chip); - } else if (this->curchip != mychip->chip) { - DoC_SelectChip(docptr, mychip->chip); - } - this->curfloor = mychip->floor; - this->curchip = mychip->chip; - - /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */ - WriteDOC((DOC_FLASH_CE | DOC_FLASH_WP), docptr, Mplus_FlashSelect); - - /* Reset the chip, see Software Requirement 11.4 item 1. */ - DoC_Command(docptr, NAND_CMD_RESET, 0); - DoC_WaitReady(docptr); - - fofs = from; - DoC_Command(docptr, DoC_GetDataOffset(mtd, &fofs), 0); - DoC_Address(this, 3, fofs, 0, 0x00); - WriteDOC(0, docptr, Mplus_FlashControl); - DoC_WaitReady(docptr); - - /* disable the ECC engine */ - WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf); - - ReadDOC(docptr, Mplus_ReadPipeInit); - ReadDOC(docptr, Mplus_ReadPipeInit); - - /* Read the data via the internal pipeline through CDSN IO - register, see Pipelined Read Operations 11.3 */ - MemReadDOC(docptr, buf, 1054); - buf[1054] = ReadDOC(docptr, Mplus_LastDataRead); - buf[1055] = ReadDOC(docptr, Mplus_LastDataRead); - - memset(&c[0], 0, sizeof(c)); - printk("DUMP OFFSET=%x:\n", (int)from); - - for (i = 0, bp = &buf[0]; (i < 1056); i++) { - if ((i % 16) == 0) - printk("%08x: ", i); - printk(" %02x", *bp); - c[(i & 0xf)] = ((*bp >= 0x20) && (*bp <= 0x7f)) ? *bp : '.'; - bp++; - if (((i + 1) % 16) == 0) - printk(" %s\n", c); - } - printk("\n"); - - /* Disable flash internally */ - WriteDOC(0, docptr, Mplus_FlashSelect); - - return 0; -} -#endif - -static int doc_read(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf) -{ - int ret, i; - volatile char dummy; - loff_t fofs; - unsigned char syndrome[6], eccbuf[6]; - struct DiskOnChip *this = mtd->priv; - void __iomem * docptr = this->virtadr; - struct Nand *mychip = &this->chips[from >> (this->chipshift)]; - - /* Don't allow read past end of device */ - if (from >= this->totlen) - return -EINVAL; - - /* Don't allow a single read to cross a 512-byte block boundary */ - if (from + len > ((from | 0x1ff) + 1)) - len = ((from | 0x1ff) + 1) - from; - - DoC_CheckASIC(docptr); - - /* Find the chip which is to be used and select it */ - if (this->curfloor != mychip->floor) { - DoC_SelectFloor(docptr, mychip->floor); - DoC_SelectChip(docptr, mychip->chip); - } else if (this->curchip != mychip->chip) { - DoC_SelectChip(docptr, mychip->chip); - } - this->curfloor = mychip->floor; - this->curchip = mychip->chip; - - /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */ - WriteDOC((DOC_FLASH_CE | DOC_FLASH_WP), docptr, Mplus_FlashSelect); - - /* Reset the chip, see Software Requirement 11.4 item 1. */ - DoC_Command(docptr, NAND_CMD_RESET, 0); - DoC_WaitReady(docptr); - - fofs = from; - DoC_Command(docptr, DoC_GetDataOffset(mtd, &fofs), 0); - DoC_Address(this, 3, fofs, 0, 0x00); - WriteDOC(0, docptr, Mplus_FlashControl); - DoC_WaitReady(docptr); - - /* init the ECC engine, see Reed-Solomon EDC/ECC 11.1 .*/ - WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf); - WriteDOC(DOC_ECC_EN, docptr, Mplus_ECCConf); - - /* Let the caller know we completed it */ - *retlen = len; - ret = 0; - - ReadDOC(docptr, Mplus_ReadPipeInit); - ReadDOC(docptr, Mplus_ReadPipeInit); - - /* Read the data via the internal pipeline through CDSN IO - register, see Pipelined Read Operations 11.3 */ - MemReadDOC(docptr, buf, len); - - /* Read the ECC data following raw data */ - MemReadDOC(docptr, eccbuf, 4); - eccbuf[4] = ReadDOC(docptr, Mplus_LastDataRead); - eccbuf[5] = ReadDOC(docptr, Mplus_LastDataRead); - - /* Flush the pipeline */ - dummy = ReadDOC(docptr, Mplus_ECCConf); - dummy = ReadDOC(docptr, Mplus_ECCConf); - - /* Check the ECC Status */ - if (ReadDOC(docptr, Mplus_ECCConf) & 0x80) { - int nb_errors; - /* There was an ECC error */ -#ifdef ECC_DEBUG - printk("DiskOnChip ECC Error: Read at %lx\n", (long)from); -#endif - /* Read the ECC syndrom through the DiskOnChip ECC logic. - These syndrome will be all ZERO when there is no error */ - for (i = 0; i < 6; i++) - syndrome[i] = ReadDOC(docptr, Mplus_ECCSyndrome0 + i); - - nb_errors = doc_decode_ecc(buf, syndrome); -#ifdef ECC_DEBUG - printk("ECC Errors corrected: %x\n", nb_errors); -#endif - if (nb_errors < 0) { - /* We return error, but have actually done the - read. Not that this can be told to user-space, via - sys_read(), but at least MTD-aware stuff can know - about it by checking *retlen */ -#ifdef ECC_DEBUG - printk("%s(%d): Millennium Plus ECC error (from=0x%x:\n", - __FILE__, __LINE__, (int)from); - printk(" syndrome= %02x:%02x:%02x:%02x:%02x:" - "%02x\n", - syndrome[0], syndrome[1], syndrome[2], - syndrome[3], syndrome[4], syndrome[5]); - printk(" eccbuf= %02x:%02x:%02x:%02x:%02x:" - "%02x\n", - eccbuf[0], eccbuf[1], eccbuf[2], - eccbuf[3], eccbuf[4], eccbuf[5]); -#endif - ret = -EIO; - } - } - -#ifdef PSYCHO_DEBUG - printk("ECC DATA at %lx: %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n", - (long)from, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3], - eccbuf[4], eccbuf[5]); -#endif - /* disable the ECC engine */ - WriteDOC(DOC_ECC_DIS, docptr , Mplus_ECCConf); - - /* Disable flash internally */ - WriteDOC(0, docptr, Mplus_FlashSelect); - - return ret; -} - -static int doc_write(struct mtd_info *mtd, loff_t to, size_t len, - size_t *retlen, const u_char *buf) -{ - int i, before, ret = 0; - loff_t fto; - volatile char dummy; - char eccbuf[6]; - struct DiskOnChip *this = mtd->priv; - void __iomem * docptr = this->virtadr; - struct Nand *mychip = &this->chips[to >> (this->chipshift)]; - - /* Don't allow write past end of device */ - if (to >= this->totlen) - return -EINVAL; - - /* Don't allow writes which aren't exactly one block (512 bytes) */ - if ((to & 0x1ff) || (len != 0x200)) - return -EINVAL; - - /* Determine position of OOB flags, before or after data */ - before = (this->interleave && (to & 0x200)); - - DoC_CheckASIC(docptr); - - /* Find the chip which is to be used and select it */ - if (this->curfloor != mychip->floor) { - DoC_SelectFloor(docptr, mychip->floor); - DoC_SelectChip(docptr, mychip->chip); - } else if (this->curchip != mychip->chip) { - DoC_SelectChip(docptr, mychip->chip); - } - this->curfloor = mychip->floor; - this->curchip = mychip->chip; - - /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */ - WriteDOC(DOC_FLASH_CE, docptr, Mplus_FlashSelect); - - /* Reset the chip, see Software Requirement 11.4 item 1. */ - DoC_Command(docptr, NAND_CMD_RESET, 0); - DoC_WaitReady(docptr); - - /* Set device to appropriate plane of flash */ - fto = to; - WriteDOC(DoC_GetDataOffset(mtd, &fto), docptr, Mplus_FlashCmd); - - /* On interleaved devices the flags for 2nd half 512 are before data */ - if (eccbuf && before) - fto -= 2; - - /* issue the Serial Data In command to initial the Page Program process */ - DoC_Command(docptr, NAND_CMD_SEQIN, 0x00); - DoC_Address(this, 3, fto, 0x00, 0x00); - - /* Disable the ECC engine */ - WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf); - - if (before) { - /* Write the block status BLOCK_USED (0x5555) */ - WriteDOC(0x55, docptr, Mil_CDSN_IO); - WriteDOC(0x55, docptr, Mil_CDSN_IO); - } - - /* init the ECC engine, see Reed-Solomon EDC/ECC 11.1 .*/ - WriteDOC(DOC_ECC_EN | DOC_ECC_RW, docptr, Mplus_ECCConf); - - MemWriteDOC(docptr, (unsigned char *) buf, len); - - /* Write ECC data to flash, the ECC info is generated by - the DiskOnChip ECC logic see Reed-Solomon EDC/ECC 11.1 */ - DoC_Delay(docptr, 3); - - /* Read the ECC data through the DiskOnChip ECC logic */ - for (i = 0; i < 6; i++) - eccbuf[i] = ReadDOC(docptr, Mplus_ECCSyndrome0 + i); - - /* disable the ECC engine */ - WriteDOC(DOC_ECC_DIS, docptr, Mplus_ECCConf); - - /* Write the ECC data to flash */ - MemWriteDOC(docptr, eccbuf, 6); - - if (!before) { - /* Write the block status BLOCK_USED (0x5555) */ - WriteDOC(0x55, docptr, Mil_CDSN_IO+6); - WriteDOC(0x55, docptr, Mil_CDSN_IO+7); - } - -#ifdef PSYCHO_DEBUG - printk("OOB data at %lx is %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n", - (long) to, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3], - eccbuf[4], eccbuf[5]); -#endif - - WriteDOC(0x00, docptr, Mplus_WritePipeTerm); - WriteDOC(0x00, docptr, Mplus_WritePipeTerm); - - /* Commit the Page Program command and wait for ready - see Software Requirement 11.4 item 1.*/ - DoC_Command(docptr, NAND_CMD_PAGEPROG, 0x00); - DoC_WaitReady(docptr); - - /* Read the status of the flash device through CDSN IO register - see Software Requirement 11.4 item 5.*/ - DoC_Command(docptr, NAND_CMD_STATUS, 0); - dummy = ReadDOC(docptr, Mplus_ReadPipeInit); - dummy = ReadDOC(docptr, Mplus_ReadPipeInit); - DoC_Delay(docptr, 2); - if ((dummy = ReadDOC(docptr, Mplus_LastDataRead)) & 1) { - printk("MTD: Error 0x%x programming at 0x%x\n", dummy, (int)to); - /* Error in programming - FIXME: implement Bad Block Replacement (in nftl.c ??) */ - *retlen = 0; - ret = -EIO; - } - dummy = ReadDOC(docptr, Mplus_LastDataRead); - - /* Disable flash internally */ - WriteDOC(0, docptr, Mplus_FlashSelect); - - /* Let the caller know we completed it */ - *retlen = len; - - return ret; -} - -static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, - struct mtd_oob_ops *ops) -{ - loff_t fofs, base; - struct DiskOnChip *this = mtd->priv; - void __iomem * docptr = this->virtadr; - struct Nand *mychip = &this->chips[ofs >> this->chipshift]; - size_t i, size, got, want; - uint8_t *buf = ops->oobbuf; - size_t len = ops->len; - - BUG_ON(ops->mode != MTD_OOB_PLACE); - - ofs += ops->ooboffs; - - DoC_CheckASIC(docptr); - - /* Find the chip which is to be used and select it */ - if (this->curfloor != mychip->floor) { - DoC_SelectFloor(docptr, mychip->floor); - DoC_SelectChip(docptr, mychip->chip); - } else if (this->curchip != mychip->chip) { - DoC_SelectChip(docptr, mychip->chip); - } - this->curfloor = mychip->floor; - this->curchip = mychip->chip; - - /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */ - WriteDOC((DOC_FLASH_CE | DOC_FLASH_WP), docptr, Mplus_FlashSelect); - - /* disable the ECC engine */ - WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf); - DoC_WaitReady(docptr); - - /* Maximum of 16 bytes in the OOB region, so limit read to that */ - if (len > 16) - len = 16; - got = 0; - want = len; - - for (i = 0; ((i < 3) && (want > 0)); i++) { - /* Figure out which region we are accessing... */ - fofs = ofs; - base = ofs & 0xf; - if (!this->interleave) { - DoC_Command(docptr, NAND_CMD_READOOB, 0); - size = 16 - base; - } else if (base < 6) { - DoC_Command(docptr, DoC_GetECCOffset(mtd, &fofs), 0); - size = 6 - base; - } else if (base < 8) { - DoC_Command(docptr, DoC_GetFlagsOffset(mtd, &fofs), 0); - size = 8 - base; - } else { - DoC_Command(docptr, DoC_GetHdrOffset(mtd, &fofs), 0); - size = 16 - base; - } - if (size > want) - size = want; - - /* Issue read command */ - DoC_Address(this, 3, fofs, 0, 0x00); - WriteDOC(0, docptr, Mplus_FlashControl); - DoC_WaitReady(docptr); - - ReadDOC(docptr, Mplus_ReadPipeInit); - ReadDOC(docptr, Mplus_ReadPipeInit); - MemReadDOC(docptr, &buf[got], size - 2); - buf[got + size - 2] = ReadDOC(docptr, Mplus_LastDataRead); - buf[got + size - 1] = ReadDOC(docptr, Mplus_LastDataRead); - - ofs += size; - got += size; - want -= size; - } - - /* Disable flash internally */ - WriteDOC(0, docptr, Mplus_FlashSelect); - - ops->retlen = len; - return 0; -} - -static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, - struct mtd_oob_ops *ops) -{ - volatile char dummy; - loff_t fofs, base; - struct DiskOnChip *this = mtd->priv; - void __iomem * docptr = this->virtadr; - struct Nand *mychip = &this->chips[ofs >> this->chipshift]; - size_t i, size, got, want; - int ret = 0; - uint8_t *buf = ops->oobbuf; - size_t len = ops->len; - - BUG_ON(ops->mode != MTD_OOB_PLACE); - - ofs += ops->ooboffs; - - DoC_CheckASIC(docptr); - - /* Find the chip which is to be used and select it */ - if (this->curfloor != mychip->floor) { - DoC_SelectFloor(docptr, mychip->floor); - DoC_SelectChip(docptr, mychip->chip); - } else if (this->curchip != mychip->chip) { - DoC_SelectChip(docptr, mychip->chip); - } - this->curfloor = mychip->floor; - this->curchip = mychip->chip; - - /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */ - WriteDOC(DOC_FLASH_CE, docptr, Mplus_FlashSelect); - - - /* Maximum of 16 bytes in the OOB region, so limit write to that */ - if (len > 16) - len = 16; - got = 0; - want = len; - - for (i = 0; ((i < 3) && (want > 0)); i++) { - /* Reset the chip, see Software Requirement 11.4 item 1. */ - DoC_Command(docptr, NAND_CMD_RESET, 0); - DoC_WaitReady(docptr); - - /* Figure out which region we are accessing... */ - fofs = ofs; - base = ofs & 0x0f; - if (!this->interleave) { - WriteDOC(NAND_CMD_READOOB, docptr, Mplus_FlashCmd); - size = 16 - base; - } else if (base < 6) { - WriteDOC(DoC_GetECCOffset(mtd, &fofs), docptr, Mplus_FlashCmd); - size = 6 - base; - } else if (base < 8) { - WriteDOC(DoC_GetFlagsOffset(mtd, &fofs), docptr, Mplus_FlashCmd); - size = 8 - base; - } else { - WriteDOC(DoC_GetHdrOffset(mtd, &fofs), docptr, Mplus_FlashCmd); - size = 16 - base; - } - if (size > want) - size = want; - - /* Issue the Serial Data In command to initial the Page Program process */ - DoC_Command(docptr, NAND_CMD_SEQIN, 0x00); - DoC_Address(this, 3, fofs, 0, 0x00); - - /* Disable the ECC engine */ - WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf); - - /* Write the data via the internal pipeline through CDSN IO - register, see Pipelined Write Operations 11.2 */ - MemWriteDOC(docptr, (unsigned char *) &buf[got], size); - WriteDOC(0x00, docptr, Mplus_WritePipeTerm); - WriteDOC(0x00, docptr, Mplus_WritePipeTerm); - - /* Commit the Page Program command and wait for ready - see Software Requirement 11.4 item 1.*/ - DoC_Command(docptr, NAND_CMD_PAGEPROG, 0x00); - DoC_WaitReady(docptr); - - /* Read the status of the flash device through CDSN IO register - see Software Requirement 11.4 item 5.*/ - DoC_Command(docptr, NAND_CMD_STATUS, 0x00); - dummy = ReadDOC(docptr, Mplus_ReadPipeInit); - dummy = ReadDOC(docptr, Mplus_ReadPipeInit); - DoC_Delay(docptr, 2); - if ((dummy = ReadDOC(docptr, Mplus_LastDataRead)) & 1) { - printk("MTD: Error 0x%x programming oob at 0x%x\n", - dummy, (int)ofs); - /* FIXME: implement Bad Block Replacement */ - ops->retlen = 0; - ret = -EIO; - } - dummy = ReadDOC(docptr, Mplus_LastDataRead); - - ofs += size; - got += size; - want -= size; - } - - /* Disable flash internally */ - WriteDOC(0, docptr, Mplus_FlashSelect); - - ops->retlen = len; - return ret; -} - -int doc_erase(struct mtd_info *mtd, struct erase_info *instr) -{ - volatile char dummy; - struct DiskOnChip *this = mtd->priv; - __u32 ofs = instr->addr; - __u32 len = instr->len; - void __iomem * docptr = this->virtadr; - struct Nand *mychip = &this->chips[ofs >> this->chipshift]; - - DoC_CheckASIC(docptr); - - if (len != mtd->erasesize) - printk(KERN_WARNING "MTD: Erase not right size (%x != %x)n", - len, mtd->erasesize); - - /* Find the chip which is to be used and select it */ - if (this->curfloor != mychip->floor) { - DoC_SelectFloor(docptr, mychip->floor); - DoC_SelectChip(docptr, mychip->chip); - } else if (this->curchip != mychip->chip) { - DoC_SelectChip(docptr, mychip->chip); - } - this->curfloor = mychip->floor; - this->curchip = mychip->chip; - - instr->state = MTD_ERASE_PENDING; - - /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */ - WriteDOC(DOC_FLASH_CE, docptr, Mplus_FlashSelect); - - DoC_Command(docptr, NAND_CMD_RESET, 0x00); - DoC_WaitReady(docptr); - - DoC_Command(docptr, NAND_CMD_ERASE1, 0); - DoC_Address(this, 2, ofs, 0, 0x00); - DoC_Command(docptr, NAND_CMD_ERASE2, 0); - DoC_WaitReady(docptr); - instr->state = MTD_ERASING; - - /* Read the status of the flash device through CDSN IO register - see Software Requirement 11.4 item 5. */ - DoC_Command(docptr, NAND_CMD_STATUS, 0); - dummy = ReadDOC(docptr, Mplus_ReadPipeInit); - dummy = ReadDOC(docptr, Mplus_ReadPipeInit); - if ((dummy = ReadDOC(docptr, Mplus_LastDataRead)) & 1) { - printk("MTD: Error 0x%x erasing at 0x%x\n", dummy, ofs); - /* FIXME: implement Bad Block Replacement (in nftl.c ??) */ - instr->state = MTD_ERASE_FAILED; - } else { - instr->state = MTD_ERASE_DONE; - } - dummy = ReadDOC(docptr, Mplus_LastDataRead); - - /* Disable flash internally */ - WriteDOC(0, docptr, Mplus_FlashSelect); - - mtd_erase_callback(instr); - - return 0; -} - -/**************************************************************************** - * - * Module stuff - * - ****************************************************************************/ - -static void __exit cleanup_doc2001plus(void) -{ - struct mtd_info *mtd; - struct DiskOnChip *this; - - while ((mtd=docmilpluslist)) { - this = mtd->priv; - docmilpluslist = this->nextdoc; - - del_mtd_device(mtd); - - iounmap(this->virtadr); - kfree(this->chips); - kfree(mtd); - } -} - -module_exit(cleanup_doc2001plus); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Greg Ungerer <gerg@snapgear.com> et al."); -MODULE_DESCRIPTION("Driver for DiskOnChip Millennium Plus"); diff --git a/drivers/mtd/devices/docecc.c b/drivers/mtd/devices/docecc.c deleted file mode 100644 index cd3db72bef9..00000000000 --- a/drivers/mtd/devices/docecc.c +++ /dev/null @@ -1,527 +0,0 @@ -/* - * ECC algorithm for M-systems disk on chip. We use the excellent Reed - * Solmon code of Phil Karn (karn@ka9q.ampr.org) available under the - * GNU GPL License. The rest is simply to convert the disk on chip - * syndrom into a standard syndom. - * - * Author: Fabrice Bellard (fabrice.bellard@netgem.com) - * Copyright (C) 2000 Netgem S.A. - * - * $Id: docecc.c,v 1.7 2005/11/07 11:14:25 gleixner Exp $ - * - * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include <linux/kernel.h> -#include <linux/module.h> -#include <asm/errno.h> -#include <asm/io.h> -#include <asm/uaccess.h> -#include <linux/miscdevice.h> -#include <linux/pci.h> -#include <linux/delay.h> -#include <linux/slab.h> -#include <linux/sched.h> -#include <linux/init.h> -#include <linux/types.h> - -#include <linux/mtd/compatmac.h> /* for min() in older kernels */ -#include <linux/mtd/mtd.h> -#include <linux/mtd/doc2000.h> - -#define DEBUG_ECC 0 -/* need to undef it (from asm/termbits.h) */ -#undef B0 - -#define MM 10 /* Symbol size in bits */ -#define KK (1023-4) /* Number of data symbols per block */ -#define B0 510 /* First root of generator polynomial, alpha form */ -#define PRIM 1 /* power of alpha used to generate roots of generator poly */ -#define NN ((1 << MM) - 1) - -typedef unsigned short dtype; - -/* 1+x^3+x^10 */ -static const int Pp[MM+1] = { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1 }; - -/* This defines the type used to store an element of the Galois Field - * used by the code. Make sure this is something larger than a char if - * if anything larger than GF(256) is used. - * - * Note: unsigned char will work up to GF(256) but int seems to run - * faster on the Pentium. - */ -typedef int gf; - -/* No legal value in index form represents zero, so - * we need a special value for this purpose - */ -#define A0 (NN) - -/* Compute x % NN, where NN is 2**MM - 1, - * without a slow divide - */ -static inline gf -modnn(int x) -{ - while (x >= NN) { - x -= NN; - x = (x >> MM) + (x & NN); - } - return x; -} - -#define CLEAR(a,n) {\ -int ci;\ -for(ci=(n)-1;ci >=0;ci--)\ -(a)[ci] = 0;\ -} - -#define COPY(a,b,n) {\ -int ci;\ -for(ci=(n)-1;ci >=0;ci--)\ -(a)[ci] = (b)[ci];\ -} - -#define COPYDOWN(a,b,n) {\ -int ci;\ -for(ci=(n)-1;ci >=0;ci--)\ -(a)[ci] = (b)[ci];\ -} - -#define Ldec 1 - -/* generate GF(2**m) from the irreducible polynomial p(X) in Pp[0]..Pp[m] - lookup tables: index->polynomial form alpha_to[] contains j=alpha**i; - polynomial form -> index form index_of[j=alpha**i] = i - alpha=2 is the primitive element of GF(2**m) - HARI's COMMENT: (4/13/94) alpha_to[] can be used as follows: - Let @ represent the primitive element commonly called "alpha" that - is the root of the primitive polynomial p(x). Then in GF(2^m), for any - 0 <= i <= 2^m-2, - @^i = a(0) + a(1) @ + a(2) @^2 + ... + a(m-1) @^(m-1) - where the binary vector (a(0),a(1),a(2),...,a(m-1)) is the representation - of the integer "alpha_to[i]" with a(0) being the LSB and a(m-1) the MSB. Thus for - example the polynomial representation of @^5 would be given by the binary - representation of the integer "alpha_to[5]". - Similarily, index_of[] can be used as follows: - As above, let @ represent the primitive element of GF(2^m) that is - the root of the primitive polynomial p(x). In order to find the power - of @ (alpha) that has the polynomial representation - a(0) + a(1) @ + a(2) @^2 + ... + a(m-1) @^(m-1) - we consider the integer "i" whose binary representation with a(0) being LSB - and a(m-1) MSB is (a(0),a(1),...,a(m-1)) and locate the entry - "index_of[i]". Now, @^index_of[i] is that element whose polynomial - representation is (a(0),a(1),a(2),...,a(m-1)). - NOTE: - The element alpha_to[2^m-1] = 0 always signifying that the - representation of "@^infinity" = 0 is (0,0,0,...,0). - Similarily, the element index_of[0] = A0 always signifying - that the power of alpha which has the polynomial representation - (0,0,...,0) is "infinity". - -*/ - -static void -generate_gf(dtype Alpha_to[NN + 1], dtype Index_of[NN + 1]) -{ - register int i, mask; - - mask = 1; - Alpha_to[MM] = 0; - for (i = 0; i < MM; i++) { - Alpha_to[i] = mask; - Index_of[Alpha_to[i]] = i; - /* If Pp[i] == 1 then, term @^i occurs in poly-repr of @^MM */ - if (Pp[i] != 0) - Alpha_to[MM] ^= mask; /* Bit-wise EXOR operation */ - mask <<= 1; /* single left-shift */ - } - Index_of[Alpha_to[MM]] = MM; - /* - * Have obtained poly-repr of @^MM. Poly-repr of @^(i+1) is given by - * poly-repr of @^i shifted left one-bit and accounting for any @^MM - * term that may occur when poly-repr of @^i is shifted. - */ - mask >>= 1; - for (i = MM + 1; i < NN; i++) { - if (Alpha_to[i - 1] >= mask) - Alpha_to[i] = Alpha_to[MM] ^ ((Alpha_to[i - 1] ^ mask) << 1); - else - Alpha_to[i] = Alpha_to[i - 1] << 1; - Index_of[Alpha_to[i]] = i; - } - Index_of[0] = A0; - Alpha_to[NN] = 0; -} - -/* - * Performs ERRORS+ERASURES decoding of RS codes. bb[] is the content - * of the feedback shift register after having processed the data and - * the ECC. - * - * Return number of symbols corrected, or -1 if codeword is illegal - * or uncorrectable. If eras_pos is non-null, the detected error locations - * are written back. NOTE! This array must be at least NN-KK elements long. - * The corrected data are written in eras_val[]. They must be xor with the data - * to retrieve the correct data : data[erase_pos[i]] ^= erase_val[i] . - * - * First "no_eras" erasures are declared by the calling program. Then, the - * maximum # of errors correctable is t_after_eras = floor((NN-KK-no_eras)/2). - * If the number of channel errors is not greater than "t_after_eras" the - * transmitted codeword will be recovered. Details of algorithm can be found - * in R. Blahut's "Theory ... of Error-Correcting Codes". - - * Warning: the eras_pos[] array must not contain duplicate entries; decoder failure - * will result. The decoder *could* check for this condition, but it would involve - * extra time on every decoding operation. - * */ -static int -eras_dec_rs(dtype Alpha_to[NN + 1], dtype Index_of[NN + 1], - gf bb[NN - KK + 1], gf eras_val[NN-KK], int eras_pos[NN-KK], - int no_eras) -{ - int deg_lambda, el, deg_omega; - int i, j, r,k; - gf u,q,tmp,num1,num2,den,discr_r; - gf lambda[NN-KK + 1], s[NN-KK + 1]; /* Err+Eras Locator poly - * and syndrome poly */ - gf b[NN-KK + 1], t[NN-KK + 1], omega[NN-KK + 1]; - gf root[NN-KK], reg[NN-KK + 1], loc[NN-KK]; - int syn_error, count; - - syn_error = 0; - for(i=0;i<NN-KK;i++) - syn_error |= bb[i]; - - if (!syn_error) { - /* if remainder is zero, data[] is a codeword and there are no - * errors to correct. So return data[] unmodified - */ - count = 0; - goto finish; - } - - for(i=1;i<=NN-KK;i++){ - s[i] = bb[0]; - } - for(j=1;j<NN-KK;j++){ - if(bb[j] == 0) - continue; - tmp = Index_of[bb[j]]; - - for(i=1;i<=NN-KK;i++) - s[i] ^= Alpha_to[modnn(tmp + (B0+i-1)*PRIM*j)]; - } - - /* undo the feedback register implicit multiplication and convert - syndromes to index form */ - - for(i=1;i<=NN-KK;i++) { - tmp = Index_of[s[i]]; - if (tmp != A0) - tmp = modnn(tmp + 2 * KK * (B0+i-1)*PRIM); - s[i] = tmp; - } - - CLEAR(&lambda[1],NN-KK); - lambda[0] = 1; - - if (no_eras > 0) { - /* Init lambda to be the erasure locator polynomial */ - lambda[1] = Alpha_to[modnn(PRIM * eras_pos[0])]; - for (i = 1; i < no_eras; i++) { - u = modnn(PRIM*eras_pos[i]); - for (j = i+1; j > 0; j--) { - tmp = Index_of[lambda[j - 1]]; - if(tmp != A0) - lambda[j] ^= Alpha_to[modnn(u + tmp)]; - } - } -#if DEBUG_ECC >= 1 - /* Test code that verifies the erasure locator polynomial just constructed - Needed only for decoder debugging. */ - - /* find roots of the erasure location polynomial */ - for(i=1;i<=no_eras;i++) - reg[i] = Index_of[lambda[i]]; - count = 0; - for (i = 1,k=NN-Ldec; i <= NN; i++,k = modnn(NN+k-Ldec)) { - q = 1; - for (j = 1; j <= no_eras; j++) - if (reg[j] != A0) { - reg[j] = modnn(reg[j] + j); - q ^= Alpha_to[reg[j]]; - } - if (q != 0) - continue; - /* store root and error location number indices */ - root[count] = i; - loc[count] = k; - count++; - } - if (count != no_eras) { - printf("\n lambda(x) is WRONG\n"); - count = -1; - goto finish; - } -#if DEBUG_ECC >= 2 - printf("\n Erasure positions as determined by roots of Eras Loc Poly:\n"); - for (i = 0; i < count; i++) - printf("%d ", loc[i]); - printf("\n"); -#endif -#endif - } - for(i=0;i<NN-KK+1;i++) - b[i] = Index_of[lambda[i]]; - - /* - * Begin Berlekamp-Massey algorithm to determine error+erasure - * locator polynomial - */ - r = no_eras; - el = no_eras; - while (++r <= NN-KK) { /* r is the step number */ - /* Compute discrepancy at the r-th step in poly-form */ - discr_r = 0; - for (i = 0; i < r; i++){ - if ((lambda[i] != 0) && (s[r - i] != A0)) { - discr_r ^= Alpha_to[modnn(Index_of[lambda[i]] + s[r - i])]; - } - } - discr_r = Index_of[discr_r]; /* Index form */ - if (discr_r == A0) { - /* 2 lines below: B(x) <-- x*B(x) */ - COPYDOWN(&b[1],b,NN-KK); - b[0] = A0; - } else { - /* 7 lines below: T(x) <-- lambda(x) - discr_r*x*b(x) */ - t[0] = lambda[0]; - for (i = 0 ; i < NN-KK; i++) { - if(b[i] != A0) - t[i+1] = lambda[i+1] ^ Alpha_to[modnn(discr_r + b[i])]; - else - t[i+1] = lambda[i+1]; - } - if (2 * el <= r + no_eras - 1) { - el = r + no_eras - el; - /* - * 2 lines below: B(x) <-- inv(discr_r) * - * lambda(x) - */ - for (i = 0; i <= NN-KK; i++) - b[i] = (lambda[i] == 0) ? A0 : modnn(Index_of[lambda[i]] - discr_r + NN); - } else { - /* 2 lines below: B(x) <-- x*B(x) */ - COPYDOWN(&b[1],b,NN-KK); - b[0] = A0; - } - COPY(lambda,t,NN-KK+1); - } - } - - /* Convert lambda to index form and compute deg(lambda(x)) */ - deg_lambda = 0; - for(i=0;i<NN-KK+1;i++){ - lambda[i] = Index_of[lambda[i]]; - if(lambda[i] != A0) - deg_lambda = i; - } - /* - * Find roots of the error+erasure locator polynomial by Chien - * Search - */ - COPY(®[1],&lambda[1],NN-KK); - count = 0; /* Number of roots of lambda(x) */ - for (i = 1,k=NN-Ldec; i <= NN; i++,k = modnn(NN+k-Ldec)) { - q = 1; - for (j = deg_lambda; j > 0; j--){ - if (reg[j] != A0) { - reg[j] = modnn(reg[j] + j); - q ^= Alpha_to[reg[j]]; - } - } - if (q != 0) - continue; - /* store root (index-form) and error location number */ - root[count] = i; - loc[count] = k; - /* If we've already found max possible roots, - * abort the search to save time - */ - if(++count == deg_lambda) - break; - } - if (deg_lambda != count) { - /* - * deg(lambda) unequal to number of roots => uncorrectable - * error detected - */ - count = -1; - goto finish; - } - /* - * Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo - * x**(NN-KK)). in index form. Also find deg(omega). - */ - deg_omega = 0; - for (i = 0; i < NN-KK;i++){ - tmp = 0; - j = (deg_lambda < i) ? deg_lambda : i; - for(;j >= 0; j--){ - if ((s[i + 1 - j] != A0) && (lambda[j] != A0)) - tmp ^= Alpha_to[modnn(s[i + 1 - j] + lambda[j])]; - } - if(tmp != 0) - deg_omega = i; - omega[i] = Index_of[tmp]; - } - omega[NN-KK] = A0; - - /* - * Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = - * inv(X(l))**(B0-1) and den = lambda_pr(inv(X(l))) all in poly-form - */ - for (j = count-1; j >=0; j--) { - num1 = 0; - for (i = deg_omega; i >= 0; i--) { - if (omega[i] != A0) - num1 ^= Alpha_to[modnn(omega[i] + i * root[j])]; - } - num2 = Alpha_to[modnn(root[j] * (B0 - 1) + NN)]; - den = 0; - - /* lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] */ - for (i = min(deg_lambda,NN-KK-1) & ~1; i >= 0; i -=2) { - if(lambda[i+1] != A0) - den ^= Alpha_to[modnn(lambda[i+1] + i * root[j])]; - } - if (den == 0) { -#if DEBUG_ECC >= 1 - printf("\n ERROR: denominator = 0\n"); -#endif - /* Convert to dual- basis */ - count = -1; - goto finish; - } - /* Apply error to data */ - if (num1 != 0) { - eras_val[j] = Alpha_to[modnn(Index_of[num1] + Index_of[num2] + NN - Index_of[den])]; - } else { - eras_val[j] = 0; - } - } - finish: - for(i=0;i<count;i++) - eras_pos[i] = loc[i]; - return count; -} - -/***************************************************************************/ -/* The DOC specific code begins here */ - -#define SECTOR_SIZE 512 -/* The sector bytes are packed into NB_DATA MM bits words */ -#define NB_DATA (((SECTOR_SIZE + 1) * 8 + 6) / MM) - -/* - * Correct the errors in 'sector[]' by using 'ecc1[]' which is the - * content of the feedback shift register applyied to the sector and - * the ECC. Return the number of errors corrected (and correct them in - * sector), or -1 if error - */ -int doc_decode_ecc(unsigned char sector[SECTOR_SIZE], unsigned char ecc1[6]) -{ - int parity, i, nb_errors; - gf bb[NN - KK + 1]; - gf error_val[NN-KK]; - int error_pos[NN-KK], pos, bitpos, index, val; - dtype *Alpha_to, *Index_of; - - /* init log and exp tables here to save memory. However, it is slower */ - Alpha_to = kmalloc((NN + 1) * sizeof(dtype), GFP_KERNEL); - if (!Alpha_to) - return -1; - - Index_of = kmalloc((NN + 1) * sizeof(dtype), GFP_KERNEL); - if (!Index_of) { - kfree(Alpha_to); - return -1; - } - - generate_gf(Alpha_to, Index_of); - - parity = ecc1[1]; - - bb[0] = (ecc1[4] & 0xff) | ((ecc1[5] & 0x03) << 8); - bb[1] = ((ecc1[5] & 0xfc) >> 2) | ((ecc1[2] & 0x0f) << 6); - bb[2] = ((ecc1[2] & 0xf0) >> 4) | ((ecc1[3] & 0x3f) << 4); - bb[3] = ((ecc1[3] & 0xc0) >> 6) | ((ecc1[0] & 0xff) << 2); - - nb_errors = eras_dec_rs(Alpha_to, Index_of, bb, - error_val, error_pos, 0); - if (nb_errors <= 0) - goto the_end; - - /* correct the errors */ - for(i=0;i<nb_errors;i++) { - pos = error_pos[i]; - if (pos >= NB_DATA && pos < KK) { - nb_errors = -1; - goto the_end; - } - if (pos < NB_DATA) { - /* extract bit position (MSB first) */ - pos = 10 * (NB_DATA - 1 - pos) - 6; - /* now correct the following 10 bits. At most two bytes - can be modified since pos is even */ - index = (pos >> 3) ^ 1; - bitpos = pos & 7; - if ((index >= 0 && index < SECTOR_SIZE) || - index == (SECTOR_SIZE + 1)) { - val = error_val[i] >> (2 + bitpos); - parity ^= val; - if (index < SECTOR_SIZE) - sector[index] ^= val; - } - index = ((pos >> 3) + 1) ^ 1; - bitpos = (bitpos + 10) & 7; - if (bitpos == 0) - bitpos = 8; - if ((index >= 0 && index < SECTOR_SIZE) || - index == (SECTOR_SIZE + 1)) { - val = error_val[i] << (8 - bitpos); - parity ^= val; - if (index < SECTOR_SIZE) - sector[index] ^= val; - } - } - } - - /* use parity to test extra errors */ - if ((parity & 0xff) != 0) - nb_errors = -1; - - the_end: - kfree(Alpha_to); - kfree(Index_of); - return nb_errors; -} - -EXPORT_SYMBOL_GPL(doc_decode_ecc); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Fabrice Bellard <fabrice.bellard@netgem.com>"); -MODULE_DESCRIPTION("ECC code for correcting errors detected by DiskOnChip 2000 and Millennium ECC hardware"); diff --git a/drivers/mtd/devices/docg3.c b/drivers/mtd/devices/docg3.c new file mode 100644 index 00000000000..91a169c44b3 --- /dev/null +++ b/drivers/mtd/devices/docg3.c @@ -0,0 +1,2143 @@ +/* + * Handles the M-Systems DiskOnChip G3 chip + * + * Copyright (C) 2011 Robert Jarzmik + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/bitmap.h> +#include <linux/bitrev.h> +#include <linux/bch.h> + +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +#define CREATE_TRACE_POINTS +#include "docg3.h" + +/* + * This driver handles the DiskOnChip G3 flash memory. + * + * As no specification is available from M-Systems/Sandisk, this drivers lacks + * several functions available on the chip, as : + * - IPL write + * + * The bus data width (8bits versus 16bits) is not handled (if_cfg flag), and + * the driver assumes a 16bits data bus. + * + * DocG3 relies on 2 ECC algorithms, which are handled in hardware : + * - a 1 byte Hamming code stored in the OOB for each page + * - a 7 bytes BCH code stored in the OOB for each page + * The BCH ECC is : + * - BCH is in GF(2^14) + * - BCH is over data of 520 bytes (512 page + 7 page_info bytes + * + 1 hamming byte) + * - BCH can correct up to 4 bits (t = 4) + * - BCH syndroms are calculated in hardware, and checked in hardware as well + * + */ + +static unsigned int reliable_mode; +module_param(reliable_mode, uint, 0); +MODULE_PARM_DESC(reliable_mode, "Set the docg3 mode (0=normal MLC, 1=fast, " + "2=reliable) : MLC normal operations are in normal mode"); + +/** + * struct docg3_oobinfo - DiskOnChip G3 OOB layout + * @eccbytes: 8 bytes are used (1 for Hamming ECC, 7 for BCH ECC) + * @eccpos: ecc positions (byte 7 is Hamming ECC, byte 8-14 are BCH ECC) + * @oobfree: free pageinfo bytes (byte 0 until byte 6, byte 15 + * @oobavail: 8 available bytes remaining after ECC toll + */ +static struct nand_ecclayout docg3_oobinfo = { + .eccbytes = 8, + .eccpos = {7, 8, 9, 10, 11, 12, 13, 14}, + .oobfree = {{0, 7}, {15, 1} }, + .oobavail = 8, +}; + +static inline u8 doc_readb(struct docg3 *docg3, u16 reg) +{ + u8 val = readb(docg3->cascade->base + reg); + + trace_docg3_io(0, 8, reg, (int)val); + return val; +} + +static inline u16 doc_readw(struct docg3 *docg3, u16 reg) +{ + u16 val = readw(docg3->cascade->base + reg); + + trace_docg3_io(0, 16, reg, (int)val); + return val; +} + +static inline void doc_writeb(struct docg3 *docg3, u8 val, u16 reg) +{ + writeb(val, docg3->cascade->base + reg); + trace_docg3_io(1, 8, reg, val); +} + +static inline void doc_writew(struct docg3 *docg3, u16 val, u16 reg) +{ + writew(val, docg3->cascade->base + reg); + trace_docg3_io(1, 16, reg, val); +} + +static inline void doc_flash_command(struct docg3 *docg3, u8 cmd) +{ + doc_writeb(docg3, cmd, DOC_FLASHCOMMAND); +} + +static inline void doc_flash_sequence(struct docg3 *docg3, u8 seq) +{ + doc_writeb(docg3, seq, DOC_FLASHSEQUENCE); +} + +static inline void doc_flash_address(struct docg3 *docg3, u8 addr) +{ + doc_writeb(docg3, addr, DOC_FLASHADDRESS); +} + +static char const * const part_probes[] = { "cmdlinepart", "saftlpart", NULL }; + +static int doc_register_readb(struct docg3 *docg3, int reg) +{ + u8 val; + + doc_writew(docg3, reg, DOC_READADDRESS); + val = doc_readb(docg3, reg); + doc_vdbg("Read register %04x : %02x\n", reg, val); + return val; +} + +static int doc_register_readw(struct docg3 *docg3, int reg) +{ + u16 val; + + doc_writew(docg3, reg, DOC_READADDRESS); + val = doc_readw(docg3, reg); + doc_vdbg("Read register %04x : %04x\n", reg, val); + return val; +} + +/** + * doc_delay - delay docg3 operations + * @docg3: the device + * @nbNOPs: the number of NOPs to issue + * + * As no specification is available, the right timings between chip commands are + * unknown. The only available piece of information are the observed nops on a + * working docg3 chip. + * Therefore, doc_delay relies on a busy loop of NOPs, instead of scheduler + * friendlier msleep() functions or blocking mdelay(). + */ +static void doc_delay(struct docg3 *docg3, int nbNOPs) +{ + int i; + + doc_vdbg("NOP x %d\n", nbNOPs); + for (i = 0; i < nbNOPs; i++) + doc_writeb(docg3, 0, DOC_NOP); +} + +static int is_prot_seq_error(struct docg3 *docg3) +{ + int ctrl; + + ctrl = doc_register_readb(docg3, DOC_FLASHCONTROL); + return ctrl & (DOC_CTRL_PROTECTION_ERROR | DOC_CTRL_SEQUENCE_ERROR); +} + +static int doc_is_ready(struct docg3 *docg3) +{ + int ctrl; + + ctrl = doc_register_readb(docg3, DOC_FLASHCONTROL); + return ctrl & DOC_CTRL_FLASHREADY; +} + +static int doc_wait_ready(struct docg3 *docg3) +{ + int maxWaitCycles = 100; + + do { + doc_delay(docg3, 4); + cpu_relax(); + } while (!doc_is_ready(docg3) && maxWaitCycles--); + doc_delay(docg3, 2); + if (maxWaitCycles > 0) + return 0; + else + return -EIO; +} + +static int doc_reset_seq(struct docg3 *docg3) +{ + int ret; + + doc_writeb(docg3, 0x10, DOC_FLASHCONTROL); + doc_flash_sequence(docg3, DOC_SEQ_RESET); + doc_flash_command(docg3, DOC_CMD_RESET); + doc_delay(docg3, 2); + ret = doc_wait_ready(docg3); + + doc_dbg("doc_reset_seq() -> isReady=%s\n", ret ? "false" : "true"); + return ret; +} + +/** + * doc_read_data_area - Read data from data area + * @docg3: the device + * @buf: the buffer to fill in (might be NULL is dummy reads) + * @len: the length to read + * @first: first time read, DOC_READADDRESS should be set + * + * Reads bytes from flash data. Handles the single byte / even bytes reads. + */ +static void doc_read_data_area(struct docg3 *docg3, void *buf, int len, + int first) +{ + int i, cdr, len4; + u16 data16, *dst16; + u8 data8, *dst8; + + doc_dbg("doc_read_data_area(buf=%p, len=%d)\n", buf, len); + cdr = len & 0x1; + len4 = len - cdr; + + if (first) + doc_writew(docg3, DOC_IOSPACE_DATA, DOC_READADDRESS); + dst16 = buf; + for (i = 0; i < len4; i += 2) { + data16 = doc_readw(docg3, DOC_IOSPACE_DATA); + if (dst16) { + *dst16 = data16; + dst16++; + } + } + + if (cdr) { + doc_writew(docg3, DOC_IOSPACE_DATA | DOC_READADDR_ONE_BYTE, + DOC_READADDRESS); + doc_delay(docg3, 1); + dst8 = (u8 *)dst16; + for (i = 0; i < cdr; i++) { + data8 = doc_readb(docg3, DOC_IOSPACE_DATA); + if (dst8) { + *dst8 = data8; + dst8++; + } + } + } +} + +/** + * doc_write_data_area - Write data into data area + * @docg3: the device + * @buf: the buffer to get input bytes from + * @len: the length to write + * + * Writes bytes into flash data. Handles the single byte / even bytes writes. + */ +static void doc_write_data_area(struct docg3 *docg3, const void *buf, int len) +{ + int i, cdr, len4; + u16 *src16; + u8 *src8; + + doc_dbg("doc_write_data_area(buf=%p, len=%d)\n", buf, len); + cdr = len & 0x3; + len4 = len - cdr; + + doc_writew(docg3, DOC_IOSPACE_DATA, DOC_READADDRESS); + src16 = (u16 *)buf; + for (i = 0; i < len4; i += 2) { + doc_writew(docg3, *src16, DOC_IOSPACE_DATA); + src16++; + } + + src8 = (u8 *)src16; + for (i = 0; i < cdr; i++) { + doc_writew(docg3, DOC_IOSPACE_DATA | DOC_READADDR_ONE_BYTE, + DOC_READADDRESS); + doc_writeb(docg3, *src8, DOC_IOSPACE_DATA); + src8++; + } +} + +/** + * doc_set_data_mode - Sets the flash to normal or reliable data mode + * @docg3: the device + * + * The reliable data mode is a bit slower than the fast mode, but less errors + * occur. Entering the reliable mode cannot be done without entering the fast + * mode first. + * + * In reliable mode, pages 2*n and 2*n+1 are clones. Writing to page 0 of blocks + * (4,5) make the hardware write also to page 1 of blocks blocks(4,5). Reading + * from page 0 of blocks (4,5) or from page 1 of blocks (4,5) gives the same + * result, which is a logical and between bytes from page 0 and page 1 (which is + * consistent with the fact that writing to a page is _clearing_ bits of that + * page). + */ +static void doc_set_reliable_mode(struct docg3 *docg3) +{ + static char *strmode[] = { "normal", "fast", "reliable", "invalid" }; + + doc_dbg("doc_set_reliable_mode(%s)\n", strmode[docg3->reliable]); + switch (docg3->reliable) { + case 0: + break; + case 1: + doc_flash_sequence(docg3, DOC_SEQ_SET_FASTMODE); + doc_flash_command(docg3, DOC_CMD_FAST_MODE); + break; + case 2: + doc_flash_sequence(docg3, DOC_SEQ_SET_RELIABLEMODE); + doc_flash_command(docg3, DOC_CMD_FAST_MODE); + doc_flash_command(docg3, DOC_CMD_RELIABLE_MODE); + break; + default: + doc_err("doc_set_reliable_mode(): invalid mode\n"); + break; + } + doc_delay(docg3, 2); +} + +/** + * doc_set_asic_mode - Set the ASIC mode + * @docg3: the device + * @mode: the mode + * + * The ASIC can work in 3 modes : + * - RESET: all registers are zeroed + * - NORMAL: receives and handles commands + * - POWERDOWN: minimal poweruse, flash parts shut off + */ +static void doc_set_asic_mode(struct docg3 *docg3, u8 mode) +{ + int i; + + for (i = 0; i < 12; i++) + doc_readb(docg3, DOC_IOSPACE_IPL); + + mode |= DOC_ASICMODE_MDWREN; + doc_dbg("doc_set_asic_mode(%02x)\n", mode); + doc_writeb(docg3, mode, DOC_ASICMODE); + doc_writeb(docg3, ~mode, DOC_ASICMODECONFIRM); + doc_delay(docg3, 1); +} + +/** + * doc_set_device_id - Sets the devices id for cascaded G3 chips + * @docg3: the device + * @id: the chip to select (amongst 0, 1, 2, 3) + * + * There can be 4 cascaded G3 chips. This function selects the one which will + * should be the active one. + */ +static void doc_set_device_id(struct docg3 *docg3, int id) +{ + u8 ctrl; + + doc_dbg("doc_set_device_id(%d)\n", id); + doc_writeb(docg3, id, DOC_DEVICESELECT); + ctrl = doc_register_readb(docg3, DOC_FLASHCONTROL); + + ctrl &= ~DOC_CTRL_VIOLATION; + ctrl |= DOC_CTRL_CE; + doc_writeb(docg3, ctrl, DOC_FLASHCONTROL); +} + +/** + * doc_set_extra_page_mode - Change flash page layout + * @docg3: the device + * + * Normally, the flash page is split into the data (512 bytes) and the out of + * band data (16 bytes). For each, 4 more bytes can be accessed, where the wear + * leveling counters are stored. To access this last area of 4 bytes, a special + * mode must be input to the flash ASIC. + * + * Returns 0 if no error occurred, -EIO else. + */ +static int doc_set_extra_page_mode(struct docg3 *docg3) +{ + int fctrl; + + doc_dbg("doc_set_extra_page_mode()\n"); + doc_flash_sequence(docg3, DOC_SEQ_PAGE_SIZE_532); + doc_flash_command(docg3, DOC_CMD_PAGE_SIZE_532); + doc_delay(docg3, 2); + + fctrl = doc_register_readb(docg3, DOC_FLASHCONTROL); + if (fctrl & (DOC_CTRL_PROTECTION_ERROR | DOC_CTRL_SEQUENCE_ERROR)) + return -EIO; + else + return 0; +} + +/** + * doc_setup_addr_sector - Setup blocks/page/ofs address for one plane + * @docg3: the device + * @sector: the sector + */ +static void doc_setup_addr_sector(struct docg3 *docg3, int sector) +{ + doc_delay(docg3, 1); + doc_flash_address(docg3, sector & 0xff); + doc_flash_address(docg3, (sector >> 8) & 0xff); + doc_flash_address(docg3, (sector >> 16) & 0xff); + doc_delay(docg3, 1); +} + +/** + * doc_setup_writeaddr_sector - Setup blocks/page/ofs address for one plane + * @docg3: the device + * @sector: the sector + * @ofs: the offset in the page, between 0 and (512 + 16 + 512) + */ +static void doc_setup_writeaddr_sector(struct docg3 *docg3, int sector, int ofs) +{ + ofs = ofs >> 2; + doc_delay(docg3, 1); + doc_flash_address(docg3, ofs & 0xff); + doc_flash_address(docg3, sector & 0xff); + doc_flash_address(docg3, (sector >> 8) & 0xff); + doc_flash_address(docg3, (sector >> 16) & 0xff); + doc_delay(docg3, 1); +} + +/** + * doc_seek - Set both flash planes to the specified block, page for reading + * @docg3: the device + * @block0: the first plane block index + * @block1: the second plane block index + * @page: the page index within the block + * @wear: if true, read will occur on the 4 extra bytes of the wear area + * @ofs: offset in page to read + * + * Programs the flash even and odd planes to the specific block and page. + * Alternatively, programs the flash to the wear area of the specified page. + */ +static int doc_read_seek(struct docg3 *docg3, int block0, int block1, int page, + int wear, int ofs) +{ + int sector, ret = 0; + + doc_dbg("doc_seek(blocks=(%d,%d), page=%d, ofs=%d, wear=%d)\n", + block0, block1, page, ofs, wear); + + if (!wear && (ofs < 2 * DOC_LAYOUT_PAGE_SIZE)) { + doc_flash_sequence(docg3, DOC_SEQ_SET_PLANE1); + doc_flash_command(docg3, DOC_CMD_READ_PLANE1); + doc_delay(docg3, 2); + } else { + doc_flash_sequence(docg3, DOC_SEQ_SET_PLANE2); + doc_flash_command(docg3, DOC_CMD_READ_PLANE2); + doc_delay(docg3, 2); + } + + doc_set_reliable_mode(docg3); + if (wear) + ret = doc_set_extra_page_mode(docg3); + if (ret) + goto out; + + doc_flash_sequence(docg3, DOC_SEQ_READ); + sector = (block0 << DOC_ADDR_BLOCK_SHIFT) + (page & DOC_ADDR_PAGE_MASK); + doc_flash_command(docg3, DOC_CMD_PROG_BLOCK_ADDR); + doc_setup_addr_sector(docg3, sector); + + sector = (block1 << DOC_ADDR_BLOCK_SHIFT) + (page & DOC_ADDR_PAGE_MASK); + doc_flash_command(docg3, DOC_CMD_PROG_BLOCK_ADDR); + doc_setup_addr_sector(docg3, sector); + doc_delay(docg3, 1); + +out: + return ret; +} + +/** + * doc_write_seek - Set both flash planes to the specified block, page for writing + * @docg3: the device + * @block0: the first plane block index + * @block1: the second plane block index + * @page: the page index within the block + * @ofs: offset in page to write + * + * Programs the flash even and odd planes to the specific block and page. + * Alternatively, programs the flash to the wear area of the specified page. + */ +static int doc_write_seek(struct docg3 *docg3, int block0, int block1, int page, + int ofs) +{ + int ret = 0, sector; + + doc_dbg("doc_write_seek(blocks=(%d,%d), page=%d, ofs=%d)\n", + block0, block1, page, ofs); + + doc_set_reliable_mode(docg3); + + if (ofs < 2 * DOC_LAYOUT_PAGE_SIZE) { + doc_flash_sequence(docg3, DOC_SEQ_SET_PLANE1); + doc_flash_command(docg3, DOC_CMD_READ_PLANE1); + doc_delay(docg3, 2); + } else { + doc_flash_sequence(docg3, DOC_SEQ_SET_PLANE2); + doc_flash_command(docg3, DOC_CMD_READ_PLANE2); + doc_delay(docg3, 2); + } + + doc_flash_sequence(docg3, DOC_SEQ_PAGE_SETUP); + doc_flash_command(docg3, DOC_CMD_PROG_CYCLE1); + + sector = (block0 << DOC_ADDR_BLOCK_SHIFT) + (page & DOC_ADDR_PAGE_MASK); + doc_setup_writeaddr_sector(docg3, sector, ofs); + + doc_flash_command(docg3, DOC_CMD_PROG_CYCLE3); + doc_delay(docg3, 2); + ret = doc_wait_ready(docg3); + if (ret) + goto out; + + doc_flash_command(docg3, DOC_CMD_PROG_CYCLE1); + sector = (block1 << DOC_ADDR_BLOCK_SHIFT) + (page & DOC_ADDR_PAGE_MASK); + doc_setup_writeaddr_sector(docg3, sector, ofs); + doc_delay(docg3, 1); + +out: + return ret; +} + + +/** + * doc_read_page_ecc_init - Initialize hardware ECC engine + * @docg3: the device + * @len: the number of bytes covered by the ECC (BCH covered) + * + * The function does initialize the hardware ECC engine to compute the Hamming + * ECC (on 1 byte) and the BCH hardware ECC (on 7 bytes). + * + * Return 0 if succeeded, -EIO on error + */ +static int doc_read_page_ecc_init(struct docg3 *docg3, int len) +{ + doc_writew(docg3, DOC_ECCCONF0_READ_MODE + | DOC_ECCCONF0_BCH_ENABLE | DOC_ECCCONF0_HAMMING_ENABLE + | (len & DOC_ECCCONF0_DATA_BYTES_MASK), + DOC_ECCCONF0); + doc_delay(docg3, 4); + doc_register_readb(docg3, DOC_FLASHCONTROL); + return doc_wait_ready(docg3); +} + +/** + * doc_write_page_ecc_init - Initialize hardware BCH ECC engine + * @docg3: the device + * @len: the number of bytes covered by the ECC (BCH covered) + * + * The function does initialize the hardware ECC engine to compute the Hamming + * ECC (on 1 byte) and the BCH hardware ECC (on 7 bytes). + * + * Return 0 if succeeded, -EIO on error + */ +static int doc_write_page_ecc_init(struct docg3 *docg3, int len) +{ + doc_writew(docg3, DOC_ECCCONF0_WRITE_MODE + | DOC_ECCCONF0_BCH_ENABLE | DOC_ECCCONF0_HAMMING_ENABLE + | (len & DOC_ECCCONF0_DATA_BYTES_MASK), + DOC_ECCCONF0); + doc_delay(docg3, 4); + doc_register_readb(docg3, DOC_FLASHCONTROL); + return doc_wait_ready(docg3); +} + +/** + * doc_ecc_disable - Disable Hamming and BCH ECC hardware calculator + * @docg3: the device + * + * Disables the hardware ECC generator and checker, for unchecked reads (as when + * reading OOB only or write status byte). + */ +static void doc_ecc_disable(struct docg3 *docg3) +{ + doc_writew(docg3, DOC_ECCCONF0_READ_MODE, DOC_ECCCONF0); + doc_delay(docg3, 4); +} + +/** + * doc_hamming_ecc_init - Initialize hardware Hamming ECC engine + * @docg3: the device + * @nb_bytes: the number of bytes covered by the ECC (Hamming covered) + * + * This function programs the ECC hardware to compute the hamming code on the + * last provided N bytes to the hardware generator. + */ +static void doc_hamming_ecc_init(struct docg3 *docg3, int nb_bytes) +{ + u8 ecc_conf1; + + ecc_conf1 = doc_register_readb(docg3, DOC_ECCCONF1); + ecc_conf1 &= ~DOC_ECCCONF1_HAMMING_BITS_MASK; + ecc_conf1 |= (nb_bytes & DOC_ECCCONF1_HAMMING_BITS_MASK); + doc_writeb(docg3, ecc_conf1, DOC_ECCCONF1); +} + +/** + * doc_ecc_bch_fix_data - Fix if need be read data from flash + * @docg3: the device + * @buf: the buffer of read data (512 + 7 + 1 bytes) + * @hwecc: the hardware calculated ECC. + * It's in fact recv_ecc ^ calc_ecc, where recv_ecc was read from OOB + * area data, and calc_ecc the ECC calculated by the hardware generator. + * + * Checks if the received data matches the ECC, and if an error is detected, + * tries to fix the bit flips (at most 4) in the buffer buf. As the docg3 + * understands the (data, ecc, syndroms) in an inverted order in comparison to + * the BCH library, the function reverses the order of bits (ie. bit7 and bit0, + * bit6 and bit 1, ...) for all ECC data. + * + * The hardware ecc unit produces oob_ecc ^ calc_ecc. The kernel's bch + * algorithm is used to decode this. However the hw operates on page + * data in a bit order that is the reverse of that of the bch alg, + * requiring that the bits be reversed on the result. Thanks to Ivan + * Djelic for his analysis. + * + * Returns number of fixed bits (0, 1, 2, 3, 4) or -EBADMSG if too many bit + * errors were detected and cannot be fixed. + */ +static int doc_ecc_bch_fix_data(struct docg3 *docg3, void *buf, u8 *hwecc) +{ + u8 ecc[DOC_ECC_BCH_SIZE]; + int errorpos[DOC_ECC_BCH_T], i, numerrs; + + for (i = 0; i < DOC_ECC_BCH_SIZE; i++) + ecc[i] = bitrev8(hwecc[i]); + numerrs = decode_bch(docg3->cascade->bch, NULL, + DOC_ECC_BCH_COVERED_BYTES, + NULL, ecc, NULL, errorpos); + BUG_ON(numerrs == -EINVAL); + if (numerrs < 0) + goto out; + + for (i = 0; i < numerrs; i++) + errorpos[i] = (errorpos[i] & ~7) | (7 - (errorpos[i] & 7)); + for (i = 0; i < numerrs; i++) + if (errorpos[i] < DOC_ECC_BCH_COVERED_BYTES*8) + /* error is located in data, correct it */ + change_bit(errorpos[i], buf); +out: + doc_dbg("doc_ecc_bch_fix_data: flipped %d bits\n", numerrs); + return numerrs; +} + + +/** + * doc_read_page_prepare - Prepares reading data from a flash page + * @docg3: the device + * @block0: the first plane block index on flash memory + * @block1: the second plane block index on flash memory + * @page: the page index in the block + * @offset: the offset in the page (must be a multiple of 4) + * + * Prepares the page to be read in the flash memory : + * - tell ASIC to map the flash pages + * - tell ASIC to be in read mode + * + * After a call to this method, a call to doc_read_page_finish is mandatory, + * to end the read cycle of the flash. + * + * Read data from a flash page. The length to be read must be between 0 and + * (page_size + oob_size + wear_size), ie. 532, and a multiple of 4 (because + * the extra bytes reading is not implemented). + * + * As pages are grouped by 2 (in 2 planes), reading from a page must be done + * in two steps: + * - one read of 512 bytes at offset 0 + * - one read of 512 bytes at offset 512 + 16 + * + * Returns 0 if successful, -EIO if a read error occurred. + */ +static int doc_read_page_prepare(struct docg3 *docg3, int block0, int block1, + int page, int offset) +{ + int wear_area = 0, ret = 0; + + doc_dbg("doc_read_page_prepare(blocks=(%d,%d), page=%d, ofsInPage=%d)\n", + block0, block1, page, offset); + if (offset >= DOC_LAYOUT_WEAR_OFFSET) + wear_area = 1; + if (!wear_area && offset > (DOC_LAYOUT_PAGE_OOB_SIZE * 2)) + return -EINVAL; + + doc_set_device_id(docg3, docg3->device_id); + ret = doc_reset_seq(docg3); + if (ret) + goto err; + + /* Program the flash address block and page */ + ret = doc_read_seek(docg3, block0, block1, page, wear_area, offset); + if (ret) + goto err; + + doc_flash_command(docg3, DOC_CMD_READ_ALL_PLANES); + doc_delay(docg3, 2); + doc_wait_ready(docg3); + + doc_flash_command(docg3, DOC_CMD_SET_ADDR_READ); + doc_delay(docg3, 1); + if (offset >= DOC_LAYOUT_PAGE_SIZE * 2) + offset -= 2 * DOC_LAYOUT_PAGE_SIZE; + doc_flash_address(docg3, offset >> 2); + doc_delay(docg3, 1); + doc_wait_ready(docg3); + + doc_flash_command(docg3, DOC_CMD_READ_FLASH); + + return 0; +err: + doc_writeb(docg3, 0, DOC_DATAEND); + doc_delay(docg3, 2); + return -EIO; +} + +/** + * doc_read_page_getbytes - Reads bytes from a prepared page + * @docg3: the device + * @len: the number of bytes to be read (must be a multiple of 4) + * @buf: the buffer to be filled in (or NULL is forget bytes) + * @first: 1 if first time read, DOC_READADDRESS should be set + * @last_odd: 1 if last read ended up on an odd byte + * + * Reads bytes from a prepared page. There is a trickery here : if the last read + * ended up on an odd offset in the 1024 bytes double page, ie. between the 2 + * planes, the first byte must be read apart. If a word (16bit) read was used, + * the read would return the byte of plane 2 as low *and* high endian, which + * will mess the read. + * + */ +static int doc_read_page_getbytes(struct docg3 *docg3, int len, u_char *buf, + int first, int last_odd) +{ + if (last_odd && len > 0) { + doc_read_data_area(docg3, buf, 1, first); + doc_read_data_area(docg3, buf ? buf + 1 : buf, len - 1, 0); + } else { + doc_read_data_area(docg3, buf, len, first); + } + doc_delay(docg3, 2); + return len; +} + +/** + * doc_write_page_putbytes - Writes bytes into a prepared page + * @docg3: the device + * @len: the number of bytes to be written + * @buf: the buffer of input bytes + * + */ +static void doc_write_page_putbytes(struct docg3 *docg3, int len, + const u_char *buf) +{ + doc_write_data_area(docg3, buf, len); + doc_delay(docg3, 2); +} + +/** + * doc_get_bch_hw_ecc - Get hardware calculated BCH ECC + * @docg3: the device + * @hwecc: the array of 7 integers where the hardware ecc will be stored + */ +static void doc_get_bch_hw_ecc(struct docg3 *docg3, u8 *hwecc) +{ + int i; + + for (i = 0; i < DOC_ECC_BCH_SIZE; i++) + hwecc[i] = doc_register_readb(docg3, DOC_BCH_HW_ECC(i)); +} + +/** + * doc_page_finish - Ends reading/writing of a flash page + * @docg3: the device + */ +static void doc_page_finish(struct docg3 *docg3) +{ + doc_writeb(docg3, 0, DOC_DATAEND); + doc_delay(docg3, 2); +} + +/** + * doc_read_page_finish - Ends reading of a flash page + * @docg3: the device + * + * As a side effect, resets the chip selector to 0. This ensures that after each + * read operation, the floor 0 is selected. Therefore, if the systems halts, the + * reboot will boot on floor 0, where the IPL is. + */ +static void doc_read_page_finish(struct docg3 *docg3) +{ + doc_page_finish(docg3); + doc_set_device_id(docg3, 0); +} + +/** + * calc_block_sector - Calculate blocks, pages and ofs. + + * @from: offset in flash + * @block0: first plane block index calculated + * @block1: second plane block index calculated + * @page: page calculated + * @ofs: offset in page + * @reliable: 0 if docg3 in normal mode, 1 if docg3 in fast mode, 2 if docg3 in + * reliable mode. + * + * The calculation is based on the reliable/normal mode. In normal mode, the 64 + * pages of a block are available. In reliable mode, as pages 2*n and 2*n+1 are + * clones, only 32 pages per block are available. + */ +static void calc_block_sector(loff_t from, int *block0, int *block1, int *page, + int *ofs, int reliable) +{ + uint sector, pages_biblock; + + pages_biblock = DOC_LAYOUT_PAGES_PER_BLOCK * DOC_LAYOUT_NBPLANES; + if (reliable == 1 || reliable == 2) + pages_biblock /= 2; + + sector = from / DOC_LAYOUT_PAGE_SIZE; + *block0 = sector / pages_biblock * DOC_LAYOUT_NBPLANES; + *block1 = *block0 + 1; + *page = sector % pages_biblock; + *page /= DOC_LAYOUT_NBPLANES; + if (reliable == 1 || reliable == 2) + *page *= 2; + if (sector % 2) + *ofs = DOC_LAYOUT_PAGE_OOB_SIZE; + else + *ofs = 0; +} + +/** + * doc_read_oob - Read out of band bytes from flash + * @mtd: the device + * @from: the offset from first block and first page, in bytes, aligned on page + * size + * @ops: the mtd oob structure + * + * Reads flash memory OOB area of pages. + * + * Returns 0 if read successful, of -EIO, -EINVAL if an error occurred + */ +static int doc_read_oob(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct docg3 *docg3 = mtd->priv; + int block0, block1, page, ret, skip, ofs = 0; + u8 *oobbuf = ops->oobbuf; + u8 *buf = ops->datbuf; + size_t len, ooblen, nbdata, nboob; + u8 hwecc[DOC_ECC_BCH_SIZE], eccconf1; + int max_bitflips = 0; + + if (buf) + len = ops->len; + else + len = 0; + if (oobbuf) + ooblen = ops->ooblen; + else + ooblen = 0; + + if (oobbuf && ops->mode == MTD_OPS_PLACE_OOB) + oobbuf += ops->ooboffs; + + doc_dbg("doc_read_oob(from=%lld, mode=%d, data=(%p:%zu), oob=(%p:%zu))\n", + from, ops->mode, buf, len, oobbuf, ooblen); + if (ooblen % DOC_LAYOUT_OOB_SIZE) + return -EINVAL; + + if (from + len > mtd->size) + return -EINVAL; + + ops->oobretlen = 0; + ops->retlen = 0; + ret = 0; + skip = from % DOC_LAYOUT_PAGE_SIZE; + mutex_lock(&docg3->cascade->lock); + while (ret >= 0 && (len > 0 || ooblen > 0)) { + calc_block_sector(from - skip, &block0, &block1, &page, &ofs, + docg3->reliable); + nbdata = min_t(size_t, len, DOC_LAYOUT_PAGE_SIZE - skip); + nboob = min_t(size_t, ooblen, (size_t)DOC_LAYOUT_OOB_SIZE); + ret = doc_read_page_prepare(docg3, block0, block1, page, ofs); + if (ret < 0) + goto out; + ret = doc_read_page_ecc_init(docg3, DOC_ECC_BCH_TOTAL_BYTES); + if (ret < 0) + goto err_in_read; + ret = doc_read_page_getbytes(docg3, skip, NULL, 1, 0); + if (ret < skip) + goto err_in_read; + ret = doc_read_page_getbytes(docg3, nbdata, buf, 0, skip % 2); + if (ret < nbdata) + goto err_in_read; + doc_read_page_getbytes(docg3, + DOC_LAYOUT_PAGE_SIZE - nbdata - skip, + NULL, 0, (skip + nbdata) % 2); + ret = doc_read_page_getbytes(docg3, nboob, oobbuf, 0, 0); + if (ret < nboob) + goto err_in_read; + doc_read_page_getbytes(docg3, DOC_LAYOUT_OOB_SIZE - nboob, + NULL, 0, nboob % 2); + + doc_get_bch_hw_ecc(docg3, hwecc); + eccconf1 = doc_register_readb(docg3, DOC_ECCCONF1); + + if (nboob >= DOC_LAYOUT_OOB_SIZE) { + doc_dbg("OOB - INFO: %*phC\n", 7, oobbuf); + doc_dbg("OOB - HAMMING: %02x\n", oobbuf[7]); + doc_dbg("OOB - BCH_ECC: %*phC\n", 7, oobbuf + 8); + doc_dbg("OOB - UNUSED: %02x\n", oobbuf[15]); + } + doc_dbg("ECC checks: ECCConf1=%x\n", eccconf1); + doc_dbg("ECC HW_ECC: %*phC\n", 7, hwecc); + + ret = -EIO; + if (is_prot_seq_error(docg3)) + goto err_in_read; + ret = 0; + if ((block0 >= DOC_LAYOUT_BLOCK_FIRST_DATA) && + (eccconf1 & DOC_ECCCONF1_BCH_SYNDROM_ERR) && + (eccconf1 & DOC_ECCCONF1_PAGE_IS_WRITTEN) && + (ops->mode != MTD_OPS_RAW) && + (nbdata == DOC_LAYOUT_PAGE_SIZE)) { + ret = doc_ecc_bch_fix_data(docg3, buf, hwecc); + if (ret < 0) { + mtd->ecc_stats.failed++; + ret = -EBADMSG; + } + if (ret > 0) { + mtd->ecc_stats.corrected += ret; + max_bitflips = max(max_bitflips, ret); + ret = max_bitflips; + } + } + + doc_read_page_finish(docg3); + ops->retlen += nbdata; + ops->oobretlen += nboob; + buf += nbdata; + oobbuf += nboob; + len -= nbdata; + ooblen -= nboob; + from += DOC_LAYOUT_PAGE_SIZE; + skip = 0; + } + +out: + mutex_unlock(&docg3->cascade->lock); + return ret; +err_in_read: + doc_read_page_finish(docg3); + goto out; +} + +/** + * doc_read - Read bytes from flash + * @mtd: the device + * @from: the offset from first block and first page, in bytes, aligned on page + * size + * @len: the number of bytes to read (must be a multiple of 4) + * @retlen: the number of bytes actually read + * @buf: the filled in buffer + * + * Reads flash memory pages. This function does not read the OOB chunk, but only + * the page data. + * + * Returns 0 if read successful, of -EIO, -EINVAL if an error occurred + */ +static int doc_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct mtd_oob_ops ops; + size_t ret; + + memset(&ops, 0, sizeof(ops)); + ops.datbuf = buf; + ops.len = len; + ops.mode = MTD_OPS_AUTO_OOB; + + ret = doc_read_oob(mtd, from, &ops); + *retlen = ops.retlen; + return ret; +} + +static int doc_reload_bbt(struct docg3 *docg3) +{ + int block = DOC_LAYOUT_BLOCK_BBT; + int ret = 0, nbpages, page; + u_char *buf = docg3->bbt; + + nbpages = DIV_ROUND_UP(docg3->max_block + 1, 8 * DOC_LAYOUT_PAGE_SIZE); + for (page = 0; !ret && (page < nbpages); page++) { + ret = doc_read_page_prepare(docg3, block, block + 1, + page + DOC_LAYOUT_PAGE_BBT, 0); + if (!ret) + ret = doc_read_page_ecc_init(docg3, + DOC_LAYOUT_PAGE_SIZE); + if (!ret) + doc_read_page_getbytes(docg3, DOC_LAYOUT_PAGE_SIZE, + buf, 1, 0); + buf += DOC_LAYOUT_PAGE_SIZE; + } + doc_read_page_finish(docg3); + return ret; +} + +/** + * doc_block_isbad - Checks whether a block is good or not + * @mtd: the device + * @from: the offset to find the correct block + * + * Returns 1 if block is bad, 0 if block is good + */ +static int doc_block_isbad(struct mtd_info *mtd, loff_t from) +{ + struct docg3 *docg3 = mtd->priv; + int block0, block1, page, ofs, is_good; + + calc_block_sector(from, &block0, &block1, &page, &ofs, + docg3->reliable); + doc_dbg("doc_block_isbad(from=%lld) => block=(%d,%d), page=%d, ofs=%d\n", + from, block0, block1, page, ofs); + + if (block0 < DOC_LAYOUT_BLOCK_FIRST_DATA) + return 0; + if (block1 > docg3->max_block) + return -EINVAL; + + is_good = docg3->bbt[block0 >> 3] & (1 << (block0 & 0x7)); + return !is_good; +} + +#if 0 +/** + * doc_get_erase_count - Get block erase count + * @docg3: the device + * @from: the offset in which the block is. + * + * Get the number of times a block was erased. The number is the maximum of + * erase times between first and second plane (which should be equal normally). + * + * Returns The number of erases, or -EINVAL or -EIO on error. + */ +static int doc_get_erase_count(struct docg3 *docg3, loff_t from) +{ + u8 buf[DOC_LAYOUT_WEAR_SIZE]; + int ret, plane1_erase_count, plane2_erase_count; + int block0, block1, page, ofs; + + doc_dbg("doc_get_erase_count(from=%lld, buf=%p)\n", from, buf); + if (from % DOC_LAYOUT_PAGE_SIZE) + return -EINVAL; + calc_block_sector(from, &block0, &block1, &page, &ofs, docg3->reliable); + if (block1 > docg3->max_block) + return -EINVAL; + + ret = doc_reset_seq(docg3); + if (!ret) + ret = doc_read_page_prepare(docg3, block0, block1, page, + ofs + DOC_LAYOUT_WEAR_OFFSET, 0); + if (!ret) + ret = doc_read_page_getbytes(docg3, DOC_LAYOUT_WEAR_SIZE, + buf, 1, 0); + doc_read_page_finish(docg3); + + if (ret || (buf[0] != DOC_ERASE_MARK) || (buf[2] != DOC_ERASE_MARK)) + return -EIO; + plane1_erase_count = (u8)(~buf[1]) | ((u8)(~buf[4]) << 8) + | ((u8)(~buf[5]) << 16); + plane2_erase_count = (u8)(~buf[3]) | ((u8)(~buf[6]) << 8) + | ((u8)(~buf[7]) << 16); + + return max(plane1_erase_count, plane2_erase_count); +} +#endif + +/** + * doc_get_op_status - get erase/write operation status + * @docg3: the device + * + * Queries the status from the chip, and returns it + * + * Returns the status (bits DOC_PLANES_STATUS_*) + */ +static int doc_get_op_status(struct docg3 *docg3) +{ + u8 status; + + doc_flash_sequence(docg3, DOC_SEQ_PLANES_STATUS); + doc_flash_command(docg3, DOC_CMD_PLANES_STATUS); + doc_delay(docg3, 5); + + doc_ecc_disable(docg3); + doc_read_data_area(docg3, &status, 1, 1); + return status; +} + +/** + * doc_write_erase_wait_status - wait for write or erase completion + * @docg3: the device + * + * Wait for the chip to be ready again after erase or write operation, and check + * erase/write status. + * + * Returns 0 if erase successful, -EIO if erase/write issue, -ETIMEOUT if + * timeout + */ +static int doc_write_erase_wait_status(struct docg3 *docg3) +{ + int i, status, ret = 0; + + for (i = 0; !doc_is_ready(docg3) && i < 5; i++) + msleep(20); + if (!doc_is_ready(docg3)) { + doc_dbg("Timeout reached and the chip is still not ready\n"); + ret = -EAGAIN; + goto out; + } + + status = doc_get_op_status(docg3); + if (status & DOC_PLANES_STATUS_FAIL) { + doc_dbg("Erase/Write failed on (a) plane(s), status = %x\n", + status); + ret = -EIO; + } + +out: + doc_page_finish(docg3); + return ret; +} + +/** + * doc_erase_block - Erase a couple of blocks + * @docg3: the device + * @block0: the first block to erase (leftmost plane) + * @block1: the second block to erase (rightmost plane) + * + * Erase both blocks, and return operation status + * + * Returns 0 if erase successful, -EIO if erase issue, -ETIMEOUT if chip not + * ready for too long + */ +static int doc_erase_block(struct docg3 *docg3, int block0, int block1) +{ + int ret, sector; + + doc_dbg("doc_erase_block(blocks=(%d,%d))\n", block0, block1); + ret = doc_reset_seq(docg3); + if (ret) + return -EIO; + + doc_set_reliable_mode(docg3); + doc_flash_sequence(docg3, DOC_SEQ_ERASE); + + sector = block0 << DOC_ADDR_BLOCK_SHIFT; + doc_flash_command(docg3, DOC_CMD_PROG_BLOCK_ADDR); + doc_setup_addr_sector(docg3, sector); + sector = block1 << DOC_ADDR_BLOCK_SHIFT; + doc_flash_command(docg3, DOC_CMD_PROG_BLOCK_ADDR); + doc_setup_addr_sector(docg3, sector); + doc_delay(docg3, 1); + + doc_flash_command(docg3, DOC_CMD_ERASECYCLE2); + doc_delay(docg3, 2); + + if (is_prot_seq_error(docg3)) { + doc_err("Erase blocks %d,%d error\n", block0, block1); + return -EIO; + } + + return doc_write_erase_wait_status(docg3); +} + +/** + * doc_erase - Erase a portion of the chip + * @mtd: the device + * @info: the erase info + * + * Erase a bunch of contiguous blocks, by pairs, as a "mtd" page of 1024 is + * split into 2 pages of 512 bytes on 2 contiguous blocks. + * + * Returns 0 if erase successful, -EINVAL if addressing error, -EIO if erase + * issue + */ +static int doc_erase(struct mtd_info *mtd, struct erase_info *info) +{ + struct docg3 *docg3 = mtd->priv; + uint64_t len; + int block0, block1, page, ret, ofs = 0; + + doc_dbg("doc_erase(from=%lld, len=%lld\n", info->addr, info->len); + + info->state = MTD_ERASE_PENDING; + calc_block_sector(info->addr + info->len, &block0, &block1, &page, + &ofs, docg3->reliable); + ret = -EINVAL; + if (info->addr + info->len > mtd->size || page || ofs) + goto reset_err; + + ret = 0; + calc_block_sector(info->addr, &block0, &block1, &page, &ofs, + docg3->reliable); + mutex_lock(&docg3->cascade->lock); + doc_set_device_id(docg3, docg3->device_id); + doc_set_reliable_mode(docg3); + for (len = info->len; !ret && len > 0; len -= mtd->erasesize) { + info->state = MTD_ERASING; + ret = doc_erase_block(docg3, block0, block1); + block0 += 2; + block1 += 2; + } + mutex_unlock(&docg3->cascade->lock); + + if (ret) + goto reset_err; + + info->state = MTD_ERASE_DONE; + return 0; + +reset_err: + info->state = MTD_ERASE_FAILED; + return ret; +} + +/** + * doc_write_page - Write a single page to the chip + * @docg3: the device + * @to: the offset from first block and first page, in bytes, aligned on page + * size + * @buf: buffer to get bytes from + * @oob: buffer to get out of band bytes from (can be NULL if no OOB should be + * written) + * @autoecc: if 0, all 16 bytes from OOB are taken, regardless of HW Hamming or + * BCH computations. If 1, only bytes 0-7 and byte 15 are taken, + * remaining ones are filled with hardware Hamming and BCH + * computations. Its value is not meaningfull is oob == NULL. + * + * Write one full page (ie. 1 page split on two planes), of 512 bytes, with the + * OOB data. The OOB ECC is automatically computed by the hardware Hamming and + * BCH generator if autoecc is not null. + * + * Returns 0 if write successful, -EIO if write error, -EAGAIN if timeout + */ +static int doc_write_page(struct docg3 *docg3, loff_t to, const u_char *buf, + const u_char *oob, int autoecc) +{ + int block0, block1, page, ret, ofs = 0; + u8 hwecc[DOC_ECC_BCH_SIZE], hamming; + + doc_dbg("doc_write_page(to=%lld)\n", to); + calc_block_sector(to, &block0, &block1, &page, &ofs, docg3->reliable); + + doc_set_device_id(docg3, docg3->device_id); + ret = doc_reset_seq(docg3); + if (ret) + goto err; + + /* Program the flash address block and page */ + ret = doc_write_seek(docg3, block0, block1, page, ofs); + if (ret) + goto err; + + doc_write_page_ecc_init(docg3, DOC_ECC_BCH_TOTAL_BYTES); + doc_delay(docg3, 2); + doc_write_page_putbytes(docg3, DOC_LAYOUT_PAGE_SIZE, buf); + + if (oob && autoecc) { + doc_write_page_putbytes(docg3, DOC_LAYOUT_OOB_PAGEINFO_SZ, oob); + doc_delay(docg3, 2); + oob += DOC_LAYOUT_OOB_UNUSED_OFS; + + hamming = doc_register_readb(docg3, DOC_HAMMINGPARITY); + doc_delay(docg3, 2); + doc_write_page_putbytes(docg3, DOC_LAYOUT_OOB_HAMMING_SZ, + &hamming); + doc_delay(docg3, 2); + + doc_get_bch_hw_ecc(docg3, hwecc); + doc_write_page_putbytes(docg3, DOC_LAYOUT_OOB_BCH_SZ, hwecc); + doc_delay(docg3, 2); + + doc_write_page_putbytes(docg3, DOC_LAYOUT_OOB_UNUSED_SZ, oob); + } + if (oob && !autoecc) + doc_write_page_putbytes(docg3, DOC_LAYOUT_OOB_SIZE, oob); + + doc_delay(docg3, 2); + doc_page_finish(docg3); + doc_delay(docg3, 2); + doc_flash_command(docg3, DOC_CMD_PROG_CYCLE2); + doc_delay(docg3, 2); + + /* + * The wait status will perform another doc_page_finish() call, but that + * seems to please the docg3, so leave it. + */ + ret = doc_write_erase_wait_status(docg3); + return ret; +err: + doc_read_page_finish(docg3); + return ret; +} + +/** + * doc_guess_autoecc - Guess autoecc mode from mbd_oob_ops + * @ops: the oob operations + * + * Returns 0 or 1 if success, -EINVAL if invalid oob mode + */ +static int doc_guess_autoecc(struct mtd_oob_ops *ops) +{ + int autoecc; + + switch (ops->mode) { + case MTD_OPS_PLACE_OOB: + case MTD_OPS_AUTO_OOB: + autoecc = 1; + break; + case MTD_OPS_RAW: + autoecc = 0; + break; + default: + autoecc = -EINVAL; + } + return autoecc; +} + +/** + * doc_fill_autooob - Fill a 16 bytes OOB from 8 non-ECC bytes + * @dst: the target 16 bytes OOB buffer + * @oobsrc: the source 8 bytes non-ECC OOB buffer + * + */ +static void doc_fill_autooob(u8 *dst, u8 *oobsrc) +{ + memcpy(dst, oobsrc, DOC_LAYOUT_OOB_PAGEINFO_SZ); + dst[DOC_LAYOUT_OOB_UNUSED_OFS] = oobsrc[DOC_LAYOUT_OOB_PAGEINFO_SZ]; +} + +/** + * doc_backup_oob - Backup OOB into docg3 structure + * @docg3: the device + * @to: the page offset in the chip + * @ops: the OOB size and buffer + * + * As the docg3 should write a page with its OOB in one pass, and some userland + * applications do write_oob() to setup the OOB and then write(), store the OOB + * into a temporary storage. This is very dangerous, as 2 concurrent + * applications could store an OOB, and then write their pages (which will + * result into one having its OOB corrupted). + * + * The only reliable way would be for userland to call doc_write_oob() with both + * the page data _and_ the OOB area. + * + * Returns 0 if success, -EINVAL if ops content invalid + */ +static int doc_backup_oob(struct docg3 *docg3, loff_t to, + struct mtd_oob_ops *ops) +{ + int ooblen = ops->ooblen, autoecc; + + if (ooblen != DOC_LAYOUT_OOB_SIZE) + return -EINVAL; + autoecc = doc_guess_autoecc(ops); + if (autoecc < 0) + return autoecc; + + docg3->oob_write_ofs = to; + docg3->oob_autoecc = autoecc; + if (ops->mode == MTD_OPS_AUTO_OOB) { + doc_fill_autooob(docg3->oob_write_buf, ops->oobbuf); + ops->oobretlen = 8; + } else { + memcpy(docg3->oob_write_buf, ops->oobbuf, DOC_LAYOUT_OOB_SIZE); + ops->oobretlen = DOC_LAYOUT_OOB_SIZE; + } + return 0; +} + +/** + * doc_write_oob - Write out of band bytes to flash + * @mtd: the device + * @ofs: the offset from first block and first page, in bytes, aligned on page + * size + * @ops: the mtd oob structure + * + * Either write OOB data into a temporary buffer, for the subsequent write + * page. The provided OOB should be 16 bytes long. If a data buffer is provided + * as well, issue the page write. + * Or provide data without OOB, and then a all zeroed OOB will be used (ECC will + * still be filled in if asked for). + * + * Returns 0 is successful, EINVAL if length is not 14 bytes + */ +static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, + struct mtd_oob_ops *ops) +{ + struct docg3 *docg3 = mtd->priv; + int ret, autoecc, oobdelta; + u8 *oobbuf = ops->oobbuf; + u8 *buf = ops->datbuf; + size_t len, ooblen; + u8 oob[DOC_LAYOUT_OOB_SIZE]; + + if (buf) + len = ops->len; + else + len = 0; + if (oobbuf) + ooblen = ops->ooblen; + else + ooblen = 0; + + if (oobbuf && ops->mode == MTD_OPS_PLACE_OOB) + oobbuf += ops->ooboffs; + + doc_dbg("doc_write_oob(from=%lld, mode=%d, data=(%p:%zu), oob=(%p:%zu))\n", + ofs, ops->mode, buf, len, oobbuf, ooblen); + switch (ops->mode) { + case MTD_OPS_PLACE_OOB: + case MTD_OPS_RAW: + oobdelta = mtd->oobsize; + break; + case MTD_OPS_AUTO_OOB: + oobdelta = mtd->ecclayout->oobavail; + break; + default: + return -EINVAL; + } + if ((len % DOC_LAYOUT_PAGE_SIZE) || (ooblen % oobdelta) || + (ofs % DOC_LAYOUT_PAGE_SIZE)) + return -EINVAL; + if (len && ooblen && + (len / DOC_LAYOUT_PAGE_SIZE) != (ooblen / oobdelta)) + return -EINVAL; + if (ofs + len > mtd->size) + return -EINVAL; + + ops->oobretlen = 0; + ops->retlen = 0; + ret = 0; + if (len == 0 && ooblen == 0) + return -EINVAL; + if (len == 0 && ooblen > 0) + return doc_backup_oob(docg3, ofs, ops); + + autoecc = doc_guess_autoecc(ops); + if (autoecc < 0) + return autoecc; + + mutex_lock(&docg3->cascade->lock); + while (!ret && len > 0) { + memset(oob, 0, sizeof(oob)); + if (ofs == docg3->oob_write_ofs) + memcpy(oob, docg3->oob_write_buf, DOC_LAYOUT_OOB_SIZE); + else if (ooblen > 0 && ops->mode == MTD_OPS_AUTO_OOB) + doc_fill_autooob(oob, oobbuf); + else if (ooblen > 0) + memcpy(oob, oobbuf, DOC_LAYOUT_OOB_SIZE); + ret = doc_write_page(docg3, ofs, buf, oob, autoecc); + + ofs += DOC_LAYOUT_PAGE_SIZE; + len -= DOC_LAYOUT_PAGE_SIZE; + buf += DOC_LAYOUT_PAGE_SIZE; + if (ooblen) { + oobbuf += oobdelta; + ooblen -= oobdelta; + ops->oobretlen += oobdelta; + } + ops->retlen += DOC_LAYOUT_PAGE_SIZE; + } + + doc_set_device_id(docg3, 0); + mutex_unlock(&docg3->cascade->lock); + return ret; +} + +/** + * doc_write - Write a buffer to the chip + * @mtd: the device + * @to: the offset from first block and first page, in bytes, aligned on page + * size + * @len: the number of bytes to write (must be a full page size, ie. 512) + * @retlen: the number of bytes actually written (0 or 512) + * @buf: the buffer to get bytes from + * + * Writes data to the chip. + * + * Returns 0 if write successful, -EIO if write error + */ +static int doc_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct docg3 *docg3 = mtd->priv; + int ret; + struct mtd_oob_ops ops; + + doc_dbg("doc_write(to=%lld, len=%zu)\n", to, len); + ops.datbuf = (char *)buf; + ops.len = len; + ops.mode = MTD_OPS_PLACE_OOB; + ops.oobbuf = NULL; + ops.ooblen = 0; + ops.ooboffs = 0; + + ret = doc_write_oob(mtd, to, &ops); + *retlen = ops.retlen; + return ret; +} + +static struct docg3 *sysfs_dev2docg3(struct device *dev, + struct device_attribute *attr) +{ + int floor; + struct platform_device *pdev = to_platform_device(dev); + struct mtd_info **docg3_floors = platform_get_drvdata(pdev); + + floor = attr->attr.name[1] - '0'; + if (floor < 0 || floor >= DOC_MAX_NBFLOORS) + return NULL; + else + return docg3_floors[floor]->priv; +} + +static ssize_t dps0_is_key_locked(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct docg3 *docg3 = sysfs_dev2docg3(dev, attr); + int dps0; + + mutex_lock(&docg3->cascade->lock); + doc_set_device_id(docg3, docg3->device_id); + dps0 = doc_register_readb(docg3, DOC_DPS0_STATUS); + doc_set_device_id(docg3, 0); + mutex_unlock(&docg3->cascade->lock); + + return sprintf(buf, "%d\n", !(dps0 & DOC_DPS_KEY_OK)); +} + +static ssize_t dps1_is_key_locked(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct docg3 *docg3 = sysfs_dev2docg3(dev, attr); + int dps1; + + mutex_lock(&docg3->cascade->lock); + doc_set_device_id(docg3, docg3->device_id); + dps1 = doc_register_readb(docg3, DOC_DPS1_STATUS); + doc_set_device_id(docg3, 0); + mutex_unlock(&docg3->cascade->lock); + + return sprintf(buf, "%d\n", !(dps1 & DOC_DPS_KEY_OK)); +} + +static ssize_t dps0_insert_key(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct docg3 *docg3 = sysfs_dev2docg3(dev, attr); + int i; + + if (count != DOC_LAYOUT_DPS_KEY_LENGTH) + return -EINVAL; + + mutex_lock(&docg3->cascade->lock); + doc_set_device_id(docg3, docg3->device_id); + for (i = 0; i < DOC_LAYOUT_DPS_KEY_LENGTH; i++) + doc_writeb(docg3, buf[i], DOC_DPS0_KEY); + doc_set_device_id(docg3, 0); + mutex_unlock(&docg3->cascade->lock); + return count; +} + +static ssize_t dps1_insert_key(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct docg3 *docg3 = sysfs_dev2docg3(dev, attr); + int i; + + if (count != DOC_LAYOUT_DPS_KEY_LENGTH) + return -EINVAL; + + mutex_lock(&docg3->cascade->lock); + doc_set_device_id(docg3, docg3->device_id); + for (i = 0; i < DOC_LAYOUT_DPS_KEY_LENGTH; i++) + doc_writeb(docg3, buf[i], DOC_DPS1_KEY); + doc_set_device_id(docg3, 0); + mutex_unlock(&docg3->cascade->lock); + return count; +} + +#define FLOOR_SYSFS(id) { \ + __ATTR(f##id##_dps0_is_keylocked, S_IRUGO, dps0_is_key_locked, NULL), \ + __ATTR(f##id##_dps1_is_keylocked, S_IRUGO, dps1_is_key_locked, NULL), \ + __ATTR(f##id##_dps0_protection_key, S_IWUSR|S_IWGRP, NULL, dps0_insert_key), \ + __ATTR(f##id##_dps1_protection_key, S_IWUSR|S_IWGRP, NULL, dps1_insert_key), \ +} + +static struct device_attribute doc_sys_attrs[DOC_MAX_NBFLOORS][4] = { + FLOOR_SYSFS(0), FLOOR_SYSFS(1), FLOOR_SYSFS(2), FLOOR_SYSFS(3) +}; + +static int doc_register_sysfs(struct platform_device *pdev, + struct docg3_cascade *cascade) +{ + int ret = 0, floor, i = 0; + struct device *dev = &pdev->dev; + + for (floor = 0; !ret && floor < DOC_MAX_NBFLOORS && + cascade->floors[floor]; floor++) + for (i = 0; !ret && i < 4; i++) + ret = device_create_file(dev, &doc_sys_attrs[floor][i]); + if (!ret) + return 0; + do { + while (--i >= 0) + device_remove_file(dev, &doc_sys_attrs[floor][i]); + i = 4; + } while (--floor >= 0); + return ret; +} + +static void doc_unregister_sysfs(struct platform_device *pdev, + struct docg3_cascade *cascade) +{ + struct device *dev = &pdev->dev; + int floor, i; + + for (floor = 0; floor < DOC_MAX_NBFLOORS && cascade->floors[floor]; + floor++) + for (i = 0; i < 4; i++) + device_remove_file(dev, &doc_sys_attrs[floor][i]); +} + +/* + * Debug sysfs entries + */ +static int dbg_flashctrl_show(struct seq_file *s, void *p) +{ + struct docg3 *docg3 = (struct docg3 *)s->private; + + int pos = 0; + u8 fctrl; + + mutex_lock(&docg3->cascade->lock); + fctrl = doc_register_readb(docg3, DOC_FLASHCONTROL); + mutex_unlock(&docg3->cascade->lock); + + pos += seq_printf(s, + "FlashControl : 0x%02x (%s,CE# %s,%s,%s,flash %s)\n", + fctrl, + fctrl & DOC_CTRL_VIOLATION ? "protocol violation" : "-", + fctrl & DOC_CTRL_CE ? "active" : "inactive", + fctrl & DOC_CTRL_PROTECTION_ERROR ? "protection error" : "-", + fctrl & DOC_CTRL_SEQUENCE_ERROR ? "sequence error" : "-", + fctrl & DOC_CTRL_FLASHREADY ? "ready" : "not ready"); + return pos; +} +DEBUGFS_RO_ATTR(flashcontrol, dbg_flashctrl_show); + +static int dbg_asicmode_show(struct seq_file *s, void *p) +{ + struct docg3 *docg3 = (struct docg3 *)s->private; + + int pos = 0, pctrl, mode; + + mutex_lock(&docg3->cascade->lock); + pctrl = doc_register_readb(docg3, DOC_ASICMODE); + mode = pctrl & 0x03; + mutex_unlock(&docg3->cascade->lock); + + pos += seq_printf(s, + "%04x : RAM_WE=%d,RSTIN_RESET=%d,BDETCT_RESET=%d,WRITE_ENABLE=%d,POWERDOWN=%d,MODE=%d%d (", + pctrl, + pctrl & DOC_ASICMODE_RAM_WE ? 1 : 0, + pctrl & DOC_ASICMODE_RSTIN_RESET ? 1 : 0, + pctrl & DOC_ASICMODE_BDETCT_RESET ? 1 : 0, + pctrl & DOC_ASICMODE_MDWREN ? 1 : 0, + pctrl & DOC_ASICMODE_POWERDOWN ? 1 : 0, + mode >> 1, mode & 0x1); + + switch (mode) { + case DOC_ASICMODE_RESET: + pos += seq_printf(s, "reset"); + break; + case DOC_ASICMODE_NORMAL: + pos += seq_printf(s, "normal"); + break; + case DOC_ASICMODE_POWERDOWN: + pos += seq_printf(s, "powerdown"); + break; + } + pos += seq_printf(s, ")\n"); + return pos; +} +DEBUGFS_RO_ATTR(asic_mode, dbg_asicmode_show); + +static int dbg_device_id_show(struct seq_file *s, void *p) +{ + struct docg3 *docg3 = (struct docg3 *)s->private; + int pos = 0; + int id; + + mutex_lock(&docg3->cascade->lock); + id = doc_register_readb(docg3, DOC_DEVICESELECT); + mutex_unlock(&docg3->cascade->lock); + + pos += seq_printf(s, "DeviceId = %d\n", id); + return pos; +} +DEBUGFS_RO_ATTR(device_id, dbg_device_id_show); + +static int dbg_protection_show(struct seq_file *s, void *p) +{ + struct docg3 *docg3 = (struct docg3 *)s->private; + int pos = 0; + int protect, dps0, dps0_low, dps0_high, dps1, dps1_low, dps1_high; + + mutex_lock(&docg3->cascade->lock); + protect = doc_register_readb(docg3, DOC_PROTECTION); + dps0 = doc_register_readb(docg3, DOC_DPS0_STATUS); + dps0_low = doc_register_readw(docg3, DOC_DPS0_ADDRLOW); + dps0_high = doc_register_readw(docg3, DOC_DPS0_ADDRHIGH); + dps1 = doc_register_readb(docg3, DOC_DPS1_STATUS); + dps1_low = doc_register_readw(docg3, DOC_DPS1_ADDRLOW); + dps1_high = doc_register_readw(docg3, DOC_DPS1_ADDRHIGH); + mutex_unlock(&docg3->cascade->lock); + + pos += seq_printf(s, "Protection = 0x%02x (", + protect); + if (protect & DOC_PROTECT_FOUNDRY_OTP_LOCK) + pos += seq_printf(s, "FOUNDRY_OTP_LOCK,"); + if (protect & DOC_PROTECT_CUSTOMER_OTP_LOCK) + pos += seq_printf(s, "CUSTOMER_OTP_LOCK,"); + if (protect & DOC_PROTECT_LOCK_INPUT) + pos += seq_printf(s, "LOCK_INPUT,"); + if (protect & DOC_PROTECT_STICKY_LOCK) + pos += seq_printf(s, "STICKY_LOCK,"); + if (protect & DOC_PROTECT_PROTECTION_ENABLED) + pos += seq_printf(s, "PROTECTION ON,"); + if (protect & DOC_PROTECT_IPL_DOWNLOAD_LOCK) + pos += seq_printf(s, "IPL_DOWNLOAD_LOCK,"); + if (protect & DOC_PROTECT_PROTECTION_ERROR) + pos += seq_printf(s, "PROTECT_ERR,"); + else + pos += seq_printf(s, "NO_PROTECT_ERR"); + pos += seq_printf(s, ")\n"); + + pos += seq_printf(s, "DPS0 = 0x%02x : " + "Protected area [0x%x - 0x%x] : OTP=%d, READ=%d, " + "WRITE=%d, HW_LOCK=%d, KEY_OK=%d\n", + dps0, dps0_low, dps0_high, + !!(dps0 & DOC_DPS_OTP_PROTECTED), + !!(dps0 & DOC_DPS_READ_PROTECTED), + !!(dps0 & DOC_DPS_WRITE_PROTECTED), + !!(dps0 & DOC_DPS_HW_LOCK_ENABLED), + !!(dps0 & DOC_DPS_KEY_OK)); + pos += seq_printf(s, "DPS1 = 0x%02x : " + "Protected area [0x%x - 0x%x] : OTP=%d, READ=%d, " + "WRITE=%d, HW_LOCK=%d, KEY_OK=%d\n", + dps1, dps1_low, dps1_high, + !!(dps1 & DOC_DPS_OTP_PROTECTED), + !!(dps1 & DOC_DPS_READ_PROTECTED), + !!(dps1 & DOC_DPS_WRITE_PROTECTED), + !!(dps1 & DOC_DPS_HW_LOCK_ENABLED), + !!(dps1 & DOC_DPS_KEY_OK)); + return pos; +} +DEBUGFS_RO_ATTR(protection, dbg_protection_show); + +static int __init doc_dbg_register(struct docg3 *docg3) +{ + struct dentry *root, *entry; + + root = debugfs_create_dir("docg3", NULL); + if (!root) + return -ENOMEM; + + entry = debugfs_create_file("flashcontrol", S_IRUSR, root, docg3, + &flashcontrol_fops); + if (entry) + entry = debugfs_create_file("asic_mode", S_IRUSR, root, + docg3, &asic_mode_fops); + if (entry) + entry = debugfs_create_file("device_id", S_IRUSR, root, + docg3, &device_id_fops); + if (entry) + entry = debugfs_create_file("protection", S_IRUSR, root, + docg3, &protection_fops); + if (entry) { + docg3->debugfs_root = root; + return 0; + } else { + debugfs_remove_recursive(root); + return -ENOMEM; + } +} + +static void __exit doc_dbg_unregister(struct docg3 *docg3) +{ + debugfs_remove_recursive(docg3->debugfs_root); +} + +/** + * doc_set_driver_info - Fill the mtd_info structure and docg3 structure + * @chip_id: The chip ID of the supported chip + * @mtd: The structure to fill + */ +static void __init doc_set_driver_info(int chip_id, struct mtd_info *mtd) +{ + struct docg3 *docg3 = mtd->priv; + int cfg; + + cfg = doc_register_readb(docg3, DOC_CONFIGURATION); + docg3->if_cfg = (cfg & DOC_CONF_IF_CFG ? 1 : 0); + docg3->reliable = reliable_mode; + + switch (chip_id) { + case DOC_CHIPID_G3: + mtd->name = kasprintf(GFP_KERNEL, "docg3.%d", + docg3->device_id); + docg3->max_block = 2047; + break; + } + mtd->type = MTD_NANDFLASH; + mtd->flags = MTD_CAP_NANDFLASH; + mtd->size = (docg3->max_block + 1) * DOC_LAYOUT_BLOCK_SIZE; + if (docg3->reliable == 2) + mtd->size /= 2; + mtd->erasesize = DOC_LAYOUT_BLOCK_SIZE * DOC_LAYOUT_NBPLANES; + if (docg3->reliable == 2) + mtd->erasesize /= 2; + mtd->writebufsize = mtd->writesize = DOC_LAYOUT_PAGE_SIZE; + mtd->oobsize = DOC_LAYOUT_OOB_SIZE; + mtd->owner = THIS_MODULE; + mtd->_erase = doc_erase; + mtd->_read = doc_read; + mtd->_write = doc_write; + mtd->_read_oob = doc_read_oob; + mtd->_write_oob = doc_write_oob; + mtd->_block_isbad = doc_block_isbad; + mtd->ecclayout = &docg3_oobinfo; + mtd->ecc_strength = DOC_ECC_BCH_T; +} + +/** + * doc_probe_device - Check if a device is available + * @base: the io space where the device is probed + * @floor: the floor of the probed device + * @dev: the device + * @cascade: the cascade of chips this devices will belong to + * + * Checks whether a device at the specified IO range, and floor is available. + * + * Returns a mtd_info struct if there is a device, ENODEV if none found, ENOMEM + * if a memory allocation failed. If floor 0 is checked, a reset of the ASIC is + * launched. + */ +static struct mtd_info * __init +doc_probe_device(struct docg3_cascade *cascade, int floor, struct device *dev) +{ + int ret, bbt_nbpages; + u16 chip_id, chip_id_inv; + struct docg3 *docg3; + struct mtd_info *mtd; + + ret = -ENOMEM; + docg3 = kzalloc(sizeof(struct docg3), GFP_KERNEL); + if (!docg3) + goto nomem1; + mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL); + if (!mtd) + goto nomem2; + mtd->priv = docg3; + bbt_nbpages = DIV_ROUND_UP(docg3->max_block + 1, + 8 * DOC_LAYOUT_PAGE_SIZE); + docg3->bbt = kzalloc(bbt_nbpages * DOC_LAYOUT_PAGE_SIZE, GFP_KERNEL); + if (!docg3->bbt) + goto nomem3; + + docg3->dev = dev; + docg3->device_id = floor; + docg3->cascade = cascade; + doc_set_device_id(docg3, docg3->device_id); + if (!floor) + doc_set_asic_mode(docg3, DOC_ASICMODE_RESET); + doc_set_asic_mode(docg3, DOC_ASICMODE_NORMAL); + + chip_id = doc_register_readw(docg3, DOC_CHIPID); + chip_id_inv = doc_register_readw(docg3, DOC_CHIPID_INV); + + ret = 0; + if (chip_id != (u16)(~chip_id_inv)) { + goto nomem3; + } + + switch (chip_id) { + case DOC_CHIPID_G3: + doc_info("Found a G3 DiskOnChip at addr %p, floor %d\n", + docg3->cascade->base, floor); + break; + default: + doc_err("Chip id %04x is not a DiskOnChip G3 chip\n", chip_id); + goto nomem3; + } + + doc_set_driver_info(chip_id, mtd); + + doc_hamming_ecc_init(docg3, DOC_LAYOUT_OOB_PAGEINFO_SZ); + doc_reload_bbt(docg3); + return mtd; + +nomem3: + kfree(mtd); +nomem2: + kfree(docg3); +nomem1: + return ERR_PTR(ret); +} + +/** + * doc_release_device - Release a docg3 floor + * @mtd: the device + */ +static void doc_release_device(struct mtd_info *mtd) +{ + struct docg3 *docg3 = mtd->priv; + + mtd_device_unregister(mtd); + kfree(docg3->bbt); + kfree(docg3); + kfree(mtd->name); + kfree(mtd); +} + +/** + * docg3_resume - Awakens docg3 floor + * @pdev: platfrom device + * + * Returns 0 (always successful) + */ +static int docg3_resume(struct platform_device *pdev) +{ + int i; + struct docg3_cascade *cascade; + struct mtd_info **docg3_floors, *mtd; + struct docg3 *docg3; + + cascade = platform_get_drvdata(pdev); + docg3_floors = cascade->floors; + mtd = docg3_floors[0]; + docg3 = mtd->priv; + + doc_dbg("docg3_resume()\n"); + for (i = 0; i < 12; i++) + doc_readb(docg3, DOC_IOSPACE_IPL); + return 0; +} + +/** + * docg3_suspend - Put in low power mode the docg3 floor + * @pdev: platform device + * @state: power state + * + * Shuts off most of docg3 circuitery to lower power consumption. + * + * Returns 0 if suspend succeeded, -EIO if chip refused suspend + */ +static int docg3_suspend(struct platform_device *pdev, pm_message_t state) +{ + int floor, i; + struct docg3_cascade *cascade; + struct mtd_info **docg3_floors, *mtd; + struct docg3 *docg3; + u8 ctrl, pwr_down; + + cascade = platform_get_drvdata(pdev); + docg3_floors = cascade->floors; + for (floor = 0; floor < DOC_MAX_NBFLOORS; floor++) { + mtd = docg3_floors[floor]; + if (!mtd) + continue; + docg3 = mtd->priv; + + doc_writeb(docg3, floor, DOC_DEVICESELECT); + ctrl = doc_register_readb(docg3, DOC_FLASHCONTROL); + ctrl &= ~DOC_CTRL_VIOLATION & ~DOC_CTRL_CE; + doc_writeb(docg3, ctrl, DOC_FLASHCONTROL); + + for (i = 0; i < 10; i++) { + usleep_range(3000, 4000); + pwr_down = doc_register_readb(docg3, DOC_POWERMODE); + if (pwr_down & DOC_POWERDOWN_READY) + break; + } + if (pwr_down & DOC_POWERDOWN_READY) { + doc_dbg("docg3_suspend(): floor %d powerdown ok\n", + floor); + } else { + doc_err("docg3_suspend(): floor %d powerdown failed\n", + floor); + return -EIO; + } + } + + mtd = docg3_floors[0]; + docg3 = mtd->priv; + doc_set_asic_mode(docg3, DOC_ASICMODE_POWERDOWN); + return 0; +} + +/** + * doc_probe - Probe the IO space for a DiskOnChip G3 chip + * @pdev: platform device + * + * Probes for a G3 chip at the specified IO space in the platform data + * ressources. The floor 0 must be available. + * + * Returns 0 on success, -ENOMEM, -ENXIO on error + */ +static int __init docg3_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtd_info *mtd; + struct resource *ress; + void __iomem *base; + int ret, floor, found = 0; + struct docg3_cascade *cascade; + + ret = -ENXIO; + ress = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!ress) { + dev_err(dev, "No I/O memory resource defined\n"); + return ret; + } + base = devm_ioremap(dev, ress->start, DOC_IOSPACE_SIZE); + + ret = -ENOMEM; + cascade = devm_kzalloc(dev, sizeof(*cascade) * DOC_MAX_NBFLOORS, + GFP_KERNEL); + if (!cascade) + return ret; + cascade->base = base; + mutex_init(&cascade->lock); + cascade->bch = init_bch(DOC_ECC_BCH_M, DOC_ECC_BCH_T, + DOC_ECC_BCH_PRIMPOLY); + if (!cascade->bch) + return ret; + + for (floor = 0; floor < DOC_MAX_NBFLOORS; floor++) { + mtd = doc_probe_device(cascade, floor, dev); + if (IS_ERR(mtd)) { + ret = PTR_ERR(mtd); + goto err_probe; + } + if (!mtd) { + if (floor == 0) + goto notfound; + else + continue; + } + cascade->floors[floor] = mtd; + ret = mtd_device_parse_register(mtd, part_probes, NULL, NULL, + 0); + if (ret) + goto err_probe; + found++; + } + + ret = doc_register_sysfs(pdev, cascade); + if (ret) + goto err_probe; + if (!found) + goto notfound; + + platform_set_drvdata(pdev, cascade); + doc_dbg_register(cascade->floors[0]->priv); + return 0; + +notfound: + ret = -ENODEV; + dev_info(dev, "No supported DiskOnChip found\n"); +err_probe: + free_bch(cascade->bch); + for (floor = 0; floor < DOC_MAX_NBFLOORS; floor++) + if (cascade->floors[floor]) + doc_release_device(cascade->floors[floor]); + return ret; +} + +/** + * docg3_release - Release the driver + * @pdev: the platform device + * + * Returns 0 + */ +static int __exit docg3_release(struct platform_device *pdev) +{ + struct docg3_cascade *cascade = platform_get_drvdata(pdev); + struct docg3 *docg3 = cascade->floors[0]->priv; + int floor; + + doc_unregister_sysfs(pdev, cascade); + doc_dbg_unregister(docg3); + for (floor = 0; floor < DOC_MAX_NBFLOORS; floor++) + if (cascade->floors[floor]) + doc_release_device(cascade->floors[floor]); + + free_bch(docg3->cascade->bch); + return 0; +} + +static struct platform_driver g3_driver = { + .driver = { + .name = "docg3", + .owner = THIS_MODULE, + }, + .suspend = docg3_suspend, + .resume = docg3_resume, + .remove = __exit_p(docg3_release), +}; + +module_platform_driver_probe(g3_driver, docg3_probe); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Robert Jarzmik <robert.jarzmik@free.fr>"); +MODULE_DESCRIPTION("MTD driver for DiskOnChip G3"); diff --git a/drivers/mtd/devices/docg3.h b/drivers/mtd/devices/docg3.h new file mode 100644 index 00000000000..19fb93f96a3 --- /dev/null +++ b/drivers/mtd/devices/docg3.h @@ -0,0 +1,370 @@ +/* + * Handles the M-Systems DiskOnChip G3 chip + * + * Copyright (C) 2011 Robert Jarzmik + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _MTD_DOCG3_H +#define _MTD_DOCG3_H + +#include <linux/mtd/mtd.h> + +/* + * Flash memory areas : + * - 0x0000 .. 0x07ff : IPL + * - 0x0800 .. 0x0fff : Data area + * - 0x1000 .. 0x17ff : Registers + * - 0x1800 .. 0x1fff : Unknown + */ +#define DOC_IOSPACE_IPL 0x0000 +#define DOC_IOSPACE_DATA 0x0800 +#define DOC_IOSPACE_SIZE 0x2000 + +/* + * DOC G3 layout and adressing scheme + * A page address for the block "b", plane "P" and page "p": + * address = [bbbb bPpp pppp] + */ + +#define DOC_ADDR_PAGE_MASK 0x3f +#define DOC_ADDR_BLOCK_SHIFT 6 +#define DOC_LAYOUT_NBPLANES 2 +#define DOC_LAYOUT_PAGES_PER_BLOCK 64 +#define DOC_LAYOUT_PAGE_SIZE 512 +#define DOC_LAYOUT_OOB_SIZE 16 +#define DOC_LAYOUT_WEAR_SIZE 8 +#define DOC_LAYOUT_PAGE_OOB_SIZE \ + (DOC_LAYOUT_PAGE_SIZE + DOC_LAYOUT_OOB_SIZE) +#define DOC_LAYOUT_WEAR_OFFSET (DOC_LAYOUT_PAGE_OOB_SIZE * 2) +#define DOC_LAYOUT_BLOCK_SIZE \ + (DOC_LAYOUT_PAGES_PER_BLOCK * DOC_LAYOUT_PAGE_SIZE) + +/* + * ECC related constants + */ +#define DOC_ECC_BCH_M 14 +#define DOC_ECC_BCH_T 4 +#define DOC_ECC_BCH_PRIMPOLY 0x4443 +#define DOC_ECC_BCH_SIZE 7 +#define DOC_ECC_BCH_COVERED_BYTES \ + (DOC_LAYOUT_PAGE_SIZE + DOC_LAYOUT_OOB_PAGEINFO_SZ + \ + DOC_LAYOUT_OOB_HAMMING_SZ) +#define DOC_ECC_BCH_TOTAL_BYTES \ + (DOC_ECC_BCH_COVERED_BYTES + DOC_LAYOUT_OOB_BCH_SZ) + +/* + * Blocks distribution + */ +#define DOC_LAYOUT_BLOCK_BBT 0 +#define DOC_LAYOUT_BLOCK_OTP 0 +#define DOC_LAYOUT_BLOCK_FIRST_DATA 6 + +#define DOC_LAYOUT_PAGE_BBT 4 + +/* + * Extra page OOB (16 bytes wide) layout + */ +#define DOC_LAYOUT_OOB_PAGEINFO_OFS 0 +#define DOC_LAYOUT_OOB_HAMMING_OFS 7 +#define DOC_LAYOUT_OOB_BCH_OFS 8 +#define DOC_LAYOUT_OOB_UNUSED_OFS 15 +#define DOC_LAYOUT_OOB_PAGEINFO_SZ 7 +#define DOC_LAYOUT_OOB_HAMMING_SZ 1 +#define DOC_LAYOUT_OOB_BCH_SZ 7 +#define DOC_LAYOUT_OOB_UNUSED_SZ 1 + + +#define DOC_CHIPID_G3 0x200 +#define DOC_ERASE_MARK 0xaa +#define DOC_MAX_NBFLOORS 4 +/* + * Flash registers + */ +#define DOC_CHIPID 0x1000 +#define DOC_TEST 0x1004 +#define DOC_BUSLOCK 0x1006 +#define DOC_ENDIANCONTROL 0x1008 +#define DOC_DEVICESELECT 0x100a +#define DOC_ASICMODE 0x100c +#define DOC_CONFIGURATION 0x100e +#define DOC_INTERRUPTCONTROL 0x1010 +#define DOC_READADDRESS 0x101a +#define DOC_DATAEND 0x101e +#define DOC_INTERRUPTSTATUS 0x1020 + +#define DOC_FLASHSEQUENCE 0x1032 +#define DOC_FLASHCOMMAND 0x1034 +#define DOC_FLASHADDRESS 0x1036 +#define DOC_FLASHCONTROL 0x1038 +#define DOC_NOP 0x103e + +#define DOC_ECCCONF0 0x1040 +#define DOC_ECCCONF1 0x1042 +#define DOC_ECCPRESET 0x1044 +#define DOC_HAMMINGPARITY 0x1046 +#define DOC_BCH_HW_ECC(idx) (0x1048 + idx) + +#define DOC_PROTECTION 0x1056 +#define DOC_DPS0_KEY 0x105c +#define DOC_DPS1_KEY 0x105e +#define DOC_DPS0_ADDRLOW 0x1060 +#define DOC_DPS0_ADDRHIGH 0x1062 +#define DOC_DPS1_ADDRLOW 0x1064 +#define DOC_DPS1_ADDRHIGH 0x1066 +#define DOC_DPS0_STATUS 0x106c +#define DOC_DPS1_STATUS 0x106e + +#define DOC_ASICMODECONFIRM 0x1072 +#define DOC_CHIPID_INV 0x1074 +#define DOC_POWERMODE 0x107c + +/* + * Flash sequences + * A sequence is preset before one or more commands are input to the chip. + */ +#define DOC_SEQ_RESET 0x00 +#define DOC_SEQ_PAGE_SIZE_532 0x03 +#define DOC_SEQ_SET_FASTMODE 0x05 +#define DOC_SEQ_SET_RELIABLEMODE 0x09 +#define DOC_SEQ_READ 0x12 +#define DOC_SEQ_SET_PLANE1 0x0e +#define DOC_SEQ_SET_PLANE2 0x10 +#define DOC_SEQ_PAGE_SETUP 0x1d +#define DOC_SEQ_ERASE 0x27 +#define DOC_SEQ_PLANES_STATUS 0x31 + +/* + * Flash commands + */ +#define DOC_CMD_READ_PLANE1 0x00 +#define DOC_CMD_SET_ADDR_READ 0x05 +#define DOC_CMD_READ_ALL_PLANES 0x30 +#define DOC_CMD_READ_PLANE2 0x50 +#define DOC_CMD_READ_FLASH 0xe0 +#define DOC_CMD_PAGE_SIZE_532 0x3c + +#define DOC_CMD_PROG_BLOCK_ADDR 0x60 +#define DOC_CMD_PROG_CYCLE1 0x80 +#define DOC_CMD_PROG_CYCLE2 0x10 +#define DOC_CMD_PROG_CYCLE3 0x11 +#define DOC_CMD_ERASECYCLE2 0xd0 +#define DOC_CMD_READ_STATUS 0x70 +#define DOC_CMD_PLANES_STATUS 0x71 + +#define DOC_CMD_RELIABLE_MODE 0x22 +#define DOC_CMD_FAST_MODE 0xa2 + +#define DOC_CMD_RESET 0xff + +/* + * Flash register : DOC_FLASHCONTROL + */ +#define DOC_CTRL_VIOLATION 0x20 +#define DOC_CTRL_CE 0x10 +#define DOC_CTRL_UNKNOWN_BITS 0x08 +#define DOC_CTRL_PROTECTION_ERROR 0x04 +#define DOC_CTRL_SEQUENCE_ERROR 0x02 +#define DOC_CTRL_FLASHREADY 0x01 + +/* + * Flash register : DOC_ASICMODE + */ +#define DOC_ASICMODE_RESET 0x00 +#define DOC_ASICMODE_NORMAL 0x01 +#define DOC_ASICMODE_POWERDOWN 0x02 +#define DOC_ASICMODE_MDWREN 0x04 +#define DOC_ASICMODE_BDETCT_RESET 0x08 +#define DOC_ASICMODE_RSTIN_RESET 0x10 +#define DOC_ASICMODE_RAM_WE 0x20 + +/* + * Flash register : DOC_ECCCONF0 + */ +#define DOC_ECCCONF0_WRITE_MODE 0x0000 +#define DOC_ECCCONF0_READ_MODE 0x8000 +#define DOC_ECCCONF0_AUTO_ECC_ENABLE 0x4000 +#define DOC_ECCCONF0_HAMMING_ENABLE 0x1000 +#define DOC_ECCCONF0_BCH_ENABLE 0x0800 +#define DOC_ECCCONF0_DATA_BYTES_MASK 0x07ff + +/* + * Flash register : DOC_ECCCONF1 + */ +#define DOC_ECCCONF1_BCH_SYNDROM_ERR 0x80 +#define DOC_ECCCONF1_UNKOWN1 0x40 +#define DOC_ECCCONF1_PAGE_IS_WRITTEN 0x20 +#define DOC_ECCCONF1_UNKOWN3 0x10 +#define DOC_ECCCONF1_HAMMING_BITS_MASK 0x0f + +/* + * Flash register : DOC_PROTECTION + */ +#define DOC_PROTECT_FOUNDRY_OTP_LOCK 0x01 +#define DOC_PROTECT_CUSTOMER_OTP_LOCK 0x02 +#define DOC_PROTECT_LOCK_INPUT 0x04 +#define DOC_PROTECT_STICKY_LOCK 0x08 +#define DOC_PROTECT_PROTECTION_ENABLED 0x10 +#define DOC_PROTECT_IPL_DOWNLOAD_LOCK 0x20 +#define DOC_PROTECT_PROTECTION_ERROR 0x80 + +/* + * Flash register : DOC_DPS0_STATUS and DOC_DPS1_STATUS + */ +#define DOC_DPS_OTP_PROTECTED 0x01 +#define DOC_DPS_READ_PROTECTED 0x02 +#define DOC_DPS_WRITE_PROTECTED 0x04 +#define DOC_DPS_HW_LOCK_ENABLED 0x08 +#define DOC_DPS_KEY_OK 0x80 + +/* + * Flash register : DOC_CONFIGURATION + */ +#define DOC_CONF_IF_CFG 0x80 +#define DOC_CONF_MAX_ID_MASK 0x30 +#define DOC_CONF_VCCQ_3V 0x01 + +/* + * Flash register : DOC_READADDRESS + */ +#define DOC_READADDR_INC 0x8000 +#define DOC_READADDR_ONE_BYTE 0x4000 +#define DOC_READADDR_ADDR_MASK 0x1fff + +/* + * Flash register : DOC_POWERMODE + */ +#define DOC_POWERDOWN_READY 0x80 + +/* + * Status of erase and write operation + */ +#define DOC_PLANES_STATUS_FAIL 0x01 +#define DOC_PLANES_STATUS_PLANE0_KO 0x02 +#define DOC_PLANES_STATUS_PLANE1_KO 0x04 + +/* + * DPS key management + * + * Each floor of docg3 has 2 protection areas: DPS0 and DPS1. These areas span + * across block boundaries, and define whether these blocks can be read or + * written. + * The definition is dynamically stored in page 0 of blocks (2,3) for DPS0, and + * page 0 of blocks (4,5) for DPS1. + */ +#define DOC_LAYOUT_DPS_KEY_LENGTH 8 + +/** + * struct docg3_cascade - Cascade of 1 to 4 docg3 chips + * @floors: floors (ie. one physical docg3 chip is one floor) + * @base: IO space to access all chips in the cascade + * @bch: the BCH correcting control structure + * @lock: lock to protect docg3 IO space from concurrent accesses + */ +struct docg3_cascade { + struct mtd_info *floors[DOC_MAX_NBFLOORS]; + void __iomem *base; + struct bch_control *bch; + struct mutex lock; +}; + +/** + * struct docg3 - DiskOnChip driver private data + * @dev: the device currently under control + * @cascade: the cascade this device belongs to + * @device_id: number of the cascaded DoCG3 device (0, 1, 2 or 3) + * @if_cfg: if true, reads are on 16bits, else reads are on 8bits + + * @reliable: if 0, docg3 in normal mode, if 1 docg3 in fast mode, if 2 in + * reliable mode + * Fast mode implies more errors than normal mode. + * Reliable mode implies that page 2*n and 2*n+1 are clones. + * @bbt: bad block table cache + * @oob_write_ofs: offset of the MTD where this OOB should belong (ie. in next + * page_write) + * @oob_autoecc: if 1, use only bytes 0-7, 15, and fill the others with HW ECC + * if 0, use all the 16 bytes. + * @oob_write_buf: prepared OOB for next page_write + * @debugfs_root: debugfs root node + */ +struct docg3 { + struct device *dev; + struct docg3_cascade *cascade; + unsigned int device_id:4; + unsigned int if_cfg:1; + unsigned int reliable:2; + int max_block; + u8 *bbt; + loff_t oob_write_ofs; + int oob_autoecc; + u8 oob_write_buf[DOC_LAYOUT_OOB_SIZE]; + struct dentry *debugfs_root; +}; + +#define doc_err(fmt, arg...) dev_err(docg3->dev, (fmt), ## arg) +#define doc_info(fmt, arg...) dev_info(docg3->dev, (fmt), ## arg) +#define doc_dbg(fmt, arg...) dev_dbg(docg3->dev, (fmt), ## arg) +#define doc_vdbg(fmt, arg...) dev_vdbg(docg3->dev, (fmt), ## arg) + +#define DEBUGFS_RO_ATTR(name, show_fct) \ + static int name##_open(struct inode *inode, struct file *file) \ + { return single_open(file, show_fct, inode->i_private); } \ + static const struct file_operations name##_fops = { \ + .owner = THIS_MODULE, \ + .open = name##_open, \ + .llseek = seq_lseek, \ + .read = seq_read, \ + .release = single_release \ + }; +#endif + +/* + * Trace events part + */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM docg3 + +#if !defined(_MTD_DOCG3_TRACE) || defined(TRACE_HEADER_MULTI_READ) +#define _MTD_DOCG3_TRACE + +#include <linux/tracepoint.h> + +TRACE_EVENT(docg3_io, + TP_PROTO(int op, int width, u16 reg, int val), + TP_ARGS(op, width, reg, val), + TP_STRUCT__entry( + __field(int, op) + __field(unsigned char, width) + __field(u16, reg) + __field(int, val)), + TP_fast_assign( + __entry->op = op; + __entry->width = width; + __entry->reg = reg; + __entry->val = val;), + TP_printk("docg3: %s%02d reg=%04x, val=%04x", + __entry->op ? "write" : "read", __entry->width, + __entry->reg, __entry->val) + ); +#endif + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH . +#define TRACE_INCLUDE_FILE docg3 +#include <trace/define_trace.h> diff --git a/drivers/mtd/devices/docprobe.c b/drivers/mtd/devices/docprobe.c deleted file mode 100644 index 78872c3f376..00000000000 --- a/drivers/mtd/devices/docprobe.c +++ /dev/null @@ -1,348 +0,0 @@ - -/* Linux driver for Disk-On-Chip devices */ -/* Probe routines common to all DoC devices */ -/* (C) 1999 Machine Vision Holdings, Inc. */ -/* (C) 1999-2003 David Woodhouse <dwmw2@infradead.org> */ - -/* $Id: docprobe.c,v 1.46 2005/11/07 11:14:25 gleixner Exp $ */ - - - -/* DOC_PASSIVE_PROBE: - In order to ensure that the BIOS checksum is correct at boot time, and - hence that the onboard BIOS extension gets executed, the DiskOnChip - goes into reset mode when it is read sequentially: all registers - return 0xff until the chip is woken up again by writing to the - DOCControl register. - - Unfortunately, this means that the probe for the DiskOnChip is unsafe, - because one of the first things it does is write to where it thinks - the DOCControl register should be - which may well be shared memory - for another device. I've had machines which lock up when this is - attempted. Hence the possibility to do a passive probe, which will fail - to detect a chip in reset mode, but is at least guaranteed not to lock - the machine. - - If you have this problem, uncomment the following line: -#define DOC_PASSIVE_PROBE -*/ - - -/* DOC_SINGLE_DRIVER: - Millennium driver has been merged into DOC2000 driver. - - The old Millennium-only driver has been retained just in case there - are problems with the new code. If the combined driver doesn't work - for you, you can try the old one by undefining DOC_SINGLE_DRIVER - below and also enabling it in your configuration. If this fixes the - problems, please send a report to the MTD mailing list at - <linux-mtd@lists.infradead.org>. -*/ -#define DOC_SINGLE_DRIVER - -#include <linux/kernel.h> -#include <linux/module.h> -#include <asm/errno.h> -#include <asm/io.h> -#include <linux/delay.h> -#include <linux/slab.h> -#include <linux/init.h> -#include <linux/types.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/nand.h> -#include <linux/mtd/doc2000.h> -#include <linux/mtd/compatmac.h> - -/* Where to look for the devices? */ -#ifndef CONFIG_MTD_DOCPROBE_ADDRESS -#define CONFIG_MTD_DOCPROBE_ADDRESS 0 -#endif - - -static unsigned long doc_config_location = CONFIG_MTD_DOCPROBE_ADDRESS; -module_param(doc_config_location, ulong, 0); -MODULE_PARM_DESC(doc_config_location, "Physical memory address at which to probe for DiskOnChip"); - -static unsigned long __initdata doc_locations[] = { -#if defined (__alpha__) || defined(__i386__) || defined(__x86_64__) -#ifdef CONFIG_MTD_DOCPROBE_HIGH - 0xfffc8000, 0xfffca000, 0xfffcc000, 0xfffce000, - 0xfffd0000, 0xfffd2000, 0xfffd4000, 0xfffd6000, - 0xfffd8000, 0xfffda000, 0xfffdc000, 0xfffde000, - 0xfffe0000, 0xfffe2000, 0xfffe4000, 0xfffe6000, - 0xfffe8000, 0xfffea000, 0xfffec000, 0xfffee000, -#else /* CONFIG_MTD_DOCPROBE_HIGH */ - 0xc8000, 0xca000, 0xcc000, 0xce000, - 0xd0000, 0xd2000, 0xd4000, 0xd6000, - 0xd8000, 0xda000, 0xdc000, 0xde000, - 0xe0000, 0xe2000, 0xe4000, 0xe6000, - 0xe8000, 0xea000, 0xec000, 0xee000, -#endif /* CONFIG_MTD_DOCPROBE_HIGH */ -#elif defined(__PPC__) - 0xe4000000, -#elif defined(CONFIG_MOMENCO_OCELOT) - 0x2f000000, - 0xff000000, -#elif defined(CONFIG_MOMENCO_OCELOT_G) || defined (CONFIG_MOMENCO_OCELOT_C) - 0xff000000, -##else -#warning Unknown architecture for DiskOnChip. No default probe locations defined -#endif - 0xffffffff }; - -/* doccheck: Probe a given memory window to see if there's a DiskOnChip present */ - -static inline int __init doccheck(void __iomem *potential, unsigned long physadr) -{ - void __iomem *window=potential; - unsigned char tmp, tmpb, tmpc, ChipID; -#ifndef DOC_PASSIVE_PROBE - unsigned char tmp2; -#endif - - /* Routine copied from the Linux DOC driver */ - -#ifdef CONFIG_MTD_DOCPROBE_55AA - /* Check for 0x55 0xAA signature at beginning of window, - this is no longer true once we remove the IPL (for Millennium */ - if (ReadDOC(window, Sig1) != 0x55 || ReadDOC(window, Sig2) != 0xaa) - return 0; -#endif /* CONFIG_MTD_DOCPROBE_55AA */ - -#ifndef DOC_PASSIVE_PROBE - /* It's not possible to cleanly detect the DiskOnChip - the - * bootup procedure will put the device into reset mode, and - * it's not possible to talk to it without actually writing - * to the DOCControl register. So we store the current contents - * of the DOCControl register's location, in case we later decide - * that it's not a DiskOnChip, and want to put it back how we - * found it. - */ - tmp2 = ReadDOC(window, DOCControl); - - /* Reset the DiskOnChip ASIC */ - WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_RESET, - window, DOCControl); - WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_RESET, - window, DOCControl); - - /* Enable the DiskOnChip ASIC */ - WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_NORMAL, - window, DOCControl); - WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_NORMAL, - window, DOCControl); -#endif /* !DOC_PASSIVE_PROBE */ - - /* We need to read the ChipID register four times. For some - newer DiskOnChip 2000 units, the first three reads will - return the DiskOnChip Millennium ident. Don't ask. */ - ChipID = ReadDOC(window, ChipID); - - switch (ChipID) { - case DOC_ChipID_Doc2k: - /* Check the TOGGLE bit in the ECC register */ - tmp = ReadDOC(window, 2k_ECCStatus) & DOC_TOGGLE_BIT; - tmpb = ReadDOC(window, 2k_ECCStatus) & DOC_TOGGLE_BIT; - tmpc = ReadDOC(window, 2k_ECCStatus) & DOC_TOGGLE_BIT; - if (tmp != tmpb && tmp == tmpc) - return ChipID; - break; - - case DOC_ChipID_DocMil: - /* Check for the new 2000 with Millennium ASIC */ - ReadDOC(window, ChipID); - ReadDOC(window, ChipID); - if (ReadDOC(window, ChipID) != DOC_ChipID_DocMil) - ChipID = DOC_ChipID_Doc2kTSOP; - - /* Check the TOGGLE bit in the ECC register */ - tmp = ReadDOC(window, ECCConf) & DOC_TOGGLE_BIT; - tmpb = ReadDOC(window, ECCConf) & DOC_TOGGLE_BIT; - tmpc = ReadDOC(window, ECCConf) & DOC_TOGGLE_BIT; - if (tmp != tmpb && tmp == tmpc) - return ChipID; - break; - - case DOC_ChipID_DocMilPlus16: - case DOC_ChipID_DocMilPlus32: - case 0: - /* Possible Millennium+, need to do more checks */ -#ifndef DOC_PASSIVE_PROBE - /* Possibly release from power down mode */ - for (tmp = 0; (tmp < 4); tmp++) - ReadDOC(window, Mplus_Power); - - /* Reset the DiskOnChip ASIC */ - tmp = DOC_MODE_RESET | DOC_MODE_MDWREN | DOC_MODE_RST_LAT | - DOC_MODE_BDECT; - WriteDOC(tmp, window, Mplus_DOCControl); - WriteDOC(~tmp, window, Mplus_CtrlConfirm); - - mdelay(1); - /* Enable the DiskOnChip ASIC */ - tmp = DOC_MODE_NORMAL | DOC_MODE_MDWREN | DOC_MODE_RST_LAT | - DOC_MODE_BDECT; - WriteDOC(tmp, window, Mplus_DOCControl); - WriteDOC(~tmp, window, Mplus_CtrlConfirm); - mdelay(1); -#endif /* !DOC_PASSIVE_PROBE */ - - ChipID = ReadDOC(window, ChipID); - - switch (ChipID) { - case DOC_ChipID_DocMilPlus16: - case DOC_ChipID_DocMilPlus32: - /* Check the TOGGLE bit in the toggle register */ - tmp = ReadDOC(window, Mplus_Toggle) & DOC_TOGGLE_BIT; - tmpb = ReadDOC(window, Mplus_Toggle) & DOC_TOGGLE_BIT; - tmpc = ReadDOC(window, Mplus_Toggle) & DOC_TOGGLE_BIT; - if (tmp != tmpb && tmp == tmpc) - return ChipID; - default: - break; - } - /* FALL TRHU */ - - default: - -#ifdef CONFIG_MTD_DOCPROBE_55AA - printk(KERN_DEBUG "Possible DiskOnChip with unknown ChipID %2.2X found at 0x%lx\n", - ChipID, physadr); -#endif -#ifndef DOC_PASSIVE_PROBE - /* Put back the contents of the DOCControl register, in case it's not - * actually a DiskOnChip. - */ - WriteDOC(tmp2, window, DOCControl); -#endif - return 0; - } - - printk(KERN_WARNING "DiskOnChip failed TOGGLE test, dropping.\n"); - -#ifndef DOC_PASSIVE_PROBE - /* Put back the contents of the DOCControl register: it's not a DiskOnChip */ - WriteDOC(tmp2, window, DOCControl); -#endif - return 0; -} - -static int docfound; - -extern void DoC2k_init(struct mtd_info *); -extern void DoCMil_init(struct mtd_info *); -extern void DoCMilPlus_init(struct mtd_info *); - -static void __init DoC_Probe(unsigned long physadr) -{ - void __iomem *docptr; - struct DiskOnChip *this; - struct mtd_info *mtd; - int ChipID; - char namebuf[15]; - char *name = namebuf; - void (*initroutine)(struct mtd_info *) = NULL; - - docptr = ioremap(physadr, DOC_IOREMAP_LEN); - - if (!docptr) - return; - - if ((ChipID = doccheck(docptr, physadr))) { - if (ChipID == DOC_ChipID_Doc2kTSOP) { - /* Remove this at your own peril. The hardware driver works but nothing prevents you from erasing bad blocks */ - printk(KERN_NOTICE "Refusing to drive DiskOnChip 2000 TSOP until Bad Block Table is correctly supported by INFTL\n"); - iounmap(docptr); - return; - } - docfound = 1; - mtd = kmalloc(sizeof(struct DiskOnChip) + sizeof(struct mtd_info), GFP_KERNEL); - - if (!mtd) { - printk(KERN_WARNING "Cannot allocate memory for data structures. Dropping.\n"); - iounmap(docptr); - return; - } - - this = (struct DiskOnChip *)(&mtd[1]); - - memset((char *)mtd,0, sizeof(struct mtd_info)); - memset((char *)this, 0, sizeof(struct DiskOnChip)); - - mtd->priv = this; - this->virtadr = docptr; - this->physadr = physadr; - this->ChipID = ChipID; - sprintf(namebuf, "with ChipID %2.2X", ChipID); - - switch(ChipID) { - case DOC_ChipID_Doc2kTSOP: - name="2000 TSOP"; - initroutine = symbol_request(DoC2k_init); - break; - - case DOC_ChipID_Doc2k: - name="2000"; - initroutine = symbol_request(DoC2k_init); - break; - - case DOC_ChipID_DocMil: - name="Millennium"; -#ifdef DOC_SINGLE_DRIVER - initroutine = symbol_request(DoC2k_init); -#else - initroutine = symbol_request(DoCMil_init); -#endif /* DOC_SINGLE_DRIVER */ - break; - - case DOC_ChipID_DocMilPlus16: - case DOC_ChipID_DocMilPlus32: - name="MillenniumPlus"; - initroutine = symbol_request(DoCMilPlus_init); - break; - } - - if (initroutine) { - (*initroutine)(mtd); - symbol_put_addr(initroutine); - return; - } - printk(KERN_NOTICE "Cannot find driver for DiskOnChip %s at 0x%lX\n", name, physadr); - kfree(mtd); - } - iounmap(docptr); -} - - -/**************************************************************************** - * - * Module stuff - * - ****************************************************************************/ - -static int __init init_doc(void) -{ - int i; - - if (doc_config_location) { - printk(KERN_INFO "Using configured DiskOnChip probe address 0x%lx\n", doc_config_location); - DoC_Probe(doc_config_location); - } else { - for (i=0; (doc_locations[i] != 0xffffffff); i++) { - DoC_Probe(doc_locations[i]); - } - } - /* No banner message any more. Print a message if no DiskOnChip - found, so the user knows we at least tried. */ - if (!docfound) - printk(KERN_INFO "No recognised DiskOnChip devices found\n"); - return -EAGAIN; -} - -module_init(init_doc); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); -MODULE_DESCRIPTION("Probe code for DiskOnChip 2000 and Millennium devices"); - diff --git a/drivers/mtd/devices/elm.c b/drivers/mtd/devices/elm.c new file mode 100644 index 00000000000..b4f61c7fc16 --- /dev/null +++ b/drivers/mtd/devices/elm.c @@ -0,0 +1,579 @@ +/* + * Error Location Module + * + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.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. + * + */ + +#define DRIVER_NAME "omap-elm" + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/sched.h> +#include <linux/pm_runtime.h> +#include <linux/platform_data/elm.h> + +#define ELM_SYSCONFIG 0x010 +#define ELM_IRQSTATUS 0x018 +#define ELM_IRQENABLE 0x01c +#define ELM_LOCATION_CONFIG 0x020 +#define ELM_PAGE_CTRL 0x080 +#define ELM_SYNDROME_FRAGMENT_0 0x400 +#define ELM_SYNDROME_FRAGMENT_1 0x404 +#define ELM_SYNDROME_FRAGMENT_2 0x408 +#define ELM_SYNDROME_FRAGMENT_3 0x40c +#define ELM_SYNDROME_FRAGMENT_4 0x410 +#define ELM_SYNDROME_FRAGMENT_5 0x414 +#define ELM_SYNDROME_FRAGMENT_6 0x418 +#define ELM_LOCATION_STATUS 0x800 +#define ELM_ERROR_LOCATION_0 0x880 + +/* ELM Interrupt Status Register */ +#define INTR_STATUS_PAGE_VALID BIT(8) + +/* ELM Interrupt Enable Register */ +#define INTR_EN_PAGE_MASK BIT(8) + +/* ELM Location Configuration Register */ +#define ECC_BCH_LEVEL_MASK 0x3 + +/* ELM syndrome */ +#define ELM_SYNDROME_VALID BIT(16) + +/* ELM_LOCATION_STATUS Register */ +#define ECC_CORRECTABLE_MASK BIT(8) +#define ECC_NB_ERRORS_MASK 0x1f + +/* ELM_ERROR_LOCATION_0-15 Registers */ +#define ECC_ERROR_LOCATION_MASK 0x1fff + +#define ELM_ECC_SIZE 0x7ff + +#define SYNDROME_FRAGMENT_REG_SIZE 0x40 +#define ERROR_LOCATION_SIZE 0x100 + +struct elm_registers { + u32 elm_irqenable; + u32 elm_sysconfig; + u32 elm_location_config; + u32 elm_page_ctrl; + u32 elm_syndrome_fragment_6[ERROR_VECTOR_MAX]; + u32 elm_syndrome_fragment_5[ERROR_VECTOR_MAX]; + u32 elm_syndrome_fragment_4[ERROR_VECTOR_MAX]; + u32 elm_syndrome_fragment_3[ERROR_VECTOR_MAX]; + u32 elm_syndrome_fragment_2[ERROR_VECTOR_MAX]; + u32 elm_syndrome_fragment_1[ERROR_VECTOR_MAX]; + u32 elm_syndrome_fragment_0[ERROR_VECTOR_MAX]; +}; + +struct elm_info { + struct device *dev; + void __iomem *elm_base; + struct completion elm_completion; + struct list_head list; + enum bch_ecc bch_type; + struct elm_registers elm_regs; + int ecc_steps; + int ecc_syndrome_size; +}; + +static LIST_HEAD(elm_devices); + +static void elm_write_reg(struct elm_info *info, int offset, u32 val) +{ + writel(val, info->elm_base + offset); +} + +static u32 elm_read_reg(struct elm_info *info, int offset) +{ + return readl(info->elm_base + offset); +} + +/** + * elm_config - Configure ELM module + * @dev: ELM device + * @bch_type: Type of BCH ecc + */ +int elm_config(struct device *dev, enum bch_ecc bch_type, + int ecc_steps, int ecc_step_size, int ecc_syndrome_size) +{ + u32 reg_val; + struct elm_info *info = dev_get_drvdata(dev); + + if (!info) { + dev_err(dev, "Unable to configure elm - device not probed?\n"); + return -ENODEV; + } + /* ELM cannot detect ECC errors for chunks > 1KB */ + if (ecc_step_size > ((ELM_ECC_SIZE + 1) / 2)) { + dev_err(dev, "unsupported config ecc-size=%d\n", ecc_step_size); + return -EINVAL; + } + /* ELM support 8 error syndrome process */ + if (ecc_steps > ERROR_VECTOR_MAX) { + dev_err(dev, "unsupported config ecc-step=%d\n", ecc_steps); + return -EINVAL; + } + + reg_val = (bch_type & ECC_BCH_LEVEL_MASK) | (ELM_ECC_SIZE << 16); + elm_write_reg(info, ELM_LOCATION_CONFIG, reg_val); + info->bch_type = bch_type; + info->ecc_steps = ecc_steps; + info->ecc_syndrome_size = ecc_syndrome_size; + + return 0; +} +EXPORT_SYMBOL(elm_config); + +/** + * elm_configure_page_mode - Enable/Disable page mode + * @info: elm info + * @index: index number of syndrome fragment vector + * @enable: enable/disable flag for page mode + * + * Enable page mode for syndrome fragment index + */ +static void elm_configure_page_mode(struct elm_info *info, int index, + bool enable) +{ + u32 reg_val; + + reg_val = elm_read_reg(info, ELM_PAGE_CTRL); + if (enable) + reg_val |= BIT(index); /* enable page mode */ + else + reg_val &= ~BIT(index); /* disable page mode */ + + elm_write_reg(info, ELM_PAGE_CTRL, reg_val); +} + +/** + * elm_load_syndrome - Load ELM syndrome reg + * @info: elm info + * @err_vec: elm error vectors + * @ecc: buffer with calculated ecc + * + * Load syndrome fragment registers with calculated ecc in reverse order. + */ +static void elm_load_syndrome(struct elm_info *info, + struct elm_errorvec *err_vec, u8 *ecc) +{ + int i, offset; + u32 val; + + for (i = 0; i < info->ecc_steps; i++) { + + /* Check error reported */ + if (err_vec[i].error_reported) { + elm_configure_page_mode(info, i, true); + offset = ELM_SYNDROME_FRAGMENT_0 + + SYNDROME_FRAGMENT_REG_SIZE * i; + switch (info->bch_type) { + case BCH8_ECC: + /* syndrome fragment 0 = ecc[9-12B] */ + val = cpu_to_be32(*(u32 *) &ecc[9]); + elm_write_reg(info, offset, val); + + /* syndrome fragment 1 = ecc[5-8B] */ + offset += 4; + val = cpu_to_be32(*(u32 *) &ecc[5]); + elm_write_reg(info, offset, val); + + /* syndrome fragment 2 = ecc[1-4B] */ + offset += 4; + val = cpu_to_be32(*(u32 *) &ecc[1]); + elm_write_reg(info, offset, val); + + /* syndrome fragment 3 = ecc[0B] */ + offset += 4; + val = ecc[0]; + elm_write_reg(info, offset, val); + break; + case BCH4_ECC: + /* syndrome fragment 0 = ecc[20-52b] bits */ + val = (cpu_to_be32(*(u32 *) &ecc[3]) >> 4) | + ((ecc[2] & 0xf) << 28); + elm_write_reg(info, offset, val); + + /* syndrome fragment 1 = ecc[0-20b] bits */ + offset += 4; + val = cpu_to_be32(*(u32 *) &ecc[0]) >> 12; + elm_write_reg(info, offset, val); + break; + case BCH16_ECC: + val = cpu_to_be32(*(u32 *) &ecc[22]); + elm_write_reg(info, offset, val); + offset += 4; + val = cpu_to_be32(*(u32 *) &ecc[18]); + elm_write_reg(info, offset, val); + offset += 4; + val = cpu_to_be32(*(u32 *) &ecc[14]); + elm_write_reg(info, offset, val); + offset += 4; + val = cpu_to_be32(*(u32 *) &ecc[10]); + elm_write_reg(info, offset, val); + offset += 4; + val = cpu_to_be32(*(u32 *) &ecc[6]); + elm_write_reg(info, offset, val); + offset += 4; + val = cpu_to_be32(*(u32 *) &ecc[2]); + elm_write_reg(info, offset, val); + offset += 4; + val = cpu_to_be32(*(u32 *) &ecc[0]) >> 16; + elm_write_reg(info, offset, val); + break; + default: + pr_err("invalid config bch_type\n"); + } + } + + /* Update ecc pointer with ecc byte size */ + ecc += info->ecc_syndrome_size; + } +} + +/** + * elm_start_processing - start elm syndrome processing + * @info: elm info + * @err_vec: elm error vectors + * + * Set syndrome valid bit for syndrome fragment registers for which + * elm syndrome fragment registers are loaded. This enables elm module + * to start processing syndrome vectors. + */ +static void elm_start_processing(struct elm_info *info, + struct elm_errorvec *err_vec) +{ + int i, offset; + u32 reg_val; + + /* + * Set syndrome vector valid, so that ELM module + * will process it for vectors error is reported + */ + for (i = 0; i < info->ecc_steps; i++) { + if (err_vec[i].error_reported) { + offset = ELM_SYNDROME_FRAGMENT_6 + + SYNDROME_FRAGMENT_REG_SIZE * i; + reg_val = elm_read_reg(info, offset); + reg_val |= ELM_SYNDROME_VALID; + elm_write_reg(info, offset, reg_val); + } + } +} + +/** + * elm_error_correction - locate correctable error position + * @info: elm info + * @err_vec: elm error vectors + * + * On completion of processing by elm module, error location status + * register updated with correctable/uncorrectable error information. + * In case of correctable errors, number of errors located from + * elm location status register & read the positions from + * elm error location register. + */ +static void elm_error_correction(struct elm_info *info, + struct elm_errorvec *err_vec) +{ + int i, j, errors = 0; + int offset; + u32 reg_val; + + for (i = 0; i < info->ecc_steps; i++) { + + /* Check error reported */ + if (err_vec[i].error_reported) { + offset = ELM_LOCATION_STATUS + ERROR_LOCATION_SIZE * i; + reg_val = elm_read_reg(info, offset); + + /* Check correctable error or not */ + if (reg_val & ECC_CORRECTABLE_MASK) { + offset = ELM_ERROR_LOCATION_0 + + ERROR_LOCATION_SIZE * i; + + /* Read count of correctable errors */ + err_vec[i].error_count = reg_val & + ECC_NB_ERRORS_MASK; + + /* Update the error locations in error vector */ + for (j = 0; j < err_vec[i].error_count; j++) { + + reg_val = elm_read_reg(info, offset); + err_vec[i].error_loc[j] = reg_val & + ECC_ERROR_LOCATION_MASK; + + /* Update error location register */ + offset += 4; + } + + errors += err_vec[i].error_count; + } else { + err_vec[i].error_uncorrectable = true; + } + + /* Clearing interrupts for processed error vectors */ + elm_write_reg(info, ELM_IRQSTATUS, BIT(i)); + + /* Disable page mode */ + elm_configure_page_mode(info, i, false); + } + } +} + +/** + * elm_decode_bch_error_page - Locate error position + * @dev: device pointer + * @ecc_calc: calculated ECC bytes from GPMC + * @err_vec: elm error vectors + * + * Called with one or more error reported vectors & vectors with + * error reported is updated in err_vec[].error_reported + */ +void elm_decode_bch_error_page(struct device *dev, u8 *ecc_calc, + struct elm_errorvec *err_vec) +{ + struct elm_info *info = dev_get_drvdata(dev); + u32 reg_val; + + /* Enable page mode interrupt */ + reg_val = elm_read_reg(info, ELM_IRQSTATUS); + elm_write_reg(info, ELM_IRQSTATUS, reg_val & INTR_STATUS_PAGE_VALID); + elm_write_reg(info, ELM_IRQENABLE, INTR_EN_PAGE_MASK); + + /* Load valid ecc byte to syndrome fragment register */ + elm_load_syndrome(info, err_vec, ecc_calc); + + /* Enable syndrome processing for which syndrome fragment is updated */ + elm_start_processing(info, err_vec); + + /* Wait for ELM module to finish locating error correction */ + wait_for_completion(&info->elm_completion); + + /* Disable page mode interrupt */ + reg_val = elm_read_reg(info, ELM_IRQENABLE); + elm_write_reg(info, ELM_IRQENABLE, reg_val & ~INTR_EN_PAGE_MASK); + elm_error_correction(info, err_vec); +} +EXPORT_SYMBOL(elm_decode_bch_error_page); + +static irqreturn_t elm_isr(int this_irq, void *dev_id) +{ + u32 reg_val; + struct elm_info *info = dev_id; + + reg_val = elm_read_reg(info, ELM_IRQSTATUS); + + /* All error vectors processed */ + if (reg_val & INTR_STATUS_PAGE_VALID) { + elm_write_reg(info, ELM_IRQSTATUS, + reg_val & INTR_STATUS_PAGE_VALID); + complete(&info->elm_completion); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int elm_probe(struct platform_device *pdev) +{ + int ret = 0; + struct resource *res, *irq; + struct elm_info *info; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->dev = &pdev->dev; + + irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!irq) { + dev_err(&pdev->dev, "no irq resource defined\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + info->elm_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(info->elm_base)) + return PTR_ERR(info->elm_base); + + ret = devm_request_irq(&pdev->dev, irq->start, elm_isr, 0, + pdev->name, info); + if (ret) { + dev_err(&pdev->dev, "failure requesting irq %i\n", irq->start); + return ret; + } + + pm_runtime_enable(&pdev->dev); + if (pm_runtime_get_sync(&pdev->dev) < 0) { + ret = -EINVAL; + pm_runtime_disable(&pdev->dev); + dev_err(&pdev->dev, "can't enable clock\n"); + return ret; + } + + init_completion(&info->elm_completion); + INIT_LIST_HEAD(&info->list); + list_add(&info->list, &elm_devices); + platform_set_drvdata(pdev, info); + return ret; +} + +static int elm_remove(struct platform_device *pdev) +{ + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +/** + * elm_context_save + * saves ELM configurations to preserve them across Hardware powered-down + */ +static int elm_context_save(struct elm_info *info) +{ + struct elm_registers *regs = &info->elm_regs; + enum bch_ecc bch_type = info->bch_type; + u32 offset = 0, i; + + regs->elm_irqenable = elm_read_reg(info, ELM_IRQENABLE); + regs->elm_sysconfig = elm_read_reg(info, ELM_SYSCONFIG); + regs->elm_location_config = elm_read_reg(info, ELM_LOCATION_CONFIG); + regs->elm_page_ctrl = elm_read_reg(info, ELM_PAGE_CTRL); + for (i = 0; i < ERROR_VECTOR_MAX; i++) { + offset = i * SYNDROME_FRAGMENT_REG_SIZE; + switch (bch_type) { + case BCH16_ECC: + regs->elm_syndrome_fragment_6[i] = elm_read_reg(info, + ELM_SYNDROME_FRAGMENT_6 + offset); + regs->elm_syndrome_fragment_5[i] = elm_read_reg(info, + ELM_SYNDROME_FRAGMENT_5 + offset); + regs->elm_syndrome_fragment_4[i] = elm_read_reg(info, + ELM_SYNDROME_FRAGMENT_4 + offset); + case BCH8_ECC: + regs->elm_syndrome_fragment_3[i] = elm_read_reg(info, + ELM_SYNDROME_FRAGMENT_3 + offset); + regs->elm_syndrome_fragment_2[i] = elm_read_reg(info, + ELM_SYNDROME_FRAGMENT_2 + offset); + case BCH4_ECC: + regs->elm_syndrome_fragment_1[i] = elm_read_reg(info, + ELM_SYNDROME_FRAGMENT_1 + offset); + regs->elm_syndrome_fragment_0[i] = elm_read_reg(info, + ELM_SYNDROME_FRAGMENT_0 + offset); + break; + default: + return -EINVAL; + } + /* ELM SYNDROME_VALID bit in SYNDROME_FRAGMENT_6[] needs + * to be saved for all BCH schemes*/ + regs->elm_syndrome_fragment_6[i] = elm_read_reg(info, + ELM_SYNDROME_FRAGMENT_6 + offset); + } + return 0; +} + +/** + * elm_context_restore + * writes configurations saved duing power-down back into ELM registers + */ +static int elm_context_restore(struct elm_info *info) +{ + struct elm_registers *regs = &info->elm_regs; + enum bch_ecc bch_type = info->bch_type; + u32 offset = 0, i; + + elm_write_reg(info, ELM_IRQENABLE, regs->elm_irqenable); + elm_write_reg(info, ELM_SYSCONFIG, regs->elm_sysconfig); + elm_write_reg(info, ELM_LOCATION_CONFIG, regs->elm_location_config); + elm_write_reg(info, ELM_PAGE_CTRL, regs->elm_page_ctrl); + for (i = 0; i < ERROR_VECTOR_MAX; i++) { + offset = i * SYNDROME_FRAGMENT_REG_SIZE; + switch (bch_type) { + case BCH16_ECC: + elm_write_reg(info, ELM_SYNDROME_FRAGMENT_6 + offset, + regs->elm_syndrome_fragment_6[i]); + elm_write_reg(info, ELM_SYNDROME_FRAGMENT_5 + offset, + regs->elm_syndrome_fragment_5[i]); + elm_write_reg(info, ELM_SYNDROME_FRAGMENT_4 + offset, + regs->elm_syndrome_fragment_4[i]); + case BCH8_ECC: + elm_write_reg(info, ELM_SYNDROME_FRAGMENT_3 + offset, + regs->elm_syndrome_fragment_3[i]); + elm_write_reg(info, ELM_SYNDROME_FRAGMENT_2 + offset, + regs->elm_syndrome_fragment_2[i]); + case BCH4_ECC: + elm_write_reg(info, ELM_SYNDROME_FRAGMENT_1 + offset, + regs->elm_syndrome_fragment_1[i]); + elm_write_reg(info, ELM_SYNDROME_FRAGMENT_0 + offset, + regs->elm_syndrome_fragment_0[i]); + break; + default: + return -EINVAL; + } + /* ELM_SYNDROME_VALID bit to be set in last to trigger FSM */ + elm_write_reg(info, ELM_SYNDROME_FRAGMENT_6 + offset, + regs->elm_syndrome_fragment_6[i] & + ELM_SYNDROME_VALID); + } + return 0; +} + +static int elm_suspend(struct device *dev) +{ + struct elm_info *info = dev_get_drvdata(dev); + elm_context_save(info); + pm_runtime_put_sync(dev); + return 0; +} + +static int elm_resume(struct device *dev) +{ + struct elm_info *info = dev_get_drvdata(dev); + pm_runtime_get_sync(dev); + elm_context_restore(info); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(elm_pm_ops, elm_suspend, elm_resume); + +#ifdef CONFIG_OF +static const struct of_device_id elm_of_match[] = { + { .compatible = "ti,am3352-elm" }, + {}, +}; +MODULE_DEVICE_TABLE(of, elm_of_match); +#endif + +static struct platform_driver elm_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(elm_of_match), + .pm = &elm_pm_ops, + }, + .probe = elm_probe, + .remove = elm_remove, +}; + +module_platform_driver(elm_driver); + +MODULE_DESCRIPTION("ELM driver for BCH error correction"); +MODULE_AUTHOR("Texas Instruments"); +MODULE_ALIAS("platform: elm"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/devices/lart.c b/drivers/mtd/devices/lart.c index 4ea50a1dda8..82bd00af5cc 100644 --- a/drivers/mtd/devices/lart.c +++ b/drivers/mtd/devices/lart.c @@ -2,8 +2,6 @@ /* * MTD driver for the 28F160F3 Flash Memory (non-CFI) on LART. * - * $Id: lart.c,v 1.9 2005/11/07 11:14:25 gleixner Exp $ - * * Author: Abraham vd Merwe <abraham@2d3d.co.za> * * Copyright (c) 2001, 2d3D, Inc. @@ -19,7 +17,7 @@ * - January 2000 * * [2] MTD internal API documentation - * - http://www.linux-mtd.infradead.org/tech/ + * - http://www.linux-mtd.infradead.org/ * * Limitations: * @@ -36,9 +34,6 @@ /* debugging */ //#define LART_DEBUG -/* partition support */ -#define HAVE_PARTITIONS - #include <linux/kernel.h> #include <linux/module.h> #include <linux/types.h> @@ -46,9 +41,7 @@ #include <linux/errno.h> #include <linux/string.h> #include <linux/mtd/mtd.h> -#ifdef HAVE_PARTITIONS #include <linux/mtd/partitions.h> -#endif #ifndef CONFIG_SA1100_LART #error This is for LART architecture only @@ -275,7 +268,7 @@ static __u8 read8 (__u32 offset) { volatile __u8 *data = (__u8 *) (FLASH_OFFSET + offset); #ifdef LART_DEBUG - printk (KERN_DEBUG "%s(): 0x%.8x -> 0x%.2x\n",__FUNCTION__,offset,*data); + printk (KERN_DEBUG "%s(): 0x%.8x -> 0x%.2x\n", __func__, offset, *data); #endif return (*data); } @@ -284,7 +277,7 @@ static __u32 read32 (__u32 offset) { volatile __u32 *data = (__u32 *) (FLASH_OFFSET + offset); #ifdef LART_DEBUG - printk (KERN_DEBUG "%s(): 0x%.8x -> 0x%.8x\n",__FUNCTION__,offset,*data); + printk (KERN_DEBUG "%s(): 0x%.8x -> 0x%.8x\n", __func__, offset, *data); #endif return (*data); } @@ -294,7 +287,7 @@ static void write32 (__u32 x,__u32 offset) volatile __u32 *data = (__u32 *) (FLASH_OFFSET + offset); *data = x; #ifdef LART_DEBUG - printk (KERN_DEBUG "%s(): 0x%.8x <- 0x%.8x\n",__FUNCTION__,offset,*data); + printk (KERN_DEBUG "%s(): 0x%.8x <- 0x%.8x\n", __func__, offset, *data); #endif } @@ -323,7 +316,7 @@ static int flash_probe (void) /* put the flash back into command mode */ write32 (DATA_TO_FLASH (READ_ARRAY),0x00000000); - return (manufacturer == FLASH_MANUFACTURER && (devtype == FLASH_DEVICE_16mbit_TOP || FLASH_DEVICE_16mbit_BOTTOM)); + return (manufacturer == FLASH_MANUFACTURER && (devtype == FLASH_DEVICE_16mbit_TOP || devtype == FLASH_DEVICE_16mbit_BOTTOM)); } /* @@ -337,7 +330,7 @@ static inline int erase_block (__u32 offset) __u32 status; #ifdef LART_DEBUG - printk (KERN_DEBUG "%s(): 0x%.8x\n",__FUNCTION__,offset); + printk (KERN_DEBUG "%s(): 0x%.8x\n", __func__, offset); #endif /* erase and confirm */ @@ -355,7 +348,7 @@ static inline int erase_block (__u32 offset) /* put the flash back into command mode */ write32 (DATA_TO_FLASH (READ_ARRAY),offset); - /* was the erase successfull? */ + /* was the erase successful? */ if ((status & STATUS_ERASE_ERR)) { printk (KERN_WARNING "%s: erase error at address 0x%.8x.\n",module_name,offset); @@ -371,12 +364,9 @@ static int flash_erase (struct mtd_info *mtd,struct erase_info *instr) int i,first; #ifdef LART_DEBUG - printk (KERN_DEBUG "%s(addr = 0x%.8x, len = %d)\n",__FUNCTION__,instr->addr,instr->len); + printk (KERN_DEBUG "%s(addr = 0x%.8x, len = %d)\n", __func__, instr->addr, instr->len); #endif - /* sanity checks */ - if (instr->addr + instr->len > mtd->size) return (-EINVAL); - /* * check that both start and end of the requested erase are * aligned with the erasesize at the appropriate addresses. @@ -395,7 +385,8 @@ static int flash_erase (struct mtd_info *mtd,struct erase_info *instr) * erase range is aligned with the erase size which is in * effect here. */ - if (instr->addr & (mtd->eraseregions[i].erasesize - 1)) return (-EINVAL); + if (i < 0 || (instr->addr & (mtd->eraseregions[i].erasesize - 1))) + return -EINVAL; /* Remember the erase region we start on */ first = i; @@ -411,7 +402,8 @@ static int flash_erase (struct mtd_info *mtd,struct erase_info *instr) i--; /* is the end aligned on a block boundary? */ - if ((instr->addr + instr->len) & (mtd->eraseregions[i].erasesize - 1)) return (-EINVAL); + if (i < 0 || ((instr->addr + instr->len) & (mtd->eraseregions[i].erasesize - 1))) + return -EINVAL; addr = instr->addr; len = instr->len; @@ -442,13 +434,9 @@ static int flash_erase (struct mtd_info *mtd,struct erase_info *instr) static int flash_read (struct mtd_info *mtd,loff_t from,size_t len,size_t *retlen,u_char *buf) { #ifdef LART_DEBUG - printk (KERN_DEBUG "%s(from = 0x%.8x, len = %d)\n",__FUNCTION__,(__u32) from,len); + printk (KERN_DEBUG "%s(from = 0x%.8x, len = %d)\n", __func__, (__u32)from, len); #endif - /* sanity checks */ - if (!len) return (0); - if (from + len > mtd->size) return (-EINVAL); - /* we always read len bytes */ *retlen = len; @@ -488,7 +476,7 @@ static inline int write_dword (__u32 offset,__u32 x) __u32 status; #ifdef LART_DEBUG - printk (KERN_DEBUG "%s(): 0x%.8x <- 0x%.8x\n",__FUNCTION__,offset,x); + printk (KERN_DEBUG "%s(): 0x%.8x <- 0x%.8x\n", __func__, offset, x); #endif /* setup writing */ @@ -508,7 +496,7 @@ static inline int write_dword (__u32 offset,__u32 x) /* put the flash back into command mode */ write32 (DATA_TO_FLASH (READ_ARRAY),offset); - /* was the write successfull? */ + /* was the write successful? */ if ((status & STATUS_PGM_ERR) || read32 (offset) != x) { printk (KERN_WARNING "%s: write error at address 0x%.8x.\n",module_name,offset); @@ -524,14 +512,11 @@ static int flash_write (struct mtd_info *mtd,loff_t to,size_t len,size_t *retlen int i,n; #ifdef LART_DEBUG - printk (KERN_DEBUG "%s(to = 0x%.8x, len = %d)\n",__FUNCTION__,(__u32) to,len); + printk (KERN_DEBUG "%s(to = 0x%.8x, len = %d)\n", __func__, (__u32)to, len); #endif - *retlen = 0; - /* sanity checks */ if (!len) return (0); - if (to + len > mtd->size) return (-EINVAL); /* first, we write a 0xFF.... padded byte until we reach a dword boundary */ if (to & (BUSWIDTH - 1)) @@ -598,7 +583,6 @@ static struct mtd_erase_region_info erase_regions[] = { } }; -#ifdef HAVE_PARTITIONS static struct mtd_partition lart_partitions[] = { /* blob */ { @@ -619,9 +603,9 @@ static struct mtd_partition lart_partitions[] = { .size = INITRD_LEN, /* MTDPART_SIZ_FULL */ } }; -#endif +#define NUM_PARTITIONS ARRAY_SIZE(lart_partitions) -int __init lart_flash_init (void) +static int __init lart_flash_init (void) { int result; memset (&mtd,0,sizeof (mtd)); @@ -636,14 +620,15 @@ int __init lart_flash_init (void) mtd.name = module_name; mtd.type = MTD_NORFLASH; mtd.writesize = 1; + mtd.writebufsize = 4; mtd.flags = MTD_CAP_NORFLASH; mtd.size = FLASH_BLOCKSIZE_PARAM * FLASH_NUMBLOCKS_16m_PARAM + FLASH_BLOCKSIZE_MAIN * FLASH_NUMBLOCKS_16m_MAIN; mtd.erasesize = FLASH_BLOCKSIZE_MAIN; mtd.numeraseregions = ARRAY_SIZE(erase_regions); mtd.eraseregions = erase_regions; - mtd.erase = flash_erase; - mtd.read = flash_read; - mtd.write = flash_write; + mtd._erase = flash_erase; + mtd._read = flash_read; + mtd._write = flash_write; mtd.owner = THIS_MODULE; #ifdef LART_DEBUG @@ -668,7 +653,6 @@ int __init lart_flash_init (void) result,mtd.eraseregions[result].erasesize,mtd.eraseregions[result].erasesize / 1024, result,mtd.eraseregions[result].numblocks); -#ifdef HAVE_PARTITIONS printk ("\npartitions = %d\n", ARRAY_SIZE(lart_partitions)); for (result = 0; result < ARRAY_SIZE(lart_partitions); result++) @@ -681,24 +665,16 @@ int __init lart_flash_init (void) result,lart_partitions[result].offset, result,lart_partitions[result].size,lart_partitions[result].size / 1024); #endif -#endif -#ifndef HAVE_PARTITIONS - result = add_mtd_device (&mtd); -#else - result = add_mtd_partitions (&mtd,lart_partitions, ARRAY_SIZE(lart_partitions)); -#endif + result = mtd_device_register(&mtd, lart_partitions, + ARRAY_SIZE(lart_partitions)); return (result); } -void __exit lart_flash_exit (void) +static void __exit lart_flash_exit (void) { -#ifndef HAVE_PARTITIONS - del_mtd_device (&mtd); -#else - del_mtd_partitions (&mtd); -#endif + mtd_device_unregister(&mtd); } module_init (lart_flash_init); @@ -707,5 +683,3 @@ module_exit (lart_flash_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Abraham vd Merwe <abraham@2d3d.co.za>"); MODULE_DESCRIPTION("MTD driver for Intel 28F160F3 on LART board"); - - diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c index ef4a731ca5c..ed7e0a1bed3 100644 --- a/drivers/mtd/devices/m25p80.c +++ b/drivers/mtd/devices/m25p80.c @@ -1,5 +1,5 @@ /* - * MTD SPI driver for ST M25Pxx flash chips + * MTD SPI driver for ST M25Pxx (and similar) serial flash chips * * Author: Mike Lavender, mike@steroidmicros.com * @@ -15,568 +15,252 @@ * */ -#include <linux/init.h> +#include <linux/err.h> +#include <linux/errno.h> #include <linux/module.h> #include <linux/device.h> -#include <linux/interrupt.h> -#include <linux/interrupt.h> + #include <linux/mtd/mtd.h> #include <linux/mtd/partitions.h> + #include <linux/spi/spi.h> #include <linux/spi/flash.h> +#include <linux/mtd/spi-nor.h> -#include <asm/semaphore.h> - - -/* NOTE: AT 25F and SST 25LF series are very similar, - * but commands for sector erase and chip id differ... - */ - -#define FLASH_PAGESIZE 256 - -/* Flash opcodes. */ -#define OPCODE_WREN 6 /* Write enable */ -#define OPCODE_RDSR 5 /* Read status register */ -#define OPCODE_READ 3 /* Read data bytes */ -#define OPCODE_PP 2 /* Page program */ -#define OPCODE_SE 0xd8 /* Sector erase */ -#define OPCODE_RES 0xab /* Read Electronic Signature */ -#define OPCODE_RDID 0x9f /* Read JEDEC ID */ - -/* Status Register bits. */ -#define SR_WIP 1 /* Write in progress */ -#define SR_WEL 2 /* Write enable latch */ -#define SR_BP0 4 /* Block protect 0 */ -#define SR_BP1 8 /* Block protect 1 */ -#define SR_BP2 0x10 /* Block protect 2 */ -#define SR_SRWD 0x80 /* SR write protect */ - -/* Define max times to check status register before we give up. */ -#define MAX_READY_WAIT_COUNT 100000 - - -#ifdef CONFIG_MTD_PARTITIONS -#define mtd_has_partitions() (1) -#else -#define mtd_has_partitions() (0) -#endif - -/****************************************************************************/ - +#define MAX_CMD_SIZE 6 struct m25p { struct spi_device *spi; - struct semaphore lock; + struct spi_nor spi_nor; struct mtd_info mtd; - unsigned partitioned; - u8 command[4]; + u8 command[MAX_CMD_SIZE]; }; -static inline struct m25p *mtd_to_m25p(struct mtd_info *mtd) +static int m25p80_read_reg(struct spi_nor *nor, u8 code, u8 *val, int len) { - return container_of(mtd, struct m25p, mtd); -} + struct m25p *flash = nor->priv; + struct spi_device *spi = flash->spi; + int ret; -/****************************************************************************/ + ret = spi_write_then_read(spi, &code, 1, val, len); + if (ret < 0) + dev_err(&spi->dev, "error %d reading %x\n", ret, code); -/* - * Internal helper functions - */ + return ret; +} -/* - * Read the status register, returning its value in the location - * Return the status register value. - * Returns negative if error occurred. - */ -static int read_sr(struct m25p *flash) +static void m25p_addr2cmd(struct spi_nor *nor, unsigned int addr, u8 *cmd) { - ssize_t retval; - u8 code = OPCODE_RDSR; - u8 val; - - retval = spi_write_then_read(flash->spi, &code, 1, &val, 1); - - if (retval < 0) { - dev_err(&flash->spi->dev, "error %d reading SR\n", - (int) retval); - return retval; - } - - return val; + /* opcode is in cmd[0] */ + cmd[1] = addr >> (nor->addr_width * 8 - 8); + cmd[2] = addr >> (nor->addr_width * 8 - 16); + cmd[3] = addr >> (nor->addr_width * 8 - 24); + cmd[4] = addr >> (nor->addr_width * 8 - 32); } - -/* - * Set write enable latch with Write Enable command. - * Returns negative if error occurred. - */ -static inline int write_enable(struct m25p *flash) +static int m25p_cmdsz(struct spi_nor *nor) { - u8 code = OPCODE_WREN; - - return spi_write_then_read(flash->spi, &code, 1, NULL, 0); + return 1 + nor->addr_width; } - -/* - * Service routine to read status register until ready, or timeout occurs. - * Returns non-zero if error. - */ -static int wait_till_ready(struct m25p *flash) +static int m25p80_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len, + int wr_en) { - int count; - int sr; + struct m25p *flash = nor->priv; + struct spi_device *spi = flash->spi; - /* one chip guarantees max 5 msec wait here after page writes, - * but potentially three seconds (!) after page erase. - */ - for (count = 0; count < MAX_READY_WAIT_COUNT; count++) { - if ((sr = read_sr(flash)) < 0) - break; - else if (!(sr & SR_WIP)) - return 0; - - /* REVISIT sometimes sleeping would be best */ - } + flash->command[0] = opcode; + if (buf) + memcpy(&flash->command[1], buf, len); - return 1; + return spi_write(spi, flash->command, len + 1); } - -/* - * Erase one sector of flash memory at offset ``offset'' which is any - * address within the sector which should be erased. - * - * Returns 0 if successful, non-zero otherwise. - */ -static int erase_sector(struct m25p *flash, u32 offset) +static void m25p80_write(struct spi_nor *nor, loff_t to, size_t len, + size_t *retlen, const u_char *buf) { - DEBUG(MTD_DEBUG_LEVEL3, "%s: %s at 0x%08x\n", flash->spi->dev.bus_id, - __FUNCTION__, offset); + struct m25p *flash = nor->priv; + struct spi_device *spi = flash->spi; + struct spi_transfer t[2] = {}; + struct spi_message m; + int cmd_sz = m25p_cmdsz(nor); - /* Wait until finished previous write command. */ - if (wait_till_ready(flash)) - return 1; + spi_message_init(&m); - /* Send write enable, then erase commands. */ - write_enable(flash); + if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second) + cmd_sz = 1; - /* Set up command buffer. */ - flash->command[0] = OPCODE_SE; - flash->command[1] = offset >> 16; - flash->command[2] = offset >> 8; - flash->command[3] = offset; + flash->command[0] = nor->program_opcode; + m25p_addr2cmd(nor, to, flash->command); - spi_write(flash->spi, flash->command, sizeof(flash->command)); + t[0].tx_buf = flash->command; + t[0].len = cmd_sz; + spi_message_add_tail(&t[0], &m); - return 0; -} + t[1].tx_buf = buf; + t[1].len = len; + spi_message_add_tail(&t[1], &m); -/****************************************************************************/ + spi_sync(spi, &m); -/* - * MTD implementation - */ + *retlen += m.actual_length - cmd_sz; +} -/* - * Erase an address range on the flash chip. The address range may extend - * one or more erase sectors. Return an error is there is a problem erasing. - */ -static int m25p80_erase(struct mtd_info *mtd, struct erase_info *instr) +static inline unsigned int m25p80_rx_nbits(struct spi_nor *nor) { - struct m25p *flash = mtd_to_m25p(mtd); - u32 addr,len; - - DEBUG(MTD_DEBUG_LEVEL2, "%s: %s %s 0x%08x, len %d\n", - flash->spi->dev.bus_id, __FUNCTION__, "at", - (u32)instr->addr, instr->len); - - /* sanity checks */ - if (instr->addr + instr->len > flash->mtd.size) - return -EINVAL; - if ((instr->addr % mtd->erasesize) != 0 - || (instr->len % mtd->erasesize) != 0) { - return -EINVAL; - } - - addr = instr->addr; - len = instr->len; - - down(&flash->lock); - - /* now erase those sectors */ - while (len) { - if (erase_sector(flash, addr)) { - instr->state = MTD_ERASE_FAILED; - up(&flash->lock); - return -EIO; - } - - addr += mtd->erasesize; - len -= mtd->erasesize; + switch (nor->flash_read) { + case SPI_NOR_DUAL: + return 2; + case SPI_NOR_QUAD: + return 4; + default: + return 0; } - - up(&flash->lock); - - instr->state = MTD_ERASE_DONE; - mtd_erase_callback(instr); - - return 0; } /* - * Read an address range from the flash chip. The address range + * Read an address range from the nor chip. The address range * may be any size provided it is within the physical boundaries. */ -static int m25p80_read(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf) +static int m25p80_read(struct spi_nor *nor, loff_t from, size_t len, + size_t *retlen, u_char *buf) { - struct m25p *flash = mtd_to_m25p(mtd); + struct m25p *flash = nor->priv; + struct spi_device *spi = flash->spi; struct spi_transfer t[2]; struct spi_message m; + int dummy = nor->read_dummy; + int ret; - DEBUG(MTD_DEBUG_LEVEL2, "%s: %s %s 0x%08x, len %zd\n", - flash->spi->dev.bus_id, __FUNCTION__, "from", - (u32)from, len); - - /* sanity checks */ - if (!len) - return 0; - - if (from + len > flash->mtd.size) - return -EINVAL; + /* Wait till previous write/erase is done. */ + ret = nor->wait_till_ready(nor); + if (ret) + return ret; spi_message_init(&m); memset(t, 0, (sizeof t)); + flash->command[0] = nor->read_opcode; + m25p_addr2cmd(nor, from, flash->command); + t[0].tx_buf = flash->command; - t[0].len = sizeof(flash->command); + t[0].len = m25p_cmdsz(nor) + dummy; spi_message_add_tail(&t[0], &m); t[1].rx_buf = buf; + t[1].rx_nbits = m25p80_rx_nbits(nor); t[1].len = len; spi_message_add_tail(&t[1], &m); - /* Byte count starts at zero. */ - if (retlen) - *retlen = 0; - - down(&flash->lock); - - /* Wait till previous write/erase is done. */ - if (wait_till_ready(flash)) { - /* REVISIT status return?? */ - up(&flash->lock); - return 1; - } - - /* NOTE: OPCODE_FAST_READ (if available) is faster... */ - - /* Set up the write data buffer. */ - flash->command[0] = OPCODE_READ; - flash->command[1] = from >> 16; - flash->command[2] = from >> 8; - flash->command[3] = from; - - spi_sync(flash->spi, &m); - - *retlen = m.actual_length - sizeof(flash->command); - - up(&flash->lock); + spi_sync(spi, &m); + *retlen = m.actual_length - m25p_cmdsz(nor) - dummy; return 0; } -/* - * Write an address range to the flash chip. Data must be written in - * FLASH_PAGESIZE chunks. The address range may be any size provided - * it is within the physical boundaries. - */ -static int m25p80_write(struct mtd_info *mtd, loff_t to, size_t len, - size_t *retlen, const u_char *buf) +static int m25p80_erase(struct spi_nor *nor, loff_t offset) { - struct m25p *flash = mtd_to_m25p(mtd); - u32 page_offset, page_size; - struct spi_transfer t[2]; - struct spi_message m; - - DEBUG(MTD_DEBUG_LEVEL2, "%s: %s %s 0x%08x, len %zd\n", - flash->spi->dev.bus_id, __FUNCTION__, "to", - (u32)to, len); - - if (retlen) - *retlen = 0; - - /* sanity checks */ - if (!len) - return(0); - - if (to + len > flash->mtd.size) - return -EINVAL; - - spi_message_init(&m); - memset(t, 0, (sizeof t)); - - t[0].tx_buf = flash->command; - t[0].len = sizeof(flash->command); - spi_message_add_tail(&t[0], &m); + struct m25p *flash = nor->priv; + int ret; - t[1].tx_buf = buf; - spi_message_add_tail(&t[1], &m); - - down(&flash->lock); + dev_dbg(nor->dev, "%dKiB at 0x%08x\n", + flash->mtd.erasesize / 1024, (u32)offset); /* Wait until finished previous write command. */ - if (wait_till_ready(flash)) - return 1; - - write_enable(flash); - - /* Set up the opcode in the write buffer. */ - flash->command[0] = OPCODE_PP; - flash->command[1] = to >> 16; - flash->command[2] = to >> 8; - flash->command[3] = to; - - /* what page do we start with? */ - page_offset = to % FLASH_PAGESIZE; - - /* do all the bytes fit onto one page? */ - if (page_offset + len <= FLASH_PAGESIZE) { - t[1].len = len; - - spi_sync(flash->spi, &m); - - *retlen = m.actual_length - sizeof(flash->command); - } else { - u32 i; - - /* the size of data remaining on the first page */ - page_size = FLASH_PAGESIZE - page_offset; - - t[1].len = page_size; - spi_sync(flash->spi, &m); - - *retlen = m.actual_length - sizeof(flash->command); - - /* write everything in PAGESIZE chunks */ - for (i = page_size; i < len; i += page_size) { - page_size = len - i; - if (page_size > FLASH_PAGESIZE) - page_size = FLASH_PAGESIZE; - - /* write the next page to flash */ - flash->command[1] = (to + i) >> 16; - flash->command[2] = (to + i) >> 8; - flash->command[3] = (to + i); - - t[1].tx_buf = buf + i; - t[1].len = page_size; - - wait_till_ready(flash); - - write_enable(flash); + ret = nor->wait_till_ready(nor); + if (ret) + return ret; - spi_sync(flash->spi, &m); + /* Send write enable, then erase commands. */ + ret = nor->write_reg(nor, SPINOR_OP_WREN, NULL, 0, 0); + if (ret) + return ret; - if (retlen) - *retlen += m.actual_length - - sizeof(flash->command); - } - } + /* Set up command buffer. */ + flash->command[0] = nor->erase_opcode; + m25p_addr2cmd(nor, offset, flash->command); - up(&flash->lock); + spi_write(flash->spi, flash->command, m25p_cmdsz(nor)); return 0; } - -/****************************************************************************/ - -/* - * SPI device driver setup and teardown - */ - -struct flash_info { - char *name; - u8 id; - u16 jedec_id; - unsigned sector_size; - unsigned n_sectors; -}; - -static struct flash_info __devinitdata m25p_data [] = { - /* REVISIT: fill in JEDEC ids, for parts that have them */ - { "m25p05", 0x05, 0x2010, 32 * 1024, 2 }, - { "m25p10", 0x10, 0x2011, 32 * 1024, 4 }, - { "m25p20", 0x11, 0x2012, 64 * 1024, 4 }, - { "m25p40", 0x12, 0x2013, 64 * 1024, 8 }, - { "m25p80", 0x13, 0x0000, 64 * 1024, 16 }, - { "m25p16", 0x14, 0x2015, 64 * 1024, 32 }, - { "m25p32", 0x15, 0x2016, 64 * 1024, 64 }, - { "m25p64", 0x16, 0x2017, 64 * 1024, 128 }, -}; - /* * board specific setup should have ensured the SPI clock used here * matches what the READ command supports, at least until this driver * understands FAST_READ (for clocks over 25 MHz). */ -static int __devinit m25p_probe(struct spi_device *spi) +static int m25p_probe(struct spi_device *spi) { + struct mtd_part_parser_data ppdata; struct flash_platform_data *data; - struct m25p *flash; - struct flash_info *info; - unsigned i; - - /* Platform data helps sort out which chip type we have, as - * well as how this board partitions it. - */ - data = spi->dev.platform_data; - if (!data || !data->type) { - /* FIXME some chips can identify themselves with RES - * or JEDEC get-id commands. Try them ... - */ - DEBUG(MTD_DEBUG_LEVEL1, "%s: no chip id\n", - flash->spi->dev.bus_id); - return -ENODEV; - } + struct m25p *flash; + struct spi_nor *nor; + enum read_mode mode = SPI_NOR_NORMAL; + int ret; - for (i = 0, info = m25p_data; i < ARRAY_SIZE(m25p_data); i++, info++) { - if (strcmp(data->type, info->name) == 0) - break; - } - if (i == ARRAY_SIZE(m25p_data)) { - DEBUG(MTD_DEBUG_LEVEL1, "%s: unrecognized id %s\n", - flash->spi->dev.bus_id, data->type); - return -ENODEV; - } - - flash = kzalloc(sizeof *flash, SLAB_KERNEL); + flash = devm_kzalloc(&spi->dev, sizeof(*flash), GFP_KERNEL); if (!flash) return -ENOMEM; + nor = &flash->spi_nor; + + /* install the hooks */ + nor->read = m25p80_read; + nor->write = m25p80_write; + nor->erase = m25p80_erase; + nor->write_reg = m25p80_write_reg; + nor->read_reg = m25p80_read_reg; + + nor->dev = &spi->dev; + nor->mtd = &flash->mtd; + nor->priv = flash; + + spi_set_drvdata(spi, flash); + flash->mtd.priv = nor; flash->spi = spi; - init_MUTEX(&flash->lock); - dev_set_drvdata(&spi->dev, flash); - - if (data->name) - flash->mtd.name = data->name; - else - flash->mtd.name = spi->dev.bus_id; - - flash->mtd.type = MTD_NORFLASH; - flash->mtd.writesize = 1; - flash->mtd.flags = MTD_CAP_NORFLASH; - flash->mtd.size = info->sector_size * info->n_sectors; - flash->mtd.erasesize = info->sector_size; - flash->mtd.erase = m25p80_erase; - flash->mtd.read = m25p80_read; - flash->mtd.write = m25p80_write; - - dev_info(&spi->dev, "%s (%d Kbytes)\n", info->name, - flash->mtd.size / 1024); - - DEBUG(MTD_DEBUG_LEVEL2, - "mtd .name = %s, .size = 0x%.8x (%uM) " - ".erasesize = 0x%.8x (%uK) .numeraseregions = %d\n", - flash->mtd.name, - flash->mtd.size, flash->mtd.size / (1024*1024), - flash->mtd.erasesize, flash->mtd.erasesize / 1024, - flash->mtd.numeraseregions); - - if (flash->mtd.numeraseregions) - for (i = 0; i < flash->mtd.numeraseregions; i++) - DEBUG(MTD_DEBUG_LEVEL2, - "mtd.eraseregions[%d] = { .offset = 0x%.8x, " - ".erasesize = 0x%.8x (%uK), " - ".numblocks = %d }\n", - i, flash->mtd.eraseregions[i].offset, - flash->mtd.eraseregions[i].erasesize, - flash->mtd.eraseregions[i].erasesize / 1024, - flash->mtd.eraseregions[i].numblocks); - - - /* partitions should match sector boundaries; and it may be good to - * use readonly partitions for writeprotected sectors (BP2..BP0). - */ - if (mtd_has_partitions()) { - struct mtd_partition *parts = NULL; - int nr_parts = 0; - -#ifdef CONFIG_MTD_CMDLINE_PARTS - static const char *part_probes[] = { "cmdlinepart", NULL, }; - - nr_parts = parse_mtd_partitions(&flash->mtd, - part_probes, &parts, 0); -#endif - - if (nr_parts <= 0 && data && data->parts) { - parts = data->parts; - nr_parts = data->nr_parts; - } - - if (nr_parts > 0) { - for (i = 0; i < data->nr_parts; i++) { - DEBUG(MTD_DEBUG_LEVEL2, "partitions[%d] = " - "{.name = %s, .offset = 0x%.8x, " - ".size = 0x%.8x (%uK) }\n", - i, data->parts[i].name, - data->parts[i].offset, - data->parts[i].size, - data->parts[i].size / 1024); - } - flash->partitioned = 1; - return add_mtd_partitions(&flash->mtd, parts, nr_parts); - } - } else if (data->nr_parts) - dev_warn(&spi->dev, "ignoring %d default partitions on %s\n", - data->nr_parts, data->name); - - return add_mtd_device(&flash->mtd) == 1 ? -ENODEV : 0; + + if (spi->mode & SPI_RX_QUAD) + mode = SPI_NOR_QUAD; + else if (spi->mode & SPI_RX_DUAL) + mode = SPI_NOR_DUAL; + ret = spi_nor_scan(nor, spi_get_device_id(spi), mode); + if (ret) + return ret; + + data = dev_get_platdata(&spi->dev); + ppdata.of_node = spi->dev.of_node; + + return mtd_device_parse_register(&flash->mtd, NULL, &ppdata, + data ? data->parts : NULL, + data ? data->nr_parts : 0); } -static int __devexit m25p_remove(struct spi_device *spi) +static int m25p_remove(struct spi_device *spi) { - struct m25p *flash = dev_get_drvdata(&spi->dev); - int status; + struct m25p *flash = spi_get_drvdata(spi); /* Clean up MTD stuff. */ - if (mtd_has_partitions() && flash->partitioned) - status = del_mtd_partitions(&flash->mtd); - else - status = del_mtd_device(&flash->mtd); - if (status == 0) - kfree(flash); - return 0; + return mtd_device_unregister(&flash->mtd); } static struct spi_driver m25p80_driver = { .driver = { .name = "m25p80", - .bus = &spi_bus_type, .owner = THIS_MODULE, }, + .id_table = spi_nor_ids, .probe = m25p_probe, - .remove = __devexit_p(m25p_remove), -}; - - -static int m25p80_init(void) -{ - return spi_register_driver(&m25p80_driver); -} - - -static void m25p80_exit(void) -{ - spi_unregister_driver(&m25p80_driver); -} + .remove = m25p_remove, + /* REVISIT: many of these chips have deep power-down modes, which + * should clearly be entered on suspend() to minimize power use. + * And also when they're otherwise idle... + */ +}; -module_init(m25p80_init); -module_exit(m25p80_exit); +module_spi_driver(m25p80_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Mike Lavender"); diff --git a/drivers/mtd/devices/ms02-nv.c b/drivers/mtd/devices/ms02-nv.c index 08dfb899b27..5c8b322ba90 100644 --- a/drivers/mtd/devices/ms02-nv.c +++ b/drivers/mtd/devices/ms02-nv.c @@ -5,8 +5,6 @@ * 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. - * - * $Id: ms02-nv.c,v 1.11 2005/11/14 13:41:47 macro Exp $ */ #include <linux/init.h> @@ -61,12 +59,8 @@ static int ms02nv_read(struct mtd_info *mtd, loff_t from, { struct ms02nv_private *mp = mtd->priv; - if (from + len > mtd->size) - return -EINVAL; - memcpy(buf, mp->uaddr + from, len); *retlen = len; - return 0; } @@ -75,12 +69,8 @@ static int ms02nv_write(struct mtd_info *mtd, loff_t to, { struct ms02nv_private *mp = mtd->priv; - if (to + len > mtd->size) - return -EINVAL; - memcpy(mp->uaddr + to, buf, len); *retlen = len; - return 0; } @@ -131,11 +121,10 @@ static int __init ms02nv_init_one(ulong addr) int ret = -ENODEV; /* The module decodes 8MiB of address space. */ - mod_res = kmalloc(sizeof(*mod_res), GFP_KERNEL); + mod_res = kzalloc(sizeof(*mod_res), GFP_KERNEL); if (!mod_res) return -ENOMEM; - memset(mod_res, 0, sizeof(*mod_res)); mod_res->name = ms02nv_name; mod_res->start = addr; mod_res->end = addr + MS02NV_SLOT_SIZE - 1; @@ -153,24 +142,21 @@ static int __init ms02nv_init_one(ulong addr) } ret = -ENOMEM; - mtd = kmalloc(sizeof(*mtd), GFP_KERNEL); + mtd = kzalloc(sizeof(*mtd), GFP_KERNEL); if (!mtd) goto err_out_mod_res_rel; - memset(mtd, 0, sizeof(*mtd)); - mp = kmalloc(sizeof(*mp), GFP_KERNEL); + mp = kzalloc(sizeof(*mp), GFP_KERNEL); if (!mp) goto err_out_mtd; - memset(mp, 0, sizeof(*mp)); mtd->priv = mp; mp->resource.module = mod_res; /* Firmware's diagnostic NVRAM area. */ - diag_res = kmalloc(sizeof(*diag_res), GFP_KERNEL); + diag_res = kzalloc(sizeof(*diag_res), GFP_KERNEL); if (!diag_res) goto err_out_mp; - memset(diag_res, 0, sizeof(*diag_res)); diag_res->name = ms02nv_res_diag_ram; diag_res->start = addr; diag_res->end = addr + MS02NV_RAM - 1; @@ -180,11 +166,10 @@ static int __init ms02nv_init_one(ulong addr) mp->resource.diag_ram = diag_res; /* User-available general-purpose NVRAM area. */ - user_res = kmalloc(sizeof(*user_res), GFP_KERNEL); + user_res = kzalloc(sizeof(*user_res), GFP_KERNEL); if (!user_res) goto err_out_diag_res; - memset(user_res, 0, sizeof(*user_res)); user_res->name = ms02nv_res_user_ram; user_res->start = addr + MS02NV_RAM; user_res->end = addr + size - 1; @@ -194,11 +179,10 @@ static int __init ms02nv_init_one(ulong addr) mp->resource.user_ram = user_res; /* Control and status register. */ - csr_res = kmalloc(sizeof(*csr_res), GFP_KERNEL); + csr_res = kzalloc(sizeof(*csr_res), GFP_KERNEL); if (!csr_res) goto err_out_user_res; - memset(csr_res, 0, sizeof(*csr_res)); csr_res->name = ms02nv_res_csr; csr_res->start = addr + MS02NV_CSR; csr_res->end = addr + MS02NV_CSR + 3; @@ -221,14 +205,14 @@ static int __init ms02nv_init_one(ulong addr) mtd->type = MTD_RAM; mtd->flags = MTD_CAP_RAM; mtd->size = fixsize; - mtd->name = (char *)ms02nv_name; + mtd->name = ms02nv_name; mtd->owner = THIS_MODULE; - mtd->read = ms02nv_read; - mtd->write = ms02nv_write; + mtd->_read = ms02nv_read; + mtd->_write = ms02nv_write; mtd->writesize = 1; ret = -EIO; - if (add_mtd_device(mtd)) { + if (mtd_device_register(mtd, NULL, 0)) { printk(KERN_ERR "ms02-nv: Unable to register MTD device, aborting!\n"); goto err_out_csr_res; @@ -270,7 +254,7 @@ static void __exit ms02nv_remove_one(void) root_ms02nv_mtd = mp->next; - del_mtd_device(mtd); + mtd_device_unregister(mtd); release_resource(mp->resource.csr); kfree(mp->resource.csr); diff --git a/drivers/mtd/devices/ms02-nv.h b/drivers/mtd/devices/ms02-nv.h index 8a6eef7cfee..04deafd3a77 100644 --- a/drivers/mtd/devices/ms02-nv.h +++ b/drivers/mtd/devices/ms02-nv.h @@ -9,8 +9,6 @@ * 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. - * - * $Id: ms02-nv.h,v 1.3 2003/08/19 09:25:36 dwmw2 Exp $ */ #include <linux/ioport.h> diff --git a/drivers/mtd/devices/mtd_dataflash.c b/drivers/mtd/devices/mtd_dataflash.c index 5db71604592..dd22ce2cc9a 100644 --- a/drivers/mtd/devices/mtd_dataflash.c +++ b/drivers/mtd/devices/mtd_dataflash.c @@ -10,29 +10,31 @@ * 2 of the License, or (at your option) any later version. */ #include <linux/module.h> -#include <linux/init.h> #include <linux/slab.h> #include <linux/delay.h> #include <linux/device.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/math64.h> +#include <linux/of.h> +#include <linux/of_device.h> + #include <linux/spi/spi.h> #include <linux/spi/flash.h> #include <linux/mtd/mtd.h> #include <linux/mtd/partitions.h> - /* * DataFlash is a kind of SPI flash. Most AT45 chips have two buffers in * each chip, which may be used for double buffered I/O; but this driver * doesn't (yet) use these for any kind of i/o overlap or prefetching. * * Sometimes DataFlash is packaged in MMC-format cards, although the - * MMC stack can't use SPI (yet), or distinguish between MMC and DataFlash + * MMC stack can't (yet?) distinguish between MMC and DataFlash * protocols during enumeration. */ -#define CONFIG_DATAFLASH_WRITE_VERIFY - /* reads can bypass the buffers */ #define OP_READ_CONTINUOUS 0xE8 #define OP_READ_PAGE 0xD2 @@ -77,28 +79,29 @@ */ #define OP_READ_ID 0x9F #define OP_READ_SECURITY 0x77 -#define OP_WRITE_SECURITY 0x9A /* OTP bits */ +#define OP_WRITE_SECURITY_REVC 0x9A +#define OP_WRITE_SECURITY 0x9B /* revision D */ struct dataflash { - u8 command[4]; + uint8_t command[4]; char name[24]; - unsigned partitioned:1; - unsigned short page_offset; /* offset in flash address */ unsigned int page_size; /* of bytes per page */ - struct semaphore lock; + struct mutex lock; struct spi_device *spi; struct mtd_info mtd; }; -#ifdef CONFIG_MTD_PARTITIONS -#define mtd_has_partitions() (1) -#else -#define mtd_has_partitions() (0) +#ifdef CONFIG_OF +static const struct of_device_id dataflash_dt_ids[] = { + { .compatible = "atmel,at45", }, + { .compatible = "atmel,dataflash", }, + { /* sentinel */ } +}; #endif /* ......................................................................... */ @@ -125,8 +128,8 @@ static int dataflash_waitready(struct spi_device *spi) for (;;) { status = dataflash_status(spi); if (status < 0) { - DEBUG(MTD_DEBUG_LEVEL1, "%s: status %d?\n", - spi->dev.bus_id, status); + pr_debug("%s: status %d?\n", + dev_name(&spi->dev), status); status = 0; } @@ -144,21 +147,23 @@ static int dataflash_waitready(struct spi_device *spi) */ static int dataflash_erase(struct mtd_info *mtd, struct erase_info *instr) { - struct dataflash *priv = (struct dataflash *)mtd->priv; + struct dataflash *priv = mtd->priv; struct spi_device *spi = priv->spi; struct spi_transfer x = { .tx_dma = 0, }; struct spi_message msg; unsigned blocksize = priv->page_size << 3; - u8 *command; + uint8_t *command; + uint32_t rem; - DEBUG(MTD_DEBUG_LEVEL2, "%s: erase addr=0x%x len 0x%x\n", - spi->dev.bus_id, - instr->addr, instr->len); + pr_debug("%s: erase addr=0x%llx len 0x%llx\n", + dev_name(&spi->dev), (long long)instr->addr, + (long long)instr->len); - /* Sanity checks */ - if ((instr->addr + instr->len) > mtd->size - || (instr->len % priv->page_size) != 0 - || (instr->addr % priv->page_size) != 0) + div_u64_rem(instr->len, priv->page_size, &rem); + if (rem) + return -EINVAL; + div_u64_rem(instr->addr, priv->page_size, &rem); + if (rem) return -EINVAL; spi_message_init(&msg); @@ -167,7 +172,7 @@ static int dataflash_erase(struct mtd_info *mtd, struct erase_info *instr) x.len = 4; spi_message_add_tail(&x, &msg); - down(&priv->lock); + mutex_lock(&priv->lock); while (instr->len > 0) { unsigned int pageaddr; int status; @@ -176,16 +181,16 @@ static int dataflash_erase(struct mtd_info *mtd, struct erase_info *instr) /* Calculate flash page address; use block erase (for speed) if * we're at a block boundary and need to erase the whole block. */ - pageaddr = instr->addr / priv->page_size; + pageaddr = div_u64(instr->addr, priv->page_size); do_block = (pageaddr & 0x7) == 0 && instr->len >= blocksize; pageaddr = pageaddr << priv->page_offset; command[0] = do_block ? OP_ERASE_BLOCK : OP_ERASE_PAGE; - command[1] = (u8)(pageaddr >> 16); - command[2] = (u8)(pageaddr >> 8); + command[1] = (uint8_t)(pageaddr >> 16); + command[2] = (uint8_t)(pageaddr >> 8); command[3] = 0; - DEBUG(MTD_DEBUG_LEVEL3, "ERASE %s: (%x) %x %x %x [%i]\n", + pr_debug("ERASE %s: (%x) %x %x %x [%i]\n", do_block ? "block" : "page", command[0], command[1], command[2], command[3], pageaddr); @@ -195,7 +200,7 @@ static int dataflash_erase(struct mtd_info *mtd, struct erase_info *instr) if (status < 0) { printk(KERN_ERR "%s: erase %x, err %d\n", - spi->dev.bus_id, pageaddr, status); + dev_name(&spi->dev), pageaddr, status); /* REVISIT: can retry instr->retries times; or * giveup and instr->fail_addr = instr->addr; */ @@ -210,7 +215,7 @@ static int dataflash_erase(struct mtd_info *mtd, struct erase_info *instr) instr->len -= priv->page_size; } } - up(&priv->lock); + mutex_unlock(&priv->lock); /* Inform MTD subsystem that erase is complete */ instr->state = MTD_ERASE_DONE; @@ -229,23 +234,15 @@ static int dataflash_erase(struct mtd_info *mtd, struct erase_info *instr) static int dataflash_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { - struct dataflash *priv = (struct dataflash *)mtd->priv; + struct dataflash *priv = mtd->priv; struct spi_transfer x[2] = { { .tx_dma = 0, }, }; struct spi_message msg; unsigned int addr; - u8 *command; + uint8_t *command; int status; - DEBUG(MTD_DEBUG_LEVEL2, "%s: read 0x%x..0x%x\n", - priv->spi->dev.bus_id, (unsigned)from, (unsigned)(from + len)); - - *retlen = 0; - - /* Sanity checks */ - if (!len) - return 0; - if (from + len > mtd->size) - return -EINVAL; + pr_debug("%s: read 0x%x..0x%x\n", dev_name(&priv->spi->dev), + (unsigned)from, (unsigned)(from + len)); /* Calculate flash page/byte address */ addr = (((unsigned)from / priv->page_size) << priv->page_offset) @@ -253,7 +250,7 @@ static int dataflash_read(struct mtd_info *mtd, loff_t from, size_t len, command = priv->command; - DEBUG(MTD_DEBUG_LEVEL3, "READ: (%x) %x %x %x\n", + pr_debug("READ: (%x) %x %x %x\n", command[0], command[1], command[2], command[3]); spi_message_init(&msg); @@ -266,27 +263,27 @@ static int dataflash_read(struct mtd_info *mtd, loff_t from, size_t len, x[1].len = len; spi_message_add_tail(&x[1], &msg); - down(&priv->lock); + mutex_lock(&priv->lock); /* Continuous read, max clock = f(car) which may be less than * the peak rate available. Some chips support commands with * fewer "don't care" bytes. Both buffers stay unchanged. */ command[0] = OP_READ_CONTINUOUS; - command[1] = (u8)(addr >> 16); - command[2] = (u8)(addr >> 8); - command[3] = (u8)(addr >> 0); + command[1] = (uint8_t)(addr >> 16); + command[2] = (uint8_t)(addr >> 8); + command[3] = (uint8_t)(addr >> 0); /* plus 4 "don't care" bytes */ status = spi_sync(priv->spi, &msg); - up(&priv->lock); + mutex_unlock(&priv->lock); if (status >= 0) { *retlen = msg.actual_length - 8; status = 0; } else - DEBUG(MTD_DEBUG_LEVEL1, "%s: read %x..%x --> %d\n", - priv->spi->dev.bus_id, + pr_debug("%s: read %x..%x --> %d\n", + dev_name(&priv->spi->dev), (unsigned)from, (unsigned)(from + len), status); return status; @@ -302,7 +299,7 @@ static int dataflash_read(struct mtd_info *mtd, loff_t from, size_t len, static int dataflash_write(struct mtd_info *mtd, loff_t to, size_t len, size_t * retlen, const u_char * buf) { - struct dataflash *priv = (struct dataflash *)mtd->priv; + struct dataflash *priv = mtd->priv; struct spi_device *spi = priv->spi; struct spi_transfer x[2] = { { .tx_dma = 0, }, }; struct spi_message msg; @@ -310,18 +307,10 @@ static int dataflash_write(struct mtd_info *mtd, loff_t to, size_t len, size_t remaining = len; u_char *writebuf = (u_char *) buf; int status = -EINVAL; - u8 *command; - - DEBUG(MTD_DEBUG_LEVEL2, "%s: write 0x%x..0x%x\n", - spi->dev.bus_id, (unsigned)to, (unsigned)(to + len)); + uint8_t *command; - *retlen = 0; - - /* Sanity checks */ - if (!len) - return 0; - if ((to + len) > mtd->size) - return -EINVAL; + pr_debug("%s: write 0x%x..0x%x\n", + dev_name(&spi->dev), (unsigned)to, (unsigned)(to + len)); spi_message_init(&msg); @@ -336,9 +325,9 @@ static int dataflash_write(struct mtd_info *mtd, loff_t to, size_t len, else writelen = len; - down(&priv->lock); + mutex_lock(&priv->lock); while (remaining > 0) { - DEBUG(MTD_DEBUG_LEVEL3, "write @ %i:%i len=%i\n", + pr_debug("write @ %i:%i len=%i\n", pageaddr, offset, writelen); /* REVISIT: @@ -366,13 +355,13 @@ static int dataflash_write(struct mtd_info *mtd, loff_t to, size_t len, command[2] = (addr & 0x0000FF00) >> 8; command[3] = 0; - DEBUG(MTD_DEBUG_LEVEL3, "TRANSFER: (%x) %x %x %x\n", + pr_debug("TRANSFER: (%x) %x %x %x\n", command[0], command[1], command[2], command[3]); status = spi_sync(spi, &msg); if (status < 0) - DEBUG(MTD_DEBUG_LEVEL1, "%s: xfer %u -> %d \n", - spi->dev.bus_id, addr, status); + pr_debug("%s: xfer %u -> %d\n", + dev_name(&spi->dev), addr, status); (void) dataflash_waitready(priv->spi); } @@ -384,7 +373,7 @@ static int dataflash_write(struct mtd_info *mtd, loff_t to, size_t len, command[2] = (addr & 0x0000FF00) >> 8; command[3] = (addr & 0x000000FF); - DEBUG(MTD_DEBUG_LEVEL3, "PROGRAM: (%x) %x %x %x\n", + pr_debug("PROGRAM: (%x) %x %x %x\n", command[0], command[1], command[2], command[3]); x[1].tx_buf = writebuf; @@ -393,13 +382,13 @@ static int dataflash_write(struct mtd_info *mtd, loff_t to, size_t len, status = spi_sync(spi, &msg); spi_transfer_del(x + 1); if (status < 0) - DEBUG(MTD_DEBUG_LEVEL1, "%s: pgm %u/%u -> %d \n", - spi->dev.bus_id, addr, writelen, status); + pr_debug("%s: pgm %u/%u -> %d\n", + dev_name(&spi->dev), addr, writelen, status); (void) dataflash_waitready(priv->spi); -#ifdef CONFIG_DATAFLASH_WRITE_VERIFY +#ifdef CONFIG_MTD_DATAFLASH_WRITE_VERIFY /* (3) Compare to Buffer1 */ addr = pageaddr << priv->page_offset; @@ -408,27 +397,27 @@ static int dataflash_write(struct mtd_info *mtd, loff_t to, size_t len, command[2] = (addr & 0x0000FF00) >> 8; command[3] = 0; - DEBUG(MTD_DEBUG_LEVEL3, "COMPARE: (%x) %x %x %x\n", + pr_debug("COMPARE: (%x) %x %x %x\n", command[0], command[1], command[2], command[3]); status = spi_sync(spi, &msg); if (status < 0) - DEBUG(MTD_DEBUG_LEVEL1, "%s: compare %u -> %d \n", - spi->dev.bus_id, addr, status); + pr_debug("%s: compare %u -> %d\n", + dev_name(&spi->dev), addr, status); status = dataflash_waitready(priv->spi); /* Check result of the compare operation */ - if ((status & (1 << 6)) == 1) { + if (status & (1 << 6)) { printk(KERN_ERR "%s: compare page %u, err %d\n", - spi->dev.bus_id, pageaddr, status); + dev_name(&spi->dev), pageaddr, status); remaining = 0; status = -EIO; break; } else status = 0; -#endif /* CONFIG_DATAFLASH_WRITE_VERIFY */ +#endif /* CONFIG_MTD_DATAFLASH_WRITE_VERIFY */ remaining = remaining - writelen; pageaddr++; @@ -441,29 +430,209 @@ static int dataflash_write(struct mtd_info *mtd, loff_t to, size_t len, else writelen = remaining; } - up(&priv->lock); + mutex_unlock(&priv->lock); return status; } /* ......................................................................... */ +#ifdef CONFIG_MTD_DATAFLASH_OTP + +static int dataflash_get_otp_info(struct mtd_info *mtd, size_t len, + size_t *retlen, struct otp_info *info) +{ + /* Report both blocks as identical: bytes 0..64, locked. + * Unless the user block changed from all-ones, we can't + * tell whether it's still writable; so we assume it isn't. + */ + info->start = 0; + info->length = 64; + info->locked = 1; + *retlen = sizeof(*info); + return 0; +} + +static ssize_t otp_read(struct spi_device *spi, unsigned base, + uint8_t *buf, loff_t off, size_t len) +{ + struct spi_message m; + size_t l; + uint8_t *scratch; + struct spi_transfer t; + int status; + + if (off > 64) + return -EINVAL; + + if ((off + len) > 64) + len = 64 - off; + + spi_message_init(&m); + + l = 4 + base + off + len; + scratch = kzalloc(l, GFP_KERNEL); + if (!scratch) + return -ENOMEM; + + /* OUT: OP_READ_SECURITY, 3 don't-care bytes, zeroes + * IN: ignore 4 bytes, data bytes 0..N (max 127) + */ + scratch[0] = OP_READ_SECURITY; + + memset(&t, 0, sizeof t); + t.tx_buf = scratch; + t.rx_buf = scratch; + t.len = l; + spi_message_add_tail(&t, &m); + + dataflash_waitready(spi); + + status = spi_sync(spi, &m); + if (status >= 0) { + memcpy(buf, scratch + 4 + base + off, len); + status = len; + } + + kfree(scratch); + return status; +} + +static int dataflash_read_fact_otp(struct mtd_info *mtd, + loff_t from, size_t len, size_t *retlen, u_char *buf) +{ + struct dataflash *priv = mtd->priv; + int status; + + /* 64 bytes, from 0..63 ... start at 64 on-chip */ + mutex_lock(&priv->lock); + status = otp_read(priv->spi, 64, buf, from, len); + mutex_unlock(&priv->lock); + + if (status < 0) + return status; + *retlen = status; + return 0; +} + +static int dataflash_read_user_otp(struct mtd_info *mtd, + loff_t from, size_t len, size_t *retlen, u_char *buf) +{ + struct dataflash *priv = mtd->priv; + int status; + + /* 64 bytes, from 0..63 ... start at 0 on-chip */ + mutex_lock(&priv->lock); + status = otp_read(priv->spi, 0, buf, from, len); + mutex_unlock(&priv->lock); + + if (status < 0) + return status; + *retlen = status; + return 0; +} + +static int dataflash_write_user_otp(struct mtd_info *mtd, + loff_t from, size_t len, size_t *retlen, u_char *buf) +{ + struct spi_message m; + const size_t l = 4 + 64; + uint8_t *scratch; + struct spi_transfer t; + struct dataflash *priv = mtd->priv; + int status; + + if (from >= 64) { + /* + * Attempting to write beyond the end of OTP memory, + * no data can be written. + */ + *retlen = 0; + return 0; + } + + /* Truncate the write to fit into OTP memory. */ + if ((from + len) > 64) + len = 64 - from; + + /* OUT: OP_WRITE_SECURITY, 3 zeroes, 64 data-or-zero bytes + * IN: ignore all + */ + scratch = kzalloc(l, GFP_KERNEL); + if (!scratch) + return -ENOMEM; + scratch[0] = OP_WRITE_SECURITY; + memcpy(scratch + 4 + from, buf, len); + + spi_message_init(&m); + + memset(&t, 0, sizeof t); + t.tx_buf = scratch; + t.len = l; + spi_message_add_tail(&t, &m); + + /* Write the OTP bits, if they've not yet been written. + * This modifies SRAM buffer1. + */ + mutex_lock(&priv->lock); + dataflash_waitready(priv->spi); + status = spi_sync(priv->spi, &m); + mutex_unlock(&priv->lock); + + kfree(scratch); + + if (status >= 0) { + status = 0; + *retlen = len; + } + return status; +} + +static char *otp_setup(struct mtd_info *device, char revision) +{ + device->_get_fact_prot_info = dataflash_get_otp_info; + device->_read_fact_prot_reg = dataflash_read_fact_otp; + device->_get_user_prot_info = dataflash_get_otp_info; + device->_read_user_prot_reg = dataflash_read_user_otp; + + /* rev c parts (at45db321c and at45db1281 only!) use a + * different write procedure; not (yet?) implemented. + */ + if (revision > 'c') + device->_write_user_prot_reg = dataflash_write_user_otp; + + return ", OTP"; +} + +#else + +static char *otp_setup(struct mtd_info *device, char revision) +{ + return " (OTP)"; +} + +#endif + +/* ......................................................................... */ + /* * Register DataFlash device with MTD subsystem. */ -static int __devinit -add_dataflash(struct spi_device *spi, char *name, - int nr_pages, int pagesize, int pageoffset) +static int add_dataflash_otp(struct spi_device *spi, char *name, int nr_pages, + int pagesize, int pageoffset, char revision) { struct dataflash *priv; struct mtd_info *device; - struct flash_platform_data *pdata = spi->dev.platform_data; + struct mtd_part_parser_data ppdata; + struct flash_platform_data *pdata = dev_get_platdata(&spi->dev); + char *otp_tag = ""; + int err = 0; - priv = (struct dataflash *) kzalloc(sizeof *priv, GFP_KERNEL); + priv = kzalloc(sizeof *priv, GFP_KERNEL); if (!priv) return -ENOMEM; - init_MUTEX(&priv->lock); + mutex_init(&priv->lock); priv->spi = spi; priv->page_size = pagesize; priv->page_offset = pageoffset; @@ -480,47 +649,170 @@ add_dataflash(struct spi_device *spi, char *name, device->writesize = pagesize; device->owner = THIS_MODULE; device->type = MTD_DATAFLASH; - device->flags = MTD_CAP_NORFLASH; - device->erase = dataflash_erase; - device->read = dataflash_read; - device->write = dataflash_write; + device->flags = MTD_WRITEABLE; + device->_erase = dataflash_erase; + device->_read = dataflash_read; + device->_write = dataflash_write; device->priv = priv; - dev_info(&spi->dev, "%s (%d KBytes)\n", name, device->size/1024); - dev_set_drvdata(&spi->dev, priv); + device->dev.parent = &spi->dev; - if (mtd_has_partitions()) { - struct mtd_partition *parts; - int nr_parts = 0; + if (revision >= 'c') + otp_tag = otp_setup(device, revision); -#ifdef CONFIG_MTD_CMDLINE_PARTS - static const char *part_probes[] = { "cmdlinepart", NULL, }; + dev_info(&spi->dev, "%s (%lld KBytes) pagesize %d bytes%s\n", + name, (long long)((device->size + 1023) >> 10), + pagesize, otp_tag); + spi_set_drvdata(spi, priv); - nr_parts = parse_mtd_partitions(device, part_probes, &parts, 0); -#endif + ppdata.of_node = spi->dev.of_node; + err = mtd_device_parse_register(device, NULL, &ppdata, + pdata ? pdata->parts : NULL, + pdata ? pdata->nr_parts : 0); - if (nr_parts <= 0 && pdata && pdata->parts) { - parts = pdata->parts; - nr_parts = pdata->nr_parts; - } + if (!err) + return 0; + + kfree(priv); + return err; +} + +static inline int add_dataflash(struct spi_device *spi, char *name, + int nr_pages, int pagesize, int pageoffset) +{ + return add_dataflash_otp(spi, name, nr_pages, pagesize, + pageoffset, 0); +} - if (nr_parts > 0) { - priv->partitioned = 1; - return add_mtd_partitions(device, parts, nr_parts); +struct flash_info { + char *name; + + /* JEDEC id has a high byte of zero plus three data bytes: + * the manufacturer id, then a two byte device id. + */ + uint32_t jedec_id; + + /* The size listed here is what works with OP_ERASE_PAGE. */ + unsigned nr_pages; + uint16_t pagesize; + uint16_t pageoffset; + + uint16_t flags; +#define SUP_POW2PS 0x0002 /* supports 2^N byte pages */ +#define IS_POW2PS 0x0001 /* uses 2^N byte pages */ +}; + +static struct flash_info dataflash_data[] = { + + /* + * NOTE: chips with SUP_POW2PS (rev D and up) need two entries, + * one with IS_POW2PS and the other without. The entry with the + * non-2^N byte page size can't name exact chip revisions without + * losing backwards compatibility for cmdlinepart. + * + * These newer chips also support 128-byte security registers (with + * 64 bytes one-time-programmable) and software write-protection. + */ + { "AT45DB011B", 0x1f2200, 512, 264, 9, SUP_POW2PS}, + { "at45db011d", 0x1f2200, 512, 256, 8, SUP_POW2PS | IS_POW2PS}, + + { "AT45DB021B", 0x1f2300, 1024, 264, 9, SUP_POW2PS}, + { "at45db021d", 0x1f2300, 1024, 256, 8, SUP_POW2PS | IS_POW2PS}, + + { "AT45DB041x", 0x1f2400, 2048, 264, 9, SUP_POW2PS}, + { "at45db041d", 0x1f2400, 2048, 256, 8, SUP_POW2PS | IS_POW2PS}, + + { "AT45DB081B", 0x1f2500, 4096, 264, 9, SUP_POW2PS}, + { "at45db081d", 0x1f2500, 4096, 256, 8, SUP_POW2PS | IS_POW2PS}, + + { "AT45DB161x", 0x1f2600, 4096, 528, 10, SUP_POW2PS}, + { "at45db161d", 0x1f2600, 4096, 512, 9, SUP_POW2PS | IS_POW2PS}, + + { "AT45DB321x", 0x1f2700, 8192, 528, 10, 0}, /* rev C */ + + { "AT45DB321x", 0x1f2701, 8192, 528, 10, SUP_POW2PS}, + { "at45db321d", 0x1f2701, 8192, 512, 9, SUP_POW2PS | IS_POW2PS}, + + { "AT45DB642x", 0x1f2800, 8192, 1056, 11, SUP_POW2PS}, + { "at45db642d", 0x1f2800, 8192, 1024, 10, SUP_POW2PS | IS_POW2PS}, +}; + +static struct flash_info *jedec_probe(struct spi_device *spi) +{ + int tmp; + uint8_t code = OP_READ_ID; + uint8_t id[3]; + uint32_t jedec; + struct flash_info *info; + int status; + + /* JEDEC also defines an optional "extended device information" + * string for after vendor-specific data, after the three bytes + * we use here. Supporting some chips might require using it. + * + * If the vendor ID isn't Atmel's (0x1f), assume this call failed. + * That's not an error; only rev C and newer chips handle it, and + * only Atmel sells these chips. + */ + tmp = spi_write_then_read(spi, &code, 1, id, 3); + if (tmp < 0) { + pr_debug("%s: error %d reading JEDEC ID\n", + dev_name(&spi->dev), tmp); + return ERR_PTR(tmp); + } + if (id[0] != 0x1f) + return NULL; + + jedec = id[0]; + jedec = jedec << 8; + jedec |= id[1]; + jedec = jedec << 8; + jedec |= id[2]; + + for (tmp = 0, info = dataflash_data; + tmp < ARRAY_SIZE(dataflash_data); + tmp++, info++) { + if (info->jedec_id == jedec) { + pr_debug("%s: OTP, sector protect%s\n", + dev_name(&spi->dev), + (info->flags & SUP_POW2PS) + ? ", binary pagesize" : "" + ); + if (info->flags & SUP_POW2PS) { + status = dataflash_status(spi); + if (status < 0) { + pr_debug("%s: status error %d\n", + dev_name(&spi->dev), status); + return ERR_PTR(status); + } + if (status & 0x1) { + if (info->flags & IS_POW2PS) + return info; + } else { + if (!(info->flags & IS_POW2PS)) + return info; + } + } else + return info; } - } else if (pdata && pdata->nr_parts) - dev_warn(&spi->dev, "ignoring %d default partitions on %s\n", - pdata->nr_parts, device->name); + } - return add_mtd_device(device) == 1 ? -ENODEV : 0; + /* + * Treat other chips as errors ... we won't know the right page + * size (it might be binary) even when we can tell which density + * class is involved (legacy chip id scheme). + */ + dev_warn(&spi->dev, "JEDEC id %06x not handled\n", jedec); + return ERR_PTR(-ENODEV); } /* - * Detect and initialize DataFlash device: + * Detect and initialize DataFlash device, using JEDEC IDs on newer chips + * or else the ID code embedded in the status bits: * * Device Density ID code #Pages PageSize Offset * AT45DB011B 1Mbit (128K) xx0011xx (0x0c) 512 264 9 - * AT45DB021B 2Mbit (256K) xx0101xx (0x14) 1025 264 9 + * AT45DB021B 2Mbit (256K) xx0101xx (0x14) 1024 264 9 * AT45DB041B 4Mbit (512K) xx0111xx (0x1c) 2048 264 9 * AT45DB081B 8Mbit (1M) xx1001xx (0x24) 4096 264 9 * AT45DB0161B 16Mbit (2M) xx1011xx (0x2c) 4096 528 10 @@ -528,15 +820,35 @@ add_dataflash(struct spi_device *spi, char *name, * AT45DB0642 64Mbit (8M) xx111xxx (0x3c) 8192 1056 11 * AT45DB1282 128Mbit (16M) xx0100xx (0x10) 16384 1056 11 */ -static int __devinit dataflash_probe(struct spi_device *spi) +static int dataflash_probe(struct spi_device *spi) { int status; - + struct flash_info *info; + + /* + * Try to detect dataflash by JEDEC ID. + * If it succeeds we know we have either a C or D part. + * D will support power of 2 pagesize option. + * Both support the security register, though with different + * write procedures. + */ + info = jedec_probe(spi); + if (IS_ERR(info)) + return PTR_ERR(info); + if (info != NULL) + return add_dataflash_otp(spi, info->name, info->nr_pages, + info->pagesize, info->pageoffset, + (info->flags & SUP_POW2PS) ? 'd' : 'c'); + + /* + * Older chips support only legacy commands, identifing + * capacity using bits in the status byte. + */ status = dataflash_status(spi); if (status <= 0 || status == 0xff) { - DEBUG(MTD_DEBUG_LEVEL1, "%s: status error %d\n", - spi->dev.bus_id, status); - if (status == 0xff) + pr_debug("%s: status error %d\n", + dev_name(&spi->dev), status); + if (status == 0 || status == 0xff) status = -ENODEV; return status; } @@ -550,7 +862,7 @@ static int __devinit dataflash_probe(struct spi_device *spi) status = add_dataflash(spi, "AT45DB011B", 512, 264, 9); break; case 0x14: /* 0 1 0 1 x x */ - status = add_dataflash(spi, "AT45DB021B", 1025, 264, 9); + status = add_dataflash(spi, "AT45DB021B", 1024, 264, 9); break; case 0x1c: /* 0 1 1 1 x x */ status = add_dataflash(spi, "AT45DB041x", 2048, 264, 9); @@ -570,29 +882,26 @@ static int __devinit dataflash_probe(struct spi_device *spi) break; /* obsolete AT45DB1282 not (yet?) supported */ default: - DEBUG(MTD_DEBUG_LEVEL1, "%s: unsupported device (%x)\n", - spi->dev.bus_id, status & 0x3c); + dev_info(&spi->dev, "unsupported device (%x)\n", + status & 0x3c); status = -ENODEV; } if (status < 0) - DEBUG(MTD_DEBUG_LEVEL1, "%s: add_dataflash --> %d\n", - spi->dev.bus_id, status); + pr_debug("%s: add_dataflash --> %d\n", dev_name(&spi->dev), + status); return status; } -static int __devexit dataflash_remove(struct spi_device *spi) +static int dataflash_remove(struct spi_device *spi) { - struct dataflash *flash = dev_get_drvdata(&spi->dev); + struct dataflash *flash = spi_get_drvdata(spi); int status; - DEBUG(MTD_DEBUG_LEVEL1, "%s: remove\n", spi->dev.bus_id); + pr_debug("%s: remove\n", dev_name(&spi->dev)); - if (mtd_has_partitions() && flash->partitioned) - status = del_mtd_partitions(&flash->mtd); - else - status = del_mtd_device(&flash->mtd); + status = mtd_device_unregister(&flash->mtd); if (status == 0) kfree(flash); return status; @@ -601,29 +910,19 @@ static int __devexit dataflash_remove(struct spi_device *spi) static struct spi_driver dataflash_driver = { .driver = { .name = "mtd_dataflash", - .bus = &spi_bus_type, .owner = THIS_MODULE, + .of_match_table = of_match_ptr(dataflash_dt_ids), }, .probe = dataflash_probe, - .remove = __devexit_p(dataflash_remove), + .remove = dataflash_remove, /* FIXME: investigate suspend and resume... */ }; -static int __init dataflash_init(void) -{ - return spi_register_driver(&dataflash_driver); -} -module_init(dataflash_init); - -static void __exit dataflash_exit(void) -{ - spi_unregister_driver(&dataflash_driver); -} -module_exit(dataflash_exit); - +module_spi_driver(dataflash_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Andrew Victor, David Brownell"); MODULE_DESCRIPTION("MTD DataFlash driver"); +MODULE_ALIAS("spi:mtd_dataflash"); diff --git a/drivers/mtd/devices/mtdram.c b/drivers/mtd/devices/mtdram.c index e427c82d5f4..8e285089229 100644 --- a/drivers/mtd/devices/mtdram.c +++ b/drivers/mtd/devices/mtdram.c @@ -1,6 +1,5 @@ /* * mtdram - a test mtd device - * $Id: mtdram.c,v 1.37 2005/04/21 03:42:11 joern Exp $ * Author: Alexander Larsson <alex@cendio.se> * * Copyright (c) 1999 Alexander Larsson <alex@cendio.se> @@ -15,8 +14,8 @@ #include <linux/ioport.h> #include <linux/vmalloc.h> #include <linux/init.h> -#include <linux/mtd/compatmac.h> #include <linux/mtd/mtd.h> +#include <linux/mtd/mtdram.h> static unsigned long total_size = CONFIG_MTDRAM_TOTAL_SIZE; static unsigned long erase_size = CONFIG_MTDRAM_ERASE_SIZE; @@ -35,41 +34,42 @@ static struct mtd_info *mtd_info; static int ram_erase(struct mtd_info *mtd, struct erase_info *instr) { - if (instr->addr + instr->len > mtd->size) - return -EINVAL; - memset((char *)mtd->priv + instr->addr, 0xff, instr->len); - instr->state = MTD_ERASE_DONE; mtd_erase_callback(instr); - return 0; } static int ram_point(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char **mtdbuf) + size_t *retlen, void **virt, resource_size_t *phys) { - if (from + len > mtd->size) - return -EINVAL; - - *mtdbuf = mtd->priv + from; + *virt = mtd->priv + from; *retlen = len; return 0; } -static void ram_unpoint(struct mtd_info *mtd, u_char * addr, loff_t from, - size_t len) +static int ram_unpoint(struct mtd_info *mtd, loff_t from, size_t len) +{ + return 0; +} + +/* + * 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) { - if (from + len > mtd->size) - return -EINVAL; - memcpy(buf, mtd->priv + from, len); - *retlen = len; return 0; } @@ -77,11 +77,7 @@ static int ram_read(struct mtd_info *mtd, loff_t from, size_t len, static int ram_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { - if (to + len > mtd->size) - return -EINVAL; - memcpy((char *)mtd->priv + to, buf, len); - *retlen = len; return 0; } @@ -89,14 +85,14 @@ static int ram_write(struct mtd_info *mtd, loff_t to, size_t len, static void __exit cleanup_mtdram(void) { if (mtd_info) { - del_mtd_device(mtd_info); + mtd_device_unregister(mtd_info); vfree(mtd_info->priv); kfree(mtd_info); } } int mtdram_init_device(struct mtd_info *mtd, void *mapped_address, - unsigned long size, char *name) + unsigned long size, const char *name) { memset(mtd, 0, sizeof(*mtd)); @@ -106,19 +102,20 @@ int mtdram_init_device(struct mtd_info *mtd, void *mapped_address, mtd->flags = MTD_CAP_RAM; mtd->size = size; mtd->writesize = 1; + mtd->writebufsize = 64; /* Mimic CFI NOR flashes */ mtd->erasesize = MTDRAM_ERASE_SIZE; mtd->priv = mapped_address; mtd->owner = THIS_MODULE; - mtd->erase = ram_erase; - mtd->point = ram_point; - mtd->unpoint = ram_unpoint; - mtd->read = ram_read; - mtd->write = ram_write; - - if (add_mtd_device(mtd)) { + 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; + + if (mtd_device_register(mtd, NULL, 0)) return -EIO; - } return 0; } diff --git a/drivers/mtd/devices/phram.c b/drivers/mtd/devices/phram.c index 6c7337f9ebb..2cceebfb251 100644 --- a/drivers/mtd/devices/phram.c +++ b/drivers/mtd/devices/phram.c @@ -1,8 +1,6 @@ /** - * $Id: phram.c,v 1.16 2005/11/07 11:14:25 gleixner Exp $ - * * Copyright (c) ???? Jochen Schäuble <psionic@psionic.de> - * Copyright (c) 2003-2004 Jörn Engel <joern@wh.fh-wedel.de> + * Copyright (c) 2003-2004 Joern Engel <joern@wh.fh-wedel.de> * * Usage: * @@ -16,6 +14,9 @@ * Example: * phram=swap,64Mi,128Mi phram=test,900Mi,1Mi */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <asm/io.h> #include <linux/init.h> #include <linux/kernel.h> @@ -25,8 +26,6 @@ #include <linux/slab.h> #include <linux/mtd/mtd.h> -#define ERROR(fmt, args...) printk(KERN_ERR "phram: " fmt , ## args) - struct phram_mtd_list { struct mtd_info mtd; struct list_head list; @@ -34,44 +33,33 @@ struct phram_mtd_list { static LIST_HEAD(phram_list); - static int phram_erase(struct mtd_info *mtd, struct erase_info *instr) { u_char *start = mtd->priv; - if (instr->addr + instr->len > mtd->size) - return -EINVAL; - memset(start + instr->addr, 0xff, instr->len); - /* This'll catch a few races. Free the thing before returning :) + /* + * This'll catch a few races. Free the thing before returning :) * I don't feel at all ashamed. This kind of thing is possible anyway * with flash, but unlikely. */ - instr->state = MTD_ERASE_DONE; - mtd_erase_callback(instr); - return 0; } static int phram_point(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char **mtdbuf) + size_t *retlen, void **virt, resource_size_t *phys) { - u_char *start = mtd->priv; - - if (from + len > mtd->size) - return -EINVAL; - - *mtdbuf = start + from; + *virt = mtd->priv + from; *retlen = len; return 0; } -static void phram_unpoint(struct mtd_info *mtd, u_char *addr, loff_t from, - size_t len) +static int phram_unpoint(struct mtd_info *mtd, loff_t from, size_t len) { + return 0; } static int phram_read(struct mtd_info *mtd, loff_t from, size_t len, @@ -79,14 +67,7 @@ static int phram_read(struct mtd_info *mtd, loff_t from, size_t len, { u_char *start = mtd->priv; - if (from >= mtd->size) - return -EINVAL; - - if (len > mtd->size - from) - len = mtd->size - from; - memcpy(buf, start + from, len); - *retlen = len; return 0; } @@ -96,46 +77,36 @@ static int phram_write(struct mtd_info *mtd, loff_t to, size_t len, { u_char *start = mtd->priv; - if (to >= mtd->size) - return -EINVAL; - - if (len > mtd->size - to) - len = mtd->size - to; - memcpy(start + to, buf, len); - *retlen = len; return 0; } - - static void unregister_devices(void) { struct phram_mtd_list *this, *safe; list_for_each_entry_safe(this, safe, &phram_list, list) { - del_mtd_device(&this->mtd); + mtd_device_unregister(&this->mtd); iounmap(this->mtd.priv); + kfree(this->mtd.name); kfree(this); } } -static int register_device(char *name, unsigned long start, unsigned long len) +static int register_device(char *name, phys_addr_t start, size_t len) { struct phram_mtd_list *new; int ret = -ENOMEM; - new = kmalloc(sizeof(*new), GFP_KERNEL); + new = kzalloc(sizeof(*new), GFP_KERNEL); if (!new) goto out0; - memset(new, 0, sizeof(*new)); - ret = -EIO; new->mtd.priv = ioremap(start, len); if (!new->mtd.priv) { - ERROR("ioremap failed\n"); + pr_err("ioremap failed\n"); goto out1; } @@ -143,19 +114,19 @@ static int register_device(char *name, unsigned long start, unsigned long len) new->mtd.name = name; new->mtd.size = len; new->mtd.flags = MTD_CAP_RAM; - new->mtd.erase = phram_erase; - new->mtd.point = phram_point; - new->mtd.unpoint = phram_unpoint; - new->mtd.read = phram_read; - new->mtd.write = phram_write; + new->mtd._erase = phram_erase; + new->mtd._point = phram_point; + new->mtd._unpoint = phram_unpoint; + new->mtd._read = phram_read; + new->mtd._write = phram_write; new->mtd.owner = THIS_MODULE; new->mtd.type = MTD_RAM; new->mtd.erasesize = PAGE_SIZE; new->mtd.writesize = 1; ret = -EAGAIN; - if (add_mtd_device(&new->mtd)) { - ERROR("Failed to register new device\n"); + if (mtd_device_register(&new->mtd, NULL, 0)) { + pr_err("Failed to register new device\n"); goto out2; } @@ -170,35 +141,35 @@ out0: return ret; } -static int ustrtoul(const char *cp, char **endp, unsigned int base) +static int parse_num64(uint64_t *num64, char *token) { - unsigned long result = simple_strtoul(cp, endp, base); - - switch (**endp) { - case 'G': - result *= 1024; - case 'M': - result *= 1024; - case 'k': - result *= 1024; + size_t len; + int shift = 0; + int ret; + + len = strlen(token); /* By dwmw2 editorial decree, "ki", "Mi" or "Gi" are to be used. */ - if ((*endp)[1] == 'i') - (*endp) += 2; + if (len > 2) { + if (token[len - 1] == 'i') { + switch (token[len - 2]) { + case 'G': + shift += 10; + case 'M': + shift += 10; + case 'k': + shift += 10; + token[len - 2] = 0; + break; + default: + return -EINVAL; + } + } } - return result; -} - -static int parse_num32(uint32_t *num32, const char *token) -{ - char *endp; - unsigned long n; - n = ustrtoul(token, &endp, 0); - if (*endp) - return -EINVAL; + ret = kstrtou64(token, 0, num64); + *num64 <<= shift; - *num32 = n; - return 0; + return ret; } static int parse_name(char **pname, const char *token) @@ -230,17 +201,30 @@ static inline void kill_final_newline(char *str) #define parse_err(fmt, args...) do { \ - ERROR(fmt , ## args); \ - return 0; \ + pr_err(fmt , ## args); \ + return 1; \ } while (0) -static int phram_setup(const char *val, struct kernel_param *kp) +#ifndef MODULE +static int phram_init_called; +/* + * This shall contain the module parameter if any. It is of the form: + * - phram=<device>,<address>,<size> for module case + * - phram.phram=<device>,<address>,<size> for built-in case + * We leave 64 bytes for the device name, 20 for the address and 20 for the + * size. + * Example: phram.phram=rootfs,0xa0000000,512Mi + */ +static char phram_paramline[64 + 20 + 20]; +#endif + +static int phram_setup(const char *val) { - char buf[64+12+12], *str = buf; + char buf[64 + 20 + 20], *str = buf; char *token[3]; char *name; - uint32_t start; - uint32_t len; + uint64_t start; + uint64_t len; int i, ret; if (strnlen(val, sizeof(buf)) >= sizeof(buf)) @@ -259,37 +243,77 @@ static int phram_setup(const char *val, struct kernel_param *kp) parse_err("not enough arguments\n"); ret = parse_name(&name, token[0]); - if (ret == -ENOMEM) - parse_err("out of memory\n"); - if (ret == -ENOSPC) - parse_err("name too long\n"); if (ret) - return 0; + return ret; - ret = parse_num32(&start, token[1]); + ret = parse_num64(&start, token[1]); if (ret) { kfree(name); parse_err("illegal start address\n"); } - ret = parse_num32(&len, token[2]); + ret = parse_num64(&len, token[2]); if (ret) { kfree(name); parse_err("illegal device length\n"); } - register_device(name, start, len); + ret = register_device(name, start, len); + if (!ret) + pr_info("%s device: %#llx at %#llx\n", name, len, start); + else + kfree(name); + + return ret; +} + +static int phram_param_call(const char *val, struct kernel_param *kp) +{ +#ifdef MODULE + return phram_setup(val); +#else + /* + * If more parameters are later passed in via + * /sys/module/phram/parameters/phram + * and init_phram() has already been called, + * we can parse the argument now. + */ + + if (phram_init_called) + return phram_setup(val); + + /* + * During early boot stage, we only save the parameters + * here. We must parse them later: if the param passed + * from kernel boot command line, phram_param_call() is + * called so early that it is not possible to resolve + * the device (even kmalloc() fails). Defer that work to + * phram_setup(). + */ + + if (strlen(val) >= sizeof(phram_paramline)) + return -ENOSPC; + strcpy(phram_paramline, val); return 0; +#endif } -module_param_call(phram, phram_setup, NULL, NULL, 000); -MODULE_PARM_DESC(phram,"Memory region to map. \"map=<name>,<start>,<length>\""); +module_param_call(phram, phram_param_call, NULL, NULL, 000); +MODULE_PARM_DESC(phram, "Memory region to map. \"phram=<name>,<start>,<length>\""); static int __init init_phram(void) { - return 0; + int ret = 0; + +#ifndef MODULE + if (phram_paramline[0]) + ret = phram_setup(phram_paramline); + phram_init_called = 1; +#endif + + return ret; } static void __exit cleanup_phram(void) @@ -301,5 +325,5 @@ module_init(init_phram); module_exit(cleanup_phram); MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Jörn Engel <joern@wh.fh-wedel.de>"); +MODULE_AUTHOR("Joern Engel <joern@wh.fh-wedel.de>"); MODULE_DESCRIPTION("MTD driver for physical RAM"); diff --git a/drivers/mtd/devices/pmc551.c b/drivers/mtd/devices/pmc551.c index 354e1657cc2..f02603e1bfe 100644 --- a/drivers/mtd/devices/pmc551.c +++ b/drivers/mtd/devices/pmc551.c @@ -1,6 +1,4 @@ /* - * $Id: pmc551.c,v 1.32 2005/11/07 11:14:25 gleixner Exp $ - * * PMC551 PCI Mezzanine Ram Device * * Author: @@ -30,13 +28,13 @@ * * Notes: * Due to what I assume is more buggy SROM, the 64M PMC551 I - * have available claims that all 4 of it's DRAM banks have 64M - * of ram configured (making a grand total of 256M onboard). + * have available claims that all 4 of its DRAM banks have 64MiB + * of ram configured (making a grand total of 256MiB onboard). * This is slightly annoying since the BAR0 size reflects the * aperture size, not the dram size, and the V370PDC supplies no * other method for memory size discovery. This problem is * mostly only relevant when compiled as a module, as the - * unloading of the module with an aperture size smaller then + * unloading of the module with an aperture size smaller than * the ram will cause the driver to detect the onboard memory * size to be equal to the aperture size when the module is * reloaded. Soooo, to help, the module supports an msize @@ -70,7 +68,7 @@ * made the memory unusable, added a fix to code to touch up * the DRAM some. * - * Bugs/FIXME's: + * Bugs/FIXMEs: * * MUST fix the init function to not spin on a register * waiting for it to set .. this does not safely handle busted * devices that never reset the register correctly which will @@ -86,7 +84,6 @@ #include <linux/module.h> #include <asm/uaccess.h> #include <linux/types.h> -#include <linux/sched.h> #include <linux/init.h> #include <linux/ptrace.h> #include <linux/slab.h> @@ -96,15 +93,49 @@ #include <linux/fs.h> #include <linux/ioctl.h> #include <asm/io.h> -#include <asm/system.h> #include <linux/pci.h> - #include <linux/mtd/mtd.h> -#include <linux/mtd/pmc551.h> -#include <linux/mtd/compatmac.h> + +#define PMC551_VERSION \ + "Ramix PMC551 PCI Mezzanine Ram Driver. (C) 1999,2000 Nortel Networks.\n" + +#define PCI_VENDOR_ID_V3_SEMI 0x11b0 +#define PCI_DEVICE_ID_V3_SEMI_V370PDC 0x0200 + +#define PMC551_PCI_MEM_MAP0 0x50 +#define PMC551_PCI_MEM_MAP1 0x54 +#define PMC551_PCI_MEM_MAP_MAP_ADDR_MASK 0x3ff00000 +#define PMC551_PCI_MEM_MAP_APERTURE_MASK 0x000000f0 +#define PMC551_PCI_MEM_MAP_REG_EN 0x00000002 +#define PMC551_PCI_MEM_MAP_ENABLE 0x00000001 + +#define PMC551_SDRAM_MA 0x60 +#define PMC551_SDRAM_CMD 0x62 +#define PMC551_DRAM_CFG 0x64 +#define PMC551_SYS_CTRL_REG 0x78 + +#define PMC551_DRAM_BLK0 0x68 +#define PMC551_DRAM_BLK1 0x6c +#define PMC551_DRAM_BLK2 0x70 +#define PMC551_DRAM_BLK3 0x74 +#define PMC551_DRAM_BLK_GET_SIZE(x) (524288 << ((x >> 4) & 0x0f)) +#define PMC551_DRAM_BLK_SET_COL_MUX(x, v) (((x) & ~0x00007000) | (((v) & 0x7) << 12)) +#define PMC551_DRAM_BLK_SET_ROW_MUX(x, v) (((x) & ~0x00000f00) | (((v) & 0xf) << 8)) + +struct mypriv { + struct pci_dev *dev; + u_char *start; + u32 base_map0; + u32 curr_map0; + u32 asize; + struct mtd_info *nextpmc551; +}; static struct mtd_info *pmc551list; +static int pmc551_point(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, void **virt, resource_size_t *phys); + static int pmc551_erase(struct mtd_info *mtd, struct erase_info *instr) { struct mypriv *priv = mtd->priv; @@ -120,22 +151,13 @@ static int pmc551_erase(struct mtd_info *mtd, struct erase_info *instr) #endif end = instr->addr + instr->len - 1; - - /* Is it past the end? */ - if (end > mtd->size) { -#ifdef CONFIG_MTD_PMC551_DEBUG - printk(KERN_DEBUG "pmc551_erase() out of bounds (%ld > %ld)\n", - (long)end, (long)mtd->size); -#endif - return -EINVAL; - } - eoff_hi = end & ~(priv->asize - 1); soff_hi = instr->addr & ~(priv->asize - 1); eoff_lo = end & (priv->asize - 1); soff_lo = instr->addr & (priv->asize - 1); - pmc551_point(mtd, instr->addr, instr->len, &retlen, &ptr); + pmc551_point(mtd, instr->addr, instr->len, &retlen, + (void **)&ptr, NULL); if (soff_hi == eoff_hi || mtd->size == priv->asize) { /* The whole thing fits within one access, so just one shot @@ -155,7 +177,8 @@ static int pmc551_erase(struct mtd_info *mtd, struct erase_info *instr) } soff_hi += priv->asize; pmc551_point(mtd, (priv->base_map0 | soff_hi), - priv->asize, &retlen, &ptr); + priv->asize, &retlen, + (void **)&ptr, NULL); } memset(ptr, 0xff, eoff_lo); } @@ -171,7 +194,7 @@ static int pmc551_erase(struct mtd_info *mtd, struct erase_info *instr) } static int pmc551_point(struct mtd_info *mtd, loff_t from, size_t len, - size_t * retlen, u_char ** mtdbuf) + size_t *retlen, void **virt, resource_size_t *phys) { struct mypriv *priv = mtd->priv; u32 soff_hi; @@ -181,14 +204,6 @@ static int pmc551_point(struct mtd_info *mtd, loff_t from, size_t len, printk(KERN_DEBUG "pmc551_point(%ld, %ld)\n", (long)from, (long)len); #endif - if (from + len > mtd->size) { -#ifdef CONFIG_MTD_PMC551_DEBUG - printk(KERN_DEBUG "pmc551_point() out of bounds (%ld > %ld)\n", - (long)from + len, (long)mtd->size); -#endif - return -EINVAL; - } - soff_hi = from & ~(priv->asize - 1); soff_lo = from & (priv->asize - 1); @@ -199,17 +214,17 @@ static int pmc551_point(struct mtd_info *mtd, loff_t from, size_t len, priv->curr_map0 = soff_hi; } - *mtdbuf = priv->start + soff_lo; + *virt = priv->start + soff_lo; *retlen = len; return 0; } -static void pmc551_unpoint(struct mtd_info *mtd, u_char * addr, loff_t from, - size_t len) +static int pmc551_unpoint(struct mtd_info *mtd, loff_t from, size_t len) { #ifdef CONFIG_MTD_PMC551_DEBUG printk(KERN_DEBUG "pmc551_unpoint()\n"); #endif + return 0; } static int pmc551_read(struct mtd_info *mtd, loff_t from, size_t len, @@ -228,22 +243,12 @@ static int pmc551_read(struct mtd_info *mtd, loff_t from, size_t len, #endif end = from + len - 1; - - /* Is it past the end? */ - if (end > mtd->size) { -#ifdef CONFIG_MTD_PMC551_DEBUG - printk(KERN_DEBUG "pmc551_read() out of bounds (%ld > %ld)\n", - (long)end, (long)mtd->size); -#endif - return -EINVAL; - } - soff_hi = from & ~(priv->asize - 1); eoff_hi = end & ~(priv->asize - 1); soff_lo = from & (priv->asize - 1); eoff_lo = end & (priv->asize - 1); - pmc551_point(mtd, from, len, retlen, &ptr); + pmc551_point(mtd, from, len, retlen, (void **)&ptr, NULL); if (soff_hi == eoff_hi) { /* The whole thing fits within one access, so just one shot @@ -264,7 +269,8 @@ static int pmc551_read(struct mtd_info *mtd, loff_t from, size_t len, goto out; } soff_hi += priv->asize; - pmc551_point(mtd, soff_hi, priv->asize, retlen, &ptr); + pmc551_point(mtd, soff_hi, priv->asize, retlen, + (void **)&ptr, NULL); } memcpy(copyto, ptr, eoff_lo); copyto += eoff_lo; @@ -294,22 +300,12 @@ static int pmc551_write(struct mtd_info *mtd, loff_t to, size_t len, #endif end = to + len - 1; - /* Is it past the end? or did the u32 wrap? */ - if (end > mtd->size) { -#ifdef CONFIG_MTD_PMC551_DEBUG - printk(KERN_DEBUG "pmc551_write() out of bounds (end: %ld, " - "size: %ld, to: %ld)\n", (long)end, (long)mtd->size, - (long)to); -#endif - return -EINVAL; - } - soff_hi = to & ~(priv->asize - 1); eoff_hi = end & ~(priv->asize - 1); soff_lo = to & (priv->asize - 1); eoff_lo = end & (priv->asize - 1); - pmc551_point(mtd, to, len, retlen, &ptr); + pmc551_point(mtd, to, len, retlen, (void **)&ptr, NULL); if (soff_hi == eoff_hi) { /* The whole thing fits within one access, so just one shot @@ -330,7 +326,8 @@ static int pmc551_write(struct mtd_info *mtd, loff_t to, size_t len, goto out; } soff_hi += priv->asize; - pmc551_point(mtd, soff_hi, priv->asize, retlen, &ptr); + pmc551_point(mtd, soff_hi, priv->asize, retlen, + (void **)&ptr, NULL); } memcpy(ptr, copyfrom, eoff_lo); copyfrom += eoff_lo; @@ -348,7 +345,7 @@ static int pmc551_write(struct mtd_info *mtd, loff_t to, size_t len, * Fixup routines for the V370PDC * PCI device ID 0x020011b0 * - * This function basicly kick starts the DRAM oboard the card and gets it + * This function basically kick starts the DRAM oboard the card and gets it * ready to be used. Before this is done the device reads VERY erratic, so * much that it can crash the Linux 2.2.x series kernels when a user cat's * /proc/pci .. though that is mainly a kernel bug in handling the PCI DEVSEL @@ -356,7 +353,7 @@ static int pmc551_write(struct mtd_info *mtd, loff_t to, size_t len, * mechanism * returns the size of the memory region found. */ -static u32 fixup_pmc551(struct pci_dev *dev) +static int fixup_pmc551(struct pci_dev *dev) { #ifdef CONFIG_MTD_PMC551_BUGFIX u32 dram_data; @@ -537,7 +534,7 @@ static u32 fixup_pmc551(struct pci_dev *dev) /* * Check to make certain the DEVSEL is set correctly, this device - * has a tendancy to assert DEVSEL and TRDY when a write is performed + * has a tendency to assert DEVSEL and TRDY when a write is performed * to the memory when memory is read-only */ if ((cmd & PCI_STATUS_DEVSEL_MASK) != 0x0) { @@ -563,10 +560,10 @@ static u32 fixup_pmc551(struct pci_dev *dev) /* * Some screen fun */ - printk(KERN_DEBUG "pmc551: %d%c (0x%x) of %sprefetchable memory at " + printk(KERN_DEBUG "pmc551: %d%sB (0x%x) of %sprefetchable memory at " "0x%llx\n", (size < 1024) ? size : (size < 1048576) ? size >> 10 : size >> 20, - (size < 1024) ? 'B' : (size < 1048576) ? 'K' : 'M', size, + (size < 1024) ? "" : (size < 1048576) ? "Ki" : "Mi", size, ((dcmd & (0x1 << 3)) == 0) ? "non-" : "", (unsigned long long)pci_resource_start(dev, 0)); @@ -650,14 +647,10 @@ MODULE_DESCRIPTION(PMC551_VERSION); * Stuff these outside the ifdef so as to not bust compiled in driver support */ static int msize = 0; -#if defined(CONFIG_MTD_PMC551_APERTURE_SIZE) -static int asize = CONFIG_MTD_PMC551_APERTURE_SIZE -#else static int asize = 0; -#endif module_param(msize, int, 0); -MODULE_PARM_DESC(msize, "memory size in Megabytes [1 - 1024]"); +MODULE_PARM_DESC(msize, "memory size in MiB [1 - 1024]"); module_param(asize, int, 0); MODULE_PARM_DESC(asize, "aperture size, must be <= memsize [1-1024]"); @@ -668,9 +661,9 @@ static int __init init_pmc551(void) { struct pci_dev *PCI_Device = NULL; struct mypriv *priv; - int count, found = 0; + int found = 0; struct mtd_info *mtd; - u32 length = 0; + int length = 0; if (msize) { msize = (1 << (ffs(msize) - 1)) << 20; @@ -695,7 +688,7 @@ static int __init init_pmc551(void) /* * PCU-bus chipset probe. */ - for (count = 0; count < MAX_MTD_DEVICES; count++) { + for (;;) { if ((PCI_Device = pci_get_device(PCI_VENDOR_ID_V3_SEMI, PCI_DEVICE_ID_V3_SEMI_V370PDC, @@ -732,16 +725,11 @@ static int __init init_pmc551(void) } mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL); - if (!mtd) { - printk(KERN_NOTICE "pmc551: Cannot allocate new MTD " - "device.\n"); + if (!mtd) break; - } priv = kzalloc(sizeof(struct mypriv), GFP_KERNEL); if (!priv) { - printk(KERN_NOTICE "pmc551: Cannot allocate new MTD " - "device.\n"); kfree(mtd); break; } @@ -788,37 +776,36 @@ static int __init init_pmc551(void) mtd->size = msize; mtd->flags = MTD_CAP_RAM; - mtd->erase = pmc551_erase; - mtd->read = pmc551_read; - mtd->write = pmc551_write; - mtd->point = pmc551_point; - mtd->unpoint = pmc551_unpoint; + mtd->_erase = pmc551_erase; + mtd->_read = pmc551_read; + mtd->_write = pmc551_write; + mtd->_point = pmc551_point; + mtd->_unpoint = pmc551_unpoint; mtd->type = MTD_RAM; mtd->name = "PMC551 RAM board"; mtd->erasesize = 0x10000; mtd->writesize = 1; mtd->owner = THIS_MODULE; - if (add_mtd_device(mtd)) { - printk(KERN_NOTICE "pmc551: Failed to register new " - "device\n"); + if (mtd_device_register(mtd, NULL, 0)) { + printk(KERN_NOTICE "pmc551: Failed to register new device\n"); pci_iounmap(PCI_Device, priv->start); kfree(mtd->priv); kfree(mtd); break; } - /* Keep a reference as the add_mtd_device worked */ + /* Keep a reference as the mtd_device_register worked */ pci_dev_get(PCI_Device); printk(KERN_NOTICE "Registered pmc551 memory device.\n"); - printk(KERN_NOTICE "Mapped %dM of memory from 0x%p to 0x%p\n", + printk(KERN_NOTICE "Mapped %dMiB of memory from 0x%p to 0x%p\n", priv->asize >> 20, priv->start, priv->start + priv->asize); - printk(KERN_NOTICE "Total memory is %d%c\n", + printk(KERN_NOTICE "Total memory is %d%sB\n", (length < 1024) ? length : (length < 1048576) ? length >> 10 : length >> 20, - (length < 1024) ? 'B' : (length < 1048576) ? 'K' : 'M'); + (length < 1024) ? "" : (length < 1048576) ? "Ki" : "Mi"); priv->nextpmc551 = pmc551list; pmc551list = mtd; found++; @@ -851,14 +838,14 @@ static void __exit cleanup_pmc551(void) pmc551list = priv->nextpmc551; if (priv->start) { - printk(KERN_DEBUG "pmc551: unmapping %dM starting at " + printk(KERN_DEBUG "pmc551: unmapping %dMiB starting at " "0x%p\n", priv->asize >> 20, priv->start); pci_iounmap(priv->dev, priv->start); } pci_dev_put(priv->dev); kfree(mtd->priv); - del_mtd_device(mtd); + mtd_device_unregister(mtd); kfree(mtd); found++; } diff --git a/drivers/mtd/devices/serial_flash_cmds.h b/drivers/mtd/devices/serial_flash_cmds.h new file mode 100644 index 00000000000..f59a125295d --- /dev/null +++ b/drivers/mtd/devices/serial_flash_cmds.h @@ -0,0 +1,61 @@ +/* + * Generic/SFDP Flash Commands and Device Capabilities + * + * Copyright (C) 2013 Lee Jones <lee.jones@lianro.org> + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _MTD_SERIAL_FLASH_CMDS_H +#define _MTD_SERIAL_FLASH_CMDS_H + +/* Generic Flash Commands/OPCODEs */ +#define SPINOR_OP_RDSR2 0x35 +#define SPINOR_OP_WRVCR 0x81 +#define SPINOR_OP_RDVCR 0x85 + +/* JEDEC Standard - Serial Flash Discoverable Parmeters (SFDP) Commands */ +#define SPINOR_OP_READ_1_2_2 0xbb /* DUAL I/O READ */ +#define SPINOR_OP_READ_1_4_4 0xeb /* QUAD I/O READ */ + +#define SPINOR_OP_WRITE 0x02 /* PAGE PROGRAM */ +#define SPINOR_OP_WRITE_1_1_2 0xa2 /* DUAL INPUT PROGRAM */ +#define SPINOR_OP_WRITE_1_2_2 0xd2 /* DUAL INPUT EXT PROGRAM */ +#define SPINOR_OP_WRITE_1_1_4 0x32 /* QUAD INPUT PROGRAM */ +#define SPINOR_OP_WRITE_1_4_4 0x12 /* QUAD INPUT EXT PROGRAM */ + +/* READ commands with 32-bit addressing */ +#define SPINOR_OP_READ4_1_2_2 0xbc +#define SPINOR_OP_READ4_1_4_4 0xec + +/* Configuration flags */ +#define FLASH_FLAG_SINGLE 0x000000ff +#define FLASH_FLAG_READ_WRITE 0x00000001 +#define FLASH_FLAG_READ_FAST 0x00000002 +#define FLASH_FLAG_SE_4K 0x00000004 +#define FLASH_FLAG_SE_32K 0x00000008 +#define FLASH_FLAG_CE 0x00000010 +#define FLASH_FLAG_32BIT_ADDR 0x00000020 +#define FLASH_FLAG_RESET 0x00000040 +#define FLASH_FLAG_DYB_LOCKING 0x00000080 + +#define FLASH_FLAG_DUAL 0x0000ff00 +#define FLASH_FLAG_READ_1_1_2 0x00000100 +#define FLASH_FLAG_READ_1_2_2 0x00000200 +#define FLASH_FLAG_READ_2_2_2 0x00000400 +#define FLASH_FLAG_WRITE_1_1_2 0x00001000 +#define FLASH_FLAG_WRITE_1_2_2 0x00002000 +#define FLASH_FLAG_WRITE_2_2_2 0x00004000 + +#define FLASH_FLAG_QUAD 0x00ff0000 +#define FLASH_FLAG_READ_1_1_4 0x00010000 +#define FLASH_FLAG_READ_1_4_4 0x00020000 +#define FLASH_FLAG_READ_4_4_4 0x00040000 +#define FLASH_FLAG_WRITE_1_1_4 0x00100000 +#define FLASH_FLAG_WRITE_1_4_4 0x00200000 +#define FLASH_FLAG_WRITE_4_4_4 0x00400000 + +#endif /* _MTD_SERIAL_FLASH_CMDS_H */ diff --git a/drivers/mtd/devices/slram.c b/drivers/mtd/devices/slram.c index 542a0c00900..2fc4957cbe7 100644 --- a/drivers/mtd/devices/slram.c +++ b/drivers/mtd/devices/slram.c @@ -1,7 +1,5 @@ /*====================================================================== - $Id: slram.c,v 1.36 2005/11/07 11:14:25 gleixner Exp $ - This driver provides a method to access memory not used by the kernel itself (i.e. if the kernel commandline mem=xxx is used). To actually use slram at least mtdblock or mtdchar is required (for block or @@ -20,7 +18,7 @@ to specify the offset instead of the absolute address NOTE: - With slram it's only possible to map a contigous memory region. Therfore + With slram it's only possible to map a contiguous memory region. Therefore if there's a device mapped somewhere in the region specified slram will fail to load (see kernel log if modprobe fails). @@ -35,7 +33,6 @@ #include <asm/uaccess.h> #include <linux/types.h> #include <linux/kernel.h> -#include <linux/sched.h> #include <linux/ptrace.h> #include <linux/slab.h> #include <linux/string.h> @@ -45,7 +42,6 @@ #include <linux/ioctl.h> #include <linux/init.h> #include <asm/io.h> -#include <asm/system.h> #include <linux/mtd/mtd.h> @@ -77,8 +73,9 @@ static char *map; static slram_mtd_list_t *slram_mtdlist = NULL; static int slram_erase(struct mtd_info *, struct erase_info *); -static int slram_point(struct mtd_info *, loff_t, size_t, size_t *, u_char **); -static void slram_unpoint(struct mtd_info *, u_char *, loff_t, size_t); +static int slram_point(struct mtd_info *, loff_t, size_t, size_t *, void **, + resource_size_t *); +static int slram_unpoint(struct mtd_info *, loff_t, size_t); static int slram_read(struct mtd_info *, loff_t, size_t, size_t *, u_char *); static int slram_write(struct mtd_info *, loff_t, size_t, size_t *, const u_char *); @@ -86,39 +83,29 @@ static int slram_erase(struct mtd_info *mtd, struct erase_info *instr) { slram_priv_t *priv = mtd->priv; - if (instr->addr + instr->len > mtd->size) { - return(-EINVAL); - } - memset(priv->start + instr->addr, 0xff, instr->len); - /* This'll catch a few races. Free the thing before returning :) * I don't feel at all ashamed. This kind of thing is possible anyway * with flash, but unlikely. */ - instr->state = MTD_ERASE_DONE; - mtd_erase_callback(instr); - return(0); } static int slram_point(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char **mtdbuf) + size_t *retlen, void **virt, resource_size_t *phys) { slram_priv_t *priv = mtd->priv; - if (from + len > mtd->size) - return -EINVAL; - - *mtdbuf = priv->start + from; + *virt = priv->start + from; *retlen = len; return(0); } -static void slram_unpoint(struct mtd_info *mtd, u_char *addr, loff_t from, size_t len) +static int slram_unpoint(struct mtd_info *mtd, loff_t from, size_t len) { + return 0; } static int slram_read(struct mtd_info *mtd, loff_t from, size_t len, @@ -126,14 +113,7 @@ static int slram_read(struct mtd_info *mtd, loff_t from, size_t len, { slram_priv_t *priv = mtd->priv; - if (from > mtd->size) - return -EINVAL; - - if (from + len > mtd->size) - len = mtd->size - from; - memcpy(buf, priv->start + from, len); - *retlen = len; return(0); } @@ -143,11 +123,7 @@ static int slram_write(struct mtd_info *mtd, loff_t to, size_t len, { slram_priv_t *priv = mtd->priv; - if (to + len > mtd->size) - return -EINVAL; - memcpy(priv->start + to, buf, len); - *retlen = len; return(0); } @@ -168,19 +144,16 @@ static int register_device(char *name, unsigned long start, unsigned long length E("slram: Cannot allocate new MTD device.\n"); return(-ENOMEM); } - (*curmtd)->mtdinfo = kmalloc(sizeof(struct mtd_info), GFP_KERNEL); + (*curmtd)->mtdinfo = kzalloc(sizeof(struct mtd_info), GFP_KERNEL); (*curmtd)->next = NULL; if ((*curmtd)->mtdinfo) { - memset((char *)(*curmtd)->mtdinfo, 0, sizeof(struct mtd_info)); (*curmtd)->mtdinfo->priv = - kmalloc(sizeof(slram_priv_t), GFP_KERNEL); + kzalloc(sizeof(slram_priv_t), GFP_KERNEL); if (!(*curmtd)->mtdinfo->priv) { kfree((*curmtd)->mtdinfo); (*curmtd)->mtdinfo = NULL; - } else { - memset((*curmtd)->mtdinfo->priv,0,sizeof(slram_priv_t)); } } @@ -201,17 +174,17 @@ static int register_device(char *name, unsigned long start, unsigned long length (*curmtd)->mtdinfo->name = name; (*curmtd)->mtdinfo->size = length; (*curmtd)->mtdinfo->flags = MTD_CAP_RAM; - (*curmtd)->mtdinfo->erase = slram_erase; - (*curmtd)->mtdinfo->point = slram_point; - (*curmtd)->mtdinfo->unpoint = slram_unpoint; - (*curmtd)->mtdinfo->read = slram_read; - (*curmtd)->mtdinfo->write = slram_write; + (*curmtd)->mtdinfo->_erase = slram_erase; + (*curmtd)->mtdinfo->_point = slram_point; + (*curmtd)->mtdinfo->_unpoint = slram_unpoint; + (*curmtd)->mtdinfo->_read = slram_read; + (*curmtd)->mtdinfo->_write = slram_write; (*curmtd)->mtdinfo->owner = THIS_MODULE; (*curmtd)->mtdinfo->type = MTD_RAM; (*curmtd)->mtdinfo->erasesize = SLRAM_BLK_SZ; (*curmtd)->mtdinfo->writesize = 1; - if (add_mtd_device((*curmtd)->mtdinfo)) { + if (mtd_device_register((*curmtd)->mtdinfo, NULL, 0)) { E("slram: Failed to register new device\n"); iounmap(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start); kfree((*curmtd)->mtdinfo->priv); @@ -232,7 +205,7 @@ static void unregister_devices(void) while (slram_mtdlist) { nextitem = slram_mtdlist->next; - del_mtd_device(slram_mtdlist->mtdinfo); + mtd_device_unregister(slram_mtdlist->mtdinfo); iounmap(((slram_priv_t *)slram_mtdlist->mtdinfo->priv)->start); kfree(slram_mtdlist->mtdinfo->priv); kfree(slram_mtdlist->mtdinfo); @@ -267,23 +240,29 @@ static int parse_cmdline(char *devname, char *szstart, char *szlength) if (*(szlength) != '+') { devlength = simple_strtoul(szlength, &buffer, 0); - devlength = handle_unit(devlength, buffer) - devstart; + devlength = handle_unit(devlength, buffer); + if (devlength < devstart) + goto err_out; + + devlength -= devstart; } else { devlength = simple_strtoul(szlength + 1, &buffer, 0); devlength = handle_unit(devlength, buffer); } T("slram: devname=%s, devstart=0x%lx, devlength=0x%lx\n", devname, devstart, devlength); - if ((devstart < 0) || (devlength < 0) || (devlength % SLRAM_BLK_SZ != 0)) { - E("slram: Illegal start / length parameter.\n"); - return(-EINVAL); - } + if (devlength % SLRAM_BLK_SZ != 0) + goto err_out; if ((devstart = register_device(devname, devstart, devlength))){ unregister_devices(); return((int)devstart); } return(0); + +err_out: + E("slram: Illegal length parameter.\n"); + return(-EINVAL); } #ifndef MODULE @@ -298,17 +277,14 @@ __setup("slram=", mtd_slram_setup); #endif -static int init_slram(void) +static int __init init_slram(void) { char *devname; - int i; #ifndef MODULE char *devstart; char *devlength; - i = 0; - if (!map) { E("slram: not enough parameters.\n"); return(-EINVAL); @@ -335,8 +311,9 @@ static int init_slram(void) } #else int count; + int i; - for (count = 0; (map[count]) && (count < SLRAM_MAX_DEVICES_PARAMS); + for (count = 0; count < SLRAM_MAX_DEVICES_PARAMS && map[count]; count++) { } diff --git a/drivers/mtd/devices/spear_smi.c b/drivers/mtd/devices/spear_smi.c new file mode 100644 index 00000000000..c4176b0f382 --- /dev/null +++ b/drivers/mtd/devices/spear_smi.c @@ -0,0 +1,1093 @@ +/* + * SMI (Serial Memory Controller) device driver for Serial NOR Flash on + * SPEAr platform + * The serial nor interface is largely based on drivers/mtd/m25p80.c, + * however the SPI interface has been replaced by SMI. + * + * Copyright © 2010 STMicroelectronics. + * Ashish Priyadarshi + * Shiraz Hashim <shiraz.linux.kernel@gmail.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/param.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/spear_smi.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/of.h> +#include <linux/of_address.h> + +/* SMI clock rate */ +#define SMI_MAX_CLOCK_FREQ 50000000 /* 50 MHz */ + +/* MAX time out to safely come out of a erase or write busy conditions */ +#define SMI_PROBE_TIMEOUT (HZ / 10) +#define SMI_MAX_TIME_OUT (3 * HZ) + +/* timeout for command completion */ +#define SMI_CMD_TIMEOUT (HZ / 10) + +/* registers of smi */ +#define SMI_CR1 0x0 /* SMI control register 1 */ +#define SMI_CR2 0x4 /* SMI control register 2 */ +#define SMI_SR 0x8 /* SMI status register */ +#define SMI_TR 0xC /* SMI transmit register */ +#define SMI_RR 0x10 /* SMI receive register */ + +/* defines for control_reg 1 */ +#define BANK_EN (0xF << 0) /* enables all banks */ +#define DSEL_TIME (0x6 << 4) /* Deselect time 6 + 1 SMI_CK periods */ +#define SW_MODE (0x1 << 28) /* enables SW Mode */ +#define WB_MODE (0x1 << 29) /* Write Burst Mode */ +#define FAST_MODE (0x1 << 15) /* Fast Mode */ +#define HOLD1 (0x1 << 16) /* Clock Hold period selection */ + +/* defines for control_reg 2 */ +#define SEND (0x1 << 7) /* Send data */ +#define TFIE (0x1 << 8) /* Transmission Flag Interrupt Enable */ +#define WCIE (0x1 << 9) /* Write Complete Interrupt Enable */ +#define RD_STATUS_REG (0x1 << 10) /* reads status reg */ +#define WE (0x1 << 11) /* Write Enable */ + +#define TX_LEN_SHIFT 0 +#define RX_LEN_SHIFT 4 +#define BANK_SHIFT 12 + +/* defines for status register */ +#define SR_WIP 0x1 /* Write in progress */ +#define SR_WEL 0x2 /* Write enable latch */ +#define SR_BP0 0x4 /* Block protect 0 */ +#define SR_BP1 0x8 /* Block protect 1 */ +#define SR_BP2 0x10 /* Block protect 2 */ +#define SR_SRWD 0x80 /* SR write protect */ +#define TFF 0x100 /* Transfer Finished Flag */ +#define WCF 0x200 /* Transfer Finished Flag */ +#define ERF1 0x400 /* Forbidden Write Request */ +#define ERF2 0x800 /* Forbidden Access */ + +#define WM_SHIFT 12 + +/* flash opcodes */ +#define OPCODE_RDID 0x9f /* Read JEDEC ID */ + +/* Flash Device Ids maintenance section */ + +/* data structure to maintain flash ids from different vendors */ +struct flash_device { + char *name; + u8 erase_cmd; + u32 device_id; + u32 pagesize; + unsigned long sectorsize; + unsigned long size_in_bytes; +}; + +#define FLASH_ID(n, es, id, psize, ssize, size) \ +{ \ + .name = n, \ + .erase_cmd = es, \ + .device_id = id, \ + .pagesize = psize, \ + .sectorsize = ssize, \ + .size_in_bytes = size \ +} + +static struct flash_device flash_devices[] = { + FLASH_ID("st m25p16" , 0xd8, 0x00152020, 0x100, 0x10000, 0x200000), + FLASH_ID("st m25p32" , 0xd8, 0x00162020, 0x100, 0x10000, 0x400000), + FLASH_ID("st m25p64" , 0xd8, 0x00172020, 0x100, 0x10000, 0x800000), + FLASH_ID("st m25p128" , 0xd8, 0x00182020, 0x100, 0x40000, 0x1000000), + FLASH_ID("st m25p05" , 0xd8, 0x00102020, 0x80 , 0x8000 , 0x10000), + FLASH_ID("st m25p10" , 0xd8, 0x00112020, 0x80 , 0x8000 , 0x20000), + FLASH_ID("st m25p20" , 0xd8, 0x00122020, 0x100, 0x10000, 0x40000), + FLASH_ID("st m25p40" , 0xd8, 0x00132020, 0x100, 0x10000, 0x80000), + FLASH_ID("st m25p80" , 0xd8, 0x00142020, 0x100, 0x10000, 0x100000), + FLASH_ID("st m45pe10" , 0xd8, 0x00114020, 0x100, 0x10000, 0x20000), + FLASH_ID("st m45pe20" , 0xd8, 0x00124020, 0x100, 0x10000, 0x40000), + FLASH_ID("st m45pe40" , 0xd8, 0x00134020, 0x100, 0x10000, 0x80000), + FLASH_ID("st m45pe80" , 0xd8, 0x00144020, 0x100, 0x10000, 0x100000), + FLASH_ID("sp s25fl004" , 0xd8, 0x00120201, 0x100, 0x10000, 0x80000), + FLASH_ID("sp s25fl008" , 0xd8, 0x00130201, 0x100, 0x10000, 0x100000), + FLASH_ID("sp s25fl016" , 0xd8, 0x00140201, 0x100, 0x10000, 0x200000), + FLASH_ID("sp s25fl032" , 0xd8, 0x00150201, 0x100, 0x10000, 0x400000), + FLASH_ID("sp s25fl064" , 0xd8, 0x00160201, 0x100, 0x10000, 0x800000), + FLASH_ID("atmel 25f512" , 0x52, 0x0065001F, 0x80 , 0x8000 , 0x10000), + FLASH_ID("atmel 25f1024" , 0x52, 0x0060001F, 0x100, 0x8000 , 0x20000), + FLASH_ID("atmel 25f2048" , 0x52, 0x0063001F, 0x100, 0x10000, 0x40000), + FLASH_ID("atmel 25f4096" , 0x52, 0x0064001F, 0x100, 0x10000, 0x80000), + FLASH_ID("atmel 25fs040" , 0xd7, 0x0004661F, 0x100, 0x10000, 0x80000), + FLASH_ID("mac 25l512" , 0xd8, 0x001020C2, 0x010, 0x10000, 0x10000), + FLASH_ID("mac 25l1005" , 0xd8, 0x001120C2, 0x010, 0x10000, 0x20000), + FLASH_ID("mac 25l2005" , 0xd8, 0x001220C2, 0x010, 0x10000, 0x40000), + FLASH_ID("mac 25l4005" , 0xd8, 0x001320C2, 0x010, 0x10000, 0x80000), + FLASH_ID("mac 25l4005a" , 0xd8, 0x001320C2, 0x010, 0x10000, 0x80000), + FLASH_ID("mac 25l8005" , 0xd8, 0x001420C2, 0x010, 0x10000, 0x100000), + FLASH_ID("mac 25l1605" , 0xd8, 0x001520C2, 0x100, 0x10000, 0x200000), + FLASH_ID("mac 25l1605a" , 0xd8, 0x001520C2, 0x010, 0x10000, 0x200000), + FLASH_ID("mac 25l3205" , 0xd8, 0x001620C2, 0x100, 0x10000, 0x400000), + FLASH_ID("mac 25l3205a" , 0xd8, 0x001620C2, 0x100, 0x10000, 0x400000), + FLASH_ID("mac 25l6405" , 0xd8, 0x001720C2, 0x100, 0x10000, 0x800000), +}; + +/* Define spear specific structures */ + +struct spear_snor_flash; + +/** + * struct spear_smi - Structure for SMI Device + * + * @clk: functional clock + * @status: current status register of SMI. + * @clk_rate: functional clock rate of SMI (default: SMI_MAX_CLOCK_FREQ) + * @lock: lock to prevent parallel access of SMI. + * @io_base: base address for registers of SMI. + * @pdev: platform device + * @cmd_complete: queue to wait for command completion of NOR-flash. + * @num_flashes: number of flashes actually present on board. + * @flash: separate structure for each Serial NOR-flash attached to SMI. + */ +struct spear_smi { + struct clk *clk; + u32 status; + unsigned long clk_rate; + struct mutex lock; + void __iomem *io_base; + struct platform_device *pdev; + wait_queue_head_t cmd_complete; + u32 num_flashes; + struct spear_snor_flash *flash[MAX_NUM_FLASH_CHIP]; +}; + +/** + * struct spear_snor_flash - Structure for Serial NOR Flash + * + * @bank: Bank number(0, 1, 2, 3) for each NOR-flash. + * @dev_id: Device ID of NOR-flash. + * @lock: lock to manage flash read, write and erase operations + * @mtd: MTD info for each NOR-flash. + * @num_parts: Total number of partition in each bank of NOR-flash. + * @parts: Partition info for each bank of NOR-flash. + * @page_size: Page size of NOR-flash. + * @base_addr: Base address of NOR-flash. + * @erase_cmd: erase command may vary on different flash types + * @fast_mode: flash supports read in fast mode + */ +struct spear_snor_flash { + u32 bank; + u32 dev_id; + struct mutex lock; + struct mtd_info mtd; + u32 num_parts; + struct mtd_partition *parts; + u32 page_size; + void __iomem *base_addr; + u8 erase_cmd; + u8 fast_mode; +}; + +static inline struct spear_snor_flash *get_flash_data(struct mtd_info *mtd) +{ + return container_of(mtd, struct spear_snor_flash, mtd); +} + +/** + * spear_smi_read_sr - Read status register of flash through SMI + * @dev: structure of SMI information. + * @bank: bank to which flash is connected + * + * This routine will return the status register of the flash chip present at the + * given bank. + */ +static int spear_smi_read_sr(struct spear_smi *dev, u32 bank) +{ + int ret; + u32 ctrlreg1; + + mutex_lock(&dev->lock); + dev->status = 0; /* Will be set in interrupt handler */ + + ctrlreg1 = readl(dev->io_base + SMI_CR1); + /* program smi in hw mode */ + writel(ctrlreg1 & ~(SW_MODE | WB_MODE), dev->io_base + SMI_CR1); + + /* performing a rsr instruction in hw mode */ + writel((bank << BANK_SHIFT) | RD_STATUS_REG | TFIE, + dev->io_base + SMI_CR2); + + /* wait for tff */ + ret = wait_event_interruptible_timeout(dev->cmd_complete, + dev->status & TFF, SMI_CMD_TIMEOUT); + + /* copy dev->status (lower 16 bits) in order to release lock */ + if (ret > 0) + ret = dev->status & 0xffff; + else if (ret == 0) + ret = -ETIMEDOUT; + + /* restore the ctrl regs state */ + writel(ctrlreg1, dev->io_base + SMI_CR1); + writel(0, dev->io_base + SMI_CR2); + mutex_unlock(&dev->lock); + + return ret; +} + +/** + * spear_smi_wait_till_ready - wait till flash is ready + * @dev: structure of SMI information. + * @bank: flash corresponding to this bank + * @timeout: timeout for busy wait condition + * + * This routine checks for WIP (write in progress) bit in Status register + * If successful the routine returns 0 else -EBUSY + */ +static int spear_smi_wait_till_ready(struct spear_smi *dev, u32 bank, + unsigned long timeout) +{ + unsigned long finish; + int status; + + finish = jiffies + timeout; + do { + status = spear_smi_read_sr(dev, bank); + if (status < 0) { + if (status == -ETIMEDOUT) + continue; /* try till finish */ + return status; + } else if (!(status & SR_WIP)) { + return 0; + } + + cond_resched(); + } while (!time_after_eq(jiffies, finish)); + + dev_err(&dev->pdev->dev, "smi controller is busy, timeout\n"); + return -EBUSY; +} + +/** + * spear_smi_int_handler - SMI Interrupt Handler. + * @irq: irq number + * @dev_id: structure of SMI device, embedded in dev_id. + * + * The handler clears all interrupt conditions and records the status in + * dev->status which is used by the driver later. + */ +static irqreturn_t spear_smi_int_handler(int irq, void *dev_id) +{ + u32 status = 0; + struct spear_smi *dev = dev_id; + + status = readl(dev->io_base + SMI_SR); + + if (unlikely(!status)) + return IRQ_NONE; + + /* clear all interrupt conditions */ + writel(0, dev->io_base + SMI_SR); + + /* copy the status register in dev->status */ + dev->status |= status; + + /* send the completion */ + wake_up_interruptible(&dev->cmd_complete); + + return IRQ_HANDLED; +} + +/** + * spear_smi_hw_init - initializes the smi controller. + * @dev: structure of smi device + * + * this routine initializes the smi controller wit the default values + */ +static void spear_smi_hw_init(struct spear_smi *dev) +{ + unsigned long rate = 0; + u32 prescale = 0; + u32 val; + + rate = clk_get_rate(dev->clk); + + /* functional clock of smi */ + prescale = DIV_ROUND_UP(rate, dev->clk_rate); + + /* + * setting the standard values, fast mode, prescaler for + * SMI_MAX_CLOCK_FREQ (50MHz) operation and bank enable + */ + val = HOLD1 | BANK_EN | DSEL_TIME | (prescale << 8); + + mutex_lock(&dev->lock); + /* clear all interrupt conditions */ + writel(0, dev->io_base + SMI_SR); + + writel(val, dev->io_base + SMI_CR1); + mutex_unlock(&dev->lock); +} + +/** + * get_flash_index - match chip id from a flash list. + * @flash_id: a valid nor flash chip id obtained from board. + * + * try to validate the chip id by matching from a list, if not found then simply + * returns negative. In case of success returns index in to the flash devices + * array. + */ +static int get_flash_index(u32 flash_id) +{ + int index; + + /* Matches chip-id to entire list of 'serial-nor flash' ids */ + for (index = 0; index < ARRAY_SIZE(flash_devices); index++) { + if (flash_devices[index].device_id == flash_id) + return index; + } + + /* Memory chip is not listed and not supported */ + return -ENODEV; +} + +/** + * spear_smi_write_enable - Enable the flash to do write operation + * @dev: structure of SMI device + * @bank: enable write for flash connected to this bank + * + * Set write enable latch with Write Enable command. + * Returns 0 on success. + */ +static int spear_smi_write_enable(struct spear_smi *dev, u32 bank) +{ + int ret; + u32 ctrlreg1; + + mutex_lock(&dev->lock); + dev->status = 0; /* Will be set in interrupt handler */ + + ctrlreg1 = readl(dev->io_base + SMI_CR1); + /* program smi in h/w mode */ + writel(ctrlreg1 & ~SW_MODE, dev->io_base + SMI_CR1); + + /* give the flash, write enable command */ + writel((bank << BANK_SHIFT) | WE | TFIE, dev->io_base + SMI_CR2); + + ret = wait_event_interruptible_timeout(dev->cmd_complete, + dev->status & TFF, SMI_CMD_TIMEOUT); + + /* restore the ctrl regs state */ + writel(ctrlreg1, dev->io_base + SMI_CR1); + writel(0, dev->io_base + SMI_CR2); + + if (ret == 0) { + ret = -EIO; + dev_err(&dev->pdev->dev, + "smi controller failed on write enable\n"); + } else if (ret > 0) { + /* check whether write mode status is set for required bank */ + if (dev->status & (1 << (bank + WM_SHIFT))) + ret = 0; + else { + dev_err(&dev->pdev->dev, "couldn't enable write\n"); + ret = -EIO; + } + } + + mutex_unlock(&dev->lock); + return ret; +} + +static inline u32 +get_sector_erase_cmd(struct spear_snor_flash *flash, u32 offset) +{ + u32 cmd; + u8 *x = (u8 *)&cmd; + + x[0] = flash->erase_cmd; + x[1] = offset >> 16; + x[2] = offset >> 8; + x[3] = offset; + + return cmd; +} + +/** + * spear_smi_erase_sector - erase one sector of flash + * @dev: structure of SMI information + * @command: erase command to be send + * @bank: bank to which this command needs to be send + * @bytes: size of command + * + * Erase one sector of flash memory at offset ``offset'' which is any + * address within the sector which should be erased. + * Returns 0 if successful, non-zero otherwise. + */ +static int spear_smi_erase_sector(struct spear_smi *dev, + u32 bank, u32 command, u32 bytes) +{ + u32 ctrlreg1 = 0; + int ret; + + ret = spear_smi_wait_till_ready(dev, bank, SMI_MAX_TIME_OUT); + if (ret) + return ret; + + ret = spear_smi_write_enable(dev, bank); + if (ret) + return ret; + + mutex_lock(&dev->lock); + + ctrlreg1 = readl(dev->io_base + SMI_CR1); + writel((ctrlreg1 | SW_MODE) & ~WB_MODE, dev->io_base + SMI_CR1); + + /* send command in sw mode */ + writel(command, dev->io_base + SMI_TR); + + writel((bank << BANK_SHIFT) | SEND | TFIE | (bytes << TX_LEN_SHIFT), + dev->io_base + SMI_CR2); + + ret = wait_event_interruptible_timeout(dev->cmd_complete, + dev->status & TFF, SMI_CMD_TIMEOUT); + + if (ret == 0) { + ret = -EIO; + dev_err(&dev->pdev->dev, "sector erase failed\n"); + } else if (ret > 0) + ret = 0; /* success */ + + /* restore ctrl regs */ + writel(ctrlreg1, dev->io_base + SMI_CR1); + writel(0, dev->io_base + SMI_CR2); + + mutex_unlock(&dev->lock); + return ret; +} + +/** + * spear_mtd_erase - perform flash erase operation as requested by user + * @mtd: Provides the memory characteristics + * @e_info: Provides the erase information + * + * Erase an address range on the flash chip. The address range may extend + * one or more erase sectors. Return an error is there is a problem erasing. + */ +static int spear_mtd_erase(struct mtd_info *mtd, struct erase_info *e_info) +{ + struct spear_snor_flash *flash = get_flash_data(mtd); + struct spear_smi *dev = mtd->priv; + u32 addr, command, bank; + int len, ret; + + if (!flash || !dev) + return -ENODEV; + + bank = flash->bank; + if (bank > dev->num_flashes - 1) { + dev_err(&dev->pdev->dev, "Invalid Bank Num"); + return -EINVAL; + } + + addr = e_info->addr; + len = e_info->len; + + mutex_lock(&flash->lock); + + /* now erase sectors in loop */ + while (len) { + command = get_sector_erase_cmd(flash, addr); + /* preparing the command for flash */ + ret = spear_smi_erase_sector(dev, bank, command, 4); + if (ret) { + e_info->state = MTD_ERASE_FAILED; + mutex_unlock(&flash->lock); + return ret; + } + addr += mtd->erasesize; + len -= mtd->erasesize; + } + + mutex_unlock(&flash->lock); + e_info->state = MTD_ERASE_DONE; + mtd_erase_callback(e_info); + + return 0; +} + +/** + * spear_mtd_read - performs flash read operation as requested by the user + * @mtd: MTD information of the memory bank + * @from: Address from which to start read + * @len: Number of bytes to be read + * @retlen: Fills the Number of bytes actually read + * @buf: Fills this after reading + * + * Read an address range from the flash chip. The address range + * may be any size provided it is within the physical boundaries. + * Returns 0 on success, non zero otherwise + */ +static int spear_mtd_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u8 *buf) +{ + struct spear_snor_flash *flash = get_flash_data(mtd); + struct spear_smi *dev = mtd->priv; + void __iomem *src; + u32 ctrlreg1, val; + int ret; + + if (!flash || !dev) + return -ENODEV; + + if (flash->bank > dev->num_flashes - 1) { + dev_err(&dev->pdev->dev, "Invalid Bank Num"); + return -EINVAL; + } + + /* select address as per bank number */ + src = flash->base_addr + from; + + mutex_lock(&flash->lock); + + /* wait till previous write/erase is done. */ + ret = spear_smi_wait_till_ready(dev, flash->bank, SMI_MAX_TIME_OUT); + if (ret) { + mutex_unlock(&flash->lock); + return ret; + } + + mutex_lock(&dev->lock); + /* put smi in hw mode not wbt mode */ + ctrlreg1 = val = readl(dev->io_base + SMI_CR1); + val &= ~(SW_MODE | WB_MODE); + if (flash->fast_mode) + val |= FAST_MODE; + + writel(val, dev->io_base + SMI_CR1); + + memcpy_fromio(buf, src, len); + + /* restore ctrl reg1 */ + writel(ctrlreg1, dev->io_base + SMI_CR1); + mutex_unlock(&dev->lock); + + *retlen = len; + mutex_unlock(&flash->lock); + + return 0; +} + +static inline int spear_smi_cpy_toio(struct spear_smi *dev, u32 bank, + void __iomem *dest, const void *src, size_t len) +{ + int ret; + u32 ctrlreg1; + + /* wait until finished previous write command. */ + ret = spear_smi_wait_till_ready(dev, bank, SMI_MAX_TIME_OUT); + if (ret) + return ret; + + /* put smi in write enable */ + ret = spear_smi_write_enable(dev, bank); + if (ret) + return ret; + + /* put smi in hw, write burst mode */ + mutex_lock(&dev->lock); + + ctrlreg1 = readl(dev->io_base + SMI_CR1); + writel((ctrlreg1 | WB_MODE) & ~SW_MODE, dev->io_base + SMI_CR1); + + memcpy_toio(dest, src, len); + + writel(ctrlreg1, dev->io_base + SMI_CR1); + + mutex_unlock(&dev->lock); + return 0; +} + +/** + * spear_mtd_write - performs write operation as requested by the user. + * @mtd: MTD information of the memory bank. + * @to: Address to write. + * @len: Number of bytes to be written. + * @retlen: Number of bytes actually wrote. + * @buf: Buffer from which the data to be taken. + * + * Write an address range to the flash chip. Data must be written in + * flash_page_size chunks. The address range may be any size provided + * it is within the physical boundaries. + * Returns 0 on success, non zero otherwise + */ +static int spear_mtd_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u8 *buf) +{ + struct spear_snor_flash *flash = get_flash_data(mtd); + struct spear_smi *dev = mtd->priv; + void __iomem *dest; + u32 page_offset, page_size; + int ret; + + if (!flash || !dev) + return -ENODEV; + + if (flash->bank > dev->num_flashes - 1) { + dev_err(&dev->pdev->dev, "Invalid Bank Num"); + return -EINVAL; + } + + /* select address as per bank number */ + dest = flash->base_addr + to; + mutex_lock(&flash->lock); + + page_offset = (u32)to % flash->page_size; + + /* do if all the bytes fit onto one page */ + if (page_offset + len <= flash->page_size) { + ret = spear_smi_cpy_toio(dev, flash->bank, dest, buf, len); + if (!ret) + *retlen += len; + } else { + u32 i; + + /* the size of data remaining on the first page */ + page_size = flash->page_size - page_offset; + + ret = spear_smi_cpy_toio(dev, flash->bank, dest, buf, + page_size); + if (ret) + goto err_write; + else + *retlen += page_size; + + /* write everything in pagesize chunks */ + for (i = page_size; i < len; i += page_size) { + page_size = len - i; + if (page_size > flash->page_size) + page_size = flash->page_size; + + ret = spear_smi_cpy_toio(dev, flash->bank, dest + i, + buf + i, page_size); + if (ret) + break; + else + *retlen += page_size; + } + } + +err_write: + mutex_unlock(&flash->lock); + + return ret; +} + +/** + * spear_smi_probe_flash - Detects the NOR Flash chip. + * @dev: structure of SMI information. + * @bank: bank on which flash must be probed + * + * This routine will check whether there exists a flash chip on a given memory + * bank ID. + * Return index of the probed flash in flash devices structure + */ +static int spear_smi_probe_flash(struct spear_smi *dev, u32 bank) +{ + int ret; + u32 val = 0; + + ret = spear_smi_wait_till_ready(dev, bank, SMI_PROBE_TIMEOUT); + if (ret) + return ret; + + mutex_lock(&dev->lock); + + dev->status = 0; /* Will be set in interrupt handler */ + /* put smi in sw mode */ + val = readl(dev->io_base + SMI_CR1); + writel(val | SW_MODE, dev->io_base + SMI_CR1); + + /* send readid command in sw mode */ + writel(OPCODE_RDID, dev->io_base + SMI_TR); + + val = (bank << BANK_SHIFT) | SEND | (1 << TX_LEN_SHIFT) | + (3 << RX_LEN_SHIFT) | TFIE; + writel(val, dev->io_base + SMI_CR2); + + /* wait for TFF */ + ret = wait_event_interruptible_timeout(dev->cmd_complete, + dev->status & TFF, SMI_CMD_TIMEOUT); + if (ret <= 0) { + ret = -ENODEV; + goto err_probe; + } + + /* get memory chip id */ + val = readl(dev->io_base + SMI_RR); + val &= 0x00ffffff; + ret = get_flash_index(val); + +err_probe: + /* clear sw mode */ + val = readl(dev->io_base + SMI_CR1); + writel(val & ~SW_MODE, dev->io_base + SMI_CR1); + + mutex_unlock(&dev->lock); + return ret; +} + + +#ifdef CONFIG_OF +static int spear_smi_probe_config_dt(struct platform_device *pdev, + struct device_node *np) +{ + struct spear_smi_plat_data *pdata = dev_get_platdata(&pdev->dev); + struct device_node *pp = NULL; + const __be32 *addr; + u32 val; + int len; + int i = 0; + + if (!np) + return -ENODEV; + + of_property_read_u32(np, "clock-rate", &val); + pdata->clk_rate = val; + + pdata->board_flash_info = devm_kzalloc(&pdev->dev, + sizeof(*pdata->board_flash_info), + GFP_KERNEL); + + /* Fill structs for each subnode (flash device) */ + while ((pp = of_get_next_child(np, pp))) { + struct spear_smi_flash_info *flash_info; + + flash_info = &pdata->board_flash_info[i]; + pdata->np[i] = pp; + + /* Read base-addr and size from DT */ + addr = of_get_property(pp, "reg", &len); + pdata->board_flash_info->mem_base = be32_to_cpup(&addr[0]); + pdata->board_flash_info->size = be32_to_cpup(&addr[1]); + + if (of_get_property(pp, "st,smi-fast-mode", NULL)) + pdata->board_flash_info->fast_mode = 1; + + i++; + } + + pdata->num_flashes = i; + + return 0; +} +#else +static int spear_smi_probe_config_dt(struct platform_device *pdev, + struct device_node *np) +{ + return -ENOSYS; +} +#endif + +static int spear_smi_setup_banks(struct platform_device *pdev, + u32 bank, struct device_node *np) +{ + struct spear_smi *dev = platform_get_drvdata(pdev); + struct mtd_part_parser_data ppdata = {}; + struct spear_smi_flash_info *flash_info; + struct spear_smi_plat_data *pdata; + struct spear_snor_flash *flash; + struct mtd_partition *parts = NULL; + int count = 0; + int flash_index; + int ret = 0; + + pdata = dev_get_platdata(&pdev->dev); + if (bank > pdata->num_flashes - 1) + return -EINVAL; + + flash_info = &pdata->board_flash_info[bank]; + if (!flash_info) + return -ENODEV; + + flash = devm_kzalloc(&pdev->dev, sizeof(*flash), GFP_ATOMIC); + if (!flash) + return -ENOMEM; + flash->bank = bank; + flash->fast_mode = flash_info->fast_mode ? 1 : 0; + mutex_init(&flash->lock); + + /* verify whether nor flash is really present on board */ + flash_index = spear_smi_probe_flash(dev, bank); + if (flash_index < 0) { + dev_info(&dev->pdev->dev, "smi-nor%d not found\n", bank); + return flash_index; + } + /* map the memory for nor flash chip */ + flash->base_addr = devm_ioremap(&pdev->dev, flash_info->mem_base, + flash_info->size); + if (!flash->base_addr) + return -EIO; + + dev->flash[bank] = flash; + flash->mtd.priv = dev; + + if (flash_info->name) + flash->mtd.name = flash_info->name; + else + flash->mtd.name = flash_devices[flash_index].name; + + flash->mtd.type = MTD_NORFLASH; + flash->mtd.writesize = 1; + flash->mtd.flags = MTD_CAP_NORFLASH; + flash->mtd.size = flash_info->size; + flash->mtd.erasesize = flash_devices[flash_index].sectorsize; + flash->page_size = flash_devices[flash_index].pagesize; + flash->mtd.writebufsize = flash->page_size; + flash->erase_cmd = flash_devices[flash_index].erase_cmd; + flash->mtd._erase = spear_mtd_erase; + flash->mtd._read = spear_mtd_read; + flash->mtd._write = spear_mtd_write; + flash->dev_id = flash_devices[flash_index].device_id; + + dev_info(&dev->pdev->dev, "mtd .name=%s .size=%llx(%lluM)\n", + flash->mtd.name, flash->mtd.size, + flash->mtd.size / (1024 * 1024)); + + dev_info(&dev->pdev->dev, ".erasesize = 0x%x(%uK)\n", + flash->mtd.erasesize, flash->mtd.erasesize / 1024); + +#ifndef CONFIG_OF + if (flash_info->partitions) { + parts = flash_info->partitions; + count = flash_info->nr_partitions; + } +#endif + ppdata.of_node = np; + + ret = mtd_device_parse_register(&flash->mtd, NULL, &ppdata, parts, + count); + if (ret) { + dev_err(&dev->pdev->dev, "Err MTD partition=%d\n", ret); + return ret; + } + + return 0; +} + +/** + * spear_smi_probe - Entry routine + * @pdev: platform device structure + * + * This is the first routine which gets invoked during booting and does all + * initialization/allocation work. The routine looks for available memory banks, + * and do proper init for any found one. + * Returns 0 on success, non zero otherwise + */ +static int spear_smi_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct spear_smi_plat_data *pdata = NULL; + struct spear_smi *dev; + struct resource *smi_base; + int irq, ret = 0; + int i; + + if (np) { + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + ret = -ENOMEM; + goto err; + } + pdev->dev.platform_data = pdata; + ret = spear_smi_probe_config_dt(pdev, np); + if (ret) { + ret = -ENODEV; + dev_err(&pdev->dev, "no platform data\n"); + goto err; + } + } else { + pdata = dev_get_platdata(&pdev->dev); + if (!pdata) { + ret = -ENODEV; + dev_err(&pdev->dev, "no platform data\n"); + goto err; + } + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = -ENODEV; + dev_err(&pdev->dev, "invalid smi irq\n"); + goto err; + } + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_ATOMIC); + if (!dev) { + ret = -ENOMEM; + goto err; + } + + smi_base = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + dev->io_base = devm_ioremap_resource(&pdev->dev, smi_base); + if (IS_ERR(dev->io_base)) { + ret = PTR_ERR(dev->io_base); + goto err; + } + + dev->pdev = pdev; + dev->clk_rate = pdata->clk_rate; + + if (dev->clk_rate > SMI_MAX_CLOCK_FREQ) + dev->clk_rate = SMI_MAX_CLOCK_FREQ; + + dev->num_flashes = pdata->num_flashes; + + if (dev->num_flashes > MAX_NUM_FLASH_CHIP) { + dev_err(&pdev->dev, "exceeding max number of flashes\n"); + dev->num_flashes = MAX_NUM_FLASH_CHIP; + } + + dev->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(dev->clk)) { + ret = PTR_ERR(dev->clk); + goto err; + } + + ret = clk_prepare_enable(dev->clk); + if (ret) + goto err; + + ret = devm_request_irq(&pdev->dev, irq, spear_smi_int_handler, 0, + pdev->name, dev); + if (ret) { + dev_err(&dev->pdev->dev, "SMI IRQ allocation failed\n"); + goto err_irq; + } + + mutex_init(&dev->lock); + init_waitqueue_head(&dev->cmd_complete); + spear_smi_hw_init(dev); + platform_set_drvdata(pdev, dev); + + /* loop for each serial nor-flash which is connected to smi */ + for (i = 0; i < dev->num_flashes; i++) { + ret = spear_smi_setup_banks(pdev, i, pdata->np[i]); + if (ret) { + dev_err(&dev->pdev->dev, "bank setup failed\n"); + goto err_irq; + } + } + + return 0; + +err_irq: + clk_disable_unprepare(dev->clk); +err: + return ret; +} + +/** + * spear_smi_remove - Exit routine + * @pdev: platform device structure + * + * free all allocations and delete the partitions. + */ +static int spear_smi_remove(struct platform_device *pdev) +{ + struct spear_smi *dev; + struct spear_snor_flash *flash; + int ret, i; + + dev = platform_get_drvdata(pdev); + if (!dev) { + dev_err(&pdev->dev, "dev is null\n"); + return -ENODEV; + } + + /* clean up for all nor flash */ + for (i = 0; i < dev->num_flashes; i++) { + flash = dev->flash[i]; + if (!flash) + continue; + + /* clean up mtd stuff */ + ret = mtd_device_unregister(&flash->mtd); + if (ret) + dev_err(&pdev->dev, "error removing mtd\n"); + } + + clk_disable_unprepare(dev->clk); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int spear_smi_suspend(struct device *dev) +{ + struct spear_smi *sdev = dev_get_drvdata(dev); + + if (sdev && sdev->clk) + clk_disable_unprepare(sdev->clk); + + return 0; +} + +static int spear_smi_resume(struct device *dev) +{ + struct spear_smi *sdev = dev_get_drvdata(dev); + int ret = -EPERM; + + if (sdev && sdev->clk) + ret = clk_prepare_enable(sdev->clk); + + if (!ret) + spear_smi_hw_init(sdev); + return ret; +} +#endif + +static SIMPLE_DEV_PM_OPS(spear_smi_pm_ops, spear_smi_suspend, spear_smi_resume); + +#ifdef CONFIG_OF +static const struct of_device_id spear_smi_id_table[] = { + { .compatible = "st,spear600-smi" }, + {} +}; +MODULE_DEVICE_TABLE(of, spear_smi_id_table); +#endif + +static struct platform_driver spear_smi_driver = { + .driver = { + .name = "smi", + .bus = &platform_bus_type, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(spear_smi_id_table), + .pm = &spear_smi_pm_ops, + }, + .probe = spear_smi_probe, + .remove = spear_smi_remove, +}; +module_platform_driver(spear_smi_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ashish Priyadarshi, Shiraz Hashim <shiraz.linux.kernel@gmail.com>"); +MODULE_DESCRIPTION("MTD SMI driver for serial nor flash chips"); diff --git a/drivers/mtd/devices/sst25l.c b/drivers/mtd/devices/sst25l.c new file mode 100644 index 00000000000..c63ecbcad0b --- /dev/null +++ b/drivers/mtd/devices/sst25l.c @@ -0,0 +1,431 @@ +/* + * sst25l.c + * + * Driver for SST25L SPI Flash chips + * + * Copyright © 2009 Bluewater Systems Ltd + * Author: Andre Renaud <andre@bluewatersys.com> + * Author: Ryan Mallon + * + * Based on m25p80.c + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/mutex.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/sched.h> + +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> + +#include <linux/spi/spi.h> +#include <linux/spi/flash.h> + +/* Erases can take up to 3 seconds! */ +#define MAX_READY_WAIT_JIFFIES msecs_to_jiffies(3000) + +#define SST25L_CMD_WRSR 0x01 /* Write status register */ +#define SST25L_CMD_WRDI 0x04 /* Write disable */ +#define SST25L_CMD_RDSR 0x05 /* Read status register */ +#define SST25L_CMD_WREN 0x06 /* Write enable */ +#define SST25L_CMD_READ 0x03 /* High speed read */ + +#define SST25L_CMD_EWSR 0x50 /* Enable write status register */ +#define SST25L_CMD_SECTOR_ERASE 0x20 /* Erase sector */ +#define SST25L_CMD_READ_ID 0x90 /* Read device ID */ +#define SST25L_CMD_AAI_PROGRAM 0xaf /* Auto address increment */ + +#define SST25L_STATUS_BUSY (1 << 0) /* Chip is busy */ +#define SST25L_STATUS_WREN (1 << 1) /* Write enabled */ +#define SST25L_STATUS_BP0 (1 << 2) /* Block protection 0 */ +#define SST25L_STATUS_BP1 (1 << 3) /* Block protection 1 */ + +struct sst25l_flash { + struct spi_device *spi; + struct mutex lock; + struct mtd_info mtd; +}; + +struct flash_info { + const char *name; + uint16_t device_id; + unsigned page_size; + unsigned nr_pages; + unsigned erase_size; +}; + +#define to_sst25l_flash(x) container_of(x, struct sst25l_flash, mtd) + +static struct flash_info sst25l_flash_info[] = { + {"sst25lf020a", 0xbf43, 256, 1024, 4096}, + {"sst25lf040a", 0xbf44, 256, 2048, 4096}, +}; + +static int sst25l_status(struct sst25l_flash *flash, int *status) +{ + struct spi_message m; + struct spi_transfer t; + unsigned char cmd_resp[2]; + int err; + + spi_message_init(&m); + memset(&t, 0, sizeof(struct spi_transfer)); + + cmd_resp[0] = SST25L_CMD_RDSR; + cmd_resp[1] = 0xff; + t.tx_buf = cmd_resp; + t.rx_buf = cmd_resp; + t.len = sizeof(cmd_resp); + spi_message_add_tail(&t, &m); + err = spi_sync(flash->spi, &m); + if (err < 0) + return err; + + *status = cmd_resp[1]; + return 0; +} + +static int sst25l_write_enable(struct sst25l_flash *flash, int enable) +{ + unsigned char command[2]; + int status, err; + + command[0] = enable ? SST25L_CMD_WREN : SST25L_CMD_WRDI; + err = spi_write(flash->spi, command, 1); + if (err) + return err; + + command[0] = SST25L_CMD_EWSR; + err = spi_write(flash->spi, command, 1); + if (err) + return err; + + command[0] = SST25L_CMD_WRSR; + command[1] = enable ? 0 : SST25L_STATUS_BP0 | SST25L_STATUS_BP1; + err = spi_write(flash->spi, command, 2); + if (err) + return err; + + if (enable) { + err = sst25l_status(flash, &status); + if (err) + return err; + if (!(status & SST25L_STATUS_WREN)) + return -EROFS; + } + + return 0; +} + +static int sst25l_wait_till_ready(struct sst25l_flash *flash) +{ + unsigned long deadline; + int status, err; + + deadline = jiffies + MAX_READY_WAIT_JIFFIES; + do { + err = sst25l_status(flash, &status); + if (err) + return err; + if (!(status & SST25L_STATUS_BUSY)) + return 0; + + cond_resched(); + } while (!time_after_eq(jiffies, deadline)); + + return -ETIMEDOUT; +} + +static int sst25l_erase_sector(struct sst25l_flash *flash, uint32_t offset) +{ + unsigned char command[4]; + int err; + + err = sst25l_write_enable(flash, 1); + if (err) + return err; + + command[0] = SST25L_CMD_SECTOR_ERASE; + command[1] = offset >> 16; + command[2] = offset >> 8; + command[3] = offset; + err = spi_write(flash->spi, command, 4); + if (err) + return err; + + err = sst25l_wait_till_ready(flash); + if (err) + return err; + + return sst25l_write_enable(flash, 0); +} + +static int sst25l_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + struct sst25l_flash *flash = to_sst25l_flash(mtd); + uint32_t addr, end; + int err; + + /* Sanity checks */ + if ((uint32_t)instr->len % mtd->erasesize) + return -EINVAL; + + if ((uint32_t)instr->addr % mtd->erasesize) + return -EINVAL; + + addr = instr->addr; + end = addr + instr->len; + + mutex_lock(&flash->lock); + + err = sst25l_wait_till_ready(flash); + if (err) { + mutex_unlock(&flash->lock); + return err; + } + + while (addr < end) { + err = sst25l_erase_sector(flash, addr); + if (err) { + mutex_unlock(&flash->lock); + instr->state = MTD_ERASE_FAILED; + dev_err(&flash->spi->dev, "Erase failed\n"); + return err; + } + + addr += mtd->erasesize; + } + + mutex_unlock(&flash->lock); + + instr->state = MTD_ERASE_DONE; + mtd_erase_callback(instr); + return 0; +} + +static int sst25l_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, unsigned char *buf) +{ + struct sst25l_flash *flash = to_sst25l_flash(mtd); + struct spi_transfer transfer[2]; + struct spi_message message; + unsigned char command[4]; + int ret; + + spi_message_init(&message); + memset(&transfer, 0, sizeof(transfer)); + + command[0] = SST25L_CMD_READ; + command[1] = from >> 16; + command[2] = from >> 8; + command[3] = from; + + transfer[0].tx_buf = command; + transfer[0].len = sizeof(command); + spi_message_add_tail(&transfer[0], &message); + + transfer[1].rx_buf = buf; + transfer[1].len = len; + spi_message_add_tail(&transfer[1], &message); + + mutex_lock(&flash->lock); + + /* Wait for previous write/erase to complete */ + ret = sst25l_wait_till_ready(flash); + if (ret) { + mutex_unlock(&flash->lock); + return ret; + } + + spi_sync(flash->spi, &message); + + if (retlen && message.actual_length > sizeof(command)) + *retlen += message.actual_length - sizeof(command); + + mutex_unlock(&flash->lock); + return 0; +} + +static int sst25l_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const unsigned char *buf) +{ + struct sst25l_flash *flash = to_sst25l_flash(mtd); + int i, j, ret, bytes, copied = 0; + unsigned char command[5]; + + if ((uint32_t)to % mtd->writesize) + return -EINVAL; + + mutex_lock(&flash->lock); + + ret = sst25l_write_enable(flash, 1); + if (ret) + goto out; + + for (i = 0; i < len; i += mtd->writesize) { + ret = sst25l_wait_till_ready(flash); + if (ret) + goto out; + + /* Write the first byte of the page */ + command[0] = SST25L_CMD_AAI_PROGRAM; + command[1] = (to + i) >> 16; + command[2] = (to + i) >> 8; + command[3] = (to + i); + command[4] = buf[i]; + ret = spi_write(flash->spi, command, 5); + if (ret < 0) + goto out; + copied++; + + /* + * Write the remaining bytes using auto address + * increment mode + */ + bytes = min_t(uint32_t, mtd->writesize, len - i); + for (j = 1; j < bytes; j++, copied++) { + ret = sst25l_wait_till_ready(flash); + if (ret) + goto out; + + command[1] = buf[i + j]; + ret = spi_write(flash->spi, command, 2); + if (ret) + goto out; + } + } + +out: + ret = sst25l_write_enable(flash, 0); + + if (retlen) + *retlen = copied; + + mutex_unlock(&flash->lock); + return ret; +} + +static struct flash_info *sst25l_match_device(struct spi_device *spi) +{ + struct flash_info *flash_info = NULL; + struct spi_message m; + struct spi_transfer t; + unsigned char cmd_resp[6]; + int i, err; + uint16_t id; + + spi_message_init(&m); + memset(&t, 0, sizeof(struct spi_transfer)); + + cmd_resp[0] = SST25L_CMD_READ_ID; + cmd_resp[1] = 0; + cmd_resp[2] = 0; + cmd_resp[3] = 0; + cmd_resp[4] = 0xff; + cmd_resp[5] = 0xff; + t.tx_buf = cmd_resp; + t.rx_buf = cmd_resp; + t.len = sizeof(cmd_resp); + spi_message_add_tail(&t, &m); + err = spi_sync(spi, &m); + if (err < 0) { + dev_err(&spi->dev, "error reading device id\n"); + return NULL; + } + + id = (cmd_resp[4] << 8) | cmd_resp[5]; + + for (i = 0; i < ARRAY_SIZE(sst25l_flash_info); i++) + if (sst25l_flash_info[i].device_id == id) + flash_info = &sst25l_flash_info[i]; + + if (!flash_info) + dev_err(&spi->dev, "unknown id %.4x\n", id); + + return flash_info; +} + +static int sst25l_probe(struct spi_device *spi) +{ + struct flash_info *flash_info; + struct sst25l_flash *flash; + struct flash_platform_data *data; + int ret; + + flash_info = sst25l_match_device(spi); + if (!flash_info) + return -ENODEV; + + flash = devm_kzalloc(&spi->dev, sizeof(*flash), GFP_KERNEL); + if (!flash) + return -ENOMEM; + + flash->spi = spi; + mutex_init(&flash->lock); + spi_set_drvdata(spi, flash); + + data = dev_get_platdata(&spi->dev); + if (data && data->name) + flash->mtd.name = data->name; + else + flash->mtd.name = dev_name(&spi->dev); + + flash->mtd.type = MTD_NORFLASH; + flash->mtd.flags = MTD_CAP_NORFLASH; + flash->mtd.erasesize = flash_info->erase_size; + flash->mtd.writesize = flash_info->page_size; + flash->mtd.writebufsize = flash_info->page_size; + flash->mtd.size = flash_info->page_size * flash_info->nr_pages; + flash->mtd._erase = sst25l_erase; + flash->mtd._read = sst25l_read; + flash->mtd._write = sst25l_write; + + dev_info(&spi->dev, "%s (%lld KiB)\n", flash_info->name, + (long long)flash->mtd.size >> 10); + + pr_debug("mtd .name = %s, .size = 0x%llx (%lldMiB) " + ".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n", + flash->mtd.name, + (long long)flash->mtd.size, (long long)(flash->mtd.size >> 20), + flash->mtd.erasesize, flash->mtd.erasesize / 1024, + flash->mtd.numeraseregions); + + + ret = mtd_device_parse_register(&flash->mtd, NULL, NULL, + data ? data->parts : NULL, + data ? data->nr_parts : 0); + if (ret) + return -ENODEV; + + return 0; +} + +static int sst25l_remove(struct spi_device *spi) +{ + struct sst25l_flash *flash = spi_get_drvdata(spi); + + return mtd_device_unregister(&flash->mtd); +} + +static struct spi_driver sst25l_driver = { + .driver = { + .name = "sst25l", + .owner = THIS_MODULE, + }, + .probe = sst25l_probe, + .remove = sst25l_remove, +}; + +module_spi_driver(sst25l_driver); + +MODULE_DESCRIPTION("MTD SPI driver for SST25L Flash chips"); +MODULE_AUTHOR("Andre Renaud <andre@bluewatersys.com>, " + "Ryan Mallon"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/devices/st_spi_fsm.c b/drivers/mtd/devices/st_spi_fsm.c new file mode 100644 index 00000000000..d252514d3e9 --- /dev/null +++ b/drivers/mtd/devices/st_spi_fsm.c @@ -0,0 +1,2080 @@ +/* + * st_spi_fsm.c - ST Fast Sequence Mode (FSM) Serial Flash Controller + * + * Author: Angus Clark <angus.clark@st.com> + * + * Copyright (C) 2010-2014 STMicroelectronics Limited + * + * JEDEC probe based on drivers/mtd/devices/m25p80.c + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/platform_device.h> +#include <linux/mfd/syscon.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/spi-nor.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/of.h> + +#include "serial_flash_cmds.h" + +/* + * FSM SPI Controller Registers + */ +#define SPI_CLOCKDIV 0x0010 +#define SPI_MODESELECT 0x0018 +#define SPI_CONFIGDATA 0x0020 +#define SPI_STA_MODE_CHANGE 0x0028 +#define SPI_FAST_SEQ_TRANSFER_SIZE 0x0100 +#define SPI_FAST_SEQ_ADD1 0x0104 +#define SPI_FAST_SEQ_ADD2 0x0108 +#define SPI_FAST_SEQ_ADD_CFG 0x010c +#define SPI_FAST_SEQ_OPC1 0x0110 +#define SPI_FAST_SEQ_OPC2 0x0114 +#define SPI_FAST_SEQ_OPC3 0x0118 +#define SPI_FAST_SEQ_OPC4 0x011c +#define SPI_FAST_SEQ_OPC5 0x0120 +#define SPI_MODE_BITS 0x0124 +#define SPI_DUMMY_BITS 0x0128 +#define SPI_FAST_SEQ_FLASH_STA_DATA 0x012c +#define SPI_FAST_SEQ_1 0x0130 +#define SPI_FAST_SEQ_2 0x0134 +#define SPI_FAST_SEQ_3 0x0138 +#define SPI_FAST_SEQ_4 0x013c +#define SPI_FAST_SEQ_CFG 0x0140 +#define SPI_FAST_SEQ_STA 0x0144 +#define SPI_QUAD_BOOT_SEQ_INIT_1 0x0148 +#define SPI_QUAD_BOOT_SEQ_INIT_2 0x014c +#define SPI_QUAD_BOOT_READ_SEQ_1 0x0150 +#define SPI_QUAD_BOOT_READ_SEQ_2 0x0154 +#define SPI_PROGRAM_ERASE_TIME 0x0158 +#define SPI_MULT_PAGE_REPEAT_SEQ_1 0x015c +#define SPI_MULT_PAGE_REPEAT_SEQ_2 0x0160 +#define SPI_STATUS_WR_TIME_REG 0x0164 +#define SPI_FAST_SEQ_DATA_REG 0x0300 + +/* + * Register: SPI_MODESELECT + */ +#define SPI_MODESELECT_CONTIG 0x01 +#define SPI_MODESELECT_FASTREAD 0x02 +#define SPI_MODESELECT_DUALIO 0x04 +#define SPI_MODESELECT_FSM 0x08 +#define SPI_MODESELECT_QUADBOOT 0x10 + +/* + * Register: SPI_CONFIGDATA + */ +#define SPI_CFG_DEVICE_ST 0x1 +#define SPI_CFG_DEVICE_ATMEL 0x4 +#define SPI_CFG_MIN_CS_HIGH(x) (((x) & 0xfff) << 4) +#define SPI_CFG_CS_SETUPHOLD(x) (((x) & 0xff) << 16) +#define SPI_CFG_DATA_HOLD(x) (((x) & 0xff) << 24) + +#define SPI_CFG_DEFAULT_MIN_CS_HIGH SPI_CFG_MIN_CS_HIGH(0x0AA) +#define SPI_CFG_DEFAULT_CS_SETUPHOLD SPI_CFG_CS_SETUPHOLD(0xA0) +#define SPI_CFG_DEFAULT_DATA_HOLD SPI_CFG_DATA_HOLD(0x00) + +/* + * Register: SPI_FAST_SEQ_TRANSFER_SIZE + */ +#define TRANSFER_SIZE(x) ((x) * 8) + +/* + * Register: SPI_FAST_SEQ_ADD_CFG + */ +#define ADR_CFG_CYCLES_ADD1(x) ((x) << 0) +#define ADR_CFG_PADS_1_ADD1 (0x0 << 6) +#define ADR_CFG_PADS_2_ADD1 (0x1 << 6) +#define ADR_CFG_PADS_4_ADD1 (0x3 << 6) +#define ADR_CFG_CSDEASSERT_ADD1 (1 << 8) +#define ADR_CFG_CYCLES_ADD2(x) ((x) << (0+16)) +#define ADR_CFG_PADS_1_ADD2 (0x0 << (6+16)) +#define ADR_CFG_PADS_2_ADD2 (0x1 << (6+16)) +#define ADR_CFG_PADS_4_ADD2 (0x3 << (6+16)) +#define ADR_CFG_CSDEASSERT_ADD2 (1 << (8+16)) + +/* + * Register: SPI_FAST_SEQ_n + */ +#define SEQ_OPC_OPCODE(x) ((x) << 0) +#define SEQ_OPC_CYCLES(x) ((x) << 8) +#define SEQ_OPC_PADS_1 (0x0 << 14) +#define SEQ_OPC_PADS_2 (0x1 << 14) +#define SEQ_OPC_PADS_4 (0x3 << 14) +#define SEQ_OPC_CSDEASSERT (1 << 16) + +/* + * Register: SPI_FAST_SEQ_CFG + */ +#define SEQ_CFG_STARTSEQ (1 << 0) +#define SEQ_CFG_SWRESET (1 << 5) +#define SEQ_CFG_CSDEASSERT (1 << 6) +#define SEQ_CFG_READNOTWRITE (1 << 7) +#define SEQ_CFG_ERASE (1 << 8) +#define SEQ_CFG_PADS_1 (0x0 << 16) +#define SEQ_CFG_PADS_2 (0x1 << 16) +#define SEQ_CFG_PADS_4 (0x3 << 16) + +/* + * Register: SPI_MODE_BITS + */ +#define MODE_DATA(x) (x & 0xff) +#define MODE_CYCLES(x) ((x & 0x3f) << 16) +#define MODE_PADS_1 (0x0 << 22) +#define MODE_PADS_2 (0x1 << 22) +#define MODE_PADS_4 (0x3 << 22) +#define DUMMY_CSDEASSERT (1 << 24) + +/* + * Register: SPI_DUMMY_BITS + */ +#define DUMMY_CYCLES(x) ((x & 0x3f) << 16) +#define DUMMY_PADS_1 (0x0 << 22) +#define DUMMY_PADS_2 (0x1 << 22) +#define DUMMY_PADS_4 (0x3 << 22) +#define DUMMY_CSDEASSERT (1 << 24) + +/* + * Register: SPI_FAST_SEQ_FLASH_STA_DATA + */ +#define STA_DATA_BYTE1(x) ((x & 0xff) << 0) +#define STA_DATA_BYTE2(x) ((x & 0xff) << 8) +#define STA_PADS_1 (0x0 << 16) +#define STA_PADS_2 (0x1 << 16) +#define STA_PADS_4 (0x3 << 16) +#define STA_CSDEASSERT (0x1 << 20) +#define STA_RDNOTWR (0x1 << 21) + +/* + * FSM SPI Instruction Opcodes + */ +#define STFSM_OPC_CMD 0x1 +#define STFSM_OPC_ADD 0x2 +#define STFSM_OPC_STA 0x3 +#define STFSM_OPC_MODE 0x4 +#define STFSM_OPC_DUMMY 0x5 +#define STFSM_OPC_DATA 0x6 +#define STFSM_OPC_WAIT 0x7 +#define STFSM_OPC_JUMP 0x8 +#define STFSM_OPC_GOTO 0x9 +#define STFSM_OPC_STOP 0xF + +/* + * FSM SPI Instructions (== opcode + operand). + */ +#define STFSM_INSTR(cmd, op) ((cmd) | ((op) << 4)) + +#define STFSM_INST_CMD1 STFSM_INSTR(STFSM_OPC_CMD, 1) +#define STFSM_INST_CMD2 STFSM_INSTR(STFSM_OPC_CMD, 2) +#define STFSM_INST_CMD3 STFSM_INSTR(STFSM_OPC_CMD, 3) +#define STFSM_INST_CMD4 STFSM_INSTR(STFSM_OPC_CMD, 4) +#define STFSM_INST_CMD5 STFSM_INSTR(STFSM_OPC_CMD, 5) +#define STFSM_INST_ADD1 STFSM_INSTR(STFSM_OPC_ADD, 1) +#define STFSM_INST_ADD2 STFSM_INSTR(STFSM_OPC_ADD, 2) + +#define STFSM_INST_DATA_WRITE STFSM_INSTR(STFSM_OPC_DATA, 1) +#define STFSM_INST_DATA_READ STFSM_INSTR(STFSM_OPC_DATA, 2) + +#define STFSM_INST_STA_RD1 STFSM_INSTR(STFSM_OPC_STA, 0x1) +#define STFSM_INST_STA_WR1 STFSM_INSTR(STFSM_OPC_STA, 0x1) +#define STFSM_INST_STA_RD2 STFSM_INSTR(STFSM_OPC_STA, 0x2) +#define STFSM_INST_STA_WR1_2 STFSM_INSTR(STFSM_OPC_STA, 0x3) + +#define STFSM_INST_MODE STFSM_INSTR(STFSM_OPC_MODE, 0) +#define STFSM_INST_DUMMY STFSM_INSTR(STFSM_OPC_DUMMY, 0) +#define STFSM_INST_WAIT STFSM_INSTR(STFSM_OPC_WAIT, 0) +#define STFSM_INST_STOP STFSM_INSTR(STFSM_OPC_STOP, 0) + +#define STFSM_DEFAULT_EMI_FREQ 100000000UL /* 100 MHz */ +#define STFSM_DEFAULT_WR_TIME (STFSM_DEFAULT_EMI_FREQ * (15/1000)) /* 15ms */ + +#define STFSM_FLASH_SAFE_FREQ 10000000UL /* 10 MHz */ + +#define STFSM_MAX_WAIT_SEQ_MS 1000 /* FSM execution time */ + +/* S25FLxxxS commands */ +#define S25FL_CMD_WRITE4_1_1_4 0x34 +#define S25FL_CMD_SE4 0xdc +#define S25FL_CMD_CLSR 0x30 +#define S25FL_CMD_DYBWR 0xe1 +#define S25FL_CMD_DYBRD 0xe0 +#define S25FL_CMD_WRITE4 0x12 /* Note, opcode clashes with + * 'SPINOR_OP_WRITE_1_4_4' + * as found on N25Qxxx devices! */ + +/* Status register */ +#define FLASH_STATUS_BUSY 0x01 +#define FLASH_STATUS_WEL 0x02 +#define FLASH_STATUS_BP0 0x04 +#define FLASH_STATUS_BP1 0x08 +#define FLASH_STATUS_BP2 0x10 +#define FLASH_STATUS_SRWP0 0x80 +#define FLASH_STATUS_TIMEOUT 0xff +/* S25FL Error Flags */ +#define S25FL_STATUS_E_ERR 0x20 +#define S25FL_STATUS_P_ERR 0x40 + +#define N25Q_CMD_WRVCR 0x81 +#define N25Q_CMD_RDVCR 0x85 +#define N25Q_CMD_RDVECR 0x65 +#define N25Q_CMD_RDNVCR 0xb5 +#define N25Q_CMD_WRNVCR 0xb1 + +#define FLASH_PAGESIZE 256 /* In Bytes */ +#define FLASH_PAGESIZE_32 (FLASH_PAGESIZE / 4) /* In uint32_t */ +#define FLASH_MAX_BUSY_WAIT (300 * HZ) /* Maximum 'CHIPERASE' time */ + +/* + * Flags to tweak operation of default read/write/erase routines + */ +#define CFG_READ_TOGGLE_32BIT_ADDR 0x00000001 +#define CFG_WRITE_TOGGLE_32BIT_ADDR 0x00000002 +#define CFG_ERASESEC_TOGGLE_32BIT_ADDR 0x00000008 +#define CFG_S25FL_CHECK_ERROR_FLAGS 0x00000010 + +struct stfsm_seq { + uint32_t data_size; + uint32_t addr1; + uint32_t addr2; + uint32_t addr_cfg; + uint32_t seq_opc[5]; + uint32_t mode; + uint32_t dummy; + uint32_t status; + uint8_t seq[16]; + uint32_t seq_cfg; +} __packed __aligned(4); + +struct stfsm { + struct device *dev; + void __iomem *base; + struct resource *region; + struct mtd_info mtd; + struct mutex lock; + struct flash_info *info; + + uint32_t configuration; + uint32_t fifo_dir_delay; + bool booted_from_spi; + bool reset_signal; + bool reset_por; + + struct stfsm_seq stfsm_seq_read; + struct stfsm_seq stfsm_seq_write; + struct stfsm_seq stfsm_seq_en_32bit_addr; +}; + +/* Parameters to configure a READ or WRITE FSM sequence */ +struct seq_rw_config { + uint32_t flags; /* flags to support config */ + uint8_t cmd; /* FLASH command */ + int write; /* Write Sequence */ + uint8_t addr_pads; /* No. of addr pads (MODE & DUMMY) */ + uint8_t data_pads; /* No. of data pads */ + uint8_t mode_data; /* MODE data */ + uint8_t mode_cycles; /* No. of MODE cycles */ + uint8_t dummy_cycles; /* No. of DUMMY cycles */ +}; + +/* SPI Flash Device Table */ +struct flash_info { + char *name; + /* + * JEDEC id zero means "no ID" (most older chips); otherwise it has + * a high byte of zero plus three data bytes: the manufacturer id, + * then a two byte device id. + */ + u32 jedec_id; + u16 ext_id; + /* + * The size listed here is what works with SPINOR_OP_SE, which isn't + * necessarily called a "sector" by the vendor. + */ + unsigned sector_size; + u16 n_sectors; + u32 flags; + /* + * Note, where FAST_READ is supported, freq_max specifies the + * FAST_READ frequency, not the READ frequency. + */ + u32 max_freq; + int (*config)(struct stfsm *); +}; + +static int stfsm_n25q_config(struct stfsm *fsm); +static int stfsm_mx25_config(struct stfsm *fsm); +static int stfsm_s25fl_config(struct stfsm *fsm); +static int stfsm_w25q_config(struct stfsm *fsm); + +static struct flash_info flash_types[] = { + /* + * ST Microelectronics/Numonyx -- + * (newer production versions may have feature updates + * (eg faster operating frequency) + */ +#define M25P_FLAG (FLASH_FLAG_READ_WRITE | FLASH_FLAG_READ_FAST) + { "m25p40", 0x202013, 0, 64 * 1024, 8, M25P_FLAG, 25, NULL }, + { "m25p80", 0x202014, 0, 64 * 1024, 16, M25P_FLAG, 25, NULL }, + { "m25p16", 0x202015, 0, 64 * 1024, 32, M25P_FLAG, 25, NULL }, + { "m25p32", 0x202016, 0, 64 * 1024, 64, M25P_FLAG, 50, NULL }, + { "m25p64", 0x202017, 0, 64 * 1024, 128, M25P_FLAG, 50, NULL }, + { "m25p128", 0x202018, 0, 256 * 1024, 64, M25P_FLAG, 50, NULL }, + +#define M25PX_FLAG (FLASH_FLAG_READ_WRITE | \ + FLASH_FLAG_READ_FAST | \ + FLASH_FLAG_READ_1_1_2 | \ + FLASH_FLAG_WRITE_1_1_2) + { "m25px32", 0x207116, 0, 64 * 1024, 64, M25PX_FLAG, 75, NULL }, + { "m25px64", 0x207117, 0, 64 * 1024, 128, M25PX_FLAG, 75, NULL }, + + /* Macronix MX25xxx + * - Support for 'FLASH_FLAG_WRITE_1_4_4' is omitted for devices + * where operating frequency must be reduced. + */ +#define MX25_FLAG (FLASH_FLAG_READ_WRITE | \ + FLASH_FLAG_READ_FAST | \ + FLASH_FLAG_READ_1_1_2 | \ + FLASH_FLAG_READ_1_2_2 | \ + FLASH_FLAG_READ_1_1_4 | \ + FLASH_FLAG_SE_4K | \ + FLASH_FLAG_SE_32K) + { "mx25l3255e", 0xc29e16, 0, 64 * 1024, 64, + (MX25_FLAG | FLASH_FLAG_WRITE_1_4_4), 86, + stfsm_mx25_config}, + { "mx25l25635e", 0xc22019, 0, 64*1024, 512, + (MX25_FLAG | FLASH_FLAG_32BIT_ADDR | FLASH_FLAG_RESET), 70, + stfsm_mx25_config }, + { "mx25l25655e", 0xc22619, 0, 64*1024, 512, + (MX25_FLAG | FLASH_FLAG_32BIT_ADDR | FLASH_FLAG_RESET), 70, + stfsm_mx25_config}, + +#define N25Q_FLAG (FLASH_FLAG_READ_WRITE | \ + FLASH_FLAG_READ_FAST | \ + FLASH_FLAG_READ_1_1_2 | \ + FLASH_FLAG_READ_1_2_2 | \ + FLASH_FLAG_READ_1_1_4 | \ + FLASH_FLAG_READ_1_4_4 | \ + FLASH_FLAG_WRITE_1_1_2 | \ + FLASH_FLAG_WRITE_1_2_2 | \ + FLASH_FLAG_WRITE_1_1_4 | \ + FLASH_FLAG_WRITE_1_4_4) + { "n25q128", 0x20ba18, 0, 64 * 1024, 256, N25Q_FLAG, 108, + stfsm_n25q_config }, + { "n25q256", 0x20ba19, 0, 64 * 1024, 512, + N25Q_FLAG | FLASH_FLAG_32BIT_ADDR, 108, stfsm_n25q_config }, + + /* + * Spansion S25FLxxxP + * - 256KiB and 64KiB sector variants (identified by ext. JEDEC) + */ +#define S25FLXXXP_FLAG (FLASH_FLAG_READ_WRITE | \ + FLASH_FLAG_READ_1_1_2 | \ + FLASH_FLAG_READ_1_2_2 | \ + FLASH_FLAG_READ_1_1_4 | \ + FLASH_FLAG_READ_1_4_4 | \ + FLASH_FLAG_WRITE_1_1_4 | \ + FLASH_FLAG_READ_FAST) + { "s25fl032p", 0x010215, 0x4d00, 64 * 1024, 64, S25FLXXXP_FLAG, 80, + stfsm_s25fl_config}, + { "s25fl129p0", 0x012018, 0x4d00, 256 * 1024, 64, S25FLXXXP_FLAG, 80, + stfsm_s25fl_config }, + { "s25fl129p1", 0x012018, 0x4d01, 64 * 1024, 256, S25FLXXXP_FLAG, 80, + stfsm_s25fl_config }, + + /* + * Spansion S25FLxxxS + * - 256KiB and 64KiB sector variants (identified by ext. JEDEC) + * - RESET# signal supported by die but not bristled out on all + * package types. The package type is a function of board design, + * so this information is captured in the board's flags. + * - Supports 'DYB' sector protection. Depending on variant, sectors + * may default to locked state on power-on. + */ +#define S25FLXXXS_FLAG (S25FLXXXP_FLAG | \ + FLASH_FLAG_RESET | \ + FLASH_FLAG_DYB_LOCKING) + { "s25fl128s0", 0x012018, 0x0300, 256 * 1024, 64, S25FLXXXS_FLAG, 80, + stfsm_s25fl_config }, + { "s25fl128s1", 0x012018, 0x0301, 64 * 1024, 256, S25FLXXXS_FLAG, 80, + stfsm_s25fl_config }, + { "s25fl256s0", 0x010219, 0x4d00, 256 * 1024, 128, + S25FLXXXS_FLAG | FLASH_FLAG_32BIT_ADDR, 80, stfsm_s25fl_config }, + { "s25fl256s1", 0x010219, 0x4d01, 64 * 1024, 512, + S25FLXXXS_FLAG | FLASH_FLAG_32BIT_ADDR, 80, stfsm_s25fl_config }, + + /* Winbond -- w25x "blocks" are 64K, "sectors" are 4KiB */ +#define W25X_FLAG (FLASH_FLAG_READ_WRITE | \ + FLASH_FLAG_READ_FAST | \ + FLASH_FLAG_READ_1_1_2 | \ + FLASH_FLAG_WRITE_1_1_2) + { "w25x40", 0xef3013, 0, 64 * 1024, 8, W25X_FLAG, 75, NULL }, + { "w25x80", 0xef3014, 0, 64 * 1024, 16, W25X_FLAG, 75, NULL }, + { "w25x16", 0xef3015, 0, 64 * 1024, 32, W25X_FLAG, 75, NULL }, + { "w25x32", 0xef3016, 0, 64 * 1024, 64, W25X_FLAG, 75, NULL }, + { "w25x64", 0xef3017, 0, 64 * 1024, 128, W25X_FLAG, 75, NULL }, + + /* Winbond -- w25q "blocks" are 64K, "sectors" are 4KiB */ +#define W25Q_FLAG (FLASH_FLAG_READ_WRITE | \ + FLASH_FLAG_READ_FAST | \ + FLASH_FLAG_READ_1_1_2 | \ + FLASH_FLAG_READ_1_2_2 | \ + FLASH_FLAG_READ_1_1_4 | \ + FLASH_FLAG_READ_1_4_4 | \ + FLASH_FLAG_WRITE_1_1_4) + { "w25q80", 0xef4014, 0, 64 * 1024, 16, W25Q_FLAG, 80, + stfsm_w25q_config }, + { "w25q16", 0xef4015, 0, 64 * 1024, 32, W25Q_FLAG, 80, + stfsm_w25q_config }, + { "w25q32", 0xef4016, 0, 64 * 1024, 64, W25Q_FLAG, 80, + stfsm_w25q_config }, + { "w25q64", 0xef4017, 0, 64 * 1024, 128, W25Q_FLAG, 80, + stfsm_w25q_config }, + + /* Sentinel */ + { NULL, 0x000000, 0, 0, 0, 0, 0, NULL }, +}; + +/* + * FSM message sequence configurations: + * + * All configs are presented in order of preference + */ + +/* Default READ configurations, in order of preference */ +static struct seq_rw_config default_read_configs[] = { + {FLASH_FLAG_READ_1_4_4, SPINOR_OP_READ_1_4_4, 0, 4, 4, 0x00, 2, 4}, + {FLASH_FLAG_READ_1_1_4, SPINOR_OP_READ_1_1_4, 0, 1, 4, 0x00, 4, 0}, + {FLASH_FLAG_READ_1_2_2, SPINOR_OP_READ_1_2_2, 0, 2, 2, 0x00, 4, 0}, + {FLASH_FLAG_READ_1_1_2, SPINOR_OP_READ_1_1_2, 0, 1, 2, 0x00, 0, 8}, + {FLASH_FLAG_READ_FAST, SPINOR_OP_READ_FAST, 0, 1, 1, 0x00, 0, 8}, + {FLASH_FLAG_READ_WRITE, SPINOR_OP_READ, 0, 1, 1, 0x00, 0, 0}, + {0x00, 0, 0, 0, 0, 0x00, 0, 0}, +}; + +/* Default WRITE configurations */ +static struct seq_rw_config default_write_configs[] = { + {FLASH_FLAG_WRITE_1_4_4, SPINOR_OP_WRITE_1_4_4, 1, 4, 4, 0x00, 0, 0}, + {FLASH_FLAG_WRITE_1_1_4, SPINOR_OP_WRITE_1_1_4, 1, 1, 4, 0x00, 0, 0}, + {FLASH_FLAG_WRITE_1_2_2, SPINOR_OP_WRITE_1_2_2, 1, 2, 2, 0x00, 0, 0}, + {FLASH_FLAG_WRITE_1_1_2, SPINOR_OP_WRITE_1_1_2, 1, 1, 2, 0x00, 0, 0}, + {FLASH_FLAG_READ_WRITE, SPINOR_OP_WRITE, 1, 1, 1, 0x00, 0, 0}, + {0x00, 0, 0, 0, 0, 0x00, 0, 0}, +}; + +/* + * [N25Qxxx] Configuration + */ +#define N25Q_VCR_DUMMY_CYCLES(x) (((x) & 0xf) << 4) +#define N25Q_VCR_XIP_DISABLED ((uint8_t)0x1 << 3) +#define N25Q_VCR_WRAP_CONT 0x3 + +/* N25Q 3-byte Address READ configurations + * - 'FAST' variants configured for 8 dummy cycles. + * + * Note, the number of dummy cycles used for 'FAST' READ operations is + * configurable and would normally be tuned according to the READ command and + * operating frequency. However, this applies universally to all 'FAST' READ + * commands, including those used by the SPIBoot controller, and remains in + * force until the device is power-cycled. Since the SPIBoot controller is + * hard-wired to use 8 dummy cycles, we must configure the device to also use 8 + * cycles. + */ +static struct seq_rw_config n25q_read3_configs[] = { + {FLASH_FLAG_READ_1_4_4, SPINOR_OP_READ_1_4_4, 0, 4, 4, 0x00, 0, 8}, + {FLASH_FLAG_READ_1_1_4, SPINOR_OP_READ_1_1_4, 0, 1, 4, 0x00, 0, 8}, + {FLASH_FLAG_READ_1_2_2, SPINOR_OP_READ_1_2_2, 0, 2, 2, 0x00, 0, 8}, + {FLASH_FLAG_READ_1_1_2, SPINOR_OP_READ_1_1_2, 0, 1, 2, 0x00, 0, 8}, + {FLASH_FLAG_READ_FAST, SPINOR_OP_READ_FAST, 0, 1, 1, 0x00, 0, 8}, + {FLASH_FLAG_READ_WRITE, SPINOR_OP_READ, 0, 1, 1, 0x00, 0, 0}, + {0x00, 0, 0, 0, 0, 0x00, 0, 0}, +}; + +/* N25Q 4-byte Address READ configurations + * - use special 4-byte address READ commands (reduces overheads, and + * reduces risk of hitting watchdog reset issues). + * - 'FAST' variants configured for 8 dummy cycles (see note above.) + */ +static struct seq_rw_config n25q_read4_configs[] = { + {FLASH_FLAG_READ_1_4_4, SPINOR_OP_READ4_1_4_4, 0, 4, 4, 0x00, 0, 8}, + {FLASH_FLAG_READ_1_1_4, SPINOR_OP_READ4_1_1_4, 0, 1, 4, 0x00, 0, 8}, + {FLASH_FLAG_READ_1_2_2, SPINOR_OP_READ4_1_2_2, 0, 2, 2, 0x00, 0, 8}, + {FLASH_FLAG_READ_1_1_2, SPINOR_OP_READ4_1_1_2, 0, 1, 2, 0x00, 0, 8}, + {FLASH_FLAG_READ_FAST, SPINOR_OP_READ4_FAST, 0, 1, 1, 0x00, 0, 8}, + {FLASH_FLAG_READ_WRITE, SPINOR_OP_READ4, 0, 1, 1, 0x00, 0, 0}, + {0x00, 0, 0, 0, 0, 0x00, 0, 0}, +}; + +/* + * [MX25xxx] Configuration + */ +#define MX25_STATUS_QE (0x1 << 6) + +static int stfsm_mx25_en_32bit_addr_seq(struct stfsm_seq *seq) +{ + seq->seq_opc[0] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_EN4B) | + SEQ_OPC_CSDEASSERT); + + seq->seq[0] = STFSM_INST_CMD1; + seq->seq[1] = STFSM_INST_WAIT; + seq->seq[2] = STFSM_INST_STOP; + + seq->seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_ERASE | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ); + + return 0; +} + +/* + * [S25FLxxx] Configuration + */ +#define STFSM_S25FL_CONFIG_QE (0x1 << 1) + +/* + * S25FLxxxS devices provide three ways of supporting 32-bit addressing: Bank + * Register, Extended Address Modes, and a 32-bit address command set. The + * 32-bit address command set is used here, since it avoids any problems with + * entering a state that is incompatible with the SPIBoot Controller. + */ +static struct seq_rw_config stfsm_s25fl_read4_configs[] = { + {FLASH_FLAG_READ_1_4_4, SPINOR_OP_READ4_1_4_4, 0, 4, 4, 0x00, 2, 4}, + {FLASH_FLAG_READ_1_1_4, SPINOR_OP_READ4_1_1_4, 0, 1, 4, 0x00, 0, 8}, + {FLASH_FLAG_READ_1_2_2, SPINOR_OP_READ4_1_2_2, 0, 2, 2, 0x00, 4, 0}, + {FLASH_FLAG_READ_1_1_2, SPINOR_OP_READ4_1_1_2, 0, 1, 2, 0x00, 0, 8}, + {FLASH_FLAG_READ_FAST, SPINOR_OP_READ4_FAST, 0, 1, 1, 0x00, 0, 8}, + {FLASH_FLAG_READ_WRITE, SPINOR_OP_READ4, 0, 1, 1, 0x00, 0, 0}, + {0x00, 0, 0, 0, 0, 0x00, 0, 0}, +}; + +static struct seq_rw_config stfsm_s25fl_write4_configs[] = { + {FLASH_FLAG_WRITE_1_1_4, S25FL_CMD_WRITE4_1_1_4, 1, 1, 4, 0x00, 0, 0}, + {FLASH_FLAG_READ_WRITE, S25FL_CMD_WRITE4, 1, 1, 1, 0x00, 0, 0}, + {0x00, 0, 0, 0, 0, 0x00, 0, 0}, +}; + +/* + * [W25Qxxx] Configuration + */ +#define W25Q_STATUS_QE (0x1 << 1) + +static struct stfsm_seq stfsm_seq_read_jedec = { + .data_size = TRANSFER_SIZE(8), + .seq_opc[0] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_RDID)), + .seq = { + STFSM_INST_CMD1, + STFSM_INST_DATA_READ, + STFSM_INST_STOP, + }, + .seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ), +}; + +static struct stfsm_seq stfsm_seq_read_status_fifo = { + .data_size = TRANSFER_SIZE(4), + .seq_opc[0] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_RDSR)), + .seq = { + STFSM_INST_CMD1, + STFSM_INST_DATA_READ, + STFSM_INST_STOP, + }, + .seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ), +}; + +static struct stfsm_seq stfsm_seq_erase_sector = { + /* 'addr_cfg' configured during initialisation */ + .seq_opc = { + (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_WREN) | SEQ_OPC_CSDEASSERT), + + (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_SE)), + }, + .seq = { + STFSM_INST_CMD1, + STFSM_INST_CMD2, + STFSM_INST_ADD1, + STFSM_INST_ADD2, + STFSM_INST_STOP, + }, + .seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ), +}; + +static struct stfsm_seq stfsm_seq_erase_chip = { + .seq_opc = { + (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_WREN) | SEQ_OPC_CSDEASSERT), + + (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_CHIP_ERASE) | SEQ_OPC_CSDEASSERT), + }, + .seq = { + STFSM_INST_CMD1, + STFSM_INST_CMD2, + STFSM_INST_WAIT, + STFSM_INST_STOP, + }, + .seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_ERASE | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ), +}; + +static struct stfsm_seq stfsm_seq_write_status = { + .seq_opc[0] = (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_WREN) | SEQ_OPC_CSDEASSERT), + .seq_opc[1] = (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_WRSR)), + .seq = { + STFSM_INST_CMD1, + STFSM_INST_CMD2, + STFSM_INST_STA_WR1, + STFSM_INST_STOP, + }, + .seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ), +}; + +static int stfsm_n25q_en_32bit_addr_seq(struct stfsm_seq *seq) +{ + seq->seq_opc[0] = (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_EN4B)); + seq->seq_opc[1] = (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_WREN) | + SEQ_OPC_CSDEASSERT); + + seq->seq[0] = STFSM_INST_CMD2; + seq->seq[1] = STFSM_INST_CMD1; + seq->seq[2] = STFSM_INST_WAIT; + seq->seq[3] = STFSM_INST_STOP; + + seq->seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_ERASE | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ); + + return 0; +} + +static inline int stfsm_is_idle(struct stfsm *fsm) +{ + return readl(fsm->base + SPI_FAST_SEQ_STA) & 0x10; +} + +static inline uint32_t stfsm_fifo_available(struct stfsm *fsm) +{ + return (readl(fsm->base + SPI_FAST_SEQ_STA) >> 5) & 0x7f; +} + +static void stfsm_clear_fifo(struct stfsm *fsm) +{ + uint32_t avail; + + for (;;) { + avail = stfsm_fifo_available(fsm); + if (!avail) + break; + + while (avail) { + readl(fsm->base + SPI_FAST_SEQ_DATA_REG); + avail--; + } + } +} + +static inline void stfsm_load_seq(struct stfsm *fsm, + const struct stfsm_seq *seq) +{ + void __iomem *dst = fsm->base + SPI_FAST_SEQ_TRANSFER_SIZE; + const uint32_t *src = (const uint32_t *)seq; + int words = sizeof(*seq) / sizeof(*src); + + BUG_ON(!stfsm_is_idle(fsm)); + + while (words--) { + writel(*src, dst); + src++; + dst += 4; + } +} + +static void stfsm_wait_seq(struct stfsm *fsm) +{ + unsigned long deadline; + int timeout = 0; + + deadline = jiffies + msecs_to_jiffies(STFSM_MAX_WAIT_SEQ_MS); + + while (!timeout) { + if (time_after_eq(jiffies, deadline)) + timeout = 1; + + if (stfsm_is_idle(fsm)) + return; + + cond_resched(); + } + + dev_err(fsm->dev, "timeout on sequence completion\n"); +} + +static void stfsm_read_fifo(struct stfsm *fsm, uint32_t *buf, uint32_t size) +{ + uint32_t remaining = size >> 2; + uint32_t avail; + uint32_t words; + + dev_dbg(fsm->dev, "Reading %d bytes from FIFO\n", size); + + BUG_ON((((uintptr_t)buf) & 0x3) || (size & 0x3)); + + while (remaining) { + for (;;) { + avail = stfsm_fifo_available(fsm); + if (avail) + break; + udelay(1); + } + words = min(avail, remaining); + remaining -= words; + + readsl(fsm->base + SPI_FAST_SEQ_DATA_REG, buf, words); + buf += words; + } +} + +static int stfsm_write_fifo(struct stfsm *fsm, const uint32_t *buf, + uint32_t size) +{ + uint32_t words = size >> 2; + + dev_dbg(fsm->dev, "writing %d bytes to FIFO\n", size); + + BUG_ON((((uintptr_t)buf) & 0x3) || (size & 0x3)); + + writesl(fsm->base + SPI_FAST_SEQ_DATA_REG, buf, words); + + return size; +} + +static int stfsm_enter_32bit_addr(struct stfsm *fsm, int enter) +{ + struct stfsm_seq *seq = &fsm->stfsm_seq_en_32bit_addr; + uint32_t cmd = enter ? SPINOR_OP_EN4B : SPINOR_OP_EX4B; + + seq->seq_opc[0] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(cmd) | + SEQ_OPC_CSDEASSERT); + + stfsm_load_seq(fsm, seq); + + stfsm_wait_seq(fsm); + + return 0; +} + +static uint8_t stfsm_wait_busy(struct stfsm *fsm) +{ + struct stfsm_seq *seq = &stfsm_seq_read_status_fifo; + unsigned long deadline; + uint32_t status; + int timeout = 0; + + /* Use RDRS1 */ + seq->seq_opc[0] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_RDSR)); + + /* Load read_status sequence */ + stfsm_load_seq(fsm, seq); + + /* + * Repeat until busy bit is deasserted, or timeout, or error (S25FLxxxS) + */ + deadline = jiffies + FLASH_MAX_BUSY_WAIT; + while (!timeout) { + if (time_after_eq(jiffies, deadline)) + timeout = 1; + + stfsm_wait_seq(fsm); + + stfsm_read_fifo(fsm, &status, 4); + + if ((status & FLASH_STATUS_BUSY) == 0) + return 0; + + if ((fsm->configuration & CFG_S25FL_CHECK_ERROR_FLAGS) && + ((status & S25FL_STATUS_P_ERR) || + (status & S25FL_STATUS_E_ERR))) + return (uint8_t)(status & 0xff); + + if (!timeout) + /* Restart */ + writel(seq->seq_cfg, fsm->base + SPI_FAST_SEQ_CFG); + + cond_resched(); + } + + dev_err(fsm->dev, "timeout on wait_busy\n"); + + return FLASH_STATUS_TIMEOUT; +} + +static int stfsm_read_status(struct stfsm *fsm, uint8_t cmd, + uint8_t *data, int bytes) +{ + struct stfsm_seq *seq = &stfsm_seq_read_status_fifo; + uint32_t tmp; + uint8_t *t = (uint8_t *)&tmp; + int i; + + dev_dbg(fsm->dev, "read 'status' register [0x%02x], %d byte(s)\n", + cmd, bytes); + + BUG_ON(bytes != 1 && bytes != 2); + + seq->seq_opc[0] = (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(cmd)), + + stfsm_load_seq(fsm, seq); + + stfsm_read_fifo(fsm, &tmp, 4); + + for (i = 0; i < bytes; i++) + data[i] = t[i]; + + stfsm_wait_seq(fsm); + + return 0; +} + +static int stfsm_write_status(struct stfsm *fsm, uint8_t cmd, + uint16_t data, int bytes, int wait_busy) +{ + struct stfsm_seq *seq = &stfsm_seq_write_status; + + dev_dbg(fsm->dev, + "write 'status' register [0x%02x], %d byte(s), 0x%04x\n" + " %s wait-busy\n", cmd, bytes, data, wait_busy ? "with" : "no"); + + BUG_ON(bytes != 1 && bytes != 2); + + seq->seq_opc[1] = (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(cmd)); + + seq->status = (uint32_t)data | STA_PADS_1 | STA_CSDEASSERT; + seq->seq[2] = (bytes == 1) ? STFSM_INST_STA_WR1 : STFSM_INST_STA_WR1_2; + + stfsm_load_seq(fsm, seq); + + stfsm_wait_seq(fsm); + + if (wait_busy) + stfsm_wait_busy(fsm); + + return 0; +} + +/* + * SoC reset on 'boot-from-spi' systems + * + * Certain modes of operation cause the Flash device to enter a particular state + * for a period of time (e.g. 'Erase Sector', 'Quad Enable', and 'Enter 32-bit + * Addr' commands). On boot-from-spi systems, it is important to consider what + * happens if a warm reset occurs during this period. The SPIBoot controller + * assumes that Flash device is in its default reset state, 24-bit address mode, + * and ready to accept commands. This can be achieved using some form of + * on-board logic/controller to force a device POR in response to a SoC-level + * reset or by making use of the device reset signal if available (limited + * number of devices only). + * + * Failure to take such precautions can cause problems following a warm reset. + * For some operations (e.g. ERASE), there is little that can be done. For + * other modes of operation (e.g. 32-bit addressing), options are often + * available that can help minimise the window in which a reset could cause a + * problem. + * + */ +static bool stfsm_can_handle_soc_reset(struct stfsm *fsm) +{ + /* Reset signal is available on the board and supported by the device */ + if (fsm->reset_signal && fsm->info->flags & FLASH_FLAG_RESET) + return true; + + /* Board-level logic forces a power-on-reset */ + if (fsm->reset_por) + return true; + + /* Reset is not properly handled and may result in failure to reboot */ + return false; +} + +/* Configure 'addr_cfg' according to addressing mode */ +static void stfsm_prepare_erasesec_seq(struct stfsm *fsm, + struct stfsm_seq *seq) +{ + int addr1_cycles = fsm->info->flags & FLASH_FLAG_32BIT_ADDR ? 16 : 8; + + seq->addr_cfg = (ADR_CFG_CYCLES_ADD1(addr1_cycles) | + ADR_CFG_PADS_1_ADD1 | + ADR_CFG_CYCLES_ADD2(16) | + ADR_CFG_PADS_1_ADD2 | + ADR_CFG_CSDEASSERT_ADD2); +} + +/* Search for preferred configuration based on available flags */ +static struct seq_rw_config * +stfsm_search_seq_rw_configs(struct stfsm *fsm, + struct seq_rw_config cfgs[]) +{ + struct seq_rw_config *config; + int flags = fsm->info->flags; + + for (config = cfgs; config->cmd != 0; config++) + if ((config->flags & flags) == config->flags) + return config; + + return NULL; +} + +/* Prepare a READ/WRITE sequence according to configuration parameters */ +static void stfsm_prepare_rw_seq(struct stfsm *fsm, + struct stfsm_seq *seq, + struct seq_rw_config *cfg) +{ + int addr1_cycles, addr2_cycles; + int i = 0; + + memset(seq, 0, sizeof(*seq)); + + /* Add READ/WRITE OPC */ + seq->seq_opc[i++] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(cfg->cmd)); + + /* Add WREN OPC for a WRITE sequence */ + if (cfg->write) + seq->seq_opc[i++] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_WREN) | + SEQ_OPC_CSDEASSERT); + + /* Address configuration (24 or 32-bit addresses) */ + addr1_cycles = (fsm->info->flags & FLASH_FLAG_32BIT_ADDR) ? 16 : 8; + addr1_cycles /= cfg->addr_pads; + addr2_cycles = 16 / cfg->addr_pads; + seq->addr_cfg = ((addr1_cycles & 0x3f) << 0 | /* ADD1 cycles */ + (cfg->addr_pads - 1) << 6 | /* ADD1 pads */ + (addr2_cycles & 0x3f) << 16 | /* ADD2 cycles */ + ((cfg->addr_pads - 1) << 22)); /* ADD2 pads */ + + /* Data/Sequence configuration */ + seq->seq_cfg = ((cfg->data_pads - 1) << 16 | + SEQ_CFG_STARTSEQ | + SEQ_CFG_CSDEASSERT); + if (!cfg->write) + seq->seq_cfg |= SEQ_CFG_READNOTWRITE; + + /* Mode configuration (no. of pads taken from addr cfg) */ + seq->mode = ((cfg->mode_data & 0xff) << 0 | /* data */ + (cfg->mode_cycles & 0x3f) << 16 | /* cycles */ + (cfg->addr_pads - 1) << 22); /* pads */ + + /* Dummy configuration (no. of pads taken from addr cfg) */ + seq->dummy = ((cfg->dummy_cycles & 0x3f) << 16 | /* cycles */ + (cfg->addr_pads - 1) << 22); /* pads */ + + + /* Instruction sequence */ + i = 0; + if (cfg->write) + seq->seq[i++] = STFSM_INST_CMD2; + + seq->seq[i++] = STFSM_INST_CMD1; + + seq->seq[i++] = STFSM_INST_ADD1; + seq->seq[i++] = STFSM_INST_ADD2; + + if (cfg->mode_cycles) + seq->seq[i++] = STFSM_INST_MODE; + + if (cfg->dummy_cycles) + seq->seq[i++] = STFSM_INST_DUMMY; + + seq->seq[i++] = + cfg->write ? STFSM_INST_DATA_WRITE : STFSM_INST_DATA_READ; + seq->seq[i++] = STFSM_INST_STOP; +} + +static int stfsm_search_prepare_rw_seq(struct stfsm *fsm, + struct stfsm_seq *seq, + struct seq_rw_config *cfgs) +{ + struct seq_rw_config *config; + + config = stfsm_search_seq_rw_configs(fsm, cfgs); + if (!config) { + dev_err(fsm->dev, "failed to find suitable config\n"); + return -EINVAL; + } + + stfsm_prepare_rw_seq(fsm, seq, config); + + return 0; +} + +/* Prepare a READ/WRITE/ERASE 'default' sequences */ +static int stfsm_prepare_rwe_seqs_default(struct stfsm *fsm) +{ + uint32_t flags = fsm->info->flags; + int ret; + + /* Configure 'READ' sequence */ + ret = stfsm_search_prepare_rw_seq(fsm, &fsm->stfsm_seq_read, + default_read_configs); + if (ret) { + dev_err(fsm->dev, + "failed to prep READ sequence with flags [0x%08x]\n", + flags); + return ret; + } + + /* Configure 'WRITE' sequence */ + ret = stfsm_search_prepare_rw_seq(fsm, &fsm->stfsm_seq_write, + default_write_configs); + if (ret) { + dev_err(fsm->dev, + "failed to prep WRITE sequence with flags [0x%08x]\n", + flags); + return ret; + } + + /* Configure 'ERASE_SECTOR' sequence */ + stfsm_prepare_erasesec_seq(fsm, &stfsm_seq_erase_sector); + + return 0; +} + +static int stfsm_mx25_config(struct stfsm *fsm) +{ + uint32_t flags = fsm->info->flags; + uint32_t data_pads; + uint8_t sta; + int ret; + bool soc_reset; + + /* + * Use default READ/WRITE sequences + */ + ret = stfsm_prepare_rwe_seqs_default(fsm); + if (ret) + return ret; + + /* + * Configure 32-bit Address Support + */ + if (flags & FLASH_FLAG_32BIT_ADDR) { + /* Configure 'enter_32bitaddr' FSM sequence */ + stfsm_mx25_en_32bit_addr_seq(&fsm->stfsm_seq_en_32bit_addr); + + soc_reset = stfsm_can_handle_soc_reset(fsm); + if (soc_reset || !fsm->booted_from_spi) + /* If we can handle SoC resets, we enable 32-bit address + * mode pervasively */ + stfsm_enter_32bit_addr(fsm, 1); + + else + /* Else, enable/disable 32-bit addressing before/after + * each operation */ + fsm->configuration = (CFG_READ_TOGGLE_32BIT_ADDR | + CFG_WRITE_TOGGLE_32BIT_ADDR | + CFG_ERASESEC_TOGGLE_32BIT_ADDR); + } + + /* Check status of 'QE' bit, update if required. */ + stfsm_read_status(fsm, SPINOR_OP_RDSR, &sta, 1); + data_pads = ((fsm->stfsm_seq_read.seq_cfg >> 16) & 0x3) + 1; + if (data_pads == 4) { + if (!(sta & MX25_STATUS_QE)) { + /* Set 'QE' */ + sta |= MX25_STATUS_QE; + + stfsm_write_status(fsm, SPINOR_OP_WRSR, sta, 1, 1); + } + } else { + if (sta & MX25_STATUS_QE) { + /* Clear 'QE' */ + sta &= ~MX25_STATUS_QE; + + stfsm_write_status(fsm, SPINOR_OP_WRSR, sta, 1, 1); + } + } + + return 0; +} + +static int stfsm_n25q_config(struct stfsm *fsm) +{ + uint32_t flags = fsm->info->flags; + uint8_t vcr; + int ret = 0; + bool soc_reset; + + /* Configure 'READ' sequence */ + if (flags & FLASH_FLAG_32BIT_ADDR) + ret = stfsm_search_prepare_rw_seq(fsm, &fsm->stfsm_seq_read, + n25q_read4_configs); + else + ret = stfsm_search_prepare_rw_seq(fsm, &fsm->stfsm_seq_read, + n25q_read3_configs); + if (ret) { + dev_err(fsm->dev, + "failed to prepare READ sequence with flags [0x%08x]\n", + flags); + return ret; + } + + /* Configure 'WRITE' sequence (default configs) */ + ret = stfsm_search_prepare_rw_seq(fsm, &fsm->stfsm_seq_write, + default_write_configs); + if (ret) { + dev_err(fsm->dev, + "preparing WRITE sequence using flags [0x%08x] failed\n", + flags); + return ret; + } + + /* * Configure 'ERASE_SECTOR' sequence */ + stfsm_prepare_erasesec_seq(fsm, &stfsm_seq_erase_sector); + + /* Configure 32-bit address support */ + if (flags & FLASH_FLAG_32BIT_ADDR) { + stfsm_n25q_en_32bit_addr_seq(&fsm->stfsm_seq_en_32bit_addr); + + soc_reset = stfsm_can_handle_soc_reset(fsm); + if (soc_reset || !fsm->booted_from_spi) { + /* + * If we can handle SoC resets, we enable 32-bit + * address mode pervasively + */ + stfsm_enter_32bit_addr(fsm, 1); + } else { + /* + * If not, enable/disable for WRITE and ERASE + * operations (READ uses special commands) + */ + fsm->configuration = (CFG_WRITE_TOGGLE_32BIT_ADDR | + CFG_ERASESEC_TOGGLE_32BIT_ADDR); + } + } + + /* + * Configure device to use 8 dummy cycles + */ + vcr = (N25Q_VCR_DUMMY_CYCLES(8) | N25Q_VCR_XIP_DISABLED | + N25Q_VCR_WRAP_CONT); + stfsm_write_status(fsm, N25Q_CMD_WRVCR, vcr, 1, 0); + + return 0; +} + +static void stfsm_s25fl_prepare_erasesec_seq_32(struct stfsm_seq *seq) +{ + seq->seq_opc[1] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(S25FL_CMD_SE4)); + + seq->addr_cfg = (ADR_CFG_CYCLES_ADD1(16) | + ADR_CFG_PADS_1_ADD1 | + ADR_CFG_CYCLES_ADD2(16) | + ADR_CFG_PADS_1_ADD2 | + ADR_CFG_CSDEASSERT_ADD2); +} + +static void stfsm_s25fl_read_dyb(struct stfsm *fsm, uint32_t offs, uint8_t *dby) +{ + uint32_t tmp; + struct stfsm_seq seq = { + .data_size = TRANSFER_SIZE(4), + .seq_opc[0] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(S25FL_CMD_DYBRD)), + .addr_cfg = (ADR_CFG_CYCLES_ADD1(16) | + ADR_CFG_PADS_1_ADD1 | + ADR_CFG_CYCLES_ADD2(16) | + ADR_CFG_PADS_1_ADD2), + .addr1 = (offs >> 16) & 0xffff, + .addr2 = offs & 0xffff, + .seq = { + STFSM_INST_CMD1, + STFSM_INST_ADD1, + STFSM_INST_ADD2, + STFSM_INST_DATA_READ, + STFSM_INST_STOP, + }, + .seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ), + }; + + stfsm_load_seq(fsm, &seq); + + stfsm_read_fifo(fsm, &tmp, 4); + + *dby = (uint8_t)(tmp >> 24); + + stfsm_wait_seq(fsm); +} + +static void stfsm_s25fl_write_dyb(struct stfsm *fsm, uint32_t offs, uint8_t dby) +{ + struct stfsm_seq seq = { + .seq_opc[0] = (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_WREN) | + SEQ_OPC_CSDEASSERT), + .seq_opc[1] = (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(S25FL_CMD_DYBWR)), + .addr_cfg = (ADR_CFG_CYCLES_ADD1(16) | + ADR_CFG_PADS_1_ADD1 | + ADR_CFG_CYCLES_ADD2(16) | + ADR_CFG_PADS_1_ADD2), + .status = (uint32_t)dby | STA_PADS_1 | STA_CSDEASSERT, + .addr1 = (offs >> 16) & 0xffff, + .addr2 = offs & 0xffff, + .seq = { + STFSM_INST_CMD1, + STFSM_INST_CMD2, + STFSM_INST_ADD1, + STFSM_INST_ADD2, + STFSM_INST_STA_WR1, + STFSM_INST_STOP, + }, + .seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ), + }; + + stfsm_load_seq(fsm, &seq); + stfsm_wait_seq(fsm); + + stfsm_wait_busy(fsm); +} + +static int stfsm_s25fl_clear_status_reg(struct stfsm *fsm) +{ + struct stfsm_seq seq = { + .seq_opc[0] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(S25FL_CMD_CLSR) | + SEQ_OPC_CSDEASSERT), + .seq_opc[1] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_WRDI) | + SEQ_OPC_CSDEASSERT), + .seq = { + STFSM_INST_CMD1, + STFSM_INST_CMD2, + STFSM_INST_WAIT, + STFSM_INST_STOP, + }, + .seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_ERASE | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ), + }; + + stfsm_load_seq(fsm, &seq); + + stfsm_wait_seq(fsm); + + return 0; +} + +static int stfsm_s25fl_config(struct stfsm *fsm) +{ + struct flash_info *info = fsm->info; + uint32_t flags = info->flags; + uint32_t data_pads; + uint32_t offs; + uint16_t sta_wr; + uint8_t sr1, cr1, dyb; + int update_sr = 0; + int ret; + + if (flags & FLASH_FLAG_32BIT_ADDR) { + /* + * Prepare Read/Write/Erase sequences according to S25FLxxx + * 32-bit address command set + */ + ret = stfsm_search_prepare_rw_seq(fsm, &fsm->stfsm_seq_read, + stfsm_s25fl_read4_configs); + if (ret) + return ret; + + ret = stfsm_search_prepare_rw_seq(fsm, &fsm->stfsm_seq_write, + stfsm_s25fl_write4_configs); + if (ret) + return ret; + + stfsm_s25fl_prepare_erasesec_seq_32(&stfsm_seq_erase_sector); + + } else { + /* Use default configurations for 24-bit addressing */ + ret = stfsm_prepare_rwe_seqs_default(fsm); + if (ret) + return ret; + } + + /* + * For devices that support 'DYB' sector locking, check lock status and + * unlock sectors if necessary (some variants power-on with sectors + * locked by default) + */ + if (flags & FLASH_FLAG_DYB_LOCKING) { + offs = 0; + for (offs = 0; offs < info->sector_size * info->n_sectors;) { + stfsm_s25fl_read_dyb(fsm, offs, &dyb); + if (dyb == 0x00) + stfsm_s25fl_write_dyb(fsm, offs, 0xff); + + /* Handle bottom/top 4KiB parameter sectors */ + if ((offs < info->sector_size * 2) || + (offs >= (info->sector_size - info->n_sectors * 4))) + offs += 0x1000; + else + offs += 0x10000; + } + } + + /* Check status of 'QE' bit, update if required. */ + stfsm_read_status(fsm, SPINOR_OP_RDSR2, &cr1, 1); + data_pads = ((fsm->stfsm_seq_read.seq_cfg >> 16) & 0x3) + 1; + if (data_pads == 4) { + if (!(cr1 & STFSM_S25FL_CONFIG_QE)) { + /* Set 'QE' */ + cr1 |= STFSM_S25FL_CONFIG_QE; + + update_sr = 1; + } + } else { + if (cr1 & STFSM_S25FL_CONFIG_QE) { + /* Clear 'QE' */ + cr1 &= ~STFSM_S25FL_CONFIG_QE; + + update_sr = 1; + } + } + if (update_sr) { + stfsm_read_status(fsm, SPINOR_OP_RDSR, &sr1, 1); + sta_wr = ((uint16_t)cr1 << 8) | sr1; + stfsm_write_status(fsm, SPINOR_OP_WRSR, sta_wr, 2, 1); + } + + /* + * S25FLxxx devices support Program and Error error flags. + * Configure driver to check flags and clear if necessary. + */ + fsm->configuration |= CFG_S25FL_CHECK_ERROR_FLAGS; + + return 0; +} + +static int stfsm_w25q_config(struct stfsm *fsm) +{ + uint32_t data_pads; + uint8_t sr1, sr2; + uint16_t sr_wr; + int update_sr = 0; + int ret; + + ret = stfsm_prepare_rwe_seqs_default(fsm); + if (ret) + return ret; + + /* Check status of 'QE' bit, update if required. */ + stfsm_read_status(fsm, SPINOR_OP_RDSR2, &sr2, 1); + data_pads = ((fsm->stfsm_seq_read.seq_cfg >> 16) & 0x3) + 1; + if (data_pads == 4) { + if (!(sr2 & W25Q_STATUS_QE)) { + /* Set 'QE' */ + sr2 |= W25Q_STATUS_QE; + update_sr = 1; + } + } else { + if (sr2 & W25Q_STATUS_QE) { + /* Clear 'QE' */ + sr2 &= ~W25Q_STATUS_QE; + update_sr = 1; + } + } + if (update_sr) { + /* Write status register */ + stfsm_read_status(fsm, SPINOR_OP_RDSR, &sr1, 1); + sr_wr = ((uint16_t)sr2 << 8) | sr1; + stfsm_write_status(fsm, SPINOR_OP_WRSR, sr_wr, 2, 1); + } + + return 0; +} + +static int stfsm_read(struct stfsm *fsm, uint8_t *buf, uint32_t size, + uint32_t offset) +{ + struct stfsm_seq *seq = &fsm->stfsm_seq_read; + uint32_t data_pads; + uint32_t read_mask; + uint32_t size_ub; + uint32_t size_lb; + uint32_t size_mop; + uint32_t tmp[4]; + uint32_t page_buf[FLASH_PAGESIZE_32]; + uint8_t *p; + + dev_dbg(fsm->dev, "reading %d bytes from 0x%08x\n", size, offset); + + /* Enter 32-bit address mode, if required */ + if (fsm->configuration & CFG_READ_TOGGLE_32BIT_ADDR) + stfsm_enter_32bit_addr(fsm, 1); + + /* Must read in multiples of 32 cycles (or 32*pads/8 Bytes) */ + data_pads = ((seq->seq_cfg >> 16) & 0x3) + 1; + read_mask = (data_pads << 2) - 1; + + /* Handle non-aligned buf */ + p = ((uintptr_t)buf & 0x3) ? (uint8_t *)page_buf : buf; + + /* Handle non-aligned size */ + size_ub = (size + read_mask) & ~read_mask; + size_lb = size & ~read_mask; + size_mop = size & read_mask; + + seq->data_size = TRANSFER_SIZE(size_ub); + seq->addr1 = (offset >> 16) & 0xffff; + seq->addr2 = offset & 0xffff; + + stfsm_load_seq(fsm, seq); + + if (size_lb) + stfsm_read_fifo(fsm, (uint32_t *)p, size_lb); + + if (size_mop) { + stfsm_read_fifo(fsm, tmp, read_mask + 1); + memcpy(p + size_lb, &tmp, size_mop); + } + + /* Handle non-aligned buf */ + if ((uintptr_t)buf & 0x3) + memcpy(buf, page_buf, size); + + /* Wait for sequence to finish */ + stfsm_wait_seq(fsm); + + stfsm_clear_fifo(fsm); + + /* Exit 32-bit address mode, if required */ + if (fsm->configuration & CFG_READ_TOGGLE_32BIT_ADDR) + stfsm_enter_32bit_addr(fsm, 0); + + return 0; +} + +static int stfsm_write(struct stfsm *fsm, const uint8_t *buf, + uint32_t size, uint32_t offset) +{ + struct stfsm_seq *seq = &fsm->stfsm_seq_write; + uint32_t data_pads; + uint32_t write_mask; + uint32_t size_ub; + uint32_t size_lb; + uint32_t size_mop; + uint32_t tmp[4]; + uint32_t page_buf[FLASH_PAGESIZE_32]; + uint8_t *t = (uint8_t *)&tmp; + const uint8_t *p; + int ret; + int i; + + dev_dbg(fsm->dev, "writing %d bytes to 0x%08x\n", size, offset); + + /* Enter 32-bit address mode, if required */ + if (fsm->configuration & CFG_WRITE_TOGGLE_32BIT_ADDR) + stfsm_enter_32bit_addr(fsm, 1); + + /* Must write in multiples of 32 cycles (or 32*pads/8 bytes) */ + data_pads = ((seq->seq_cfg >> 16) & 0x3) + 1; + write_mask = (data_pads << 2) - 1; + + /* Handle non-aligned buf */ + if ((uintptr_t)buf & 0x3) { + memcpy(page_buf, buf, size); + p = (uint8_t *)page_buf; + } else { + p = buf; + } + + /* Handle non-aligned size */ + size_ub = (size + write_mask) & ~write_mask; + size_lb = size & ~write_mask; + size_mop = size & write_mask; + + seq->data_size = TRANSFER_SIZE(size_ub); + seq->addr1 = (offset >> 16) & 0xffff; + seq->addr2 = offset & 0xffff; + + /* Need to set FIFO to write mode, before writing data to FIFO (see + * GNBvb79594) + */ + writel(0x00040000, fsm->base + SPI_FAST_SEQ_CFG); + + /* + * Before writing data to the FIFO, apply a small delay to allow a + * potential change of FIFO direction to complete. + */ + if (fsm->fifo_dir_delay == 0) + readl(fsm->base + SPI_FAST_SEQ_CFG); + else + udelay(fsm->fifo_dir_delay); + + + /* Write data to FIFO, before starting sequence (see GNBvd79593) */ + if (size_lb) { + stfsm_write_fifo(fsm, (uint32_t *)p, size_lb); + p += size_lb; + } + + /* Handle non-aligned size */ + if (size_mop) { + memset(t, 0xff, write_mask + 1); /* fill with 0xff's */ + for (i = 0; i < size_mop; i++) + t[i] = *p++; + + stfsm_write_fifo(fsm, tmp, write_mask + 1); + } + + /* Start sequence */ + stfsm_load_seq(fsm, seq); + + /* Wait for sequence to finish */ + stfsm_wait_seq(fsm); + + /* Wait for completion */ + ret = stfsm_wait_busy(fsm); + if (ret && fsm->configuration & CFG_S25FL_CHECK_ERROR_FLAGS) + stfsm_s25fl_clear_status_reg(fsm); + + /* Exit 32-bit address mode, if required */ + if (fsm->configuration & CFG_WRITE_TOGGLE_32BIT_ADDR) + stfsm_enter_32bit_addr(fsm, 0); + + return 0; +} + +/* + * Read an address range from the flash chip. The address range + * may be any size provided it is within the physical boundaries. + */ +static int stfsm_mtd_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct stfsm *fsm = dev_get_drvdata(mtd->dev.parent); + uint32_t bytes; + + dev_dbg(fsm->dev, "%s from 0x%08x, len %zd\n", + __func__, (u32)from, len); + + mutex_lock(&fsm->lock); + + while (len > 0) { + bytes = min_t(size_t, len, FLASH_PAGESIZE); + + stfsm_read(fsm, buf, bytes, from); + + buf += bytes; + from += bytes; + len -= bytes; + + *retlen += bytes; + } + + mutex_unlock(&fsm->lock); + + return 0; +} + +static int stfsm_erase_sector(struct stfsm *fsm, uint32_t offset) +{ + struct stfsm_seq *seq = &stfsm_seq_erase_sector; + int ret; + + dev_dbg(fsm->dev, "erasing sector at 0x%08x\n", offset); + + /* Enter 32-bit address mode, if required */ + if (fsm->configuration & CFG_ERASESEC_TOGGLE_32BIT_ADDR) + stfsm_enter_32bit_addr(fsm, 1); + + seq->addr1 = (offset >> 16) & 0xffff; + seq->addr2 = offset & 0xffff; + + stfsm_load_seq(fsm, seq); + + stfsm_wait_seq(fsm); + + /* Wait for completion */ + ret = stfsm_wait_busy(fsm); + if (ret && fsm->configuration & CFG_S25FL_CHECK_ERROR_FLAGS) + stfsm_s25fl_clear_status_reg(fsm); + + /* Exit 32-bit address mode, if required */ + if (fsm->configuration & CFG_ERASESEC_TOGGLE_32BIT_ADDR) + stfsm_enter_32bit_addr(fsm, 0); + + return ret; +} + +static int stfsm_erase_chip(struct stfsm *fsm) +{ + const struct stfsm_seq *seq = &stfsm_seq_erase_chip; + + dev_dbg(fsm->dev, "erasing chip\n"); + + stfsm_load_seq(fsm, seq); + + stfsm_wait_seq(fsm); + + return stfsm_wait_busy(fsm); +} + +/* + * Write an address range to the flash chip. Data must be written in + * FLASH_PAGESIZE chunks. The address range may be any size provided + * it is within the physical boundaries. + */ +static int stfsm_mtd_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct stfsm *fsm = dev_get_drvdata(mtd->dev.parent); + + u32 page_offs; + u32 bytes; + uint8_t *b = (uint8_t *)buf; + int ret = 0; + + dev_dbg(fsm->dev, "%s to 0x%08x, len %zd\n", __func__, (u32)to, len); + + /* Offset within page */ + page_offs = to % FLASH_PAGESIZE; + + mutex_lock(&fsm->lock); + + while (len) { + /* Write up to page boundary */ + bytes = min_t(size_t, FLASH_PAGESIZE - page_offs, len); + + ret = stfsm_write(fsm, b, bytes, to); + if (ret) + goto out1; + + b += bytes; + len -= bytes; + to += bytes; + + /* We are now page-aligned */ + page_offs = 0; + + *retlen += bytes; + + } + +out1: + mutex_unlock(&fsm->lock); + + return ret; +} + +/* + * Erase an address range on the flash chip. The address range may extend + * one or more erase sectors. Return an error is there is a problem erasing. + */ +static int stfsm_mtd_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + struct stfsm *fsm = dev_get_drvdata(mtd->dev.parent); + u32 addr, len; + int ret; + + dev_dbg(fsm->dev, "%s at 0x%llx, len %lld\n", __func__, + (long long)instr->addr, (long long)instr->len); + + addr = instr->addr; + len = instr->len; + + mutex_lock(&fsm->lock); + + /* Whole-chip erase? */ + if (len == mtd->size) { + ret = stfsm_erase_chip(fsm); + if (ret) + goto out1; + } else { + while (len) { + ret = stfsm_erase_sector(fsm, addr); + if (ret) + goto out1; + + addr += mtd->erasesize; + len -= mtd->erasesize; + } + } + + mutex_unlock(&fsm->lock); + + instr->state = MTD_ERASE_DONE; + mtd_erase_callback(instr); + + return 0; + +out1: + instr->state = MTD_ERASE_FAILED; + mutex_unlock(&fsm->lock); + + return ret; +} + +static void stfsm_read_jedec(struct stfsm *fsm, uint8_t *jedec) +{ + const struct stfsm_seq *seq = &stfsm_seq_read_jedec; + uint32_t tmp[2]; + + stfsm_load_seq(fsm, seq); + + stfsm_read_fifo(fsm, tmp, 8); + + memcpy(jedec, tmp, 5); + + stfsm_wait_seq(fsm); +} + +static struct flash_info *stfsm_jedec_probe(struct stfsm *fsm) +{ + struct flash_info *info; + u16 ext_jedec; + u32 jedec; + u8 id[5]; + + stfsm_read_jedec(fsm, id); + + jedec = id[0] << 16 | id[1] << 8 | id[2]; + /* + * JEDEC also defines an optional "extended device information" + * string for after vendor-specific data, after the three bytes + * we use here. Supporting some chips might require using it. + */ + ext_jedec = id[3] << 8 | id[4]; + + dev_dbg(fsm->dev, "JEDEC = 0x%08x [%02x %02x %02x %02x %02x]\n", + jedec, id[0], id[1], id[2], id[3], id[4]); + + for (info = flash_types; info->name; info++) { + if (info->jedec_id == jedec) { + if (info->ext_id && info->ext_id != ext_jedec) + continue; + return info; + } + } + dev_err(fsm->dev, "Unrecognized JEDEC id %06x\n", jedec); + + return NULL; +} + +static int stfsm_set_mode(struct stfsm *fsm, uint32_t mode) +{ + int ret, timeout = 10; + + /* Wait for controller to accept mode change */ + while (--timeout) { + ret = readl(fsm->base + SPI_STA_MODE_CHANGE); + if (ret & 0x1) + break; + udelay(1); + } + + if (!timeout) + return -EBUSY; + + writel(mode, fsm->base + SPI_MODESELECT); + + return 0; +} + +static void stfsm_set_freq(struct stfsm *fsm, uint32_t spi_freq) +{ + uint32_t emi_freq; + uint32_t clk_div; + + /* TODO: Make this dynamic */ + emi_freq = STFSM_DEFAULT_EMI_FREQ; + + /* + * Calculate clk_div - values between 2 and 128 + * Multiple of 2, rounded up + */ + clk_div = 2 * DIV_ROUND_UP(emi_freq, 2 * spi_freq); + if (clk_div < 2) + clk_div = 2; + else if (clk_div > 128) + clk_div = 128; + + /* + * Determine a suitable delay for the IP to complete a change of + * direction of the FIFO. The required delay is related to the clock + * divider used. The following heuristics are based on empirical tests, + * using a 100MHz EMI clock. + */ + if (clk_div <= 4) + fsm->fifo_dir_delay = 0; + else if (clk_div <= 10) + fsm->fifo_dir_delay = 1; + else + fsm->fifo_dir_delay = DIV_ROUND_UP(clk_div, 10); + + dev_dbg(fsm->dev, "emi_clk = %uHZ, spi_freq = %uHZ, clk_div = %u\n", + emi_freq, spi_freq, clk_div); + + writel(clk_div, fsm->base + SPI_CLOCKDIV); +} + +static int stfsm_init(struct stfsm *fsm) +{ + int ret; + + /* Perform a soft reset of the FSM controller */ + writel(SEQ_CFG_SWRESET, fsm->base + SPI_FAST_SEQ_CFG); + udelay(1); + writel(0, fsm->base + SPI_FAST_SEQ_CFG); + + /* Set clock to 'safe' frequency initially */ + stfsm_set_freq(fsm, STFSM_FLASH_SAFE_FREQ); + + /* Switch to FSM */ + ret = stfsm_set_mode(fsm, SPI_MODESELECT_FSM); + if (ret) + return ret; + + /* Set timing parameters */ + writel(SPI_CFG_DEVICE_ST | + SPI_CFG_DEFAULT_MIN_CS_HIGH | + SPI_CFG_DEFAULT_CS_SETUPHOLD | + SPI_CFG_DEFAULT_DATA_HOLD, + fsm->base + SPI_CONFIGDATA); + writel(STFSM_DEFAULT_WR_TIME, fsm->base + SPI_STATUS_WR_TIME_REG); + + /* + * Set the FSM 'WAIT' delay to the minimum workable value. Note, for + * our purposes, the WAIT instruction is used purely to achieve + * "sequence validity" rather than actually implement a delay. + */ + writel(0x00000001, fsm->base + SPI_PROGRAM_ERASE_TIME); + + /* Clear FIFO, just in case */ + stfsm_clear_fifo(fsm); + + return 0; +} + +static void stfsm_fetch_platform_configs(struct platform_device *pdev) +{ + struct stfsm *fsm = platform_get_drvdata(pdev); + struct device_node *np = pdev->dev.of_node; + struct regmap *regmap; + uint32_t boot_device_reg; + uint32_t boot_device_spi; + uint32_t boot_device; /* Value we read from *boot_device_reg */ + int ret; + + /* Booting from SPI NOR Flash is the default */ + fsm->booted_from_spi = true; + + regmap = syscon_regmap_lookup_by_phandle(np, "st,syscfg"); + if (IS_ERR(regmap)) + goto boot_device_fail; + + fsm->reset_signal = of_property_read_bool(np, "st,reset-signal"); + + fsm->reset_por = of_property_read_bool(np, "st,reset-por"); + + /* Where in the syscon the boot device information lives */ + ret = of_property_read_u32(np, "st,boot-device-reg", &boot_device_reg); + if (ret) + goto boot_device_fail; + + /* Boot device value when booted from SPI NOR */ + ret = of_property_read_u32(np, "st,boot-device-spi", &boot_device_spi); + if (ret) + goto boot_device_fail; + + ret = regmap_read(regmap, boot_device_reg, &boot_device); + if (ret) + goto boot_device_fail; + + if (boot_device != boot_device_spi) + fsm->booted_from_spi = false; + + return; + +boot_device_fail: + dev_warn(&pdev->dev, + "failed to fetch boot device, assuming boot from SPI\n"); +} + +static int stfsm_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct mtd_part_parser_data ppdata; + struct flash_info *info; + struct resource *res; + struct stfsm *fsm; + int ret; + + if (!np) { + dev_err(&pdev->dev, "No DT found\n"); + return -EINVAL; + } + ppdata.of_node = np; + + fsm = devm_kzalloc(&pdev->dev, sizeof(*fsm), GFP_KERNEL); + if (!fsm) + return -ENOMEM; + + fsm->dev = &pdev->dev; + + platform_set_drvdata(pdev, fsm); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Resource not found\n"); + return -ENODEV; + } + + fsm->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(fsm->base)) { + dev_err(&pdev->dev, + "Failed to reserve memory region %pR\n", res); + return PTR_ERR(fsm->base); + } + + mutex_init(&fsm->lock); + + ret = stfsm_init(fsm); + if (ret) { + dev_err(&pdev->dev, "Failed to initialise FSM Controller\n"); + return ret; + } + + stfsm_fetch_platform_configs(pdev); + + /* Detect SPI FLASH device */ + info = stfsm_jedec_probe(fsm); + if (!info) + return -ENODEV; + fsm->info = info; + + /* Use device size to determine address width */ + if (info->sector_size * info->n_sectors > 0x1000000) + info->flags |= FLASH_FLAG_32BIT_ADDR; + + /* + * Configure READ/WRITE/ERASE sequences according to platform and + * device flags. + */ + if (info->config) { + ret = info->config(fsm); + if (ret) + return ret; + } else { + ret = stfsm_prepare_rwe_seqs_default(fsm); + if (ret) + return ret; + } + + fsm->mtd.name = info->name; + fsm->mtd.dev.parent = &pdev->dev; + fsm->mtd.type = MTD_NORFLASH; + fsm->mtd.writesize = 4; + fsm->mtd.writebufsize = fsm->mtd.writesize; + fsm->mtd.flags = MTD_CAP_NORFLASH; + fsm->mtd.size = info->sector_size * info->n_sectors; + fsm->mtd.erasesize = info->sector_size; + + fsm->mtd._read = stfsm_mtd_read; + fsm->mtd._write = stfsm_mtd_write; + fsm->mtd._erase = stfsm_mtd_erase; + + dev_info(&pdev->dev, + "Found serial flash device: %s\n" + " size = %llx (%lldMiB) erasesize = 0x%08x (%uKiB)\n", + info->name, + (long long)fsm->mtd.size, (long long)(fsm->mtd.size >> 20), + fsm->mtd.erasesize, (fsm->mtd.erasesize >> 10)); + + return mtd_device_parse_register(&fsm->mtd, NULL, &ppdata, NULL, 0); +} + +static int stfsm_remove(struct platform_device *pdev) +{ + struct stfsm *fsm = platform_get_drvdata(pdev); + + return mtd_device_unregister(&fsm->mtd); +} + +static const struct of_device_id stfsm_match[] = { + { .compatible = "st,spi-fsm", }, + {}, +}; +MODULE_DEVICE_TABLE(of, stfsm_match); + +static struct platform_driver stfsm_driver = { + .probe = stfsm_probe, + .remove = stfsm_remove, + .driver = { + .name = "st-spi-fsm", + .owner = THIS_MODULE, + .of_match_table = stfsm_match, + }, +}; +module_platform_driver(stfsm_driver); + +MODULE_AUTHOR("Angus Clark <angus.clark@st.com>"); +MODULE_DESCRIPTION("ST SPI FSM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/ftl.c b/drivers/mtd/ftl.c index 8a878b34eca..19d637266fc 100644 --- a/drivers/mtd/ftl.c +++ b/drivers/mtd/ftl.c @@ -1,5 +1,4 @@ /* This version ported to the Linux-MTD system by dwmw2@infradead.org - * $Id: ftl.c,v 1.59 2005/11/29 14:48:31 gleixner Exp $ * * Fixes: Arnaldo Carvalho de Melo <acme@conectiva.com.br> * - fixes some leaks on failure in build_maps and ftl_notify_add, cleanups @@ -27,7 +26,7 @@ The initial developer of the original code is David A. Hinds <dahinds@users.sourceforge.net>. Portions created by David A. Hinds - are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + are Copyright © 1999 David A. Hinds. All Rights Reserved. Alternatively, the contents of this file may be used under the terms of the GNU General Public License version 2 (the "GPL"), in @@ -52,7 +51,7 @@ Use of the FTL format for non-PCMCIA applications may be an infringement of these patents. For additional information, - contact M-Systems (http://www.m-sys.com) directly. + contact M-Systems directly. M-Systems since acquired by Sandisk. ======================================================================*/ #include <linux/mtd/blktrans.h> @@ -61,7 +60,6 @@ /*#define PSYCHO_DEBUG */ #include <linux/kernel.h> -#include <linux/sched.h> #include <linux/ptrace.h> #include <linux/slab.h> #include <linux/string.h> @@ -111,34 +109,28 @@ module_param(shuffle_freq, int, 0); /* Each memory region corresponds to a minor device */ typedef struct partition_t { struct mtd_blktrans_dev mbd; - u_int32_t state; - u_int32_t *VirtualBlockMap; - u_int32_t *VirtualPageMap; - u_int32_t FreeTotal; + uint32_t state; + uint32_t *VirtualBlockMap; + uint32_t *VirtualPageMap; + uint32_t FreeTotal; struct eun_info_t { - u_int32_t Offset; - u_int32_t EraseCount; - u_int32_t Free; - u_int32_t Deleted; + uint32_t Offset; + uint32_t EraseCount; + uint32_t Free; + uint32_t Deleted; } *EUNInfo; struct xfer_info_t { - u_int32_t Offset; - u_int32_t EraseCount; - u_int16_t state; + uint32_t Offset; + uint32_t EraseCount; + uint16_t state; } *XferInfo; - u_int16_t bam_index; - u_int32_t *bam_cache; - u_int16_t DataUnits; - u_int32_t BlocksPerUnit; + uint16_t bam_index; + uint32_t *bam_cache; + uint16_t DataUnits; + uint32_t BlocksPerUnit; erase_unit_header_t header; -#if 0 - region_info_t region; - memory_handle_t handle; -#endif } partition_t; -void ftl_freepart(partition_t *part); - /* Partition state flags */ #define FTL_FORMATTED 0x01 @@ -176,8 +168,8 @@ static int scan_header(partition_t *part) (offset + sizeof(header)) < max_offset; offset += part->mbd.mtd->erasesize ? : 0x2000) { - err = part->mbd.mtd->read(part->mbd.mtd, offset, sizeof(header), &ret, - (unsigned char *)&header); + err = mtd_read(part->mbd.mtd, offset, sizeof(header), &ret, + (unsigned char *)&header); if (err) return err; @@ -207,8 +199,8 @@ static int scan_header(partition_t *part) static int build_maps(partition_t *part) { erase_unit_header_t header; - u_int16_t xvalid, xtrans, i; - u_int blocks, j; + uint16_t xvalid, xtrans, i; + unsigned blocks, j; int hdr_ok, ret = -1; ssize_t retval; loff_t offset; @@ -232,8 +224,8 @@ static int build_maps(partition_t *part) for (i = 0; i < le16_to_cpu(part->header.NumEraseUnits); i++) { offset = ((i + le16_to_cpu(part->header.FirstPhysicalEUN)) << part->header.EraseUnitSize); - ret = part->mbd.mtd->read(part->mbd.mtd, offset, sizeof(header), &retval, - (unsigned char *)&header); + ret = mtd_read(part->mbd.mtd, offset, sizeof(header), &retval, + (unsigned char *)&header); if (ret) goto out_XferInfo; @@ -277,14 +269,14 @@ static int build_maps(partition_t *part) /* Set up virtual page map */ blocks = le32_to_cpu(header.FormattedSize) >> header.BlockSize; - part->VirtualBlockMap = vmalloc(blocks * sizeof(u_int32_t)); + part->VirtualBlockMap = vmalloc(blocks * sizeof(uint32_t)); if (!part->VirtualBlockMap) goto out_XferInfo; - memset(part->VirtualBlockMap, 0xff, blocks * sizeof(u_int32_t)); + memset(part->VirtualBlockMap, 0xff, blocks * sizeof(uint32_t)); part->BlocksPerUnit = (1 << header.EraseUnitSize) >> header.BlockSize; - part->bam_cache = kmalloc(part->BlocksPerUnit * sizeof(u_int32_t), + part->bam_cache = kmalloc(part->BlocksPerUnit * sizeof(uint32_t), GFP_KERNEL); if (!part->bam_cache) goto out_VirtualBlockMap; @@ -297,9 +289,9 @@ static int build_maps(partition_t *part) part->EUNInfo[i].Deleted = 0; offset = part->EUNInfo[i].Offset + le32_to_cpu(header.BAMOffset); - ret = part->mbd.mtd->read(part->mbd.mtd, offset, - part->BlocksPerUnit * sizeof(u_int32_t), &retval, - (unsigned char *)part->bam_cache); + ret = mtd_read(part->mbd.mtd, offset, + part->BlocksPerUnit * sizeof(uint32_t), &retval, + (unsigned char *)part->bam_cache); if (ret) goto out_bam_cache; @@ -340,14 +332,14 @@ out: ======================================================================*/ static int erase_xfer(partition_t *part, - u_int16_t xfernum) + uint16_t xfernum) { int ret; struct xfer_info_t *xfer; struct erase_info *erase; xfer = &part->XferInfo[xfernum]; - DEBUG(1, "ftl_cs: erasing xfer unit at 0x%x\n", xfer->Offset); + pr_debug("ftl_cs: erasing xfer unit at 0x%x\n", xfer->Offset); xfer->state = XFER_ERASING; /* Is there a free erase slot? Always in MTD. */ @@ -363,7 +355,7 @@ static int erase_xfer(partition_t *part, erase->len = 1 << part->header.EraseUnitSize; erase->priv = (u_long)part; - ret = part->mbd.mtd->erase(part->mbd.mtd, erase); + ret = mtd_erase(part->mbd.mtd, erase); if (!ret) xfer->EraseCount++; @@ -416,38 +408,38 @@ static int prepare_xfer(partition_t *part, int i) erase_unit_header_t header; struct xfer_info_t *xfer; int nbam, ret; - u_int32_t ctl; + uint32_t ctl; ssize_t retlen; loff_t offset; xfer = &part->XferInfo[i]; xfer->state = XFER_FAILED; - DEBUG(1, "ftl_cs: preparing xfer unit at 0x%x\n", xfer->Offset); + pr_debug("ftl_cs: preparing xfer unit at 0x%x\n", xfer->Offset); /* Write the transfer unit header */ header = part->header; header.LogicalEUN = cpu_to_le16(0xffff); header.EraseCount = cpu_to_le32(xfer->EraseCount); - ret = part->mbd.mtd->write(part->mbd.mtd, xfer->Offset, sizeof(header), - &retlen, (u_char *)&header); + ret = mtd_write(part->mbd.mtd, xfer->Offset, sizeof(header), &retlen, + (u_char *)&header); if (ret) { return ret; } /* Write the BAM stub */ - nbam = (part->BlocksPerUnit * sizeof(u_int32_t) + + nbam = (part->BlocksPerUnit * sizeof(uint32_t) + le32_to_cpu(part->header.BAMOffset) + SECTOR_SIZE - 1) / SECTOR_SIZE; offset = xfer->Offset + le32_to_cpu(part->header.BAMOffset); ctl = cpu_to_le32(BLOCK_CONTROL); - for (i = 0; i < nbam; i++, offset += sizeof(u_int32_t)) { + for (i = 0; i < nbam; i++, offset += sizeof(uint32_t)) { - ret = part->mbd.mtd->write(part->mbd.mtd, offset, sizeof(u_int32_t), - &retlen, (u_char *)&ctl); + ret = mtd_write(part->mbd.mtd, offset, sizeof(uint32_t), &retlen, + (u_char *)&ctl); if (ret) return ret; @@ -469,22 +461,22 @@ static int prepare_xfer(partition_t *part, int i) ======================================================================*/ -static int copy_erase_unit(partition_t *part, u_int16_t srcunit, - u_int16_t xferunit) +static int copy_erase_unit(partition_t *part, uint16_t srcunit, + uint16_t xferunit) { u_char buf[SECTOR_SIZE]; struct eun_info_t *eun; struct xfer_info_t *xfer; - u_int32_t src, dest, free, i; - u_int16_t unit; + uint32_t src, dest, free, i; + uint16_t unit; int ret; ssize_t retlen; loff_t offset; - u_int16_t srcunitswap = cpu_to_le16(srcunit); + uint16_t srcunitswap = cpu_to_le16(srcunit); eun = &part->EUNInfo[srcunit]; xfer = &part->XferInfo[xferunit]; - DEBUG(2, "ftl_cs: copying block 0x%x to 0x%x\n", + pr_debug("ftl_cs: copying block 0x%x to 0x%x\n", eun->Offset, xfer->Offset); @@ -493,9 +485,9 @@ static int copy_erase_unit(partition_t *part, u_int16_t srcunit, offset = eun->Offset + le32_to_cpu(part->header.BAMOffset); - ret = part->mbd.mtd->read(part->mbd.mtd, offset, - part->BlocksPerUnit * sizeof(u_int32_t), - &retlen, (u_char *) (part->bam_cache)); + ret = mtd_read(part->mbd.mtd, offset, + part->BlocksPerUnit * sizeof(uint32_t), &retlen, + (u_char *)(part->bam_cache)); /* mark the cache bad, in case we get an error later */ part->bam_index = 0xffff; @@ -511,8 +503,8 @@ static int copy_erase_unit(partition_t *part, u_int16_t srcunit, offset = xfer->Offset + 20; /* Bad! */ unit = cpu_to_le16(0x7fff); - ret = part->mbd.mtd->write(part->mbd.mtd, offset, sizeof(u_int16_t), - &retlen, (u_char *) &unit); + ret = mtd_write(part->mbd.mtd, offset, sizeof(uint16_t), &retlen, + (u_char *)&unit); if (ret) { printk( KERN_WARNING "ftl: Failed to write back to BAM cache in copy_erase_unit()!\n"); @@ -531,16 +523,16 @@ static int copy_erase_unit(partition_t *part, u_int16_t srcunit, break; case BLOCK_DATA: case BLOCK_REPLACEMENT: - ret = part->mbd.mtd->read(part->mbd.mtd, src, SECTOR_SIZE, - &retlen, (u_char *) buf); + ret = mtd_read(part->mbd.mtd, src, SECTOR_SIZE, &retlen, + (u_char *)buf); if (ret) { printk(KERN_WARNING "ftl: Error reading old xfer unit in copy_erase_unit\n"); return ret; } - ret = part->mbd.mtd->write(part->mbd.mtd, dest, SECTOR_SIZE, - &retlen, (u_char *) buf); + ret = mtd_write(part->mbd.mtd, dest, SECTOR_SIZE, &retlen, + (u_char *)buf); if (ret) { printk(KERN_WARNING "ftl: Error writing new xfer unit in copy_erase_unit\n"); return ret; @@ -558,9 +550,11 @@ static int copy_erase_unit(partition_t *part, u_int16_t srcunit, } /* Write the BAM to the transfer unit */ - ret = part->mbd.mtd->write(part->mbd.mtd, xfer->Offset + le32_to_cpu(part->header.BAMOffset), - part->BlocksPerUnit * sizeof(int32_t), &retlen, - (u_char *)part->bam_cache); + ret = mtd_write(part->mbd.mtd, + xfer->Offset + le32_to_cpu(part->header.BAMOffset), + part->BlocksPerUnit * sizeof(int32_t), + &retlen, + (u_char *)part->bam_cache); if (ret) { printk( KERN_WARNING "ftl: Error writing BAM in copy_erase_unit\n"); return ret; @@ -568,8 +562,8 @@ static int copy_erase_unit(partition_t *part, u_int16_t srcunit, /* All clear? Then update the LogicalEUN again */ - ret = part->mbd.mtd->write(part->mbd.mtd, xfer->Offset + 20, sizeof(u_int16_t), - &retlen, (u_char *)&srcunitswap); + ret = mtd_write(part->mbd.mtd, xfer->Offset + 20, sizeof(uint16_t), + &retlen, (u_char *)&srcunitswap); if (ret) { printk(KERN_WARNING "ftl: Error writing new LogicalEUN in copy_erase_unit\n"); @@ -606,19 +600,19 @@ static int copy_erase_unit(partition_t *part, u_int16_t srcunit, unit with the fewest erases, and usually pick the data unit with the most deleted blocks. But with a small probability, pick the oldest data unit instead. This means that we generally postpone - the next reclaimation as long as possible, but shuffle static + the next reclamation as long as possible, but shuffle static stuff around a bit for wear leveling. ======================================================================*/ static int reclaim_block(partition_t *part) { - u_int16_t i, eun, xfer; - u_int32_t best; + uint16_t i, eun, xfer; + uint32_t best; int queued, ret; - DEBUG(0, "ftl_cs: reclaiming space...\n"); - DEBUG(3, "NumTransferUnits == %x\n", part->header.NumTransferUnits); + pr_debug("ftl_cs: reclaiming space...\n"); + pr_debug("NumTransferUnits == %x\n", part->header.NumTransferUnits); /* Pick the least erased transfer unit */ best = 0xffffffff; xfer = 0xffff; do { @@ -626,22 +620,22 @@ static int reclaim_block(partition_t *part) for (i = 0; i < part->header.NumTransferUnits; i++) { int n=0; if (part->XferInfo[i].state == XFER_UNKNOWN) { - DEBUG(3,"XferInfo[%d].state == XFER_UNKNOWN\n",i); + pr_debug("XferInfo[%d].state == XFER_UNKNOWN\n",i); n=1; erase_xfer(part, i); } if (part->XferInfo[i].state == XFER_ERASING) { - DEBUG(3,"XferInfo[%d].state == XFER_ERASING\n",i); + pr_debug("XferInfo[%d].state == XFER_ERASING\n",i); n=1; queued = 1; } else if (part->XferInfo[i].state == XFER_ERASED) { - DEBUG(3,"XferInfo[%d].state == XFER_ERASED\n",i); + pr_debug("XferInfo[%d].state == XFER_ERASED\n",i); n=1; prepare_xfer(part, i); } if (part->XferInfo[i].state == XFER_PREPARED) { - DEBUG(3,"XferInfo[%d].state == XFER_PREPARED\n",i); + pr_debug("XferInfo[%d].state == XFER_PREPARED\n",i); n=1; if (part->XferInfo[i].EraseCount <= best) { best = part->XferInfo[i].EraseCount; @@ -649,22 +643,21 @@ static int reclaim_block(partition_t *part) } } if (!n) - DEBUG(3,"XferInfo[%d].state == %x\n",i, part->XferInfo[i].state); + pr_debug("XferInfo[%d].state == %x\n",i, part->XferInfo[i].state); } if (xfer == 0xffff) { if (queued) { - DEBUG(1, "ftl_cs: waiting for transfer " + pr_debug("ftl_cs: waiting for transfer " "unit to be prepared...\n"); - if (part->mbd.mtd->sync) - part->mbd.mtd->sync(part->mbd.mtd); + mtd_sync(part->mbd.mtd); } else { static int ne = 0; if (++ne < 5) printk(KERN_NOTICE "ftl_cs: reclaim failed: no " "suitable transfer units!\n"); else - DEBUG(1, "ftl_cs: reclaim failed: no " + pr_debug("ftl_cs: reclaim failed: no " "suitable transfer units!\n"); return -EIO; @@ -674,7 +667,7 @@ static int reclaim_block(partition_t *part) eun = 0; if ((jiffies % shuffle_freq) == 0) { - DEBUG(1, "ftl_cs: recycling freshest block...\n"); + pr_debug("ftl_cs: recycling freshest block...\n"); best = 0xffffffff; for (i = 0; i < part->DataUnits; i++) if (part->EUNInfo[i].EraseCount <= best) { @@ -694,7 +687,7 @@ static int reclaim_block(partition_t *part) printk(KERN_NOTICE "ftl_cs: reclaim failed: " "no free blocks!\n"); else - DEBUG(1,"ftl_cs: reclaim failed: " + pr_debug("ftl_cs: reclaim failed: " "no free blocks!\n"); return -EIO; @@ -731,10 +724,10 @@ static void dump_lists(partition_t *part) } #endif -static u_int32_t find_free(partition_t *part) +static uint32_t find_free(partition_t *part) { - u_int16_t stop, eun; - u_int32_t blk; + uint16_t stop, eun; + uint32_t blk; size_t retlen; int ret; @@ -755,10 +748,11 @@ static u_int32_t find_free(partition_t *part) /* Invalidate cache */ part->bam_index = 0xffff; - ret = part->mbd.mtd->read(part->mbd.mtd, - part->EUNInfo[eun].Offset + le32_to_cpu(part->header.BAMOffset), - part->BlocksPerUnit * sizeof(u_int32_t), - &retlen, (u_char *) (part->bam_cache)); + ret = mtd_read(part->mbd.mtd, + part->EUNInfo[eun].Offset + le32_to_cpu(part->header.BAMOffset), + part->BlocksPerUnit * sizeof(uint32_t), + &retlen, + (u_char *)(part->bam_cache)); if (ret) { printk(KERN_WARNING"ftl: Error reading BAM in find_free\n"); @@ -779,7 +773,7 @@ static u_int32_t find_free(partition_t *part) printk(KERN_NOTICE "ftl_cs: bad free list!\n"); return 0; } - DEBUG(2, "ftl_cs: found free block at %d in %d\n", blk, eun); + pr_debug("ftl_cs: found free block at %d in %d\n", blk, eun); return blk; } /* find_free */ @@ -794,12 +788,12 @@ static u_int32_t find_free(partition_t *part) static int ftl_read(partition_t *part, caddr_t buffer, u_long sector, u_long nblocks) { - u_int32_t log_addr, bsize; + uint32_t log_addr, bsize; u_long i; int ret; size_t offset, retlen; - DEBUG(2, "ftl_cs: ftl_read(0x%p, 0x%lx, %ld)\n", + pr_debug("ftl_cs: ftl_read(0x%p, 0x%lx, %ld)\n", part, sector, nblocks); if (!(part->state & FTL_FORMATTED)) { printk(KERN_NOTICE "ftl_cs: bad partition\n"); @@ -818,8 +812,8 @@ static int ftl_read(partition_t *part, caddr_t buffer, else { offset = (part->EUNInfo[log_addr / bsize].Offset + (log_addr % bsize)); - ret = part->mbd.mtd->read(part->mbd.mtd, offset, SECTOR_SIZE, - &retlen, (u_char *) buffer); + ret = mtd_read(part->mbd.mtd, offset, SECTOR_SIZE, &retlen, + (u_char *)buffer); if (ret) { printk(KERN_WARNING "Error reading MTD device in ftl_read()\n"); @@ -837,28 +831,28 @@ static int ftl_read(partition_t *part, caddr_t buffer, ======================================================================*/ -static int set_bam_entry(partition_t *part, u_int32_t log_addr, - u_int32_t virt_addr) +static int set_bam_entry(partition_t *part, uint32_t log_addr, + uint32_t virt_addr) { - u_int32_t bsize, blk, le_virt_addr; + uint32_t bsize, blk, le_virt_addr; #ifdef PSYCHO_DEBUG - u_int32_t old_addr; + uint32_t old_addr; #endif - u_int16_t eun; + uint16_t eun; int ret; size_t retlen, offset; - DEBUG(2, "ftl_cs: set_bam_entry(0x%p, 0x%x, 0x%x)\n", + pr_debug("ftl_cs: set_bam_entry(0x%p, 0x%x, 0x%x)\n", part, log_addr, virt_addr); bsize = 1 << part->header.EraseUnitSize; eun = log_addr / bsize; blk = (log_addr % bsize) / SECTOR_SIZE; - offset = (part->EUNInfo[eun].Offset + blk * sizeof(u_int32_t) + + offset = (part->EUNInfo[eun].Offset + blk * sizeof(uint32_t) + le32_to_cpu(part->header.BAMOffset)); #ifdef PSYCHO_DEBUG - ret = part->mbd.mtd->read(part->mbd.mtd, offset, sizeof(u_int32_t), - &retlen, (u_char *)&old_addr); + ret = mtd_read(part->mbd.mtd, offset, sizeof(uint32_t), &retlen, + (u_char *)&old_addr); if (ret) { printk(KERN_WARNING"ftl: Error reading old_addr in set_bam_entry: %d\n",ret); return ret; @@ -894,8 +888,8 @@ static int set_bam_entry(partition_t *part, u_int32_t log_addr, #endif part->bam_cache[blk] = le_virt_addr; } - ret = part->mbd.mtd->write(part->mbd.mtd, offset, sizeof(u_int32_t), - &retlen, (u_char *)&le_virt_addr); + ret = mtd_write(part->mbd.mtd, offset, sizeof(uint32_t), &retlen, + (u_char *)&le_virt_addr); if (ret) { printk(KERN_NOTICE "ftl_cs: set_bam_entry() failed!\n"); @@ -908,12 +902,12 @@ static int set_bam_entry(partition_t *part, u_int32_t log_addr, static int ftl_write(partition_t *part, caddr_t buffer, u_long sector, u_long nblocks) { - u_int32_t bsize, log_addr, virt_addr, old_addr, blk; + uint32_t bsize, log_addr, virt_addr, old_addr, blk; u_long i; int ret; size_t retlen, offset; - DEBUG(2, "ftl_cs: ftl_write(0x%p, %ld, %ld)\n", + pr_debug("ftl_cs: ftl_write(0x%p, %ld, %ld)\n", part, sector, nblocks); if (!(part->state & FTL_FORMATTED)) { printk(KERN_NOTICE "ftl_cs: bad partition\n"); @@ -954,8 +948,7 @@ static int ftl_write(partition_t *part, caddr_t buffer, part->EUNInfo[part->bam_index].Deleted++; offset = (part->EUNInfo[part->bam_index].Offset + blk * SECTOR_SIZE); - ret = part->mbd.mtd->write(part->mbd.mtd, offset, SECTOR_SIZE, &retlen, - buffer); + ret = mtd_write(part->mbd.mtd, offset, SECTOR_SIZE, &retlen, buffer); if (ret) { printk(KERN_NOTICE "ftl_cs: block write failed!\n"); @@ -1013,9 +1006,32 @@ static int ftl_writesect(struct mtd_blktrans_dev *dev, return ftl_write((void *)dev, buf, block, 1); } +static int ftl_discardsect(struct mtd_blktrans_dev *dev, + unsigned long sector, unsigned nr_sects) +{ + partition_t *part = (void *)dev; + uint32_t bsize = 1 << part->header.EraseUnitSize; + + pr_debug("FTL erase sector %ld for %d sectors\n", + sector, nr_sects); + + while (nr_sects) { + uint32_t old_addr = part->VirtualBlockMap[sector]; + if (old_addr != 0xffffffff) { + part->VirtualBlockMap[sector] = 0xffffffff; + part->EUNInfo[old_addr/bsize].Deleted++; + if (set_bam_entry(part, old_addr, 0)) + return -EIO; + } + nr_sects--; + sector++; + } + + return 0; +} /*====================================================================*/ -void ftl_freepart(partition_t *part) +static void ftl_freepart(partition_t *part) { vfree(part->VirtualBlockMap); part->VirtualBlockMap = NULL; @@ -1033,7 +1049,7 @@ static void ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) { partition_t *partition; - partition = kmalloc(sizeof(partition_t), GFP_KERNEL); + partition = kzalloc(sizeof(partition_t), GFP_KERNEL); if (!partition) { printk(KERN_WARNING "No memory to scan for FTL on %s\n", @@ -1041,8 +1057,6 @@ static void ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) return; } - memset(partition, 0, sizeof(partition_t)); - partition->mbd.mtd = mtd; if ((scan_header(partition) == 0) && @@ -1054,7 +1068,7 @@ static void ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) le32_to_cpu(partition->header.FormattedSize) >> 10); #endif partition->mbd.size = le32_to_cpu(partition->header.FormattedSize) >> 9; - partition->mbd.blksize = SECTOR_SIZE; + partition->mbd.tr = tr; partition->mbd.devnum = -1; if (!add_mtd_blktrans_dev((void *)partition)) @@ -1069,25 +1083,24 @@ static void ftl_remove_dev(struct mtd_blktrans_dev *dev) { del_mtd_blktrans_dev(dev); ftl_freepart((partition_t *)dev); - kfree(dev); } -struct mtd_blktrans_ops ftl_tr = { +static struct mtd_blktrans_ops ftl_tr = { .name = "ftl", .major = FTL_MAJOR, .part_bits = PART_BITS, + .blksize = SECTOR_SIZE, .readsect = ftl_readsect, .writesect = ftl_writesect, + .discard = ftl_discardsect, .getgeo = ftl_getgeo, .add_mtd = ftl_add_mtd, .remove_dev = ftl_remove_dev, .owner = THIS_MODULE, }; -static int init_ftl(void) +static int __init init_ftl(void) { - DEBUG(0, "$Id: ftl.c,v 1.59 2005/11/29 14:48:31 gleixner Exp $\n"); - return register_mtd_blktrans(&ftl_tr); } diff --git a/drivers/mtd/inftlcore.c b/drivers/mtd/inftlcore.c index 4116535805f..b66b541877f 100644 --- a/drivers/mtd/inftlcore.c +++ b/drivers/mtd/inftlcore.c @@ -1,13 +1,11 @@ /* * inftlcore.c -- Linux driver for Inverse Flash Translation Layer (INFTL) * - * (C) Copyright 2002, Greg Ungerer (gerg@snapgear.com) + * Copyright © 2002, Greg Ungerer (gerg@snapgear.com) * * Based heavily on the nftlcore.c code which is: - * (c) 1999 Machine Vision Holdings, Inc. - * Author: David Woodhouse <dwmw2@infradead.org> - * - * $Id: inftlcore.c,v 1.19 2005/11/07 11:14:20 gleixner Exp $ + * Copyright © 1999 Machine Vision Holdings, Inc. + * Copyright © 1999 David Woodhouse <dwmw2@infradead.org> * * 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 @@ -52,32 +50,29 @@ static void inftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) struct INFTLrecord *inftl; unsigned long temp; - if (mtd->type != MTD_NANDFLASH) + if (!mtd_type_is_nand(mtd) || mtd->size > UINT_MAX) return; /* OK, this is moderately ugly. But probably safe. Alternatives? */ if (memcmp(mtd->name, "DiskOnChip", 10)) return; - if (!mtd->block_isbad) { + if (!mtd->_block_isbad) { printk(KERN_ERR "INFTL no longer supports the old DiskOnChip drivers loaded via docprobe.\n" "Please use the new diskonchip driver under the NAND subsystem.\n"); return; } - DEBUG(MTD_DEBUG_LEVEL3, "INFTL: add_mtd for %s\n", mtd->name); + pr_debug("INFTL: add_mtd for %s\n", mtd->name); - inftl = kmalloc(sizeof(*inftl), GFP_KERNEL); + inftl = kzalloc(sizeof(*inftl), GFP_KERNEL); - if (!inftl) { - printk(KERN_WARNING "INFTL: Out of memory for data structures\n"); + if (!inftl) return; - } - memset(inftl, 0, sizeof(*inftl)); inftl->mbd.mtd = mtd; inftl->mbd.devnum = -1; - inftl->mbd.blksize = 512; + inftl->mbd.tr = tr; if (INFTL_mount(inftl) < 0) { @@ -136,13 +131,12 @@ static void inftl_remove_dev(struct mtd_blktrans_dev *dev) { struct INFTLrecord *inftl = (void *)dev; - DEBUG(MTD_DEBUG_LEVEL3, "INFTL: remove_dev (i=%d)\n", dev->devnum); + pr_debug("INFTL: remove_dev (i=%d)\n", dev->devnum); del_mtd_blktrans_dev(dev); kfree(inftl->PUtable); kfree(inftl->VUtable); - kfree(inftl); } /* @@ -158,15 +152,14 @@ int inftl_read_oob(struct mtd_info *mtd, loff_t offs, size_t len, struct mtd_oob_ops ops; int res; - ops.mode = MTD_OOB_PLACE; + ops.mode = MTD_OPS_PLACE_OOB; ops.ooboffs = offs & (mtd->writesize - 1); ops.ooblen = len; ops.oobbuf = buf; ops.datbuf = NULL; - ops.len = len; - res = mtd->read_oob(mtd, offs & ~(mtd->writesize - 1), &ops); - *retlen = ops.retlen; + res = mtd_read_oob(mtd, offs & ~(mtd->writesize - 1), &ops); + *retlen = ops.oobretlen; return res; } @@ -179,15 +172,14 @@ int inftl_write_oob(struct mtd_info *mtd, loff_t offs, size_t len, struct mtd_oob_ops ops; int res; - ops.mode = MTD_OOB_PLACE; + ops.mode = MTD_OPS_PLACE_OOB; ops.ooboffs = offs & (mtd->writesize - 1); ops.ooblen = len; ops.oobbuf = buf; ops.datbuf = NULL; - ops.len = len; - res = mtd->write_oob(mtd, offs & ~(mtd->writesize - 1), &ops); - *retlen = ops.retlen; + res = mtd_write_oob(mtd, offs & ~(mtd->writesize - 1), &ops); + *retlen = ops.oobretlen; return res; } @@ -200,14 +192,14 @@ static int inftl_write(struct mtd_info *mtd, loff_t offs, size_t len, struct mtd_oob_ops ops; int res; - ops.mode = MTD_OOB_PLACE; + ops.mode = MTD_OPS_PLACE_OOB; ops.ooboffs = offs; ops.ooblen = mtd->oobsize; ops.oobbuf = oob; ops.datbuf = buf; ops.len = len; - res = mtd->write_oob(mtd, offs & ~(mtd->writesize - 1), &ops); + res = mtd_write_oob(mtd, offs & ~(mtd->writesize - 1), &ops); *retlen = ops.retlen; return res; } @@ -221,17 +213,17 @@ static u16 INFTL_findfreeblock(struct INFTLrecord *inftl, int desperate) u16 pot = inftl->LastFreeEUN; int silly = inftl->nb_blocks; - DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_findfreeblock(inftl=%p," - "desperate=%d)\n", inftl, desperate); + pr_debug("INFTL: INFTL_findfreeblock(inftl=%p,desperate=%d)\n", + inftl, desperate); /* * Normally, we force a fold to happen before we run out of free * blocks completely. */ if (!desperate && inftl->numfreeEUNs < 2) { - DEBUG(MTD_DEBUG_LEVEL1, "INFTL: there are too few free " - "EUNs (%d)\n", inftl->numfreeEUNs); - return 0xffff; + pr_debug("INFTL: there are too few free EUNs (%d)\n", + inftl->numfreeEUNs); + return BLOCK_NIL; } /* Scan for a free block */ @@ -265,8 +257,8 @@ static u16 INFTL_foldchain(struct INFTLrecord *inftl, unsigned thisVUC, unsigned struct inftl_oob oob; size_t retlen; - DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_foldchain(inftl=%p,thisVUC=%d," - "pending=%d)\n", inftl, thisVUC, pendingblock); + pr_debug("INFTL: INFTL_foldchain(inftl=%p,thisVUC=%d,pending=%d)\n", + inftl, thisVUC, pendingblock); memset(BlockMap, 0xff, sizeof(BlockMap)); memset(BlockDeleted, 0, sizeof(BlockDeleted)); @@ -286,7 +278,8 @@ static u16 INFTL_foldchain(struct INFTLrecord *inftl, unsigned thisVUC, unsigned silly = MAX_LOOPS; while (thisEUN < inftl->nb_blocks) { for (block = 0; block < inftl->EraseSize/SECTORSIZE; block ++) { - if ((BlockMap[block] != 0xffff) || BlockDeleted[block]) + if ((BlockMap[block] != BLOCK_NIL) || + BlockDeleted[block]) continue; if (inftl_read_oob(mtd, (thisEUN * inftl->EraseSize) @@ -328,8 +321,7 @@ static u16 INFTL_foldchain(struct INFTLrecord *inftl, unsigned thisVUC, unsigned * Chain, and the Erase Unit into which we are supposed to be copying. * Go for it. */ - DEBUG(MTD_DEBUG_LEVEL1, "INFTL: folding chain %d into unit %d\n", - thisVUC, targetEUN); + pr_debug("INFTL: folding chain %d into unit %d\n", thisVUC, targetEUN); for (block = 0; block < inftl->EraseSize/SECTORSIZE ; block++) { unsigned char movebuf[SECTORSIZE]; @@ -351,17 +343,19 @@ static u16 INFTL_foldchain(struct INFTLrecord *inftl, unsigned thisVUC, unsigned if (BlockMap[block] == BLOCK_NIL) continue; - ret = mtd->read(mtd, (inftl->EraseSize * BlockMap[block]) + - (block * SECTORSIZE), SECTORSIZE, &retlen, - movebuf); - if (ret < 0 && ret != -EUCLEAN) { - ret = mtd->read(mtd, - (inftl->EraseSize * BlockMap[block]) + - (block * SECTORSIZE), SECTORSIZE, - &retlen, movebuf); + ret = mtd_read(mtd, + (inftl->EraseSize * BlockMap[block]) + (block * SECTORSIZE), + SECTORSIZE, + &retlen, + movebuf); + if (ret < 0 && !mtd_is_bitflip(ret)) { + ret = mtd_read(mtd, + (inftl->EraseSize * BlockMap[block]) + (block * SECTORSIZE), + SECTORSIZE, + &retlen, + movebuf); if (ret != -EIO) - DEBUG(MTD_DEBUG_LEVEL1, "INFTL: error went " - "away on retry?\n"); + pr_debug("INFTL: error went away on retry?\n"); } memset(&oob, 0xff, sizeof(struct inftl_oob)); oob.b.Status = oob.b.Status1 = SECTOR_USED; @@ -377,8 +371,7 @@ static u16 INFTL_foldchain(struct INFTLrecord *inftl, unsigned thisVUC, unsigned * is important, by doing oldest first if we crash/reboot then it * it is relatively simple to clean up the mess). */ - DEBUG(MTD_DEBUG_LEVEL1, "INFTL: want to erase virtual chain %d\n", - thisVUC); + pr_debug("INFTL: want to erase virtual chain %d\n", thisVUC); for (;;) { /* Find oldest unit in chain. */ @@ -393,6 +386,10 @@ static u16 INFTL_foldchain(struct INFTLrecord *inftl, unsigned thisVUC, unsigned if (thisEUN == targetEUN) break; + /* Unlink the last block from the chain. */ + inftl->PUtable[prevEUN] = BLOCK_NIL; + + /* Now try to erase it. */ if (INFTL_formatblock(inftl, thisEUN) < 0) { /* * Could not erase : mark block as reserved. @@ -401,7 +398,6 @@ static u16 INFTL_foldchain(struct INFTLrecord *inftl, unsigned thisVUC, unsigned } else { /* Correctly erased : mark it as free */ inftl->PUtable[thisEUN] = BLOCK_FREE; - inftl->PUtable[prevEUN] = BLOCK_NIL; inftl->numfreeEUNs++; } } @@ -423,7 +419,7 @@ static u16 INFTL_makefreeblock(struct INFTLrecord *inftl, unsigned pendingblock) u16 ChainLength = 0, thislen; u16 chain, EUN; - DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_makefreeblock(inftl=%p," + pr_debug("INFTL: INFTL_makefreeblock(inftl=%p," "pending=%d)\n", inftl, pendingblock); for (chain = 0; chain < inftl->nb_blocks; chain++) { @@ -486,8 +482,8 @@ static inline u16 INFTL_findwriteunit(struct INFTLrecord *inftl, unsigned block) size_t retlen; int silly, silly2 = 3; - DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_findwriteunit(inftl=%p," - "block=%d)\n", inftl, block); + pr_debug("INFTL: INFTL_findwriteunit(inftl=%p,block=%d)\n", + inftl, block); do { /* @@ -503,8 +499,8 @@ static inline u16 INFTL_findwriteunit(struct INFTLrecord *inftl, unsigned block) blockofs, 8, &retlen, (char *)&bci); status = bci.Status | bci.Status1; - DEBUG(MTD_DEBUG_LEVEL3, "INFTL: status of block %d in " - "EUN %d is %x\n", block , writeEUN, status); + pr_debug("INFTL: status of block %d in EUN %d is %x\n", + block , writeEUN, status); switch(status) { case SECTOR_FREE: @@ -527,7 +523,7 @@ static inline u16 INFTL_findwriteunit(struct INFTLrecord *inftl, unsigned block) if (!silly--) { printk(KERN_WARNING "INFTL: infinite loop in " "Virtual Unit Chain 0x%x\n", thisVUC); - return 0xffff; + return BLOCK_NIL; } /* Skip to next block in chain */ @@ -551,15 +547,15 @@ hitused: * waiting to be picked up. We're going to have to fold * a chain to make room. */ - thisEUN = INFTL_makefreeblock(inftl, 0xffff); + thisEUN = INFTL_makefreeblock(inftl, block); /* * Hopefully we free something, lets try again. * This time we are desperate... */ - DEBUG(MTD_DEBUG_LEVEL1, "INFTL: using desperate==1 " - "to find free EUN to accommodate write to " - "VUC %d\n", thisVUC); + pr_debug("INFTL: using desperate==1 to find free EUN " + "to accommodate write to VUC %d\n", + thisVUC); writeEUN = INFTL_findfreeblock(inftl, 1); if (writeEUN == BLOCK_NIL) { /* @@ -633,7 +629,7 @@ hitused: printk(KERN_WARNING "INFTL: error folding to make room for Virtual " "Unit Chain 0x%x\n", thisVUC); - return 0xffff; + return BLOCK_NIL; } /* @@ -649,7 +645,7 @@ static void INFTL_trydeletechain(struct INFTLrecord *inftl, unsigned thisVUC) struct inftl_bci bci; size_t retlen; - DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_trydeletechain(inftl=%p," + pr_debug("INFTL: INFTL_trydeletechain(inftl=%p," "thisVUC=%d)\n", inftl, thisVUC); memset(BlockUsed, 0, sizeof(BlockUsed)); @@ -713,7 +709,7 @@ static void INFTL_trydeletechain(struct INFTLrecord *inftl, unsigned thisVUC) * For each block in the chain free it and make it available * for future use. Erase from the oldest unit first. */ - DEBUG(MTD_DEBUG_LEVEL1, "INFTL: deleting empty VUC %d\n", thisVUC); + pr_debug("INFTL: deleting empty VUC %d\n", thisVUC); for (;;) { u16 *prevEUN = &inftl->VUtable[thisVUC]; @@ -721,7 +717,7 @@ static void INFTL_trydeletechain(struct INFTLrecord *inftl, unsigned thisVUC) /* If the chain is all gone already, we're done */ if (thisEUN == BLOCK_NIL) { - DEBUG(MTD_DEBUG_LEVEL2, "INFTL: Empty VUC %d for deletion was already absent\n", thisEUN); + pr_debug("INFTL: Empty VUC %d for deletion was already absent\n", thisEUN); return; } @@ -733,7 +729,7 @@ static void INFTL_trydeletechain(struct INFTLrecord *inftl, unsigned thisVUC) thisEUN = *prevEUN; } - DEBUG(MTD_DEBUG_LEVEL3, "Deleting EUN %d from VUC %d\n", + pr_debug("Deleting EUN %d from VUC %d\n", thisEUN, thisVUC); if (INFTL_formatblock(inftl, thisEUN) < 0) { @@ -769,7 +765,7 @@ static int INFTL_deleteblock(struct INFTLrecord *inftl, unsigned block) size_t retlen; struct inftl_bci bci; - DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_deleteblock(inftl=%p," + pr_debug("INFTL: INFTL_deleteblock(inftl=%p," "block=%d)\n", inftl, block); while (thisEUN < inftl->nb_blocks) { @@ -828,7 +824,7 @@ static int inftl_writeblock(struct mtd_blktrans_dev *mbd, unsigned long block, struct inftl_oob oob; char *p, *pend; - DEBUG(MTD_DEBUG_LEVEL3, "INFTL: inftl_writeblock(inftl=%p,block=%ld," + pr_debug("INFTL: inftl_writeblock(inftl=%p,block=%ld," "buffer=%p)\n", inftl, block, buffer); /* Is block all zero? */ @@ -878,7 +874,7 @@ static int inftl_readblock(struct mtd_blktrans_dev *mbd, unsigned long block, struct inftl_bci bci; size_t retlen; - DEBUG(MTD_DEBUG_LEVEL3, "INFTL: inftl_readblock(inftl=%p,block=%ld," + pr_debug("INFTL: inftl_readblock(inftl=%p,block=%ld," "buffer=%p)\n", inftl, block, buffer); while (thisEUN < inftl->nb_blocks) { @@ -921,10 +917,10 @@ foundit: } else { size_t retlen; loff_t ptr = (thisEUN * inftl->EraseSize) + blockofs; - int ret = mtd->read(mtd, ptr, SECTORSIZE, &retlen, buffer); + int ret = mtd_read(mtd, ptr, SECTORSIZE, &retlen, buffer); /* Handle corrected bit flips gracefully */ - if (ret < 0 && ret != -EUCLEAN) + if (ret < 0 && !mtd_is_bitflip(ret)) return -EIO; } return 0; @@ -945,6 +941,7 @@ static struct mtd_blktrans_ops inftl_tr = { .name = "inftl", .major = INFTL_MAJOR, .part_bits = INFTL_PARTN_BITS, + .blksize = 512, .getgeo = inftl_getgeo, .readsect = inftl_readblock, .writesect = inftl_writeblock, @@ -955,9 +952,6 @@ static struct mtd_blktrans_ops inftl_tr = { static int __init init_inftl(void) { - printk(KERN_INFO "INFTL: inftlcore.c $Revision: 1.19 $, " - "inftlmount.c %s\n", inftlmountrev); - return register_mtd_blktrans(&inftl_tr); } diff --git a/drivers/mtd/inftlmount.c b/drivers/mtd/inftlmount.c index 8f6006f1a51..487e64f411a 100644 --- a/drivers/mtd/inftlmount.c +++ b/drivers/mtd/inftlmount.c @@ -2,13 +2,11 @@ * inftlmount.c -- INFTL mount code with extensive checks. * * Author: Greg Ungerer (gerg@snapgear.com) - * (C) Copyright 2002-2003, Greg Ungerer (gerg@snapgear.com) + * Copyright © 2002-2003, Greg Ungerer (gerg@snapgear.com) * * Based heavily on the nftlmount.c code which is: * Author: Fabrice Bellard (fabrice.bellard@netgem.com) - * Copyright (C) 2000 Netgem S.A. - * - * $Id: inftlmount.c,v 1.18 2005/11/07 11:14:20 gleixner Exp $ + * Copyright © 2000 Netgem S.A. * * 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 @@ -30,23 +28,11 @@ #include <asm/errno.h> #include <asm/io.h> #include <asm/uaccess.h> -#include <linux/miscdevice.h> -#include <linux/pci.h> #include <linux/delay.h> #include <linux/slab.h> -#include <linux/sched.h> -#include <linux/init.h> #include <linux/mtd/mtd.h> #include <linux/mtd/nftl.h> #include <linux/mtd/inftl.h> -#include <linux/mtd/compatmac.h> - -char inftlmountrev[]="$Revision: 1.18 $"; - -extern int inftl_read_oob(struct mtd_info *mtd, loff_t offs, size_t len, - size_t *retlen, uint8_t *buf); -extern int inftl_write_oob(struct mtd_info *mtd, loff_t offs, size_t len, - size_t *retlen, uint8_t *buf); /* * find_boot_record: Find the INFTL Media Header and its Spare copy which @@ -66,7 +52,7 @@ static int find_boot_record(struct INFTLrecord *inftl) struct INFTLPartition *ip; size_t retlen; - DEBUG(MTD_DEBUG_LEVEL3, "INFTL: find_boot_record(inftl=%p)\n", inftl); + pr_debug("INFTL: find_boot_record(inftl=%p)\n", inftl); /* * Assume logical EraseSize == physical erasesize for starting the @@ -74,7 +60,7 @@ static int find_boot_record(struct INFTLrecord *inftl) * otherwise. */ inftl->EraseSize = inftl->mbd.mtd->erasesize; - inftl->nb_blocks = inftl->mbd.mtd->size / inftl->EraseSize; + inftl->nb_blocks = (u32)inftl->mbd.mtd->size / inftl->EraseSize; inftl->MediaUnit = BLOCK_NIL; @@ -86,8 +72,8 @@ static int find_boot_record(struct INFTLrecord *inftl) * Check for BNAND header first. Then whinge if it's found * but later checks fail. */ - ret = mtd->read(mtd, block * inftl->EraseSize, - SECTORSIZE, &retlen, buf); + ret = mtd_read(mtd, block * inftl->EraseSize, SECTORSIZE, + &retlen, buf); /* We ignore ret in case the ECC of the MediaHeader is invalid (which is apparently acceptable) */ if (retlen != SECTORSIZE) { @@ -112,9 +98,10 @@ static int find_boot_record(struct INFTLrecord *inftl) } /* To be safer with BIOS, also use erase mark as discriminant */ - if ((ret = inftl_read_oob(mtd, block * inftl->EraseSize + - SECTORSIZE + 8, 8, &retlen, - (char *)&h1) < 0)) { + ret = inftl_read_oob(mtd, + block * inftl->EraseSize + SECTORSIZE + 8, + 8, &retlen,(char *)&h1); + if (ret < 0) { printk(KERN_WARNING "INFTL: ANAND header found at " "0x%x in mtd%d, but OOB data read failed " "(err %d)\n", block * inftl->EraseSize, @@ -130,8 +117,8 @@ static int find_boot_record(struct INFTLrecord *inftl) memcpy(mh, buf, sizeof(struct INFTLMediaHeader)); /* Read the spare media header at offset 4096 */ - mtd->read(mtd, block * inftl->EraseSize + 4096, - SECTORSIZE, &retlen, buf); + mtd_read(mtd, block * inftl->EraseSize + 4096, SECTORSIZE, + &retlen, buf); if (retlen != SECTORSIZE) { printk(KERN_WARNING "INFTL: Unable to read spare " "Media Header\n"); @@ -151,24 +138,20 @@ static int find_boot_record(struct INFTLrecord *inftl) mh->FormatFlags = le32_to_cpu(mh->FormatFlags); mh->PercentUsed = le32_to_cpu(mh->PercentUsed); -#ifdef CONFIG_MTD_DEBUG_VERBOSE - if (CONFIG_MTD_DEBUG_VERBOSE >= 2) { - printk("INFTL: Media Header ->\n" - " bootRecordID = %s\n" - " NoOfBootImageBlocks = %d\n" - " NoOfBinaryPartitions = %d\n" - " NoOfBDTLPartitions = %d\n" - " BlockMultiplerBits = %d\n" - " FormatFlgs = %d\n" - " OsakVersion = 0x%x\n" - " PercentUsed = %d\n", - mh->bootRecordID, mh->NoOfBootImageBlocks, - mh->NoOfBinaryPartitions, - mh->NoOfBDTLPartitions, - mh->BlockMultiplierBits, mh->FormatFlags, - mh->OsakVersion, mh->PercentUsed); - } -#endif + pr_debug("INFTL: Media Header ->\n" + " bootRecordID = %s\n" + " NoOfBootImageBlocks = %d\n" + " NoOfBinaryPartitions = %d\n" + " NoOfBDTLPartitions = %d\n" + " BlockMultiplerBits = %d\n" + " FormatFlgs = %d\n" + " OsakVersion = 0x%x\n" + " PercentUsed = %d\n", + mh->bootRecordID, mh->NoOfBootImageBlocks, + mh->NoOfBinaryPartitions, + mh->NoOfBDTLPartitions, + mh->BlockMultiplierBits, mh->FormatFlags, + mh->OsakVersion, mh->PercentUsed); if (mh->NoOfBDTLPartitions == 0) { printk(KERN_WARNING "INFTL: Media Header sanity check " @@ -198,7 +181,7 @@ static int find_boot_record(struct INFTLrecord *inftl) mh->BlockMultiplierBits); inftl->EraseSize = inftl->mbd.mtd->erasesize << mh->BlockMultiplierBits; - inftl->nb_blocks = inftl->mbd.mtd->size / inftl->EraseSize; + inftl->nb_blocks = (u32)inftl->mbd.mtd->size / inftl->EraseSize; block >>= mh->BlockMultiplierBits; } @@ -212,19 +195,15 @@ static int find_boot_record(struct INFTLrecord *inftl) ip->spareUnits = le32_to_cpu(ip->spareUnits); ip->Reserved0 = le32_to_cpu(ip->Reserved0); -#ifdef CONFIG_MTD_DEBUG_VERBOSE - if (CONFIG_MTD_DEBUG_VERBOSE >= 2) { - printk(" PARTITION[%d] ->\n" - " virtualUnits = %d\n" - " firstUnit = %d\n" - " lastUnit = %d\n" - " flags = 0x%x\n" - " spareUnits = %d\n", - i, ip->virtualUnits, ip->firstUnit, - ip->lastUnit, ip->flags, - ip->spareUnits); - } -#endif + pr_debug(" PARTITION[%d] ->\n" + " virtualUnits = %d\n" + " firstUnit = %d\n" + " lastUnit = %d\n" + " flags = 0x%x\n" + " spareUnits = %d\n", + i, ip->virtualUnits, ip->firstUnit, + ip->lastUnit, ip->flags, + ip->spareUnits); if (ip->Reserved0 != ip->firstUnit) { struct erase_info *instr = &inftl->instr; @@ -240,7 +219,7 @@ static int find_boot_record(struct INFTLrecord *inftl) */ instr->addr = ip->Reserved0 * inftl->EraseSize; instr->len = inftl->EraseSize; - mtd->erase(mtd, instr); + mtd_erase(mtd, instr); } if ((ip->lastUnit - ip->firstUnit + 1) < ip->virtualUnits) { printk(KERN_WARNING "INFTL: Media Header " @@ -326,7 +305,8 @@ static int find_boot_record(struct INFTLrecord *inftl) /* If any of the physical eraseblocks are bad, don't use the unit. */ for (physblock = 0; physblock < inftl->EraseSize; physblock += inftl->mbd.mtd->erasesize) { - if (inftl->mbd.mtd->block_isbad(inftl->mbd.mtd, i * inftl->EraseSize + physblock)) + if (mtd_block_isbad(inftl->mbd.mtd, + i * inftl->EraseSize + physblock)) inftl->PUtable[i] = BLOCK_RESERVED; } } @@ -362,7 +342,7 @@ static int check_free_sectors(struct INFTLrecord *inftl, unsigned int address, int i; for (i = 0; i < len; i += SECTORSIZE) { - if (mtd->read(mtd, address, SECTORSIZE, &retlen, buf)) + if (mtd_read(mtd, address, SECTORSIZE, &retlen, buf)) return -1; if (memcmpb(buf, 0xff, SECTORSIZE) != 0) return -1; @@ -387,7 +367,7 @@ static int check_free_sectors(struct INFTLrecord *inftl, unsigned int address, * * Return: 0 when succeed, -1 on error. * - * ToDo: 1. Is it neceressary to check_free_sector after erasing ?? + * ToDo: 1. Is it necessary to check_free_sector after erasing ?? */ int INFTL_formatblock(struct INFTLrecord *inftl, int block) { @@ -397,8 +377,7 @@ int INFTL_formatblock(struct INFTLrecord *inftl, int block) struct mtd_info *mtd = inftl->mbd.mtd; int physblock; - DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_formatblock(inftl=%p," - "block=%d)\n", inftl, block); + pr_debug("INFTL: INFTL_formatblock(inftl=%p,block=%d)\n", inftl, block); memset(instr, 0, sizeof(struct erase_info)); @@ -414,7 +393,7 @@ int INFTL_formatblock(struct INFTLrecord *inftl, int block) mark only the failed block in the bbt. */ for (physblock = 0; physblock < inftl->EraseSize; physblock += instr->len, instr->addr += instr->len) { - mtd->erase(inftl->mbd.mtd, instr); + mtd_erase(inftl->mbd.mtd, instr); if (instr->state == MTD_ERASE_FAILED) { printk(KERN_WARNING "INFTL: error while formatting block %d\n", @@ -444,7 +423,7 @@ int INFTL_formatblock(struct INFTLrecord *inftl, int block) fail: /* could not format, update the bad block table (caller is responsible for setting the PUtable to BLOCK_RESERVED on failure) */ - inftl->mbd.mtd->block_markbad(inftl->mbd.mtd, instr->addr); + mtd_block_markbad(inftl->mbd.mtd, instr->addr); return -1; } @@ -488,30 +467,30 @@ void INFTL_dumptables(struct INFTLrecord *s) { int i; - printk("-------------------------------------------" + pr_debug("-------------------------------------------" "----------------------------------\n"); - printk("VUtable[%d] ->", s->nb_blocks); + pr_debug("VUtable[%d] ->", s->nb_blocks); for (i = 0; i < s->nb_blocks; i++) { if ((i % 8) == 0) - printk("\n%04x: ", i); - printk("%04x ", s->VUtable[i]); + pr_debug("\n%04x: ", i); + pr_debug("%04x ", s->VUtable[i]); } - printk("\n-------------------------------------------" + pr_debug("\n-------------------------------------------" "----------------------------------\n"); - printk("PUtable[%d-%d=%d] ->", s->firstEUN, s->lastEUN, s->nb_blocks); + pr_debug("PUtable[%d-%d=%d] ->", s->firstEUN, s->lastEUN, s->nb_blocks); for (i = 0; i <= s->lastEUN; i++) { if ((i % 8) == 0) - printk("\n%04x: ", i); - printk("%04x ", s->PUtable[i]); + pr_debug("\n%04x: ", i); + pr_debug("%04x ", s->PUtable[i]); } - printk("\n-------------------------------------------" + pr_debug("\n-------------------------------------------" "----------------------------------\n"); - printk("INFTL ->\n" + pr_debug("INFTL ->\n" " EraseSize = %d\n" " h/s/c = %d/%d/%d\n" " numvunits = %d\n" @@ -525,7 +504,7 @@ void INFTL_dumptables(struct INFTLrecord *s) s->numvunits, s->firstEUN, s->lastEUN, s->numfreeEUNs, s->LastFreeEUN, s->nb_blocks, s->nb_boot_blocks); - printk("\n-------------------------------------------" + pr_debug("\n-------------------------------------------" "----------------------------------\n"); } @@ -533,25 +512,25 @@ void INFTL_dumpVUchains(struct INFTLrecord *s) { int logical, block, i; - printk("-------------------------------------------" + pr_debug("-------------------------------------------" "----------------------------------\n"); - printk("INFTL Virtual Unit Chains:\n"); + pr_debug("INFTL Virtual Unit Chains:\n"); for (logical = 0; logical < s->nb_blocks; logical++) { block = s->VUtable[logical]; if (block > s->nb_blocks) continue; - printk(" LOGICAL %d --> %d ", logical, block); + pr_debug(" LOGICAL %d --> %d ", logical, block); for (i = 0; i < s->nb_blocks; i++) { if (s->PUtable[block] == BLOCK_NIL) break; block = s->PUtable[block]; - printk("%d ", block); + pr_debug("%d ", block); } - printk("\n"); + pr_debug("\n"); } - printk("-------------------------------------------" + pr_debug("-------------------------------------------" "----------------------------------\n"); } @@ -567,7 +546,7 @@ int INFTL_mount(struct INFTLrecord *s) int i; u8 *ANACtable, ANAC; - DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_mount(inftl=%p)\n", s); + pr_debug("INFTL: INFTL_mount(inftl=%p)\n", s); /* Search for INFTL MediaHeader and Spare INFTL Media Header */ if (find_boot_record(s) < 0) { @@ -582,14 +561,13 @@ int INFTL_mount(struct INFTLrecord *s) logical_block = block = BLOCK_NIL; /* Temporary buffer to store ANAC numbers. */ - ANACtable = kmalloc(s->nb_blocks * sizeof(u8), GFP_KERNEL); + ANACtable = kcalloc(s->nb_blocks, sizeof(u8), GFP_KERNEL); if (!ANACtable) { printk(KERN_WARNING "INFTL: allocation of ANACtable " "failed (%zd bytes)\n", s->nb_blocks * sizeof(u8)); return -ENOMEM; } - memset(ANACtable, 0, s->nb_blocks); /* * First pass is to explore each physical unit, and construct the @@ -598,7 +576,7 @@ int INFTL_mount(struct INFTLrecord *s) * NOTEXPLORED state. Then at the end we will try to format it and * mark it as free. */ - DEBUG(MTD_DEBUG_LEVEL3, "INFTL: pass 1, explore each unit\n"); + pr_debug("INFTL: pass 1, explore each unit\n"); for (first_block = s->firstEUN; first_block <= s->lastEUN; first_block++) { if (s->PUtable[first_block] != BLOCK_NOTEXPLORED) continue; @@ -730,17 +708,14 @@ int INFTL_mount(struct INFTLrecord *s) logical_block = BLOCK_NIL; } -#ifdef CONFIG_MTD_DEBUG_VERBOSE - if (CONFIG_MTD_DEBUG_VERBOSE >= 2) - INFTL_dumptables(s); -#endif + INFTL_dumptables(s); /* * Second pass, check for infinite loops in chains. These are * possible because we don't update the previous pointers when * we fold chains. No big deal, just fix them up in PUtable. */ - DEBUG(MTD_DEBUG_LEVEL3, "INFTL: pass 2, validate virtual chains\n"); + pr_debug("INFTL: pass 2, validate virtual chains\n"); for (logical_block = 0; logical_block < s->numvunits; logical_block++) { block = s->VUtable[logical_block]; last_block = BLOCK_NIL; @@ -785,12 +760,8 @@ int INFTL_mount(struct INFTLrecord *s) } } -#ifdef CONFIG_MTD_DEBUG_VERBOSE - if (CONFIG_MTD_DEBUG_VERBOSE >= 2) - INFTL_dumptables(s); - if (CONFIG_MTD_DEBUG_VERBOSE >= 2) - INFTL_dumpVUchains(s); -#endif + INFTL_dumptables(s); + INFTL_dumpVUchains(s); /* * Third pass, format unreferenced blocks and init free block count. @@ -798,7 +769,7 @@ int INFTL_mount(struct INFTLrecord *s) s->numfreeEUNs = 0; s->LastFreeEUN = BLOCK_NIL; - DEBUG(MTD_DEBUG_LEVEL3, "INFTL: pass 3, format unused blocks\n"); + pr_debug("INFTL: pass 3, format unused blocks\n"); for (block = s->firstEUN; block <= s->lastEUN; block++) { if (s->PUtable[block] == BLOCK_NOTEXPLORED) { printk("INFTL: unreferenced block %d, formatting it\n", diff --git a/drivers/mtd/lpddr/Kconfig b/drivers/mtd/lpddr/Kconfig new file mode 100644 index 00000000000..3a19cbee24d --- /dev/null +++ b/drivers/mtd/lpddr/Kconfig @@ -0,0 +1,29 @@ +menu "LPDDR & LPDDR2 PCM memory drivers" + depends on MTD + +config MTD_LPDDR + tristate "Support for LPDDR flash chips" + select MTD_QINFO_PROBE + help + This option enables support of LPDDR (Low power double data rate) + flash chips. Synonymous with Mobile-DDR. It is a new standard for + DDR memories, intended for battery-operated systems. + +config MTD_QINFO_PROBE + depends on MTD_LPDDR + tristate "Detect flash chips by QINFO probe" + help + Device Information for LPDDR chips is offered through the Overlay + Window QINFO interface, permits software to be used for entire + families of devices. This serves similar purpose of CFI on legacy + Flash products + +config MTD_LPDDR2_NVM + # ARM dependency is only for writel_relaxed() + depends on MTD && ARM + tristate "Support for LPDDR2-NVM flash chips" + help + This option enables support of PCM memories with a LPDDR2-NVM + (Low power double data rate 2) interface. + +endmenu diff --git a/drivers/mtd/lpddr/Makefile b/drivers/mtd/lpddr/Makefile new file mode 100644 index 00000000000..881d440d483 --- /dev/null +++ b/drivers/mtd/lpddr/Makefile @@ -0,0 +1,7 @@ +# +# linux/drivers/mtd/lpddr/Makefile +# + +obj-$(CONFIG_MTD_QINFO_PROBE) += qinfo_probe.o +obj-$(CONFIG_MTD_LPDDR) += lpddr_cmds.o +obj-$(CONFIG_MTD_LPDDR2_NVM) += lpddr2_nvm.o diff --git a/drivers/mtd/lpddr/lpddr2_nvm.c b/drivers/mtd/lpddr/lpddr2_nvm.c new file mode 100644 index 00000000000..063cec40d0a --- /dev/null +++ b/drivers/mtd/lpddr/lpddr2_nvm.c @@ -0,0 +1,507 @@ +/* + * LPDDR2-NVM MTD driver. This module provides read, write, erase, lock/unlock + * support for LPDDR2-NVM PCM memories + * + * Copyright © 2012 Micron Technology, Inc. + * + * Vincenzo Aliberti <vincenzo.aliberti@gmail.com> + * Domenico Manna <domenico.manna@gmail.com> + * Many thanks to Andrea Vigilante for initial enabling + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__ + +#include <linux/init.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/mtd/map.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/ioport.h> +#include <linux/err.h> + +/* Parameters */ +#define ERASE_BLOCKSIZE (0x00020000/2) /* in Word */ +#define WRITE_BUFFSIZE (0x00000400/2) /* in Word */ +#define OW_BASE_ADDRESS 0x00000000 /* OW offset */ +#define BUS_WIDTH 0x00000020 /* x32 devices */ + +/* PFOW symbols address offset */ +#define PFOW_QUERY_STRING_P (0x0000/2) /* in Word */ +#define PFOW_QUERY_STRING_F (0x0002/2) /* in Word */ +#define PFOW_QUERY_STRING_O (0x0004/2) /* in Word */ +#define PFOW_QUERY_STRING_W (0x0006/2) /* in Word */ + +/* OW registers address */ +#define CMD_CODE_OFS (0x0080/2) /* in Word */ +#define CMD_DATA_OFS (0x0084/2) /* in Word */ +#define CMD_ADD_L_OFS (0x0088/2) /* in Word */ +#define CMD_ADD_H_OFS (0x008A/2) /* in Word */ +#define MPR_L_OFS (0x0090/2) /* in Word */ +#define MPR_H_OFS (0x0092/2) /* in Word */ +#define CMD_EXEC_OFS (0x00C0/2) /* in Word */ +#define STATUS_REG_OFS (0x00CC/2) /* in Word */ +#define PRG_BUFFER_OFS (0x0010/2) /* in Word */ + +/* Datamask */ +#define MR_CFGMASK 0x8000 +#define SR_OK_DATAMASK 0x0080 + +/* LPDDR2-NVM Commands */ +#define LPDDR2_NVM_LOCK 0x0061 +#define LPDDR2_NVM_UNLOCK 0x0062 +#define LPDDR2_NVM_SW_PROGRAM 0x0041 +#define LPDDR2_NVM_SW_OVERWRITE 0x0042 +#define LPDDR2_NVM_BUF_PROGRAM 0x00E9 +#define LPDDR2_NVM_BUF_OVERWRITE 0x00EA +#define LPDDR2_NVM_ERASE 0x0020 + +/* LPDDR2-NVM Registers offset */ +#define LPDDR2_MODE_REG_DATA 0x0040 +#define LPDDR2_MODE_REG_CFG 0x0050 + +/* + * Internal Type Definitions + * pcm_int_data contains memory controller details: + * @reg_data : LPDDR2_MODE_REG_DATA register address after remapping + * @reg_cfg : LPDDR2_MODE_REG_CFG register address after remapping + * &bus_width: memory bus-width (eg: x16 2 Bytes, x32 4 Bytes) + */ +struct pcm_int_data { + void __iomem *ctl_regs; + int bus_width; +}; + +static DEFINE_MUTEX(lpdd2_nvm_mutex); + +/* + * Build a map_word starting from an u_long + */ +static inline map_word build_map_word(u_long myword) +{ + map_word val = { {0} }; + val.x[0] = myword; + return val; +} + +/* + * Build Mode Register Configuration DataMask based on device bus-width + */ +static inline u_int build_mr_cfgmask(u_int bus_width) +{ + u_int val = MR_CFGMASK; + + if (bus_width == 0x0004) /* x32 device */ + val = val << 16; + + return val; +} + +/* + * Build Status Register OK DataMask based on device bus-width + */ +static inline u_int build_sr_ok_datamask(u_int bus_width) +{ + u_int val = SR_OK_DATAMASK; + + if (bus_width == 0x0004) /* x32 device */ + val = (val << 16)+val; + + return val; +} + +/* + * Evaluates Overlay Window Control Registers address + */ +static inline u_long ow_reg_add(struct map_info *map, u_long offset) +{ + u_long val = 0; + struct pcm_int_data *pcm_data = map->fldrv_priv; + + val = map->pfow_base + offset*pcm_data->bus_width; + + return val; +} + +/* + * Enable lpddr2-nvm Overlay Window + * Overlay Window is a memory mapped area containing all LPDDR2-NVM registers + * used by device commands as well as uservisible resources like Device Status + * Register, Device ID, etc + */ +static inline void ow_enable(struct map_info *map) +{ + struct pcm_int_data *pcm_data = map->fldrv_priv; + + writel_relaxed(build_mr_cfgmask(pcm_data->bus_width) | 0x18, + pcm_data->ctl_regs + LPDDR2_MODE_REG_CFG); + writel_relaxed(0x01, pcm_data->ctl_regs + LPDDR2_MODE_REG_DATA); +} + +/* + * Disable lpddr2-nvm Overlay Window + * Overlay Window is a memory mapped area containing all LPDDR2-NVM registers + * used by device commands as well as uservisible resources like Device Status + * Register, Device ID, etc + */ +static inline void ow_disable(struct map_info *map) +{ + struct pcm_int_data *pcm_data = map->fldrv_priv; + + writel_relaxed(build_mr_cfgmask(pcm_data->bus_width) | 0x18, + pcm_data->ctl_regs + LPDDR2_MODE_REG_CFG); + writel_relaxed(0x02, pcm_data->ctl_regs + LPDDR2_MODE_REG_DATA); +} + +/* + * Execute lpddr2-nvm operations + */ +static int lpddr2_nvm_do_op(struct map_info *map, u_long cmd_code, + u_long cmd_data, u_long cmd_add, u_long cmd_mpr, u_char *buf) +{ + map_word add_l = { {0} }, add_h = { {0} }, mpr_l = { {0} }, + mpr_h = { {0} }, data_l = { {0} }, cmd = { {0} }, + exec_cmd = { {0} }, sr; + map_word data_h = { {0} }; /* only for 2x x16 devices stacked */ + u_long i, status_reg, prg_buff_ofs; + struct pcm_int_data *pcm_data = map->fldrv_priv; + u_int sr_ok_datamask = build_sr_ok_datamask(pcm_data->bus_width); + + /* Builds low and high words for OW Control Registers */ + add_l.x[0] = cmd_add & 0x0000FFFF; + add_h.x[0] = (cmd_add >> 16) & 0x0000FFFF; + mpr_l.x[0] = cmd_mpr & 0x0000FFFF; + mpr_h.x[0] = (cmd_mpr >> 16) & 0x0000FFFF; + cmd.x[0] = cmd_code & 0x0000FFFF; + exec_cmd.x[0] = 0x0001; + data_l.x[0] = cmd_data & 0x0000FFFF; + data_h.x[0] = (cmd_data >> 16) & 0x0000FFFF; /* only for 2x x16 */ + + /* Set Overlay Window Control Registers */ + map_write(map, cmd, ow_reg_add(map, CMD_CODE_OFS)); + map_write(map, data_l, ow_reg_add(map, CMD_DATA_OFS)); + map_write(map, add_l, ow_reg_add(map, CMD_ADD_L_OFS)); + map_write(map, add_h, ow_reg_add(map, CMD_ADD_H_OFS)); + map_write(map, mpr_l, ow_reg_add(map, MPR_L_OFS)); + map_write(map, mpr_h, ow_reg_add(map, MPR_H_OFS)); + if (pcm_data->bus_width == 0x0004) { /* 2x16 devices stacked */ + map_write(map, cmd, ow_reg_add(map, CMD_CODE_OFS) + 2); + map_write(map, data_h, ow_reg_add(map, CMD_DATA_OFS) + 2); + map_write(map, add_l, ow_reg_add(map, CMD_ADD_L_OFS) + 2); + map_write(map, add_h, ow_reg_add(map, CMD_ADD_H_OFS) + 2); + map_write(map, mpr_l, ow_reg_add(map, MPR_L_OFS) + 2); + map_write(map, mpr_h, ow_reg_add(map, MPR_H_OFS) + 2); + } + + /* Fill Program Buffer */ + if ((cmd_code == LPDDR2_NVM_BUF_PROGRAM) || + (cmd_code == LPDDR2_NVM_BUF_OVERWRITE)) { + prg_buff_ofs = (map_read(map, + ow_reg_add(map, PRG_BUFFER_OFS))).x[0]; + for (i = 0; i < cmd_mpr; i++) { + map_write(map, build_map_word(buf[i]), map->pfow_base + + prg_buff_ofs + i); + } + } + + /* Command Execute */ + map_write(map, exec_cmd, ow_reg_add(map, CMD_EXEC_OFS)); + if (pcm_data->bus_width == 0x0004) /* 2x16 devices stacked */ + map_write(map, exec_cmd, ow_reg_add(map, CMD_EXEC_OFS) + 2); + + /* Status Register Check */ + do { + sr = map_read(map, ow_reg_add(map, STATUS_REG_OFS)); + status_reg = sr.x[0]; + if (pcm_data->bus_width == 0x0004) {/* 2x16 devices stacked */ + sr = map_read(map, ow_reg_add(map, + STATUS_REG_OFS) + 2); + status_reg += sr.x[0] << 16; + } + } while ((status_reg & sr_ok_datamask) != sr_ok_datamask); + + return (((status_reg & sr_ok_datamask) == sr_ok_datamask) ? 0 : -EIO); +} + +/* + * Execute lpddr2-nvm operations @ block level + */ +static int lpddr2_nvm_do_block_op(struct mtd_info *mtd, loff_t start_add, + uint64_t len, u_char block_op) +{ + struct map_info *map = mtd->priv; + u_long add, end_add; + int ret = 0; + + mutex_lock(&lpdd2_nvm_mutex); + + ow_enable(map); + + add = start_add; + end_add = add + len; + + do { + ret = lpddr2_nvm_do_op(map, block_op, 0x00, add, add, NULL); + if (ret) + goto out; + add += mtd->erasesize; + } while (add < end_add); + +out: + ow_disable(map); + mutex_unlock(&lpdd2_nvm_mutex); + return ret; +} + +/* + * verify presence of PFOW string + */ +static int lpddr2_nvm_pfow_present(struct map_info *map) +{ + map_word pfow_val[4]; + unsigned int found = 1; + + mutex_lock(&lpdd2_nvm_mutex); + + ow_enable(map); + + /* Load string from array */ + pfow_val[0] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_P)); + pfow_val[1] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_F)); + pfow_val[2] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_O)); + pfow_val[3] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_W)); + + /* Verify the string loaded vs expected */ + if (!map_word_equal(map, build_map_word('P'), pfow_val[0])) + found = 0; + if (!map_word_equal(map, build_map_word('F'), pfow_val[1])) + found = 0; + if (!map_word_equal(map, build_map_word('O'), pfow_val[2])) + found = 0; + if (!map_word_equal(map, build_map_word('W'), pfow_val[3])) + found = 0; + + ow_disable(map); + + mutex_unlock(&lpdd2_nvm_mutex); + + return found; +} + +/* + * lpddr2_nvm driver read method + */ +static int lpddr2_nvm_read(struct mtd_info *mtd, loff_t start_add, + size_t len, size_t *retlen, u_char *buf) +{ + struct map_info *map = mtd->priv; + + mutex_lock(&lpdd2_nvm_mutex); + + *retlen = len; + + map_copy_from(map, buf, start_add, *retlen); + + mutex_unlock(&lpdd2_nvm_mutex); + return 0; +} + +/* + * lpddr2_nvm driver write method + */ +static int lpddr2_nvm_write(struct mtd_info *mtd, loff_t start_add, + size_t len, size_t *retlen, const u_char *buf) +{ + struct map_info *map = mtd->priv; + struct pcm_int_data *pcm_data = map->fldrv_priv; + u_long add, current_len, tot_len, target_len, my_data; + u_char *write_buf = (u_char *)buf; + int ret = 0; + + mutex_lock(&lpdd2_nvm_mutex); + + ow_enable(map); + + /* Set start value for the variables */ + add = start_add; + target_len = len; + tot_len = 0; + + while (tot_len < target_len) { + if (!(IS_ALIGNED(add, mtd->writesize))) { /* do sw program */ + my_data = write_buf[tot_len]; + my_data += (write_buf[tot_len+1]) << 8; + if (pcm_data->bus_width == 0x0004) {/* 2x16 devices */ + my_data += (write_buf[tot_len+2]) << 16; + my_data += (write_buf[tot_len+3]) << 24; + } + ret = lpddr2_nvm_do_op(map, LPDDR2_NVM_SW_OVERWRITE, + my_data, add, 0x00, NULL); + if (ret) + goto out; + + add += pcm_data->bus_width; + tot_len += pcm_data->bus_width; + } else { /* do buffer program */ + current_len = min(target_len - tot_len, + (u_long) mtd->writesize); + ret = lpddr2_nvm_do_op(map, LPDDR2_NVM_BUF_OVERWRITE, + 0x00, add, current_len, write_buf + tot_len); + if (ret) + goto out; + + add += current_len; + tot_len += current_len; + } + } + +out: + *retlen = tot_len; + ow_disable(map); + mutex_unlock(&lpdd2_nvm_mutex); + return ret; +} + +/* + * lpddr2_nvm driver erase method + */ +static int lpddr2_nvm_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + int ret = lpddr2_nvm_do_block_op(mtd, instr->addr, instr->len, + LPDDR2_NVM_ERASE); + if (!ret) { + instr->state = MTD_ERASE_DONE; + mtd_erase_callback(instr); + } + + return ret; +} + +/* + * lpddr2_nvm driver unlock method + */ +static int lpddr2_nvm_unlock(struct mtd_info *mtd, loff_t start_add, + uint64_t len) +{ + return lpddr2_nvm_do_block_op(mtd, start_add, len, LPDDR2_NVM_UNLOCK); +} + +/* + * lpddr2_nvm driver lock method + */ +static int lpddr2_nvm_lock(struct mtd_info *mtd, loff_t start_add, + uint64_t len) +{ + return lpddr2_nvm_do_block_op(mtd, start_add, len, LPDDR2_NVM_LOCK); +} + +/* + * lpddr2_nvm driver probe method + */ +static int lpddr2_nvm_probe(struct platform_device *pdev) +{ + struct map_info *map; + struct mtd_info *mtd; + struct resource *add_range; + struct resource *control_regs; + struct pcm_int_data *pcm_data; + + /* Allocate memory control_regs data structures */ + pcm_data = devm_kzalloc(&pdev->dev, sizeof(*pcm_data), GFP_KERNEL); + if (!pcm_data) + return -ENOMEM; + + pcm_data->bus_width = BUS_WIDTH; + + /* Allocate memory for map_info & mtd_info data structures */ + map = devm_kzalloc(&pdev->dev, sizeof(*map), GFP_KERNEL); + if (!map) + return -ENOMEM; + + mtd = devm_kzalloc(&pdev->dev, sizeof(*mtd), GFP_KERNEL); + if (!mtd) + return -ENOMEM; + + /* lpddr2_nvm address range */ + add_range = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + /* Populate map_info data structure */ + *map = (struct map_info) { + .virt = devm_ioremap_resource(&pdev->dev, add_range), + .name = pdev->dev.init_name, + .phys = add_range->start, + .size = resource_size(add_range), + .bankwidth = pcm_data->bus_width / 2, + .pfow_base = OW_BASE_ADDRESS, + .fldrv_priv = pcm_data, + }; + if (IS_ERR(map->virt)) + return PTR_ERR(map->virt); + + simple_map_init(map); /* fill with default methods */ + + control_regs = platform_get_resource(pdev, IORESOURCE_MEM, 1); + pcm_data->ctl_regs = devm_ioremap_resource(&pdev->dev, control_regs); + if (IS_ERR(pcm_data->ctl_regs)) + return PTR_ERR(pcm_data->ctl_regs); + + /* Populate mtd_info data structure */ + *mtd = (struct mtd_info) { + .name = pdev->dev.init_name, + .type = MTD_RAM, + .priv = map, + .size = resource_size(add_range), + .erasesize = ERASE_BLOCKSIZE * pcm_data->bus_width, + .writesize = 1, + .writebufsize = WRITE_BUFFSIZE * pcm_data->bus_width, + .flags = (MTD_CAP_NVRAM | MTD_POWERUP_LOCK), + ._read = lpddr2_nvm_read, + ._write = lpddr2_nvm_write, + ._erase = lpddr2_nvm_erase, + ._unlock = lpddr2_nvm_unlock, + ._lock = lpddr2_nvm_lock, + }; + + /* Verify the presence of the device looking for PFOW string */ + if (!lpddr2_nvm_pfow_present(map)) { + pr_err("device not recognized\n"); + return -EINVAL; + } + /* Parse partitions and register the MTD device */ + return mtd_device_parse_register(mtd, NULL, NULL, NULL, 0); +} + +/* + * lpddr2_nvm driver remove method + */ +static int lpddr2_nvm_remove(struct platform_device *pdev) +{ + return mtd_device_unregister(dev_get_drvdata(&pdev->dev)); +} + +/* Initialize platform_driver data structure for lpddr2_nvm */ +static struct platform_driver lpddr2_nvm_drv = { + .driver = { + .name = "lpddr2_nvm", + }, + .probe = lpddr2_nvm_probe, + .remove = lpddr2_nvm_remove, +}; + +module_platform_driver(lpddr2_nvm_drv); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Vincenzo Aliberti <vincenzo.aliberti@gmail.com>"); +MODULE_DESCRIPTION("MTD driver for LPDDR2-NVM PCM memories"); diff --git a/drivers/mtd/lpddr/lpddr_cmds.c b/drivers/mtd/lpddr/lpddr_cmds.c new file mode 100644 index 00000000000..018c75faadb --- /dev/null +++ b/drivers/mtd/lpddr/lpddr_cmds.c @@ -0,0 +1,751 @@ +/* + * LPDDR flash memory device operations. This module provides read, write, + * erase, lock/unlock support for LPDDR flash memories + * (C) 2008 Korolev Alexey <akorolev@infradead.org> + * (C) 2008 Vasiliy Leonenko <vasiliy.leonenko@gmail.com> + * Many thanks to Roman Borisov for initial enabling + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * TODO: + * Implement VPP management + * Implement XIP support + * Implement OTP support + */ +#include <linux/mtd/pfow.h> +#include <linux/mtd/qinfo.h> +#include <linux/slab.h> +#include <linux/module.h> + +static int lpddr_read(struct mtd_info *mtd, loff_t adr, size_t len, + size_t *retlen, u_char *buf); +static int lpddr_write_buffers(struct mtd_info *mtd, loff_t to, + size_t len, size_t *retlen, const u_char *buf); +static int lpddr_writev(struct mtd_info *mtd, const struct kvec *vecs, + unsigned long count, loff_t to, size_t *retlen); +static int lpddr_erase(struct mtd_info *mtd, struct erase_info *instr); +static int lpddr_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len); +static int lpddr_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len); +static int lpddr_point(struct mtd_info *mtd, loff_t adr, size_t len, + size_t *retlen, void **mtdbuf, resource_size_t *phys); +static int lpddr_unpoint(struct mtd_info *mtd, loff_t adr, size_t len); +static int get_chip(struct map_info *map, struct flchip *chip, int mode); +static int chip_ready(struct map_info *map, struct flchip *chip, int mode); +static void put_chip(struct map_info *map, struct flchip *chip); + +struct mtd_info *lpddr_cmdset(struct map_info *map) +{ + struct lpddr_private *lpddr = map->fldrv_priv; + struct flchip_shared *shared; + struct flchip *chip; + struct mtd_info *mtd; + int numchips; + int i, j; + + mtd = kzalloc(sizeof(*mtd), GFP_KERNEL); + if (!mtd) + return NULL; + mtd->priv = map; + mtd->type = MTD_NORFLASH; + + /* Fill in the default mtd operations */ + mtd->_read = lpddr_read; + mtd->type = MTD_NORFLASH; + mtd->flags = MTD_CAP_NORFLASH; + mtd->flags &= ~MTD_BIT_WRITEABLE; + mtd->_erase = lpddr_erase; + mtd->_write = lpddr_write_buffers; + mtd->_writev = lpddr_writev; + mtd->_lock = lpddr_lock; + mtd->_unlock = lpddr_unlock; + if (map_is_linear(map)) { + mtd->_point = lpddr_point; + mtd->_unpoint = lpddr_unpoint; + } + mtd->size = 1 << lpddr->qinfo->DevSizeShift; + mtd->erasesize = 1 << lpddr->qinfo->UniformBlockSizeShift; + mtd->writesize = 1 << lpddr->qinfo->BufSizeShift; + + shared = kmalloc(sizeof(struct flchip_shared) * lpddr->numchips, + GFP_KERNEL); + if (!shared) { + kfree(lpddr); + kfree(mtd); + return NULL; + } + + chip = &lpddr->chips[0]; + numchips = lpddr->numchips / lpddr->qinfo->HWPartsNum; + for (i = 0; i < numchips; i++) { + shared[i].writing = shared[i].erasing = NULL; + mutex_init(&shared[i].lock); + for (j = 0; j < lpddr->qinfo->HWPartsNum; j++) { + *chip = lpddr->chips[i]; + chip->start += j << lpddr->chipshift; + chip->oldstate = chip->state = FL_READY; + chip->priv = &shared[i]; + /* those should be reset too since + they create memory references. */ + init_waitqueue_head(&chip->wq); + mutex_init(&chip->mutex); + chip++; + } + } + + return mtd; +} +EXPORT_SYMBOL(lpddr_cmdset); + +static int wait_for_ready(struct map_info *map, struct flchip *chip, + unsigned int chip_op_time) +{ + unsigned int timeo, reset_timeo, sleep_time; + unsigned int dsr; + flstate_t chip_state = chip->state; + int ret = 0; + + /* set our timeout to 8 times the expected delay */ + timeo = chip_op_time * 8; + if (!timeo) + timeo = 500000; + reset_timeo = timeo; + sleep_time = chip_op_time / 2; + + for (;;) { + dsr = CMDVAL(map_read(map, map->pfow_base + PFOW_DSR)); + if (dsr & DSR_READY_STATUS) + break; + if (!timeo) { + printk(KERN_ERR "%s: Flash timeout error state %d \n", + map->name, chip_state); + ret = -ETIME; + break; + } + + /* OK Still waiting. Drop the lock, wait a while and retry. */ + mutex_unlock(&chip->mutex); + if (sleep_time >= 1000000/HZ) { + /* + * Half of the normal delay still remaining + * can be performed with a sleeping delay instead + * of busy waiting. + */ + msleep(sleep_time/1000); + timeo -= sleep_time; + sleep_time = 1000000/HZ; + } else { + udelay(1); + cond_resched(); + timeo--; + } + mutex_lock(&chip->mutex); + + while (chip->state != chip_state) { + /* Someone's suspended the operation: sleep */ + DECLARE_WAITQUEUE(wait, current); + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + mutex_lock(&chip->mutex); + } + if (chip->erase_suspended || chip->write_suspended) { + /* Suspend has occurred while sleep: reset timeout */ + timeo = reset_timeo; + chip->erase_suspended = chip->write_suspended = 0; + } + } + /* check status for errors */ + if (dsr & DSR_ERR) { + /* Clear DSR*/ + map_write(map, CMD(~(DSR_ERR)), map->pfow_base + PFOW_DSR); + printk(KERN_WARNING"%s: Bad status on wait: 0x%x \n", + map->name, dsr); + print_drs_error(dsr); + ret = -EIO; + } + chip->state = FL_READY; + return ret; +} + +static int get_chip(struct map_info *map, struct flchip *chip, int mode) +{ + int ret; + DECLARE_WAITQUEUE(wait, current); + + retry: + if (chip->priv && (mode == FL_WRITING || mode == FL_ERASING) + && chip->state != FL_SYNCING) { + /* + * OK. We have possibility for contension on the write/erase + * operations which are global to the real chip and not per + * partition. So let's fight it over in the partition which + * currently has authority on the operation. + * + * The rules are as follows: + * + * - any write operation must own shared->writing. + * + * - any erase operation must own _both_ shared->writing and + * shared->erasing. + * + * - contension arbitration is handled in the owner's context. + * + * The 'shared' struct can be read and/or written only when + * its lock is taken. + */ + struct flchip_shared *shared = chip->priv; + struct flchip *contender; + mutex_lock(&shared->lock); + contender = shared->writing; + if (contender && contender != chip) { + /* + * The engine to perform desired operation on this + * partition is already in use by someone else. + * Let's fight over it in the context of the chip + * currently using it. If it is possible to suspend, + * that other partition will do just that, otherwise + * it'll happily send us to sleep. In any case, when + * get_chip returns success we're clear to go ahead. + */ + ret = mutex_trylock(&contender->mutex); + mutex_unlock(&shared->lock); + if (!ret) + goto retry; + mutex_unlock(&chip->mutex); + ret = chip_ready(map, contender, mode); + mutex_lock(&chip->mutex); + + if (ret == -EAGAIN) { + mutex_unlock(&contender->mutex); + goto retry; + } + if (ret) { + mutex_unlock(&contender->mutex); + return ret; + } + mutex_lock(&shared->lock); + + /* We should not own chip if it is already in FL_SYNCING + * state. Put contender and retry. */ + if (chip->state == FL_SYNCING) { + put_chip(map, contender); + mutex_unlock(&contender->mutex); + goto retry; + } + mutex_unlock(&contender->mutex); + } + + /* Check if we have suspended erase on this chip. + Must sleep in such a case. */ + if (mode == FL_ERASING && shared->erasing + && shared->erasing->oldstate == FL_ERASING) { + mutex_unlock(&shared->lock); + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + mutex_lock(&chip->mutex); + goto retry; + } + + /* We now own it */ + shared->writing = chip; + if (mode == FL_ERASING) + shared->erasing = chip; + mutex_unlock(&shared->lock); + } + + ret = chip_ready(map, chip, mode); + if (ret == -EAGAIN) + goto retry; + + return ret; +} + +static int chip_ready(struct map_info *map, struct flchip *chip, int mode) +{ + struct lpddr_private *lpddr = map->fldrv_priv; + int ret = 0; + DECLARE_WAITQUEUE(wait, current); + + /* Prevent setting state FL_SYNCING for chip in suspended state. */ + if (FL_SYNCING == mode && FL_READY != chip->oldstate) + goto sleep; + + switch (chip->state) { + case FL_READY: + case FL_JEDEC_QUERY: + return 0; + + case FL_ERASING: + if (!lpddr->qinfo->SuspEraseSupp || + !(mode == FL_READY || mode == FL_POINT)) + goto sleep; + + map_write(map, CMD(LPDDR_SUSPEND), + map->pfow_base + PFOW_PROGRAM_ERASE_SUSPEND); + chip->oldstate = FL_ERASING; + chip->state = FL_ERASE_SUSPENDING; + ret = wait_for_ready(map, chip, 0); + if (ret) { + /* Oops. something got wrong. */ + /* Resume and pretend we weren't here. */ + put_chip(map, chip); + printk(KERN_ERR "%s: suspend operation failed." + "State may be wrong \n", map->name); + return -EIO; + } + chip->erase_suspended = 1; + chip->state = FL_READY; + return 0; + /* Erase suspend */ + case FL_POINT: + /* Only if there's no operation suspended... */ + if (mode == FL_READY && chip->oldstate == FL_READY) + return 0; + + default: +sleep: + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + mutex_lock(&chip->mutex); + return -EAGAIN; + } +} + +static void put_chip(struct map_info *map, struct flchip *chip) +{ + if (chip->priv) { + struct flchip_shared *shared = chip->priv; + mutex_lock(&shared->lock); + if (shared->writing == chip && chip->oldstate == FL_READY) { + /* We own the ability to write, but we're done */ + shared->writing = shared->erasing; + if (shared->writing && shared->writing != chip) { + /* give back the ownership */ + struct flchip *loaner = shared->writing; + mutex_lock(&loaner->mutex); + mutex_unlock(&shared->lock); + mutex_unlock(&chip->mutex); + put_chip(map, loaner); + mutex_lock(&chip->mutex); + mutex_unlock(&loaner->mutex); + wake_up(&chip->wq); + return; + } + shared->erasing = NULL; + shared->writing = NULL; + } else if (shared->erasing == chip && shared->writing != chip) { + /* + * We own the ability to erase without the ability + * to write, which means the erase was suspended + * and some other partition is currently writing. + * Don't let the switch below mess things up since + * we don't have ownership to resume anything. + */ + mutex_unlock(&shared->lock); + wake_up(&chip->wq); + return; + } + mutex_unlock(&shared->lock); + } + + switch (chip->oldstate) { + case FL_ERASING: + map_write(map, CMD(LPDDR_RESUME), + map->pfow_base + PFOW_COMMAND_CODE); + map_write(map, CMD(LPDDR_START_EXECUTION), + map->pfow_base + PFOW_COMMAND_EXECUTE); + chip->oldstate = FL_READY; + chip->state = FL_ERASING; + break; + case FL_READY: + break; + default: + printk(KERN_ERR "%s: put_chip() called with oldstate %d!\n", + map->name, chip->oldstate); + } + wake_up(&chip->wq); +} + +static int do_write_buffer(struct map_info *map, struct flchip *chip, + unsigned long adr, const struct kvec **pvec, + unsigned long *pvec_seek, int len) +{ + struct lpddr_private *lpddr = map->fldrv_priv; + map_word datum; + int ret, wbufsize, word_gap, words; + const struct kvec *vec; + unsigned long vec_seek; + unsigned long prog_buf_ofs; + + wbufsize = 1 << lpddr->qinfo->BufSizeShift; + + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, FL_WRITING); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + /* Figure out the number of words to write */ + word_gap = (-adr & (map_bankwidth(map)-1)); + words = (len - word_gap + map_bankwidth(map) - 1) / map_bankwidth(map); + if (!word_gap) { + words--; + } else { + word_gap = map_bankwidth(map) - word_gap; + adr -= word_gap; + datum = map_word_ff(map); + } + /* Write data */ + /* Get the program buffer offset from PFOW register data first*/ + prog_buf_ofs = map->pfow_base + CMDVAL(map_read(map, + map->pfow_base + PFOW_PROGRAM_BUFFER_OFFSET)); + vec = *pvec; + vec_seek = *pvec_seek; + do { + int n = map_bankwidth(map) - word_gap; + + if (n > vec->iov_len - vec_seek) + n = vec->iov_len - vec_seek; + if (n > len) + n = len; + + if (!word_gap && (len < map_bankwidth(map))) + datum = map_word_ff(map); + + datum = map_word_load_partial(map, datum, + vec->iov_base + vec_seek, word_gap, n); + + len -= n; + word_gap += n; + if (!len || word_gap == map_bankwidth(map)) { + map_write(map, datum, prog_buf_ofs); + prog_buf_ofs += map_bankwidth(map); + word_gap = 0; + } + + vec_seek += n; + if (vec_seek == vec->iov_len) { + vec++; + vec_seek = 0; + } + } while (len); + *pvec = vec; + *pvec_seek = vec_seek; + + /* GO GO GO */ + send_pfow_command(map, LPDDR_BUFF_PROGRAM, adr, wbufsize, NULL); + chip->state = FL_WRITING; + ret = wait_for_ready(map, chip, (1<<lpddr->qinfo->ProgBufferTime)); + if (ret) { + printk(KERN_WARNING"%s Buffer program error: %d at %lx; \n", + map->name, ret, adr); + goto out; + } + + out: put_chip(map, chip); + mutex_unlock(&chip->mutex); + return ret; +} + +static int do_erase_oneblock(struct mtd_info *mtd, loff_t adr) +{ + struct map_info *map = mtd->priv; + struct lpddr_private *lpddr = map->fldrv_priv; + int chipnum = adr >> lpddr->chipshift; + struct flchip *chip = &lpddr->chips[chipnum]; + int ret; + + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, FL_ERASING); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + send_pfow_command(map, LPDDR_BLOCK_ERASE, adr, 0, NULL); + chip->state = FL_ERASING; + ret = wait_for_ready(map, chip, (1<<lpddr->qinfo->BlockEraseTime)*1000); + if (ret) { + printk(KERN_WARNING"%s Erase block error %d at : %llx\n", + map->name, ret, adr); + goto out; + } + out: put_chip(map, chip); + mutex_unlock(&chip->mutex); + return ret; +} + +static int lpddr_read(struct mtd_info *mtd, loff_t adr, size_t len, + size_t *retlen, u_char *buf) +{ + struct map_info *map = mtd->priv; + struct lpddr_private *lpddr = map->fldrv_priv; + int chipnum = adr >> lpddr->chipshift; + struct flchip *chip = &lpddr->chips[chipnum]; + int ret = 0; + + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, FL_READY); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + + map_copy_from(map, buf, adr, len); + *retlen = len; + + put_chip(map, chip); + mutex_unlock(&chip->mutex); + return ret; +} + +static int lpddr_point(struct mtd_info *mtd, loff_t adr, size_t len, + size_t *retlen, void **mtdbuf, resource_size_t *phys) +{ + struct map_info *map = mtd->priv; + struct lpddr_private *lpddr = map->fldrv_priv; + int chipnum = adr >> lpddr->chipshift; + unsigned long ofs, last_end = 0; + struct flchip *chip = &lpddr->chips[chipnum]; + int ret = 0; + + if (!map->virt) + return -EINVAL; + + /* ofs: offset within the first chip that the first read should start */ + ofs = adr - (chipnum << lpddr->chipshift); + *mtdbuf = (void *)map->virt + chip->start + ofs; + + while (len) { + unsigned long thislen; + + if (chipnum >= lpddr->numchips) + break; + + /* We cannot point across chips that are virtually disjoint */ + if (!last_end) + last_end = chip->start; + else if (chip->start != last_end) + break; + + if ((len + ofs - 1) >> lpddr->chipshift) + thislen = (1<<lpddr->chipshift) - ofs; + else + thislen = len; + /* get the chip */ + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, FL_POINT); + mutex_unlock(&chip->mutex); + if (ret) + break; + + chip->state = FL_POINT; + chip->ref_point_counter++; + *retlen += thislen; + len -= thislen; + + ofs = 0; + last_end += 1 << lpddr->chipshift; + chipnum++; + chip = &lpddr->chips[chipnum]; + } + return 0; +} + +static int lpddr_unpoint (struct mtd_info *mtd, loff_t adr, size_t len) +{ + struct map_info *map = mtd->priv; + struct lpddr_private *lpddr = map->fldrv_priv; + int chipnum = adr >> lpddr->chipshift, err = 0; + unsigned long ofs; + + /* ofs: offset within the first chip that the first read should start */ + ofs = adr - (chipnum << lpddr->chipshift); + + while (len) { + unsigned long thislen; + struct flchip *chip; + + chip = &lpddr->chips[chipnum]; + if (chipnum >= lpddr->numchips) + break; + + if ((len + ofs - 1) >> lpddr->chipshift) + thislen = (1<<lpddr->chipshift) - ofs; + else + thislen = len; + + mutex_lock(&chip->mutex); + if (chip->state == FL_POINT) { + chip->ref_point_counter--; + if (chip->ref_point_counter == 0) + chip->state = FL_READY; + } else { + printk(KERN_WARNING "%s: Warning: unpoint called on non" + "pointed region\n", map->name); + err = -EINVAL; + } + + put_chip(map, chip); + mutex_unlock(&chip->mutex); + + len -= thislen; + ofs = 0; + chipnum++; + } + + return err; +} + +static int lpddr_write_buffers(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct kvec vec; + + vec.iov_base = (void *) buf; + vec.iov_len = len; + + return lpddr_writev(mtd, &vec, 1, to, retlen); +} + + +static int lpddr_writev(struct mtd_info *mtd, const struct kvec *vecs, + unsigned long count, loff_t to, size_t *retlen) +{ + struct map_info *map = mtd->priv; + struct lpddr_private *lpddr = map->fldrv_priv; + int ret = 0; + int chipnum; + unsigned long ofs, vec_seek, i; + int wbufsize = 1 << lpddr->qinfo->BufSizeShift; + size_t len = 0; + + for (i = 0; i < count; i++) + len += vecs[i].iov_len; + + if (!len) + return 0; + + chipnum = to >> lpddr->chipshift; + + ofs = to; + vec_seek = 0; + + do { + /* We must not cross write block boundaries */ + int size = wbufsize - (ofs & (wbufsize-1)); + + if (size > len) + size = len; + + ret = do_write_buffer(map, &lpddr->chips[chipnum], + ofs, &vecs, &vec_seek, size); + if (ret) + return ret; + + ofs += size; + (*retlen) += size; + len -= size; + + /* Be nice and reschedule with the chip in a usable + * state for other processes */ + cond_resched(); + + } while (len); + + return 0; +} + +static int lpddr_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + unsigned long ofs, len; + int ret; + struct map_info *map = mtd->priv; + struct lpddr_private *lpddr = map->fldrv_priv; + int size = 1 << lpddr->qinfo->UniformBlockSizeShift; + + ofs = instr->addr; + len = instr->len; + + while (len > 0) { + ret = do_erase_oneblock(mtd, ofs); + if (ret) + return ret; + ofs += size; + len -= size; + } + instr->state = MTD_ERASE_DONE; + mtd_erase_callback(instr); + + return 0; +} + +#define DO_XXLOCK_LOCK 1 +#define DO_XXLOCK_UNLOCK 2 +static int do_xxlock(struct mtd_info *mtd, loff_t adr, uint32_t len, int thunk) +{ + int ret = 0; + struct map_info *map = mtd->priv; + struct lpddr_private *lpddr = map->fldrv_priv; + int chipnum = adr >> lpddr->chipshift; + struct flchip *chip = &lpddr->chips[chipnum]; + + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, FL_LOCKING); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + + if (thunk == DO_XXLOCK_LOCK) { + send_pfow_command(map, LPDDR_LOCK_BLOCK, adr, adr + len, NULL); + chip->state = FL_LOCKING; + } else if (thunk == DO_XXLOCK_UNLOCK) { + send_pfow_command(map, LPDDR_UNLOCK_BLOCK, adr, adr + len, NULL); + chip->state = FL_UNLOCKING; + } else + BUG(); + + ret = wait_for_ready(map, chip, 1); + if (ret) { + printk(KERN_ERR "%s: block unlock error status %d \n", + map->name, ret); + goto out; + } +out: put_chip(map, chip); + mutex_unlock(&chip->mutex); + return ret; +} + +static int lpddr_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + return do_xxlock(mtd, ofs, len, DO_XXLOCK_LOCK); +} + +static int lpddr_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + return do_xxlock(mtd, ofs, len, DO_XXLOCK_UNLOCK); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Alexey Korolev <akorolev@infradead.org>"); +MODULE_DESCRIPTION("MTD driver for LPDDR flash chips"); diff --git a/drivers/mtd/lpddr/qinfo_probe.c b/drivers/mtd/lpddr/qinfo_probe.c new file mode 100644 index 00000000000..69f2112340b --- /dev/null +++ b/drivers/mtd/lpddr/qinfo_probe.c @@ -0,0 +1,249 @@ +/* + * Probing flash chips with QINFO records. + * (C) 2008 Korolev Alexey <akorolev@infradead.org> + * (C) 2008 Vasiliy Leonenko <vasiliy.leonenko@gmail.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/interrupt.h> + +#include <linux/mtd/xip.h> +#include <linux/mtd/map.h> +#include <linux/mtd/pfow.h> +#include <linux/mtd/qinfo.h> + +static int lpddr_chip_setup(struct map_info *map, struct lpddr_private *lpddr); +struct mtd_info *lpddr_probe(struct map_info *map); +static struct lpddr_private *lpddr_probe_chip(struct map_info *map); +static int lpddr_pfow_present(struct map_info *map, + struct lpddr_private *lpddr); + +static struct qinfo_query_info qinfo_array[] = { + /* General device info */ + {0, 0, "DevSizeShift", "Device size 2^n bytes"}, + {0, 3, "BufSizeShift", "Program buffer size 2^n bytes"}, + /* Erase block information */ + {1, 1, "TotalBlocksNum", "Total number of blocks"}, + {1, 2, "UniformBlockSizeShift", "Uniform block size 2^n bytes"}, + /* Partition information */ + {2, 1, "HWPartsNum", "Number of hardware partitions"}, + /* Optional features */ + {5, 1, "SuspEraseSupp", "Suspend erase supported"}, + /* Operation typical time */ + {10, 0, "SingleWordProgTime", "Single word program 2^n u-sec"}, + {10, 1, "ProgBufferTime", "Program buffer write 2^n u-sec"}, + {10, 2, "BlockEraseTime", "Block erase 2^n m-sec"}, + {10, 3, "FullChipEraseTime", "Full chip erase 2^n m-sec"}, +}; + +static long lpddr_get_qinforec_pos(struct map_info *map, char *id_str) +{ + int qinfo_lines = ARRAY_SIZE(qinfo_array); + int i; + int bankwidth = map_bankwidth(map) * 8; + int major, minor; + + for (i = 0; i < qinfo_lines; i++) { + if (strcmp(id_str, qinfo_array[i].id_str) == 0) { + major = qinfo_array[i].major & ((1 << bankwidth) - 1); + minor = qinfo_array[i].minor & ((1 << bankwidth) - 1); + return minor | (major << bankwidth); + } + } + printk(KERN_ERR"%s qinfo id string is wrong! \n", map->name); + BUG(); + return -1; +} + +static uint16_t lpddr_info_query(struct map_info *map, char *id_str) +{ + unsigned int dsr, val; + int bits_per_chip = map_bankwidth(map) * 8; + unsigned long adr = lpddr_get_qinforec_pos(map, id_str); + int attempts = 20; + + /* Write a request for the PFOW record */ + map_write(map, CMD(LPDDR_INFO_QUERY), + map->pfow_base + PFOW_COMMAND_CODE); + map_write(map, CMD(adr & ((1 << bits_per_chip) - 1)), + map->pfow_base + PFOW_COMMAND_ADDRESS_L); + map_write(map, CMD(adr >> bits_per_chip), + map->pfow_base + PFOW_COMMAND_ADDRESS_H); + map_write(map, CMD(LPDDR_START_EXECUTION), + map->pfow_base + PFOW_COMMAND_EXECUTE); + + while ((attempts--) > 0) { + dsr = CMDVAL(map_read(map, map->pfow_base + PFOW_DSR)); + if (dsr & DSR_READY_STATUS) + break; + udelay(10); + } + + val = CMDVAL(map_read(map, map->pfow_base + PFOW_COMMAND_DATA)); + return val; +} + +static int lpddr_pfow_present(struct map_info *map, struct lpddr_private *lpddr) +{ + map_word pfow_val[4]; + + /* Check identification string */ + pfow_val[0] = map_read(map, map->pfow_base + PFOW_QUERY_STRING_P); + pfow_val[1] = map_read(map, map->pfow_base + PFOW_QUERY_STRING_F); + pfow_val[2] = map_read(map, map->pfow_base + PFOW_QUERY_STRING_O); + pfow_val[3] = map_read(map, map->pfow_base + PFOW_QUERY_STRING_W); + + if (!map_word_equal(map, CMD('P'), pfow_val[0])) + goto out; + + if (!map_word_equal(map, CMD('F'), pfow_val[1])) + goto out; + + if (!map_word_equal(map, CMD('O'), pfow_val[2])) + goto out; + + if (!map_word_equal(map, CMD('W'), pfow_val[3])) + goto out; + + return 1; /* "PFOW" is found */ +out: + printk(KERN_WARNING"%s: PFOW string at 0x%lx is not found \n", + map->name, map->pfow_base); + return 0; +} + +static int lpddr_chip_setup(struct map_info *map, struct lpddr_private *lpddr) +{ + + lpddr->qinfo = kzalloc(sizeof(struct qinfo_chip), GFP_KERNEL); + if (!lpddr->qinfo) + return 0; + + /* Get the ManuID */ + lpddr->ManufactId = CMDVAL(map_read(map, map->pfow_base + PFOW_MANUFACTURER_ID)); + /* Get the DeviceID */ + lpddr->DevId = CMDVAL(map_read(map, map->pfow_base + PFOW_DEVICE_ID)); + /* read parameters from chip qinfo table */ + lpddr->qinfo->DevSizeShift = lpddr_info_query(map, "DevSizeShift"); + lpddr->qinfo->TotalBlocksNum = lpddr_info_query(map, "TotalBlocksNum"); + lpddr->qinfo->BufSizeShift = lpddr_info_query(map, "BufSizeShift"); + lpddr->qinfo->HWPartsNum = lpddr_info_query(map, "HWPartsNum"); + lpddr->qinfo->UniformBlockSizeShift = + lpddr_info_query(map, "UniformBlockSizeShift"); + lpddr->qinfo->SuspEraseSupp = lpddr_info_query(map, "SuspEraseSupp"); + lpddr->qinfo->SingleWordProgTime = + lpddr_info_query(map, "SingleWordProgTime"); + lpddr->qinfo->ProgBufferTime = lpddr_info_query(map, "ProgBufferTime"); + lpddr->qinfo->BlockEraseTime = lpddr_info_query(map, "BlockEraseTime"); + return 1; +} +static struct lpddr_private *lpddr_probe_chip(struct map_info *map) +{ + struct lpddr_private lpddr; + struct lpddr_private *retlpddr; + int numvirtchips; + + + if ((map->pfow_base + 0x1000) >= map->size) { + printk(KERN_NOTICE"%s Probe at base (0x%08lx) past the end of" + "the map(0x%08lx)\n", map->name, + (unsigned long)map->pfow_base, map->size - 1); + return NULL; + } + memset(&lpddr, 0, sizeof(struct lpddr_private)); + if (!lpddr_pfow_present(map, &lpddr)) + return NULL; + + if (!lpddr_chip_setup(map, &lpddr)) + return NULL; + + /* Ok so we found a chip */ + lpddr.chipshift = lpddr.qinfo->DevSizeShift; + lpddr.numchips = 1; + + numvirtchips = lpddr.numchips * lpddr.qinfo->HWPartsNum; + retlpddr = kzalloc(sizeof(struct lpddr_private) + + numvirtchips * sizeof(struct flchip), GFP_KERNEL); + if (!retlpddr) + return NULL; + + memcpy(retlpddr, &lpddr, sizeof(struct lpddr_private)); + + retlpddr->numchips = numvirtchips; + retlpddr->chipshift = retlpddr->qinfo->DevSizeShift - + __ffs(retlpddr->qinfo->HWPartsNum); + + return retlpddr; +} + +struct mtd_info *lpddr_probe(struct map_info *map) +{ + struct mtd_info *mtd = NULL; + struct lpddr_private *lpddr; + + /* First probe the map to see if we havecan open PFOW here */ + lpddr = lpddr_probe_chip(map); + if (!lpddr) + return NULL; + + map->fldrv_priv = lpddr; + mtd = lpddr_cmdset(map); + if (mtd) { + if (mtd->size > map->size) { + printk(KERN_WARNING "Reducing visibility of %ldKiB chip" + "to %ldKiB\n", (unsigned long)mtd->size >> 10, + (unsigned long)map->size >> 10); + mtd->size = map->size; + } + return mtd; + } + + kfree(lpddr->qinfo); + kfree(lpddr); + map->fldrv_priv = NULL; + return NULL; +} + +static struct mtd_chip_driver lpddr_chipdrv = { + .probe = lpddr_probe, + .name = "qinfo_probe", + .module = THIS_MODULE +}; + +static int __init lpddr_probe_init(void) +{ + register_mtd_chip_driver(&lpddr_chipdrv); + return 0; +} + +static void __exit lpddr_probe_exit(void) +{ + unregister_mtd_chip_driver(&lpddr_chipdrv); +} + +module_init(lpddr_probe_init); +module_exit(lpddr_probe_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Vasiliy Leonenko <vasiliy.leonenko@gmail.com>"); +MODULE_DESCRIPTION("Driver to probe qinfo flash chips"); + diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index 24747bdc3e1..21b2874a303 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -1,19 +1,16 @@ -# drivers/mtd/maps/Kconfig -# $Id: Kconfig,v 1.61 2005/11/07 11:14:26 gleixner Exp $ - menu "Mapping drivers for chip access" depends on MTD!=n + depends on HAS_IOMEM config MTD_COMPLEX_MAPPINGS bool "Support non-linear mappings of flash chips" - depends on MTD help This causes the chip drivers to allow for complicated paged mappings of flash chips. config MTD_PHYSMAP - tristate "CFI Flash device in physical memory map" - depends on MTD_CFI || MTD_JEDECPROBE || MTD_ROM + tristate "Flash device in physical memory map" + depends on MTD_CFI || MTD_JEDECPROBE || MTD_ROM || MTD_LPDDR help This provides a 'mapping' driver which allows the NOR Flash and ROM driver code to communicate with chips which are mapped @@ -22,21 +19,33 @@ config MTD_PHYSMAP particular board as well as the bus width, either statically with config options or at run-time. + To compile this driver as a module, choose M here: the + module will be called physmap. + +config MTD_PHYSMAP_COMPAT + bool "Physmap compat support" + depends on MTD_PHYSMAP + default n + help + Setup a simple mapping via the Kconfig options. Normally the + physmap configuration options are done via your board's + resource file. + + If unsure, say N here. + config MTD_PHYSMAP_START hex "Physical start address of flash mapping" - depends on MTD_PHYSMAP + depends on MTD_PHYSMAP_COMPAT default "0x8000000" help This is the physical memory location at which the flash chips are mapped on your particular target board. Refer to the memory map which should hopefully be in the documentation for your board. - Ignore this option if you use run-time physmap configuration - (i.e., run-time calling physmap_configure()). config MTD_PHYSMAP_LEN hex "Physical length of flash mapping" - depends on MTD_PHYSMAP + depends on MTD_PHYSMAP_COMPAT default "0" help This is the total length of the mapping of the flash chips on @@ -45,40 +54,61 @@ config MTD_PHYSMAP_LEN than the total amount of flash present. Refer to the memory map which should hopefully be in the documentation for your board. - Ignore this option if you use run-time physmap configuration - (i.e., run-time calling physmap_configure()). config MTD_PHYSMAP_BANKWIDTH int "Bank width in octets" - depends on MTD_PHYSMAP + depends on MTD_PHYSMAP_COMPAT default "2" help This is the total width of the data bus of the flash devices in octets. For example, if you have a data bus width of 32 - bits, you would set the bus width octect value to 4. This is + bits, you would set the bus width octet value to 4. This is used internally by the CFI drivers. - Ignore this option if you use run-time physmap configuration - (i.e., run-time calling physmap_configure()). + +config MTD_PHYSMAP_OF + tristate "Memory device in physical memory map based on OF description" + depends on OF && (MTD_CFI || MTD_JEDECPROBE || MTD_ROM || MTD_RAM) + help + This provides a 'mapping' driver which allows the NOR Flash, ROM + and RAM driver code to communicate with chips which are mapped + physically into the CPU's memory. The mapping description here is + taken from OF device tree. + +config MTD_PMC_MSP_EVM + tristate "CFI Flash device mapped on PMC-Sierra MSP" + depends on PMC_MSP && MTD_CFI + help + This provides a 'mapping' driver which supports the way + in which user-programmable flash chips are connected on the + PMC-Sierra MSP eval/demo boards. + +choice + prompt "Maximum mappable memory available for flash IO" + depends on MTD_PMC_MSP_EVM + default MSP_FLASH_MAP_LIMIT_32M + +config MSP_FLASH_MAP_LIMIT_32M + bool "32M" + +endchoice + +config MSP_FLASH_MAP_LIMIT + hex + default "0x02000000" + depends on MSP_FLASH_MAP_LIMIT_32M config MTD_SUN_UFLASH tristate "Sun Microsystems userflash support" - depends on SPARC && MTD_CFI + depends on SPARC && MTD_CFI && PCI help This provides a 'mapping' driver which supports the way in which user-programmable flash chips are connected on various Sun Microsystems boardsets. This driver will require CFI support in the kernel, so if you did not enable CFI previously, do that now. -config MTD_PNC2000 - tristate "CFI Flash device mapped on Photron PNC-2000" - depends on X86 && MTD_CFI && MTD_PARTITIONS - help - PNC-2000 is the name of Network Camera product from PHOTRON - Ltd. in Japan. It uses CFI-compliant flash. - config MTD_SC520CDP tristate "CFI Flash device mapped on AMD SC520 CDP" - depends on X86 && MTD_CFI && MTD_CONCAT + depends on (MELAN || COMPILE_TEST) && MTD_CFI help The SC520 CDP board has two banks of CFI-compliant chips and one Dual-in-line JEDEC chip. This 'mapping' driver supports that @@ -86,7 +116,7 @@ config MTD_SC520CDP config MTD_NETSC520 tristate "CFI Flash device mapped on AMD NetSc520" - depends on X86 && MTD_CFI && MTD_PARTITIONS + depends on (MELAN || COMPILE_TEST) && MTD_CFI help This enables access routines for the flash chips on the AMD NetSc520 demonstration board. If you have one of these boards and would like @@ -94,8 +124,7 @@ config MTD_NETSC520 config MTD_TS5500 tristate "JEDEC Flash device mapped on Technologic Systems TS-5500" - depends on X86 - select MTD_PARTITIONS + depends on TS5500 || COMPILE_TEST select MTD_JEDECPROBE select MTD_CFI_AMDSTD help @@ -113,7 +142,7 @@ config MTD_TS5500 config MTD_SBC_GXX tristate "CFI Flash device mapped on Arcom SBC-GXx boards" - depends on X86 && MTD_CFI_INTELEXT && MTD_PARTITIONS && MTD_COMPLEX_MAPPINGS + depends on X86 && MTD_CFI_INTELEXT && MTD_COMPLEX_MAPPINGS help This provides a driver for the on-board flash of Arcom Control Systems' SBC-GXn family of boards, formerly known as SBC-MediaGX. @@ -122,42 +151,15 @@ config MTD_SBC_GXX More info at <http://www.arcomcontrols.com/products/icp/pc104/processors/SBC_GX1.htm>. -config MTD_LUBBOCK - tristate "CFI Flash device mapped on Intel Lubbock XScale eval board" - depends on ARCH_LUBBOCK && MTD_CFI_INTELEXT && MTD_PARTITIONS - help - This provides a driver for the on-board flash of the Intel - 'Lubbock' XScale evaluation board. - -config MTD_MAINSTONE - tristate "CFI Flash device mapped on Intel Mainstone XScale eval board" - depends on MACH_MAINSTONE && MTD_CFI_INTELEXT - select MTD_PARTITIONS - help - This provides a driver for the on-board flash of the Intel - 'Mainstone PXA27x evaluation board. - -config MTD_OCTAGON - tristate "JEDEC Flash device mapped on Octagon 5066 SBC" - depends on X86 && MTD_JEDEC && MTD_COMPLEX_MAPPINGS +config MTD_PXA2XX + tristate "CFI Flash device mapped on Intel XScale PXA2xx based boards" + depends on (PXA25x || PXA27x) && MTD_CFI_INTELEXT help - This provides a 'mapping' driver which supports the way in which - the flash chips are connected in the Octagon-5066 Single Board - Computer. More information on the board is available at - <http://www.octagonsystems.com/CPUpages/5066.html>. - -config MTD_VMAX - tristate "JEDEC Flash device mapped on Tempustech VMAX SBC301" - depends on X86 && MTD_JEDEC && MTD_COMPLEX_MAPPINGS - help - This provides a 'mapping' driver which supports the way in which - the flash chips are connected in the Tempustech VMAX SBC301 Single - Board Computer. More information on the board is available at - <http://www.tempustech.com/>. + This provides a driver for the NOR flash attached to a PXA2xx chip. config MTD_SCx200_DOCFLASH tristate "Flash device mapped with DOCCS on NatSemi SCx200" - depends on SCx200 && MTD_CFI && MTD_PARTITIONS + depends on SCx200 && MTD_CFI help Enable support for a flash chip mapped using the DOCCS signal on a National Semiconductor SCx200 processor. @@ -184,9 +186,27 @@ config MTD_ICHXROM BE VERY CAREFUL. +config MTD_ESB2ROM + tristate "BIOS flash chip on Intel ESB Controller Hub 2" + depends on X86 && MTD_JEDECPROBE && PCI + help + Support for treating the BIOS flash chip on ESB2 motherboards + as an MTD device - with this you can reprogram your BIOS. + + BE VERY CAREFUL. + +config MTD_CK804XROM + tristate "BIOS flash chip on Nvidia CK804" + depends on X86 && MTD_JEDECPROBE && PCI + help + Support for treating the BIOS flash chip on nvidia motherboards + as an MTD device - with this you can reprogram your BIOS. + + BE VERY CAREFUL. + config MTD_SCB2_FLASH tristate "BIOS flash chip on Intel SCB2 boards" - depends on X86 && MTD_JEDECPROBE + depends on X86 && MTD_JEDECPROBE && PCI help Support for treating the BIOS flash chip on Intel SCB2 boards as an MTD device - with this you can reprogram your BIOS. @@ -199,46 +219,17 @@ config MTD_TSUNAMI help Support for the flash chip on Tsunami TIG bus. -config MTD_LASAT - tristate "LASAT flash device" - depends on LASAT && MTD_CFI - help - Support for the flash chips on the Lasat 100 and 200 boards. - config MTD_NETtel tristate "CFI flash device on SnapGear/SecureEdge" - depends on X86 && MTD_PARTITIONS && MTD_JEDECPROBE + depends on X86 && MTD_JEDECPROBE help Support for flash chips on NETtel/SecureEdge/SnapGear boards. -config MTD_ALCHEMY - tristate "AMD Alchemy Pb1xxx/Db1xxx/RDK MTD support" - depends on SOC_AU1X00 - help - Flash memory access on AMD Alchemy Pb/Db/RDK Reference Boards - -config MTD_MTX1 - tristate "4G Systems MTX-1 Flash device" - depends on MIPS && MIPS_MTX1 +config MTD_LANTIQ + tristate "Lantiq SoC NOR support" + depends on LANTIQ help - Flash memory access on 4G Systems MTX-1 Board. If you have one of - these boards and would like to use the flash chips on it, say 'Y'. - -config MTD_DILNETPC - tristate "CFI Flash device mapped on DIL/Net PC" - depends on X86 && MTD_CONCAT && MTD_PARTITIONS && MTD_CFI_INTELEXT - help - MTD map driver for SSV DIL/Net PC Boards "DNP" and "ADNP". - For details, see <http://www.ssv-embedded.de/ssv/pc104/p169.htm> - and <http://www.ssv-embedded.de/ssv/pc104/p170.htm> - -config MTD_DILNETPC_BOOTSIZE - hex "Size of DIL/Net PC flash boot partition" - depends on MTD_DILNETPC - default "0x80000" - help - The amount of space taken up by the kernel or Etherboot - on the DIL/Net PC flash chips. + Support for NOR flash attached to the Lantiq SoC's External Bus Unit. config MTD_L440GX tristate "BIOS flash chip on Intel L440GX boards" @@ -249,49 +240,6 @@ config MTD_L440GX BE VERY CAREFUL. -config MTD_SBC8240 - tristate "Flash device on SBC8240" - depends on MTD_JEDECPROBE && 8260 - help - Flash access on the SBC8240 board from Wind River. See - <http://www.windriver.com/products/sbc8240/> - -config MTD_TQM8XXL - tristate "CFI Flash device mapped on TQM8XXL" - depends on MTD_CFI && TQM8xxL - help - The TQM8xxL PowerPC board has up to two banks of CFI-compliant - chips, currently uses AMD one. This 'mapping' driver supports - that arrangement, allowing the CFI probe and command set driver - code to communicate with the chips on the TQM8xxL board. More at - <http://www.denx.de/embedded-ppc-en.html>. - -config MTD_RPXLITE - tristate "CFI Flash device mapped on RPX Lite or CLLF" - depends on MTD_CFI && (RPXCLASSIC || RPXLITE) - help - The RPXLite PowerPC board has CFI-compliant chips mapped in - a strange sparse mapping. This 'mapping' driver supports that - arrangement, allowing the CFI probe and command set driver code - to communicate with the chips on the RPXLite board. More at - <http://www.embeddedplanet.com/>. - -config MTD_MBX860 - tristate "System flash on MBX860 board" - depends on MTD_CFI && MBX - help - This enables access routines for the flash chips on the Motorola - MBX860 board. If you have one of these boards and would like - to use the flash chips on it, say 'Y'. - -config MTD_DBOX2 - tristate "CFI Flash device mapped on D-Box2" - depends on DBOX2 && MTD_CFI_INTELSTD && MTD_CFI_INTELEXT && MTD_CFI_AMDSTD - help - This enables access routines for the flash chips on the Nokia/Sagem - D-Box 2 board. If you have one of these boards and would like to use - the flash chips on it, say 'Y'. - config MTD_CFI_FLAGADM tristate "CFI Flash device mapping on FlagaDM" depends on 8xx && MTD_CFI @@ -299,146 +247,21 @@ config MTD_CFI_FLAGADM Mapping for the Flaga digital module. If you don't have one, ignore this setting. -config MTD_BEECH - tristate "CFI Flash device mapped on IBM 405LP Beech" - depends on MTD_CFI && BEECH - help - This enables access routines for the flash chips on the IBM - 405LP Beech board. If you have one of these boards and would like - to use the flash chips on it, say 'Y'. - -config MTD_ARCTIC - tristate "CFI Flash device mapped on IBM 405LP Arctic" - depends on MTD_CFI && ARCTIC2 - help - This enables access routines for the flash chips on the IBM 405LP - Arctic board. If you have one of these boards and would like to - use the flash chips on it, say 'Y'. - -config MTD_WALNUT - tristate "Flash device mapped on IBM 405GP Walnut" - depends on MTD_JEDECPROBE && WALNUT - help - This enables access routines for the flash chips on the IBM 405GP - Walnut board. If you have one of these boards and would like to - use the flash chips on it, say 'Y'. - -config MTD_EBONY - tristate "Flash devices mapped on IBM 440GP Ebony" - depends on MTD_JEDECPROBE && EBONY - help - This enables access routines for the flash chips on the IBM 440GP - Ebony board. If you have one of these boards and would like to - use the flash chips on it, say 'Y'. - -config MTD_OCOTEA - tristate "Flash devices mapped on IBM 440GX Ocotea" - depends on MTD_CFI && OCOTEA - help - This enables access routines for the flash chips on the IBM 440GX - Ocotea board. If you have one of these boards and would like to - use the flash chips on it, say 'Y'. - -config MTD_REDWOOD - tristate "CFI Flash devices mapped on IBM Redwood" - depends on MTD_CFI && ( REDWOOD_4 || REDWOOD_5 || REDWOOD_6 ) - help - This enables access routines for the flash chips on the IBM - Redwood board. If you have one of these boards and would like to - use the flash chips on it, say 'Y'. - -config MTD_TQM834x - tristate "Flash device mapped on TQ Components TQM834x Boards" - depends on MTD_CFI && TQM834x - help - This enables access routines for the flash chips on the - TQ Components TQM834x boards. If you have one of these boards - and would like to use the flash chips on it, say 'Y'. - -config MTD_CSTM_MIPS_IXX - tristate "Flash chip mapping on ITE QED-4N-S01B, Globespan IVR or custom board" - depends on MIPS && MTD_CFI && MTD_JEDECPROBE && MTD_PARTITIONS - help - This provides a mapping driver for the Integrated Technology - Express, Inc (ITE) QED-4N-S01B eval board and the Globespan IVR - Reference Board. It provides the necessary addressing, length, - buswidth, vpp code and addition setup of the flash device for - these boards. In addition, this mapping driver can be used for - other boards via setting of the CONFIG_MTD_CSTM_MIPS_IXX_START/ - LEN/BUSWIDTH parameters. This mapping will provide one mtd device - using one partition. The start address can be offset from the - beginning of flash and the len can be less than the total flash - device size to allow a window into the flash. Both CFI and JEDEC - probes are called. - -config MTD_CSTM_MIPS_IXX_START - hex "Physical start address of flash mapping" - depends on MTD_CSTM_MIPS_IXX - default "0x8000000" - help - This is the physical memory location that the MTD driver will - use for the flash chips on your particular target board. - Refer to the memory map which should hopefully be in the - documentation for your board. - -config MTD_CSTM_MIPS_IXX_LEN - hex "Physical length of flash mapping" - depends on MTD_CSTM_MIPS_IXX - default "0x4000000" - help - This is the total length that the MTD driver will use for the - flash chips on your particular board. Refer to the memory - map which should hopefully be in the documentation for your - board. - -config MTD_CSTM_MIPS_IXX_BUSWIDTH - int "Bus width in octets" - depends on MTD_CSTM_MIPS_IXX - default "2" - help - This is the total bus width of the mapping of the flash chips - on your particular board. - -config MTD_OCELOT - tristate "Momenco Ocelot boot flash device" - depends on MIPS && MOMENCO_OCELOT - help - This enables access routines for the boot flash device and for the - NVRAM on the Momenco Ocelot board. If you have one of these boards - and would like access to either of these, say 'Y'. - config MTD_SOLUTIONENGINE tristate "CFI Flash device mapped on Hitachi SolutionEngine" - depends on SUPERH && MTD_CFI && MTD_REDBOOT_PARTS + depends on SUPERH && SOLUTION_ENGINE && MTD_CFI && MTD_REDBOOT_PARTS help This enables access to the flash chips on the Hitachi SolutionEngine and similar boards. Say 'Y' if you are building a kernel for such a board. -config MTD_ARM_INTEGRATOR - tristate "CFI Flash device mapped on ARM Integrator/P720T" - depends on ARM && MTD_CFI - -config MTD_CDB89712 - tristate "Cirrus CDB89712 evaluation board mappings" - depends on MTD_CFI && ARCH_CDB89712 - help - This enables access to the flash or ROM chips on the CDB89712 board. - If you have such a board, say 'Y'. - config MTD_SA1100 tristate "CFI Flash device mapped on StrongARM SA11x0" - depends on MTD_CFI && ARCH_SA1100 && MTD_PARTITIONS + depends on MTD_CFI && ARCH_SA1100 help This enables access to the flash chips on most platforms based on the SA1100 and SA1110, including the Assabet and the Compaq iPAQ. If you have such a board, say 'Y'. -config MTD_IPAQ - tristate "CFI Flash device mapped on Compaq/HP iPAQ" - depends on IPAQ_HANDHELD && MTD_CFI - help - This provides a driver for the on-board flash of the iPAQ. - config MTD_DC21285 tristate "CFI Flash device mapped on DC21285 Footbridge" depends on MTD_CFI && ARCH_FOOTBRIDGE && MTD_COMPLEX_MAPPINGS @@ -456,36 +279,6 @@ config MTD_IXP4XX IXDP425 and Coyote. If you have an IXP4xx based board and would like to use the flash chips on it, say 'Y'. -config MTD_IXP2000 - tristate "CFI Flash device mapped on Intel IXP2000 based systems" - depends on MTD_CFI && MTD_COMPLEX_MAPPINGS && ARCH_IXP2000 - help - This enables MTD access to flash devices on platforms based - on Intel's IXP2000 family of network processors such as the - IXDP425 and Coyote. If you have an IXP2000 based board and - would like to use the flash chips on it, say 'Y'. - -config MTD_FORTUNET - tristate "CFI Flash device mapped on the FortuNet board" - depends on MTD_CFI && MTD_PARTITIONS && SA1100_FORTUNET - help - This enables access to the Flash on the FortuNet board. If you - have such a board, say 'Y'. - -config MTD_AUTCPU12 - tristate "NV-RAM mapping AUTCPU12 board" - depends on ARCH_AUTCPU12 - help - This enables access to the NV-RAM on autronix autcpu12 board. - If you have such a board, say 'Y'. - -config MTD_EDB7312 - tristate "CFI Flash device mapped on EDB7312" - depends on ARCH_EDB7312 && MTD_CFI - help - This enables access to the CFI Flash on the Cogent EDB7312 board. - If you have such a board, say 'Y' here. - config MTD_IMPA7 tristate "JEDEC Flash device mapped on impA7" depends on ARM && MTD_JEDECPROBE @@ -493,48 +286,10 @@ config MTD_IMPA7 This enables access to the NOR Flash on the impA7 board of implementa GmbH. If you have such a board, say 'Y' here. -config MTD_CEIVA - tristate "JEDEC Flash device mapped on Ceiva/Polaroid PhotoMax Digital Picture Frame" - depends on MTD_JEDECPROBE && ARCH_CEIVA - help - This enables access to the flash chips on the Ceiva/Polaroid - PhotoMax Digital Picture Frame. - If you have such a device, say 'Y'. - -config MTD_NOR_TOTO - tristate "NOR Flash device on TOTO board" - depends on ARCH_OMAP && OMAP_TOTO - help - This enables access to the NOR flash on the Texas Instruments - TOTO board. - -config MTD_H720X - tristate "Hynix evaluation board mappings" - depends on MTD_CFI && ( ARCH_H7201 || ARCH_H7202 ) - help - This enables access to the flash chips on the Hynix evaluation boards. - If you have such a board, say 'Y'. - -config MTD_MPC1211 - tristate "CFI Flash device mapped on Interface MPC-1211" - depends on SH_MPC1211 && MTD_CFI - help - This enables access to the flash chips on the Interface MPC-1211(CTP/PCI/MPC-SH02). - If you have such a board, say 'Y'. - -config MTD_OMAP_NOR - tristate "TI OMAP board mappings" - depends on MTD_CFI && ARCH_OMAP - help - This enables access to the NOR flash chips on TI OMAP-based - boards defining flash platform devices and flash platform data. - These boards include the Innovator, H2, H3, OSK, Perseus2, and - more. If you have such a board, say 'Y'. - # This needs CFI or JEDEC, depending on the cards found. config MTD_PCI tristate "PCI MTD driver" - depends on MTD && PCI && MTD_COMPLEX_MAPPINGS + depends on PCI && MTD_COMPLEX_MAPPINGS help Mapping for accessing flash devices on add-in cards like the Intel XScale IQ80310 card, and the Intel EBSA285 card in blank ROM programming mode @@ -544,7 +299,7 @@ config MTD_PCI config MTD_PCMCIA tristate "PCMCIA MTD driver" - depends on MTD && PCMCIA && MTD_COMPLEX_MAPPINGS && BROKEN + depends on PCMCIA && MTD_COMPLEX_MAPPINGS help Map driver for accessing PCMCIA linear flash memory cards. These cards are usually around 4-16MiB in size. This does not include @@ -559,62 +314,47 @@ config MTD_PCMCIA_ANONYMOUS If unsure, say N. -config MTD_UCLINUX - tristate "Generic uClinux RAM/ROM filesystem support" - depends on MTD_PARTITIONS && !MMU +config MTD_BFIN_ASYNC + tristate "Blackfin BF533-STAMP Flash Chip Support" + depends on BFIN533_STAMP && MTD_CFI && MTD_COMPLEX_MAPPINGS + default y help - Map driver to support image based filesystems for uClinux. + Map driver which allows for simultaneous utilization of + ethernet and CFI parallel flash. -config MTD_WRSBC8260 - tristate "Map driver for WindRiver PowerQUICC II MPC82xx board" - depends on (SBC82xx || SBC8560) - select MTD_PARTITIONS - select MTD_MAP_BANK_WIDTH_4 - select MTD_MAP_BANK_WIDTH_1 - select MTD_CFI_I1 - select MTD_CFI_I4 - help - Map driver for WindRiver PowerQUICC II MPC82xx board. Drives - all three flash regions on CS0, CS1 and CS6 if they are configured - correctly by the boot loader. - -config MTD_DMV182 - tristate "Map driver for Dy-4 SVME/DMV-182 board." - depends on DMV182 - select MTD_PARTITIONS - select MTD_MAP_BANK_WIDTH_32 - select MTD_CFI_I8 - select MTD_CFI_AMDSTD - help - Map driver for Dy-4 SVME/DMV-182 board. + If compiled as a module, it will be called bfin-async-flash. -config MTD_BAST - tristate "Map driver for Simtec BAST (EB2410ITX) or Thorcom VR1000" - depends on ARCH_BAST || MACH_VR1000 - select MTD_PARTITIONS - select MTD_MAP_BANK_WIDTH_16 - select MTD_JEDECPROBE +config MTD_GPIO_ADDR + tristate "GPIO-assisted Flash Chip Support" + depends on GPIOLIB + depends on MTD_COMPLEX_MAPPINGS help - Map driver for NOR flash on the Simtec BAST (EB2410ITX), or the - Thorcom VR1000 + Map driver which allows flashes to be partially physically addressed + and assisted by GPIOs. + + If compiled as a module, it will be called gpio-addr-flash. - Note, this driver *cannot* over-ride the WP link on the - board, or currently detect the state of the link. +config MTD_UCLINUX + bool "Generic uClinux RAM/ROM filesystem support" + depends on (MTD_RAM=y || MTD_ROM=y) && (!MMU || COLDFIRE) + help + Map driver to support image based filesystems for uClinux. -config MTD_BAST_MAXSIZE - int "Maximum size for BAST flash area (MiB)" - depends on MTD_BAST - default "4" +config MTD_INTEL_VR_NOR + tristate "NOR flash on Intel Vermilion Range Expansion Bus CS0" + depends on PCI + help + Map driver for a NOR flash bank located on the Expansion Bus of the + Intel Vermilion Range chipset. -config MTD_SHARP_SL - bool "ROM maped on Sharp SL Series" - depends on MTD && ARCH_PXA +config MTD_RBTX4939 + tristate "Map driver for RBTX4939 board" + depends on TOSHIBA_RBTX4939 && MTD_CFI && MTD_COMPLEX_MAPPINGS help - This enables access to the flash chip on the Sharp SL Series of PDAs. + Map driver for NOR flash chips on RBTX4939 board. config MTD_PLATRAM tristate "Map driver for platform device RAM (mtd-ram)" - depends on MTD select MTD_RAM help Map driver for RAM areas described via the platform device @@ -622,5 +362,41 @@ config MTD_PLATRAM This selection automatically selects the map_ram driver. -endmenu +config MTD_VMU + tristate "Map driver for Dreamcast VMU" + depends on MAPLE + help + This driver enables access to the Dreamcast Visual Memory Unit (VMU). + + Most Dreamcast users will want to say Y here. + To build this as a module select M here, the module will be called + vmu-flash. + +config MTD_PISMO + tristate "MTD discovery driver for PISMO modules" + depends on I2C + depends on ARCH_VERSATILE + help + This driver allows for discovery of PISMO modules - see + <http://www.pismoworld.org/>. These are small modules containing + up to five memory devices (eg, SRAM, flash, DOC) described by an + I2C EEPROM. + + This driver does not create any MTD maps itself; instead it + creates MTD physmap and MTD SRAM platform devices. If you + enable this option, you should consider enabling MTD_PHYSMAP + and/or MTD_PLATRAM according to the devices on your module. + + When built as a module, it will be called pismo.ko + +config MTD_LATCH_ADDR + tristate "Latch-assisted Flash Chip Support" + depends on MTD_COMPLEX_MAPPINGS + help + Map driver which allows flashes to be partially physically addressed + and have the upper address lines set by a board specific code. + + If compiled as a module, it will be called latch-addr-flash. + +endmenu diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile index 191c1928bbe..141c91a5b24 100644 --- a/drivers/mtd/maps/Makefile +++ b/drivers/mtd/maps/Makefile @@ -1,72 +1,45 @@ # # linux/drivers/maps/Makefile # -# $Id: Makefile.common,v 1.34 2005/11/07 11:14:26 gleixner Exp $ ifeq ($(CONFIG_MTD_COMPLEX_MAPPINGS),y) obj-$(CONFIG_MTD) += map_funcs.o endif # Chip mappings -obj-$(CONFIG_MTD_CDB89712) += cdb89712.o -obj-$(CONFIG_MTD_ARM_INTEGRATOR)+= integrator-flash.o -obj-$(CONFIG_MTD_BAST) += bast-flash.o obj-$(CONFIG_MTD_CFI_FLAGADM) += cfi_flagadm.o -obj-$(CONFIG_MTD_CSTM_MIPS_IXX) += cstm_mips_ixx.o obj-$(CONFIG_MTD_DC21285) += dc21285.o -obj-$(CONFIG_MTD_DILNETPC) += dilnetpc.o obj-$(CONFIG_MTD_L440GX) += l440gx.o obj-$(CONFIG_MTD_AMD76XROM) += amd76xrom.o +obj-$(CONFIG_MTD_ESB2ROM) += esb2rom.o obj-$(CONFIG_MTD_ICHXROM) += ichxrom.o +obj-$(CONFIG_MTD_CK804XROM) += ck804xrom.o obj-$(CONFIG_MTD_TSUNAMI) += tsunami_flash.o -obj-$(CONFIG_MTD_LUBBOCK) += lubbock-flash.o -obj-$(CONFIG_MTD_MAINSTONE) += mainstone-flash.o -obj-$(CONFIG_MTD_MBX860) += mbx860.o -obj-$(CONFIG_MTD_CEIVA) += ceiva.o -obj-$(CONFIG_MTD_OCTAGON) += octagon-5066.o +obj-$(CONFIG_MTD_PXA2XX) += pxa2xx-flash.o obj-$(CONFIG_MTD_PHYSMAP) += physmap.o -obj-$(CONFIG_MTD_PNC2000) += pnc2000.o +obj-$(CONFIG_MTD_PHYSMAP_OF) += physmap_of.o +obj-$(CONFIG_MTD_PISMO) += pismo.o +obj-$(CONFIG_MTD_PMC_MSP_EVM) += pmcmsp-flash.o obj-$(CONFIG_MTD_PCMCIA) += pcmciamtd.o -obj-$(CONFIG_MTD_RPXLITE) += rpxlite.o -obj-$(CONFIG_MTD_TQM8XXL) += tqm8xxl.o obj-$(CONFIG_MTD_SA1100) += sa1100-flash.o -obj-$(CONFIG_MTD_IPAQ) += ipaq-flash.o obj-$(CONFIG_MTD_SBC_GXX) += sbc_gxx.o obj-$(CONFIG_MTD_SC520CDP) += sc520cdp.o obj-$(CONFIG_MTD_NETSC520) += netsc520.o obj-$(CONFIG_MTD_TS5500) += ts5500_flash.o obj-$(CONFIG_MTD_SUN_UFLASH) += sun_uflash.o -obj-$(CONFIG_MTD_VMAX) += vmax301.o obj-$(CONFIG_MTD_SCx200_DOCFLASH)+= scx200_docflash.o -obj-$(CONFIG_MTD_DBOX2) += dbox2-flash.o -obj-$(CONFIG_MTD_OCELOT) += ocelot.o obj-$(CONFIG_MTD_SOLUTIONENGINE)+= solutionengine.o obj-$(CONFIG_MTD_PCI) += pci.o -obj-$(CONFIG_MTD_ALCHEMY) += alchemy-flash.o -obj-$(CONFIG_MTD_LASAT) += lasat.o -obj-$(CONFIG_MTD_AUTCPU12) += autcpu12-nvram.o -obj-$(CONFIG_MTD_EDB7312) += edb7312.o obj-$(CONFIG_MTD_IMPA7) += impa7.o -obj-$(CONFIG_MTD_FORTUNET) += fortunet.o -obj-$(CONFIG_MTD_REDWOOD) += redwood.o obj-$(CONFIG_MTD_UCLINUX) += uclinux.o obj-$(CONFIG_MTD_NETtel) += nettel.o obj-$(CONFIG_MTD_SCB2_FLASH) += scb2_flash.o -obj-$(CONFIG_MTD_EBONY) += ebony.o -obj-$(CONFIG_MTD_OCOTEA) += ocotea.o -obj-$(CONFIG_MTD_BEECH) += beech-mtd.o -obj-$(CONFIG_MTD_ARCTIC) += arctic-mtd.o -obj-$(CONFIG_MTD_WALNUT) += walnut.o -obj-$(CONFIG_MTD_H720X) += h720x-flash.o -obj-$(CONFIG_MTD_SBC8240) += sbc8240.o -obj-$(CONFIG_MTD_NOR_TOTO) += omap-toto-flash.o -obj-$(CONFIG_MTD_MPC1211) += mpc1211.o obj-$(CONFIG_MTD_IXP4XX) += ixp4xx.o -obj-$(CONFIG_MTD_IXP2000) += ixp2000.o -obj-$(CONFIG_MTD_WRSBC8260) += wr_sbc82xx_flash.o -obj-$(CONFIG_MTD_DMV182) += dmv182.o -obj-$(CONFIG_MTD_SHARP_SL) += sharpsl-flash.o obj-$(CONFIG_MTD_PLATRAM) += plat-ram.o -obj-$(CONFIG_MTD_OMAP_NOR) += omap_nor.o -obj-$(CONFIG_MTD_MTX1) += mtx-1_flash.o -obj-$(CONFIG_MTD_TQM834x) += tqm834x.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 +obj-$(CONFIG_MTD_GPIO_ADDR) += gpio-addr-flash.o +obj-$(CONFIG_MTD_LATCH_ADDR) += latch-addr-flash.o +obj-$(CONFIG_MTD_LANTIQ) += lantiq-flash.o diff --git a/drivers/mtd/maps/alchemy-flash.c b/drivers/mtd/maps/alchemy-flash.c deleted file mode 100644 index 7fc8097e41d..00000000000 --- a/drivers/mtd/maps/alchemy-flash.c +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Flash memory access on AMD Alchemy evaluation boards - * - * $Id: alchemy-flash.c,v 1.2 2005/11/07 11:14:26 gleixner Exp $ - * - * (C) 2003, 2004 Pete Popov <ppopov@embeddedalley.com> - * - */ - -#include <linux/init.h> -#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> - -#ifdef DEBUG_RW -#define DBG(x...) printk(x) -#else -#define DBG(x...) -#endif - -#ifdef CONFIG_MIPS_PB1000 -#define BOARD_MAP_NAME "Pb1000 Flash" -#define BOARD_FLASH_SIZE 0x00800000 /* 8MB */ -#define BOARD_FLASH_WIDTH 4 /* 32-bits */ -#endif - -#ifdef CONFIG_MIPS_PB1500 -#define BOARD_MAP_NAME "Pb1500 Flash" -#define BOARD_FLASH_SIZE 0x04000000 /* 64MB */ -#define BOARD_FLASH_WIDTH 4 /* 32-bits */ -#endif - -#ifdef CONFIG_MIPS_PB1100 -#define BOARD_MAP_NAME "Pb1100 Flash" -#define BOARD_FLASH_SIZE 0x04000000 /* 64MB */ -#define BOARD_FLASH_WIDTH 4 /* 32-bits */ -#endif - -#ifdef CONFIG_MIPS_PB1550 -#define BOARD_MAP_NAME "Pb1550 Flash" -#define BOARD_FLASH_SIZE 0x08000000 /* 128MB */ -#define BOARD_FLASH_WIDTH 4 /* 32-bits */ -#endif - -#ifdef CONFIG_MIPS_PB1200 -#define BOARD_MAP_NAME "Pb1200 Flash" -#define BOARD_FLASH_SIZE 0x08000000 /* 128MB */ -#define BOARD_FLASH_WIDTH 2 /* 16-bits */ -#endif - -#ifdef CONFIG_MIPS_DB1000 -#define BOARD_MAP_NAME "Db1000 Flash" -#define BOARD_FLASH_SIZE 0x02000000 /* 32MB */ -#define BOARD_FLASH_WIDTH 4 /* 32-bits */ -#endif - -#ifdef CONFIG_MIPS_DB1500 -#define BOARD_MAP_NAME "Db1500 Flash" -#define BOARD_FLASH_SIZE 0x02000000 /* 32MB */ -#define BOARD_FLASH_WIDTH 4 /* 32-bits */ -#endif - -#ifdef CONFIG_MIPS_DB1100 -#define BOARD_MAP_NAME "Db1100 Flash" -#define BOARD_FLASH_SIZE 0x02000000 /* 32MB */ -#define BOARD_FLASH_WIDTH 4 /* 32-bits */ -#endif - -#ifdef CONFIG_MIPS_DB1550 -#define BOARD_MAP_NAME "Db1550 Flash" -#define BOARD_FLASH_SIZE 0x08000000 /* 128MB */ -#define BOARD_FLASH_WIDTH 4 /* 32-bits */ -#endif - -#ifdef CONFIG_MIPS_DB1200 -#define BOARD_MAP_NAME "Db1200 Flash" -#define BOARD_FLASH_SIZE 0x04000000 /* 64MB */ -#define BOARD_FLASH_WIDTH 2 /* 16-bits */ -#endif - -#ifdef CONFIG_MIPS_HYDROGEN3 -#define BOARD_MAP_NAME "Hydrogen3 Flash" -#define BOARD_FLASH_SIZE 0x02000000 /* 32MB */ -#define BOARD_FLASH_WIDTH 4 /* 32-bits */ -#define USE_LOCAL_ACCESSORS /* why? */ -#endif - -#ifdef CONFIG_MIPS_BOSPORUS -#define BOARD_MAP_NAME "Bosporus Flash" -#define BOARD_FLASH_SIZE 0x01000000 /* 16MB */ -#define BOARD_FLASH_WIDTH 2 /* 16-bits */ -#endif - -#ifdef CONFIG_MIPS_MIRAGE -#define BOARD_MAP_NAME "Mirage Flash" -#define BOARD_FLASH_SIZE 0x04000000 /* 64MB */ -#define BOARD_FLASH_WIDTH 4 /* 32-bits */ -#define USE_LOCAL_ACCESSORS /* why? */ -#endif - -static struct map_info alchemy_map = { - .name = BOARD_MAP_NAME, -}; - -static struct mtd_partition alchemy_partitions[] = { - { - .name = "User FS", - .size = BOARD_FLASH_SIZE - 0x00400000, - .offset = 0x0000000 - },{ - .name = "YAMON", - .size = 0x0100000, - .offset = MTDPART_OFS_APPEND, - .mask_flags = MTD_WRITEABLE - },{ - .name = "raw kernel", - .size = (0x300000 - 0x40000), /* last 256KB is yamon env */ - .offset = MTDPART_OFS_APPEND, - } -}; - -static struct mtd_info *mymtd; - -int __init alchemy_mtd_init(void) -{ - struct mtd_partition *parts; - int nb_parts = 0; - unsigned long window_addr; - unsigned long window_size; - - /* Default flash buswidth */ - alchemy_map.bankwidth = BOARD_FLASH_WIDTH; - - window_addr = 0x20000000 - BOARD_FLASH_SIZE; - window_size = BOARD_FLASH_SIZE; -#ifdef CONFIG_MIPS_MIRAGE_WHY - /* Boot ROM flash bank only; no user bank */ - window_addr = 0x1C000000; - window_size = 0x04000000; - /* USERFS from 0x1C00 0000 to 0x1FC00000 */ - alchemy_partitions[0].size = 0x03C00000; -#endif - - /* - * Static partition definition selection - */ - parts = alchemy_partitions; - nb_parts = ARRAY_SIZE(alchemy_partitions); - alchemy_map.size = window_size; - - /* - * Now let's probe for the actual flash. Do it here since - * specific machine settings might have been set above. - */ - printk(KERN_NOTICE BOARD_MAP_NAME ": probing %d-bit flash bus\n", - alchemy_map.bankwidth*8); - alchemy_map.virt = ioremap(window_addr, window_size); - mymtd = do_map_probe("cfi_probe", &alchemy_map); - if (!mymtd) { - iounmap(alchemy_map.virt); - return -ENXIO; - } - mymtd->owner = THIS_MODULE; - - add_mtd_partitions(mymtd, parts, nb_parts); - return 0; -} - -static void __exit alchemy_mtd_cleanup(void) -{ - if (mymtd) { - del_mtd_partitions(mymtd); - map_destroy(mymtd); - iounmap(alchemy_map.virt); - } -} - -module_init(alchemy_mtd_init); -module_exit(alchemy_mtd_cleanup); - -MODULE_AUTHOR("Embedded Alley Solutions, Inc"); -MODULE_DESCRIPTION(BOARD_MAP_NAME " MTD driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/maps/amd76xrom.c b/drivers/mtd/maps/amd76xrom.c index 797caffb20b..f7207b0a76d 100644 --- a/drivers/mtd/maps/amd76xrom.c +++ b/drivers/mtd/maps/amd76xrom.c @@ -2,13 +2,13 @@ * amd76xrom.c * * Normal mappings of chips in physical memory - * $Id: amd76xrom.c,v 1.21 2005/11/07 11:14:26 gleixner Exp $ */ #include <linux/module.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/init.h> +#include <linux/slab.h> #include <asm/io.h> #include <linux/mtd/mtd.h> #include <linux/mtd/map.h> @@ -44,6 +44,23 @@ struct amd76xrom_map_info { char map_name[sizeof(MOD_NAME) + 2 + ADDRESS_NAME_LEN]; }; +/* The 2 bits controlling the window size are often set to allow reading + * the BIOS, but too small to allow writing, since the lock registers are + * 4MiB lower in the address space than the data. + * + * This is intended to prevent flashing the bios, perhaps accidentally. + * + * This parameter allows the normal driver to over-ride the BIOS settings. + * + * The bits are 6 and 7. If both bits are set, it is a 5MiB window. + * If only the 7 Bit is set, it is a 4MiB window. Otherwise, a + * 64KiB window. + * + */ +static uint win_size_bits; +module_param(win_size_bits, uint, 0); +MODULE_PARM_DESC(win_size_bits, "ROM window size bits override for 0x43 byte, normally set by BIOS."); + static struct amd76xrom_window amd76xrom_window = { .maps = LIST_HEAD_INIT(amd76xrom_window.maps), }; @@ -65,7 +82,7 @@ static void amd76xrom_cleanup(struct amd76xrom_window *window) if (map->rsrc.parent) { release_resource(&map->rsrc); } - del_mtd_device(map->mtd); + mtd_device_unregister(map->mtd); map_destroy(map->mtd); list_del(&map->list); kfree(map); @@ -83,8 +100,8 @@ static void amd76xrom_cleanup(struct amd76xrom_window *window) } -static int __devinit amd76xrom_init_one (struct pci_dev *pdev, - const struct pci_device_id *ent) +static int amd76xrom_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent) { static char *rom_probe_types[] = { "cfi_probe", "jedec_probe", NULL }; u8 byte; @@ -95,6 +112,16 @@ static int __devinit amd76xrom_init_one (struct pci_dev *pdev, /* Remember the pci dev I find the window in - already have a ref */ window->pdev = pdev; + /* Enable the selected rom window. This is often incorrectly + * set up by the BIOS, and the 4MiB offset for the lock registers + * requires the full 5MiB of window space. + * + * This 'write, then read' approach leaves the bits for + * other uses of the hardware info. + */ + pci_read_config_byte(pdev, 0x43, &byte); + pci_write_config_byte(pdev, 0x43, byte | win_size_bits ); + /* Assume the rom window is properly setup, and find it's size */ pci_read_config_byte(pdev, 0x43, &byte); if ((byte & ((1<<7)|(1<<6))) == ((1<<7)|(1<<6))) { @@ -122,19 +149,11 @@ static int __devinit amd76xrom_init_one (struct pci_dev *pdev, if (request_resource(&iomem_resource, &window->rsrc)) { window->rsrc.parent = NULL; printk(KERN_ERR MOD_NAME - " %s(): Unable to register resource" - " 0x%.16llx-0x%.16llx - kernel bug?\n", - __func__, - (unsigned long long)window->rsrc.start, - (unsigned long long)window->rsrc.end); + " %s(): Unable to register resource %pR - kernel bug?\n", + __func__, &window->rsrc); + return -EBUSY; } -#if 0 - - /* Enable the selected rom window */ - pci_read_config_byte(pdev, 0x43, &byte); - pci_write_config_byte(pdev, 0x43, byte | rwindow->segen_bits); -#endif /* Enable writes through the rom window */ pci_read_config_byte(pdev, 0x40, &byte); @@ -183,8 +202,8 @@ static int __devinit amd76xrom_init_one (struct pci_dev *pdev, (((unsigned long)(window->virt)) + offset); map->map.size = 0xffffffffUL - map_top + 1UL; /* Set the name of the map to the address I am trying */ - sprintf(map->map_name, "%s @%08lx", - MOD_NAME, map->map.phys); + sprintf(map->map_name, "%s @%08Lx", + MOD_NAME, (unsigned long long)map->map.phys); /* There is no generic VPP support */ for(map->map.bankwidth = 32; map->map.bankwidth; @@ -212,8 +231,8 @@ static int __devinit amd76xrom_init_one (struct pci_dev *pdev, /* Trim the size if we are larger than the map */ if (map->mtd->size > map->map.size) { printk(KERN_WARNING MOD_NAME - " rom(%u) larger than window(%lu). fixing...\n", - map->mtd->size, map->map.size); + " rom(%llu) larger than window(%lu). fixing...\n", + (unsigned long long)map->mtd->size, map->map.size); map->mtd->size = map->map.size; } if (window->rsrc.parent) { @@ -243,7 +262,7 @@ static int __devinit amd76xrom_init_one (struct pci_dev *pdev, /* Now that the mtd devices is complete claim and export it */ map->mtd->owner = THIS_MODULE; - if (add_mtd_device(map->mtd)) { + if (mtd_device_register(map->mtd, NULL, 0)) { map_destroy(map->mtd); map->mtd = NULL; goto out; @@ -270,7 +289,7 @@ static int __devinit amd76xrom_init_one (struct pci_dev *pdev, } -static void __devexit amd76xrom_remove_one (struct pci_dev *pdev) +static void amd76xrom_remove_one(struct pci_dev *pdev) { struct amd76xrom_window *window = &amd76xrom_window; @@ -328,4 +347,3 @@ module_exit(cleanup_amd76xrom); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Eric Biederman <ebiederman@lnxi.com>"); MODULE_DESCRIPTION("MTD map driver for BIOS chips on the AMD76X southbridge"); - diff --git a/drivers/mtd/maps/arctic-mtd.c b/drivers/mtd/maps/arctic-mtd.c deleted file mode 100644 index 642d96bc891..00000000000 --- a/drivers/mtd/maps/arctic-mtd.c +++ /dev/null @@ -1,145 +0,0 @@ -/* - * $Id: arctic-mtd.c,v 1.14 2005/11/07 11:14:26 gleixner Exp $ - * - * drivers/mtd/maps/arctic-mtd.c MTD mappings and partition tables for - * IBM 405LP Arctic 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * Copyright (C) 2002, International Business Machines Corporation - * All Rights Reserved. - * - * Bishop Brock - * IBM Research, Austin Center for Low-Power Computing - * bcbrock@us.ibm.com - * March 2002 - * - * modified for Arctic by, - * David Gibson - * IBM OzLabs, Canberra, Australia - * <arctic@gibson.dropbear.id.au> - */ - -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/types.h> -#include <linux/init.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - -#include <asm/io.h> -#include <asm/ibm4xx.h> - -/* - * 0 : 0xFE00 0000 - 0xFEFF FFFF : Filesystem 1 (16MiB) - * 1 : 0xFF00 0000 - 0xFF4F FFFF : kernel (5.12MiB) - * 2 : 0xFF50 0000 - 0xFFF5 FFFF : Filesystem 2 (10.624MiB) (if non-XIP) - * 3 : 0xFFF6 0000 - 0xFFFF FFFF : PIBS Firmware (640KiB) - */ - -#define FFS1_SIZE 0x01000000 /* 16MiB */ -#define KERNEL_SIZE 0x00500000 /* 5.12MiB */ -#define FFS2_SIZE 0x00a60000 /* 10.624MiB */ -#define FIRMWARE_SIZE 0x000a0000 /* 640KiB */ - - -#define NAME "Arctic Linux Flash" -#define PADDR SUBZERO_BOOTFLASH_PADDR -#define BUSWIDTH 2 -#define SIZE SUBZERO_BOOTFLASH_SIZE -#define PARTITIONS 4 - -/* Flash memories on these boards are memory resources, accessed big-endian. */ - -{ - /* do nothing for now */ -} - -static struct map_info arctic_mtd_map = { - .name = NAME, - .size = SIZE, - .bankwidth = BUSWIDTH, - .phys = PADDR, -}; - -static struct mtd_info *arctic_mtd; - -static struct mtd_partition arctic_partitions[PARTITIONS] = { - { .name = "Filesystem", - .size = FFS1_SIZE, - .offset = 0,}, - { .name = "Kernel", - .size = KERNEL_SIZE, - .offset = FFS1_SIZE,}, - { .name = "Filesystem", - .size = FFS2_SIZE, - .offset = FFS1_SIZE + KERNEL_SIZE,}, - { .name = "Firmware", - .size = FIRMWARE_SIZE, - .offset = SUBZERO_BOOTFLASH_SIZE - FIRMWARE_SIZE,}, -}; - -static int __init -init_arctic_mtd(void) -{ - int err = 0; - - printk("%s: 0x%08x at 0x%08x\n", NAME, SIZE, PADDR); - - arctic_mtd_map.virt = ioremap(PADDR, SIZE); - - if (!arctic_mtd_map.virt) { - printk("%s: failed to ioremap 0x%x\n", NAME, PADDR); - return -EIO; - } - simple_map_init(&arctic_mtd_map); - - printk("%s: probing %d-bit flash bus\n", NAME, BUSWIDTH * 8); - arctic_mtd = do_map_probe("cfi_probe", &arctic_mtd_map); - - if (!arctic_mtd) { - iounmap((void *) arctic_mtd_map.virt); - return -ENXIO; - } - - arctic_mtd->owner = THIS_MODULE; - - err = add_mtd_partitions(arctic_mtd, arctic_partitions, PARTITIONS); - if (err) { - printk("%s: add_mtd_partitions failed\n", NAME); - iounmap((void *) arctic_mtd_map.virt); - } - - return err; -} - -static void __exit -cleanup_arctic_mtd(void) -{ - if (arctic_mtd) { - del_mtd_partitions(arctic_mtd); - map_destroy(arctic_mtd); - iounmap((void *) arctic_mtd_map.virt); - } -} - -module_init(init_arctic_mtd); -module_exit(cleanup_arctic_mtd); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("David Gibson <arctic@gibson.dropbear.id.au>"); -MODULE_DESCRIPTION("MTD map and partitions for IBM 405LP Arctic boards"); diff --git a/drivers/mtd/maps/autcpu12-nvram.c b/drivers/mtd/maps/autcpu12-nvram.c deleted file mode 100644 index 7ed3424dd95..00000000000 --- a/drivers/mtd/maps/autcpu12-nvram.c +++ /dev/null @@ -1,127 +0,0 @@ -/* - * NV-RAM memory access on autcpu12 - * (C) 2002 Thomas Gleixner (gleixner@autronix.de) - * - * $Id: autcpu12-nvram.c,v 1.9 2005/11/07 11:14:26 gleixner Exp $ - * - * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/ioport.h> -#include <linux/init.h> -#include <asm/io.h> -#include <asm/sizes.h> -#include <asm/hardware.h> -#include <asm/arch/autcpu12.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - - -static struct mtd_info *sram_mtd; - -struct map_info autcpu12_sram_map = { - .name = "SRAM", - .size = 32768, - .bankwidth = 4, - .phys = 0x12000000, -}; - -static int __init init_autcpu12_sram (void) -{ - int err, save0, save1; - - autcpu12_sram_map.virt = ioremap(0x12000000, SZ_128K); - if (!autcpu12_sram_map.virt) { - printk("Failed to ioremap autcpu12 NV-RAM space\n"); - err = -EIO; - goto out; - } - simple_map_init(&autcpu_sram_map); - - /* - * Check for 32K/128K - * read ofs 0 - * read ofs 0x10000 - * Write complement to ofs 0x100000 - * Read and check result on ofs 0x0 - * Restore contents - */ - save0 = map_read32(&autcpu12_sram_map,0); - save1 = map_read32(&autcpu12_sram_map,0x10000); - map_write32(&autcpu12_sram_map,~save0,0x10000); - /* if we find this pattern on 0x0, we have 32K size - * restore contents and exit - */ - if ( map_read32(&autcpu12_sram_map,0) != save0) { - map_write32(&autcpu12_sram_map,save0,0x0); - goto map; - } - /* We have a 128K found, restore 0x10000 and set size - * to 128K - */ - map_write32(&autcpu12_sram_map,save1,0x10000); - autcpu12_sram_map.size = SZ_128K; - -map: - sram_mtd = do_map_probe("map_ram", &autcpu12_sram_map); - if (!sram_mtd) { - printk("NV-RAM probe failed\n"); - err = -ENXIO; - goto out_ioremap; - } - - sram_mtd->owner = THIS_MODULE; - sram_mtd->erasesize = 16; - - if (add_mtd_device(sram_mtd)) { - printk("NV-RAM device addition failed\n"); - err = -ENOMEM; - goto out_probe; - } - - printk("NV-RAM device size %ldKiB registered on AUTCPU12\n",autcpu12_sram_map.size/SZ_1K); - - return 0; - -out_probe: - map_destroy(sram_mtd); - sram_mtd = 0; - -out_ioremap: - iounmap((void *)autcpu12_sram_map.virt); -out: - return err; -} - -static void __exit cleanup_autcpu12_maps(void) -{ - if (sram_mtd) { - del_mtd_device(sram_mtd); - map_destroy(sram_mtd); - iounmap((void *)autcpu12_sram_map.virt); - } -} - -module_init(init_autcpu12_sram); -module_exit(cleanup_autcpu12_maps); - -MODULE_AUTHOR("Thomas Gleixner"); -MODULE_DESCRIPTION("autcpu12 NV-RAM map driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/maps/bast-flash.c b/drivers/mtd/maps/bast-flash.c deleted file mode 100644 index 51f962dd7e3..00000000000 --- a/drivers/mtd/maps/bast-flash.c +++ /dev/null @@ -1,225 +0,0 @@ -/* linux/drivers/mtd/maps/bast_flash.c - * - * Copyright (c) 2004-2005 Simtec Electronics - * Ben Dooks <ben@simtec.co.uk> - * - * Simtec Bast (EB2410ITX) NOR MTD Mapping driver - * - * Changelog: - * 20-Sep-2004 BJD Initial version - * 17-Jan-2005 BJD Add whole device if no partitions found - * - * $Id: bast-flash.c,v 1.5 2005/11/07 11:14:26 gleixner Exp $ - * - * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/init.h> -#include <linux/kernel.h> -#include <linux/string.h> -#include <linux/ioport.h> -#include <linux/device.h> -#include <linux/slab.h> -#include <linux/platform_device.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - -#include <asm/io.h> -#include <asm/mach/flash.h> - -#include <asm/arch/map.h> -#include <asm/arch/bast-map.h> -#include <asm/arch/bast-cpld.h> - -#ifdef CONFIG_MTD_BAST_MAXSIZE -#define AREA_MAXSIZE (CONFIG_MTD_BAST_MAXSIZE * SZ_1M) -#else -#define AREA_MAXSIZE (32 * SZ_1M) -#endif - -#define PFX "bast-flash: " - -struct bast_flash_info { - struct mtd_info *mtd; - struct map_info map; - struct mtd_partition *partitions; - struct resource *area; -}; - -static const char *probes[] = { "RedBoot", "cmdlinepart", NULL }; - -static void bast_flash_setrw(int to) -{ - unsigned int val; - unsigned long flags; - - local_irq_save(flags); - val = __raw_readb(BAST_VA_CTRL3); - - if (to) - val |= BAST_CPLD_CTRL3_ROMWEN; - else - val &= ~BAST_CPLD_CTRL3_ROMWEN; - - pr_debug("new cpld ctrl3=%02x\n", val); - - __raw_writeb(val, BAST_VA_CTRL3); - local_irq_restore(flags); -} - -static int bast_flash_remove(struct platform_device *pdev) -{ - struct bast_flash_info *info = platform_get_drvdata(pdev); - - platform_set_drvdata(pdev, NULL); - - if (info == NULL) - return 0; - - if (info->map.virt != NULL) - iounmap(info->map.virt); - - if (info->mtd) { - del_mtd_partitions(info->mtd); - map_destroy(info->mtd); - } - - kfree(info->partitions); - - if (info->area) { - release_resource(info->area); - kfree(info->area); - } - - kfree(info); - - return 0; -} - -static int bast_flash_probe(struct platform_device *pdev) -{ - struct bast_flash_info *info; - struct resource *res; - int err = 0; - - info = kmalloc(sizeof(*info), GFP_KERNEL); - if (info == NULL) { - printk(KERN_ERR PFX "no memory for flash info\n"); - err = -ENOMEM; - goto exit_error; - } - - memzero(info, sizeof(*info)); - platform_set_drvdata(pdev, info); - - res = pdev->resource; /* assume that the flash has one resource */ - - info->map.phys = res->start; - info->map.size = res->end - res->start + 1; - info->map.name = pdev->dev.bus_id; - info->map.bankwidth = 2; - - if (info->map.size > AREA_MAXSIZE) - info->map.size = AREA_MAXSIZE; - - pr_debug("%s: area %08lx, size %ld\n", __FUNCTION__, - info->map.phys, info->map.size); - - info->area = request_mem_region(res->start, info->map.size, - pdev->name); - if (info->area == NULL) { - printk(KERN_ERR PFX "cannot reserve flash memory region\n"); - err = -ENOENT; - goto exit_error; - } - - info->map.virt = ioremap(res->start, info->map.size); - pr_debug("%s: virt at %08x\n", __FUNCTION__, (int)info->map.virt); - - if (info->map.virt == 0) { - printk(KERN_ERR PFX "failed to ioremap() region\n"); - err = -EIO; - goto exit_error; - } - - simple_map_init(&info->map); - - /* enable the write to the flash area */ - - bast_flash_setrw(1); - - /* probe for the device(s) */ - - info->mtd = do_map_probe("jedec_probe", &info->map); - if (info->mtd == NULL) - info->mtd = do_map_probe("cfi_probe", &info->map); - - if (info->mtd == NULL) { - printk(KERN_ERR PFX "map_probe() failed\n"); - err = -ENXIO; - goto exit_error; - } - - /* mark ourselves as the owner */ - info->mtd->owner = THIS_MODULE; - - err = parse_mtd_partitions(info->mtd, probes, &info->partitions, 0); - if (err > 0) { - err = add_mtd_partitions(info->mtd, info->partitions, err); - if (err) - printk(KERN_ERR PFX "cannot add/parse partitions\n"); - } else { - err = add_mtd_device(info->mtd); - } - - if (err == 0) - return 0; - - /* fall through to exit error */ - - exit_error: - bast_flash_remove(pdev); - return err; -} - -static struct platform_driver bast_flash_driver = { - .probe = bast_flash_probe, - .remove = bast_flash_remove, - .driver = { - .name = "bast-nor", - .owner = THIS_MODULE, - }, -}; - -static int __init bast_flash_init(void) -{ - printk("BAST NOR-Flash Driver, (c) 2004 Simtec Electronics\n"); - return platform_driver_register(&bast_flash_driver); -} - -static void __exit bast_flash_exit(void) -{ - platform_driver_unregister(&bast_flash_driver); -} - -module_init(bast_flash_init); -module_exit(bast_flash_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); -MODULE_DESCRIPTION("BAST MTD Map driver"); diff --git a/drivers/mtd/maps/beech-mtd.c b/drivers/mtd/maps/beech-mtd.c deleted file mode 100644 index a64b1a5ab31..00000000000 --- a/drivers/mtd/maps/beech-mtd.c +++ /dev/null @@ -1,122 +0,0 @@ -/* - * $Id: beech-mtd.c,v 1.11 2005/11/07 11:14:26 gleixner Exp $ - * - * drivers/mtd/maps/beech-mtd.c MTD mappings and partition tables for - * IBM 405LP Beech 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * Copyright (C) 2002, International Business Machines Corporation - * All Rights Reserved. - * - * Bishop Brock - * IBM Research, Austin Center for Low-Power Computing - * bcbrock@us.ibm.com - * March 2002 - * - */ - -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/types.h> -#include <linux/init.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - -#include <asm/io.h> -#include <asm/ibm4xx.h> - -#define NAME "Beech Linux Flash" -#define PADDR BEECH_BIGFLASH_PADDR -#define SIZE BEECH_BIGFLASH_SIZE -#define BUSWIDTH 1 - -/* Flash memories on these boards are memory resources, accessed big-endian. */ - - -static struct map_info beech_mtd_map = { - .name = NAME, - .size = SIZE, - .bankwidth = BUSWIDTH, - .phys = PADDR -}; - -static struct mtd_info *beech_mtd; - -static struct mtd_partition beech_partitions[2] = { - { - .name = "Linux Kernel", - .size = BEECH_KERNEL_SIZE, - .offset = BEECH_KERNEL_OFFSET - }, { - .name = "Free Area", - .size = BEECH_FREE_AREA_SIZE, - .offset = BEECH_FREE_AREA_OFFSET - } -}; - -static int __init -init_beech_mtd(void) -{ - int err = 0; - - printk("%s: 0x%08x at 0x%08x\n", NAME, SIZE, PADDR); - - beech_mtd_map.virt = ioremap(PADDR, SIZE); - - if (!beech_mtd_map.virt) { - printk("%s: failed to ioremap 0x%x\n", NAME, PADDR); - return -EIO; - } - - simple_map_init(&beech_mtd_map); - - printk("%s: probing %d-bit flash bus\n", NAME, BUSWIDTH * 8); - beech_mtd = do_map_probe("cfi_probe", &beech_mtd_map); - - if (!beech_mtd) { - iounmap((void *) beech_mtd_map.virt); - return -ENXIO; - } - - beech_mtd->owner = THIS_MODULE; - - err = add_mtd_partitions(beech_mtd, beech_partitions, 2); - if (err) { - printk("%s: add_mtd_partitions failed\n", NAME); - iounmap((void *) beech_mtd_map.virt); - } - - return err; -} - -static void __exit -cleanup_beech_mtd(void) -{ - if (beech_mtd) { - del_mtd_partitions(beech_mtd); - map_destroy(beech_mtd); - iounmap((void *) beech_mtd_map.virt); - } -} - -module_init(init_beech_mtd); -module_exit(cleanup_beech_mtd); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Bishop Brock <bcbrock@us.ibm.com>"); -MODULE_DESCRIPTION("MTD map and partitions for IBM 405LP Beech boards"); diff --git a/drivers/mtd/maps/bfin-async-flash.c b/drivers/mtd/maps/bfin-async-flash.c new file mode 100644 index 00000000000..6ea51e54904 --- /dev/null +++ b/drivers/mtd/maps/bfin-async-flash.c @@ -0,0 +1,197 @@ +/* + * drivers/mtd/maps/bfin-async-flash.c + * + * Handle the case where flash memory and ethernet mac/phy are + * mapped onto the same async bank. The BF533-STAMP does this + * for example. All board-specific configuration goes in your + * board resources file. + * + * Copyright 2000 Nicolas Pitre <nico@fluxnic.net> + * Copyright 2005-2008 Analog Devices Inc. + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/physmap.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include <asm/blackfin.h> +#include <linux/gpio.h> +#include <linux/io.h> +#include <asm/unaligned.h> + +#define pr_devinit(fmt, args...) \ + ({ static const char __fmt[] = fmt; printk(__fmt, ## args); }) + +#define DRIVER_NAME "bfin-async-flash" + +struct async_state { + struct mtd_info *mtd; + struct map_info map; + int enet_flash_pin; + uint32_t flash_ambctl0, flash_ambctl1; + uint32_t save_ambctl0, save_ambctl1; + unsigned long irq_flags; +}; + +static void switch_to_flash(struct async_state *state) +{ + local_irq_save(state->irq_flags); + + gpio_set_value(state->enet_flash_pin, 0); + + state->save_ambctl0 = bfin_read_EBIU_AMBCTL0(); + state->save_ambctl1 = bfin_read_EBIU_AMBCTL1(); + bfin_write_EBIU_AMBCTL0(state->flash_ambctl0); + bfin_write_EBIU_AMBCTL1(state->flash_ambctl1); + SSYNC(); +} + +static void switch_back(struct async_state *state) +{ + bfin_write_EBIU_AMBCTL0(state->save_ambctl0); + bfin_write_EBIU_AMBCTL1(state->save_ambctl1); + SSYNC(); + + gpio_set_value(state->enet_flash_pin, 1); + + local_irq_restore(state->irq_flags); +} + +static map_word bfin_flash_read(struct map_info *map, unsigned long ofs) +{ + struct async_state *state = (struct async_state *)map->map_priv_1; + uint16_t word; + map_word test; + + switch_to_flash(state); + + word = readw(map->virt + ofs); + + switch_back(state); + + test.x[0] = word; + return test; +} + +static void bfin_flash_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len) +{ + struct async_state *state = (struct async_state *)map->map_priv_1; + + switch_to_flash(state); + + memcpy(to, map->virt + from, len); + + switch_back(state); +} + +static void bfin_flash_write(struct map_info *map, map_word d1, unsigned long ofs) +{ + struct async_state *state = (struct async_state *)map->map_priv_1; + uint16_t d; + + d = d1.x[0]; + + switch_to_flash(state); + + writew(d, map->virt + ofs); + SSYNC(); + + switch_back(state); +} + +static void bfin_flash_copy_to(struct map_info *map, unsigned long to, const void *from, ssize_t len) +{ + struct async_state *state = (struct async_state *)map->map_priv_1; + + switch_to_flash(state); + + memcpy(map->virt + to, from, len); + SSYNC(); + + switch_back(state); +} + +static const char * const part_probe_types[] = { + "cmdlinepart", "RedBoot", NULL }; + +static int bfin_flash_probe(struct platform_device *pdev) +{ + int ret; + struct physmap_flash_data *pdata = dev_get_platdata(&pdev->dev); + struct resource *memory = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct resource *flash_ambctl = platform_get_resource(pdev, IORESOURCE_MEM, 1); + struct async_state *state; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + state->map.name = DRIVER_NAME; + state->map.read = bfin_flash_read; + state->map.copy_from = bfin_flash_copy_from; + state->map.write = bfin_flash_write; + state->map.copy_to = bfin_flash_copy_to; + state->map.bankwidth = pdata->width; + state->map.size = resource_size(memory); + state->map.virt = (void __iomem *)memory->start; + state->map.phys = memory->start; + state->map.map_priv_1 = (unsigned long)state; + state->enet_flash_pin = platform_get_irq(pdev, 0); + state->flash_ambctl0 = flash_ambctl->start; + state->flash_ambctl1 = flash_ambctl->end; + + if (gpio_request(state->enet_flash_pin, DRIVER_NAME)) { + pr_devinit(KERN_ERR DRIVER_NAME ": Failed to request gpio %d\n", state->enet_flash_pin); + kfree(state); + return -EBUSY; + } + gpio_direction_output(state->enet_flash_pin, 1); + + pr_devinit(KERN_NOTICE DRIVER_NAME ": probing %d-bit flash bus\n", state->map.bankwidth * 8); + state->mtd = do_map_probe(memory->name, &state->map); + if (!state->mtd) { + gpio_free(state->enet_flash_pin); + kfree(state); + return -ENXIO; + } + + mtd_device_parse_register(state->mtd, part_probe_types, NULL, + pdata->parts, pdata->nr_parts); + + platform_set_drvdata(pdev, state); + + return 0; +} + +static int bfin_flash_remove(struct platform_device *pdev) +{ + struct async_state *state = platform_get_drvdata(pdev); + gpio_free(state->enet_flash_pin); + mtd_device_unregister(state->mtd); + map_destroy(state->mtd); + kfree(state); + return 0; +} + +static struct platform_driver bfin_flash_driver = { + .probe = bfin_flash_probe, + .remove = bfin_flash_remove, + .driver = { + .name = DRIVER_NAME, + }, +}; + +module_platform_driver(bfin_flash_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MTD map driver for Blackfins with flash/ethernet on same async bank"); diff --git a/drivers/mtd/maps/cdb89712.c b/drivers/mtd/maps/cdb89712.c deleted file mode 100644 index 9f17bb6c5a9..00000000000 --- a/drivers/mtd/maps/cdb89712.c +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Flash on Cirrus CDB89712 - * - * $Id: cdb89712.c,v 1.11 2005/11/07 11:14:26 gleixner Exp $ - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/ioport.h> -#include <linux/init.h> -#include <asm/io.h> -#include <asm/arch/hardware.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - - - - -static struct mtd_info *flash_mtd; - -struct map_info cdb89712_flash_map = { - .name = "flash", - .size = FLASH_SIZE, - .bankwidth = FLASH_WIDTH, - .phys = FLASH_START, -}; - -struct resource cdb89712_flash_resource = { - .name = "Flash", - .start = FLASH_START, - .end = FLASH_START + FLASH_SIZE - 1, - .flags = IORESOURCE_IO | IORESOURCE_BUSY, -}; - -static int __init init_cdb89712_flash (void) -{ - int err; - - if (request_resource (&ioport_resource, &cdb89712_flash_resource)) { - printk(KERN_NOTICE "Failed to reserve Cdb89712 FLASH space\n"); - err = -EBUSY; - goto out; - } - - cdb89712_flash_map.virt = ioremap(FLASH_START, FLASH_SIZE); - if (!cdb89712_flash_map.virt) { - printk(KERN_NOTICE "Failed to ioremap Cdb89712 FLASH space\n"); - err = -EIO; - goto out_resource; - } - simple_map_init(&cdb89712_flash_map); - flash_mtd = do_map_probe("cfi_probe", &cdb89712_flash_map); - if (!flash_mtd) { - flash_mtd = do_map_probe("map_rom", &cdb89712_flash_map); - if (flash_mtd) - flash_mtd->erasesize = 0x10000; - } - if (!flash_mtd) { - printk("FLASH probe failed\n"); - err = -ENXIO; - goto out_ioremap; - } - - flash_mtd->owner = THIS_MODULE; - - if (add_mtd_device(flash_mtd)) { - printk("FLASH device addition failed\n"); - err = -ENOMEM; - goto out_probe; - } - - return 0; - -out_probe: - map_destroy(flash_mtd); - flash_mtd = 0; -out_ioremap: - iounmap((void *)cdb89712_flash_map.virt); -out_resource: - release_resource (&cdb89712_flash_resource); -out: - return err; -} - - - - - -static struct mtd_info *sram_mtd; - -struct map_info cdb89712_sram_map = { - .name = "SRAM", - .size = SRAM_SIZE, - .bankwidth = SRAM_WIDTH, - .phys = SRAM_START, -}; - -struct resource cdb89712_sram_resource = { - .name = "SRAM", - .start = SRAM_START, - .end = SRAM_START + SRAM_SIZE - 1, - .flags = IORESOURCE_IO | IORESOURCE_BUSY, -}; - -static int __init init_cdb89712_sram (void) -{ - int err; - - if (request_resource (&ioport_resource, &cdb89712_sram_resource)) { - printk(KERN_NOTICE "Failed to reserve Cdb89712 SRAM space\n"); - err = -EBUSY; - goto out; - } - - cdb89712_sram_map.virt = ioremap(SRAM_START, SRAM_SIZE); - if (!cdb89712_sram_map.virt) { - printk(KERN_NOTICE "Failed to ioremap Cdb89712 SRAM space\n"); - err = -EIO; - goto out_resource; - } - simple_map_init(&cdb89712_sram_map); - sram_mtd = do_map_probe("map_ram", &cdb89712_sram_map); - if (!sram_mtd) { - printk("SRAM probe failed\n"); - err = -ENXIO; - goto out_ioremap; - } - - sram_mtd->owner = THIS_MODULE; - sram_mtd->erasesize = 16; - - if (add_mtd_device(sram_mtd)) { - printk("SRAM device addition failed\n"); - err = -ENOMEM; - goto out_probe; - } - - return 0; - -out_probe: - map_destroy(sram_mtd); - sram_mtd = 0; -out_ioremap: - iounmap((void *)cdb89712_sram_map.virt); -out_resource: - release_resource (&cdb89712_sram_resource); -out: - return err; -} - - - - - - - -static struct mtd_info *bootrom_mtd; - -struct map_info cdb89712_bootrom_map = { - .name = "BootROM", - .size = BOOTROM_SIZE, - .bankwidth = BOOTROM_WIDTH, - .phys = BOOTROM_START, -}; - -struct resource cdb89712_bootrom_resource = { - .name = "BootROM", - .start = BOOTROM_START, - .end = BOOTROM_START + BOOTROM_SIZE - 1, - .flags = IORESOURCE_IO | IORESOURCE_BUSY, -}; - -static int __init init_cdb89712_bootrom (void) -{ - int err; - - if (request_resource (&ioport_resource, &cdb89712_bootrom_resource)) { - printk(KERN_NOTICE "Failed to reserve Cdb89712 BOOTROM space\n"); - err = -EBUSY; - goto out; - } - - cdb89712_bootrom_map.virt = ioremap(BOOTROM_START, BOOTROM_SIZE); - if (!cdb89712_bootrom_map.virt) { - printk(KERN_NOTICE "Failed to ioremap Cdb89712 BootROM space\n"); - err = -EIO; - goto out_resource; - } - simple_map_init(&cdb89712_bootrom_map); - bootrom_mtd = do_map_probe("map_rom", &cdb89712_bootrom_map); - if (!bootrom_mtd) { - printk("BootROM probe failed\n"); - err = -ENXIO; - goto out_ioremap; - } - - bootrom_mtd->owner = THIS_MODULE; - bootrom_mtd->erasesize = 0x10000; - - if (add_mtd_device(bootrom_mtd)) { - printk("BootROM device addition failed\n"); - err = -ENOMEM; - goto out_probe; - } - - return 0; - -out_probe: - map_destroy(bootrom_mtd); - bootrom_mtd = 0; -out_ioremap: - iounmap((void *)cdb89712_bootrom_map.virt); -out_resource: - release_resource (&cdb89712_bootrom_resource); -out: - return err; -} - - - - - -static int __init init_cdb89712_maps(void) -{ - - printk(KERN_INFO "Cirrus CDB89712 MTD mappings:\n Flash 0x%x at 0x%x\n SRAM 0x%x at 0x%x\n BootROM 0x%x at 0x%x\n", - FLASH_SIZE, FLASH_START, SRAM_SIZE, SRAM_START, BOOTROM_SIZE, BOOTROM_START); - - init_cdb89712_flash(); - init_cdb89712_sram(); - init_cdb89712_bootrom(); - - return 0; -} - - -static void __exit cleanup_cdb89712_maps(void) -{ - if (sram_mtd) { - del_mtd_device(sram_mtd); - map_destroy(sram_mtd); - iounmap((void *)cdb89712_sram_map.virt); - release_resource (&cdb89712_sram_resource); - } - - if (flash_mtd) { - del_mtd_device(flash_mtd); - map_destroy(flash_mtd); - iounmap((void *)cdb89712_flash_map.virt); - release_resource (&cdb89712_flash_resource); - } - - if (bootrom_mtd) { - del_mtd_device(bootrom_mtd); - map_destroy(bootrom_mtd); - iounmap((void *)cdb89712_bootrom_map.virt); - release_resource (&cdb89712_bootrom_resource); - } -} - -module_init(init_cdb89712_maps); -module_exit(cleanup_cdb89712_maps); - -MODULE_AUTHOR("Ray L"); -MODULE_DESCRIPTION("ARM CDB89712 map driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/maps/ceiva.c b/drivers/mtd/maps/ceiva.c deleted file mode 100644 index 0402c21e291..00000000000 --- a/drivers/mtd/maps/ceiva.c +++ /dev/null @@ -1,349 +0,0 @@ -/* - * Ceiva flash memory driver. - * Copyright (C) 2002 Rob Scott <rscott@mtrob.fdns.net> - * - * Note: this driver supports jedec compatible devices. Modification - * for CFI compatible devices should be straight forward: change - * jedec_probe to cfi_probe. - * - * Based on: sa1100-flash.c, which has the following copyright: - * Flash memory access on SA11x0 based devices - * - * (C) 2000 Nicolas Pitre <nico@cam.org> - * - * $Id: ceiva.c,v 1.11 2004/09/16 23:27:12 gleixner Exp $ - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/ioport.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <linux/slab.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> -#include <linux/mtd/concat.h> - -#include <asm/hardware.h> -#include <asm/mach-types.h> -#include <asm/io.h> -#include <asm/sizes.h> - -/* - * This isn't complete yet, so... - */ -#define CONFIG_MTD_CEIVA_STATICMAP - -#ifdef CONFIG_MTD_CEIVA_STATICMAP -/* - * See include/linux/mtd/partitions.h for definition of the mtd_partition - * structure. - * - * Please note: - * 1. The flash size given should be the largest flash size that can - * be accomodated. - * - * 2. The bus width must defined in clps_setup_flash. - * - * The MTD layer will detect flash chip aliasing and reduce the size of - * the map accordingly. - * - */ - -#ifdef CONFIG_ARCH_CEIVA -/* Flash / Partition sizing */ -/* For the 28F8003, we use the block mapping to calcuate the sizes */ -#define MAX_SIZE_KiB (16 + 8 + 8 + 96 + (7*128)) -#define BOOT_PARTITION_SIZE_KiB (16) -#define PARAMS_PARTITION_SIZE_KiB (8) -#define KERNEL_PARTITION_SIZE_KiB (4*128) -/* Use both remaing portion of first flash, and all of second flash */ -#define ROOT_PARTITION_SIZE_KiB (3*128) + (8*128) - -static struct mtd_partition ceiva_partitions[] = { - { - .name = "Ceiva BOOT partition", - .size = BOOT_PARTITION_SIZE_KiB*1024, - .offset = 0, - - },{ - .name = "Ceiva parameters partition", - .size = PARAMS_PARTITION_SIZE_KiB*1024, - .offset = (16 + 8) * 1024, - },{ - .name = "Ceiva kernel partition", - .size = (KERNEL_PARTITION_SIZE_KiB)*1024, - .offset = 0x20000, - - },{ - .name = "Ceiva root filesystem partition", - .offset = MTDPART_OFS_APPEND, - .size = (ROOT_PARTITION_SIZE_KiB)*1024, - } -}; -#endif - -static int __init clps_static_partitions(struct mtd_partition **parts) -{ - int nb_parts = 0; - -#ifdef CONFIG_ARCH_CEIVA - if (machine_is_ceiva()) { - *parts = ceiva_partitions; - nb_parts = ARRAY_SIZE(ceiva_partitions); - } -#endif - return nb_parts; -} -#endif - -struct clps_info { - unsigned long base; - unsigned long size; - int width; - void *vbase; - struct map_info *map; - struct mtd_info *mtd; - struct resource *res; -}; - -#define NR_SUBMTD 4 - -static struct clps_info info[NR_SUBMTD]; - -static int __init clps_setup_mtd(struct clps_info *clps, int nr, struct mtd_info **rmtd) -{ - struct mtd_info *subdev[nr]; - struct map_info *maps; - int i, found = 0, ret = 0; - - /* - * Allocate the map_info structs in one go. - */ - maps = kmalloc(sizeof(struct map_info) * nr, GFP_KERNEL); - if (!maps) - return -ENOMEM; - memset(maps, 0, sizeof(struct map_info) * nr); - /* - * Claim and then map the memory regions. - */ - for (i = 0; i < nr; i++) { - if (clps[i].base == (unsigned long)-1) - break; - - clps[i].res = request_mem_region(clps[i].base, clps[i].size, "clps flash"); - if (!clps[i].res) { - ret = -EBUSY; - break; - } - - clps[i].map = maps + i; - - clps[i].map->name = "clps flash"; - clps[i].map->phys = clps[i].base; - - clps[i].vbase = ioremap(clps[i].base, clps[i].size); - if (!clps[i].vbase) { - ret = -ENOMEM; - break; - } - - clps[i].map->virt = (void __iomem *)clps[i].vbase; - clps[i].map->bankwidth = clps[i].width; - clps[i].map->size = clps[i].size; - - simple_map_init(&clps[i].map); - - clps[i].mtd = do_map_probe("jedec_probe", clps[i].map); - if (clps[i].mtd == NULL) { - ret = -ENXIO; - break; - } - clps[i].mtd->owner = THIS_MODULE; - subdev[i] = clps[i].mtd; - - printk(KERN_INFO "clps flash: JEDEC device at 0x%08lx, %dMiB, " - "%d-bit\n", clps[i].base, clps[i].mtd->size >> 20, - clps[i].width * 8); - found += 1; - } - - /* - * ENXIO is special. It means we didn't find a chip when - * we probed. We need to tear down the mapping, free the - * resource and mark it as such. - */ - if (ret == -ENXIO) { - iounmap(clps[i].vbase); - clps[i].vbase = NULL; - release_resource(clps[i].res); - clps[i].res = NULL; - } - - /* - * If we found one device, don't bother with concat support. - * If we found multiple devices, use concat if we have it - * available, otherwise fail. - */ - if (ret == 0 || ret == -ENXIO) { - if (found == 1) { - *rmtd = subdev[0]; - ret = 0; - } else if (found > 1) { - /* - * We detected multiple devices. Concatenate - * them together. - */ -#ifdef CONFIG_MTD_CONCAT - *rmtd = mtd_concat_create(subdev, found, - "clps flash"); - if (*rmtd == NULL) - ret = -ENXIO; -#else - printk(KERN_ERR "clps flash: multiple devices " - "found but MTD concat support disabled.\n"); - ret = -ENXIO; -#endif - } - } - - /* - * If we failed, clean up. - */ - if (ret) { - do { - if (clps[i].mtd) - map_destroy(clps[i].mtd); - if (clps[i].vbase) - iounmap(clps[i].vbase); - if (clps[i].res) - release_resource(clps[i].res); - } while (i--); - - kfree(maps); - } - - return ret; -} - -static void __exit clps_destroy_mtd(struct clps_info *clps, struct mtd_info *mtd) -{ - int i; - - del_mtd_partitions(mtd); - - if (mtd != clps[0].mtd) - mtd_concat_destroy(mtd); - - for (i = NR_SUBMTD; i >= 0; i--) { - if (clps[i].mtd) - map_destroy(clps[i].mtd); - if (clps[i].vbase) - iounmap(clps[i].vbase); - if (clps[i].res) - release_resource(clps[i].res); - } - kfree(clps[0].map); -} - -/* - * We define the memory space, size, and width for the flash memory - * space here. - */ - -static int __init clps_setup_flash(void) -{ - int nr; - -#ifdef CONFIG_ARCH_CEIVA - if (machine_is_ceiva()) { - info[0].base = CS0_PHYS_BASE; - info[0].size = SZ_32M; - info[0].width = CEIVA_FLASH_WIDTH; - info[1].base = CS1_PHYS_BASE; - info[1].size = SZ_32M; - info[1].width = CEIVA_FLASH_WIDTH; - nr = 2; - } -#endif - return nr; -} - -static struct mtd_partition *parsed_parts; -static const char *probes[] = { "cmdlinepart", "RedBoot", NULL }; - -static void __init clps_locate_partitions(struct mtd_info *mtd) -{ - const char *part_type = NULL; - int nr_parts = 0; - do { - /* - * Partition selection stuff. - */ - nr_parts = parse_mtd_partitions(mtd, probes, &parsed_parts, 0); - if (nr_parts > 0) { - part_type = "command line"; - break; - } -#ifdef CONFIG_MTD_CEIVA_STATICMAP - nr_parts = clps_static_partitions(&parsed_parts); - if (nr_parts > 0) { - part_type = "static"; - break; - } - printk("found: %d partitions\n", nr_parts); -#endif - } while (0); - - if (nr_parts == 0) { - printk(KERN_NOTICE "clps flash: no partition info " - "available, registering whole flash\n"); - add_mtd_device(mtd); - } else { - printk(KERN_NOTICE "clps flash: using %s partition " - "definition\n", part_type); - add_mtd_partitions(mtd, parsed_parts, nr_parts); - } - - /* Always succeeds. */ -} - -static void __exit clps_destroy_partitions(void) -{ - kfree(parsed_parts); -} - -static struct mtd_info *mymtd; - -static int __init clps_mtd_init(void) -{ - int ret; - int nr; - - nr = clps_setup_flash(); - if (nr < 0) - return nr; - - ret = clps_setup_mtd(info, nr, &mymtd); - if (ret) - return ret; - - clps_locate_partitions(mymtd); - - return 0; -} - -static void __exit clps_mtd_cleanup(void) -{ - clps_destroy_mtd(info, mymtd); - clps_destroy_partitions(); -} - -module_init(clps_mtd_init); -module_exit(clps_mtd_cleanup); - -MODULE_AUTHOR("Rob Scott"); -MODULE_DESCRIPTION("Cirrus Logic JEDEC map driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/maps/cfi_flagadm.c b/drivers/mtd/maps/cfi_flagadm.c index 92b5d883d7b..d504b3d1791 100644 --- a/drivers/mtd/maps/cfi_flagadm.c +++ b/drivers/mtd/maps/cfi_flagadm.c @@ -1,8 +1,6 @@ /* * Copyright © 2001 Flaga hf. Medical Devices, Kári Davíðsson <kd@flaga.is> * - * $Id: cfi_flagadm.c,v 1.15 2005/11/07 11:14:26 gleixner Exp $ - * * 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 @@ -35,7 +33,7 @@ /* We split the flash chip up into four parts. - * 1: bootloader firts 128k (0x00000000 - 0x0001FFFF) size 0x020000 + * 1: bootloader first 128k (0x00000000 - 0x0001FFFF) size 0x020000 * 2: kernel 640k (0x00020000 - 0x000BFFFF) size 0x0A0000 * 3: compressed 1536k root ramdisk (0x000C0000 - 0x0023FFFF) size 0x180000 * 4: writeable diskpartition (jffs)(0x00240000 - 0x003FFFFF) size 0x1C0000 @@ -57,13 +55,13 @@ #define FLASH_PARTITION3_SIZE 0x001C0000 -struct map_info flagadm_map = { +static struct map_info flagadm_map = { .name = "FlagaDM flash device", .size = FLASH_SIZE, .bankwidth = 2, }; -struct mtd_partition flagadm_parts[] = { +static struct mtd_partition flagadm_parts[] = { { .name = "Bootloader", .offset = FLASH_PARTITION0_ADDR, @@ -80,7 +78,7 @@ struct mtd_partition flagadm_parts[] = { .size = FLASH_PARTITION2_SIZE }, { - .name = "Persistant storage", + .name = "Persistent storage", .offset = FLASH_PARTITION3_ADDR, .size = FLASH_PARTITION3_SIZE } @@ -90,7 +88,7 @@ struct mtd_partition flagadm_parts[] = { static struct mtd_info *mymtd; -int __init init_flagadm(void) +static int __init init_flagadm(void) { printk(KERN_NOTICE "FlagaDM flash device: %x at %x\n", FLASH_SIZE, FLASH_PHYS_ADDR); @@ -109,24 +107,24 @@ int __init init_flagadm(void) mymtd = do_map_probe("cfi_probe", &flagadm_map); if (mymtd) { mymtd->owner = THIS_MODULE; - add_mtd_partitions(mymtd, flagadm_parts, PARTITION_COUNT); + mtd_device_register(mymtd, flagadm_parts, PARTITION_COUNT); printk(KERN_NOTICE "FlagaDM flash device initialized\n"); return 0; } - iounmap((void *)flagadm_map.virt); + iounmap((void __iomem *)flagadm_map.virt); return -ENXIO; } static void __exit cleanup_flagadm(void) { if (mymtd) { - del_mtd_partitions(mymtd); + mtd_device_unregister(mymtd); map_destroy(mymtd); } if (flagadm_map.virt) { - iounmap((void *)flagadm_map.virt); - flagadm_map.virt = 0; + iounmap((void __iomem *)flagadm_map.virt); + flagadm_map.virt = NULL; } } diff --git a/drivers/mtd/maps/ck804xrom.c b/drivers/mtd/maps/ck804xrom.c new file mode 100644 index 00000000000..0455166f05f --- /dev/null +++ b/drivers/mtd/maps/ck804xrom.c @@ -0,0 +1,387 @@ +/* + * ck804xrom.c + * + * Normal mappings of chips in physical memory + * + * Dave Olsen <dolsen@lnxi.com> + * Ryan Jackson <rjackson@lnxi.com> + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <asm/io.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/cfi.h> +#include <linux/mtd/flashchip.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> +#include <linux/list.h> + + +#define MOD_NAME KBUILD_BASENAME + +#define ADDRESS_NAME_LEN 18 + +#define ROM_PROBE_STEP_SIZE (64*1024) + +#define DEV_CK804 1 +#define DEV_MCP55 2 + +struct ck804xrom_window { + void __iomem *virt; + unsigned long phys; + unsigned long size; + struct list_head maps; + struct resource rsrc; + struct pci_dev *pdev; +}; + +struct ck804xrom_map_info { + struct list_head list; + struct map_info map; + struct mtd_info *mtd; + struct resource rsrc; + char map_name[sizeof(MOD_NAME) + 2 + ADDRESS_NAME_LEN]; +}; + +/* + * The following applies to ck804 only: + * The 2 bits controlling the window size are often set to allow reading + * the BIOS, but too small to allow writing, since the lock registers are + * 4MiB lower in the address space than the data. + * + * This is intended to prevent flashing the bios, perhaps accidentally. + * + * This parameter allows the normal driver to override the BIOS settings. + * + * The bits are 6 and 7. If both bits are set, it is a 5MiB window. + * If only the 7 Bit is set, it is a 4MiB window. Otherwise, a + * 64KiB window. + * + * The following applies to mcp55 only: + * The 15 bits controlling the window size are distributed as follows: + * byte @0x88: bit 0..7 + * byte @0x8c: bit 8..15 + * word @0x90: bit 16..30 + * If all bits are enabled, we have a 16? MiB window + * Please set win_size_bits to 0x7fffffff if you actually want to do something + */ +static uint win_size_bits = 0; +module_param(win_size_bits, uint, 0); +MODULE_PARM_DESC(win_size_bits, "ROM window size bits override, normally set by BIOS."); + +static struct ck804xrom_window ck804xrom_window = { + .maps = LIST_HEAD_INIT(ck804xrom_window.maps), +}; + +static void ck804xrom_cleanup(struct ck804xrom_window *window) +{ + struct ck804xrom_map_info *map, *scratch; + u8 byte; + + if (window->pdev) { + /* Disable writes through the rom window */ + pci_read_config_byte(window->pdev, 0x6d, &byte); + pci_write_config_byte(window->pdev, 0x6d, byte & ~1); + } + + /* Free all of the mtd devices */ + list_for_each_entry_safe(map, scratch, &window->maps, list) { + if (map->rsrc.parent) + release_resource(&map->rsrc); + + mtd_device_unregister(map->mtd); + map_destroy(map->mtd); + list_del(&map->list); + kfree(map); + } + if (window->rsrc.parent) + release_resource(&window->rsrc); + + if (window->virt) { + iounmap(window->virt); + window->virt = NULL; + window->phys = 0; + window->size = 0; + } + pci_dev_put(window->pdev); +} + + +static int ck804xrom_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + static char *rom_probe_types[] = { "cfi_probe", "jedec_probe", NULL }; + u8 byte; + u16 word; + struct ck804xrom_window *window = &ck804xrom_window; + struct ck804xrom_map_info *map = NULL; + unsigned long map_top; + + /* Remember the pci dev I find the window in */ + window->pdev = pci_dev_get(pdev); + + switch (ent->driver_data) { + case DEV_CK804: + /* Enable the selected rom window. This is often incorrectly + * set up by the BIOS, and the 4MiB offset for the lock registers + * requires the full 5MiB of window space. + * + * This 'write, then read' approach leaves the bits for + * other uses of the hardware info. + */ + pci_read_config_byte(pdev, 0x88, &byte); + pci_write_config_byte(pdev, 0x88, byte | win_size_bits ); + + /* Assume the rom window is properly setup, and find it's size */ + pci_read_config_byte(pdev, 0x88, &byte); + + if ((byte & ((1<<7)|(1<<6))) == ((1<<7)|(1<<6))) + window->phys = 0xffb00000; /* 5MiB */ + else if ((byte & (1<<7)) == (1<<7)) + window->phys = 0xffc00000; /* 4MiB */ + else + window->phys = 0xffff0000; /* 64KiB */ + break; + + case DEV_MCP55: + pci_read_config_byte(pdev, 0x88, &byte); + pci_write_config_byte(pdev, 0x88, byte | (win_size_bits & 0xff)); + + pci_read_config_byte(pdev, 0x8c, &byte); + pci_write_config_byte(pdev, 0x8c, byte | ((win_size_bits & 0xff00) >> 8)); + + pci_read_config_word(pdev, 0x90, &word); + pci_write_config_word(pdev, 0x90, word | ((win_size_bits & 0x7fff0000) >> 16)); + + window->phys = 0xff000000; /* 16MiB, hardcoded for now */ + break; + } + + window->size = 0xffffffffUL - window->phys + 1UL; + + /* + * Try to reserve the window mem region. If this fails then + * it is likely due to a fragment of the window being + * "reserved" by the BIOS. In the case that the + * request_mem_region() fails then once the rom size is + * discovered we will try to reserve the unreserved fragment. + */ + window->rsrc.name = MOD_NAME; + window->rsrc.start = window->phys; + window->rsrc.end = window->phys + window->size - 1; + window->rsrc.flags = IORESOURCE_MEM | IORESOURCE_BUSY; + if (request_resource(&iomem_resource, &window->rsrc)) { + window->rsrc.parent = NULL; + printk(KERN_ERR MOD_NAME + " %s(): Unable to register resource %pR - kernel bug?\n", + __func__, &window->rsrc); + } + + + /* Enable writes through the rom window */ + pci_read_config_byte(pdev, 0x6d, &byte); + pci_write_config_byte(pdev, 0x6d, byte | 1); + + /* FIXME handle registers 0x80 - 0x8C the bios region locks */ + + /* For write accesses caches are useless */ + window->virt = ioremap_nocache(window->phys, window->size); + if (!window->virt) { + printk(KERN_ERR MOD_NAME ": ioremap(%08lx, %08lx) failed\n", + window->phys, window->size); + goto out; + } + + /* Get the first address to look for a rom chip at */ + map_top = window->phys; +#if 1 + /* The probe sequence run over the firmware hub lock + * registers sets them to 0x7 (no access). + * Probe at most the last 4MiB of the address space. + */ + if (map_top < 0xffc00000) + map_top = 0xffc00000; +#endif + /* Loop through and look for rom chips. Since we don't know the + * starting address for each chip, probe every ROM_PROBE_STEP_SIZE + * bytes from the starting address of the window. + */ + while((map_top - 1) < 0xffffffffUL) { + struct cfi_private *cfi; + unsigned long offset; + int i; + + if (!map) + map = kmalloc(sizeof(*map), GFP_KERNEL); + + if (!map) { + printk(KERN_ERR MOD_NAME ": kmalloc failed"); + goto out; + } + memset(map, 0, sizeof(*map)); + INIT_LIST_HEAD(&map->list); + map->map.name = map->map_name; + map->map.phys = map_top; + offset = map_top - window->phys; + map->map.virt = (void __iomem *) + (((unsigned long)(window->virt)) + offset); + map->map.size = 0xffffffffUL - map_top + 1UL; + /* Set the name of the map to the address I am trying */ + sprintf(map->map_name, "%s @%08Lx", + MOD_NAME, (unsigned long long)map->map.phys); + + /* There is no generic VPP support */ + for(map->map.bankwidth = 32; map->map.bankwidth; + map->map.bankwidth >>= 1) + { + char **probe_type; + /* Skip bankwidths that are not supported */ + if (!map_bankwidth_supported(map->map.bankwidth)) + continue; + + /* Setup the map methods */ + simple_map_init(&map->map); + + /* Try all of the probe methods */ + probe_type = rom_probe_types; + for(; *probe_type; probe_type++) { + map->mtd = do_map_probe(*probe_type, &map->map); + if (map->mtd) + goto found; + } + } + map_top += ROM_PROBE_STEP_SIZE; + continue; + found: + /* Trim the size if we are larger than the map */ + if (map->mtd->size > map->map.size) { + printk(KERN_WARNING MOD_NAME + " rom(%llu) larger than window(%lu). fixing...\n", + (unsigned long long)map->mtd->size, map->map.size); + map->mtd->size = map->map.size; + } + if (window->rsrc.parent) { + /* + * Registering the MTD device in iomem may not be possible + * if there is a BIOS "reserved" and BUSY range. If this + * fails then continue anyway. + */ + map->rsrc.name = map->map_name; + map->rsrc.start = map->map.phys; + map->rsrc.end = map->map.phys + map->mtd->size - 1; + map->rsrc.flags = IORESOURCE_MEM | IORESOURCE_BUSY; + if (request_resource(&window->rsrc, &map->rsrc)) { + printk(KERN_ERR MOD_NAME + ": cannot reserve MTD resource\n"); + map->rsrc.parent = NULL; + } + } + + /* Make the whole region visible in the map */ + map->map.virt = window->virt; + map->map.phys = window->phys; + cfi = map->map.fldrv_priv; + for(i = 0; i < cfi->numchips; i++) + cfi->chips[i].start += offset; + + /* Now that the mtd devices is complete claim and export it */ + map->mtd->owner = THIS_MODULE; + if (mtd_device_register(map->mtd, NULL, 0)) { + map_destroy(map->mtd); + map->mtd = NULL; + goto out; + } + + + /* Calculate the new value of map_top */ + map_top += map->mtd->size; + + /* File away the map structure */ + list_add(&map->list, &window->maps); + map = NULL; + } + + out: + /* Free any left over map structures */ + kfree(map); + + /* See if I have any map structures */ + if (list_empty(&window->maps)) { + ck804xrom_cleanup(window); + return -ENODEV; + } + return 0; +} + + +static void ck804xrom_remove_one(struct pci_dev *pdev) +{ + struct ck804xrom_window *window = &ck804xrom_window; + + ck804xrom_cleanup(window); +} + +static struct pci_device_id ck804xrom_pci_tbl[] = { + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, 0x0051), .driver_data = DEV_CK804 }, + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, 0x0360), .driver_data = DEV_MCP55 }, + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, 0x0361), .driver_data = DEV_MCP55 }, + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, 0x0362), .driver_data = DEV_MCP55 }, + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, 0x0363), .driver_data = DEV_MCP55 }, + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, 0x0364), .driver_data = DEV_MCP55 }, + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, 0x0365), .driver_data = DEV_MCP55 }, + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, 0x0366), .driver_data = DEV_MCP55 }, + { PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, 0x0367), .driver_data = DEV_MCP55 }, + { 0, } +}; + +#if 0 +MODULE_DEVICE_TABLE(pci, ck804xrom_pci_tbl); + +static struct pci_driver ck804xrom_driver = { + .name = MOD_NAME, + .id_table = ck804xrom_pci_tbl, + .probe = ck804xrom_init_one, + .remove = ck804xrom_remove_one, +}; +#endif + +static int __init init_ck804xrom(void) +{ + struct pci_dev *pdev; + struct pci_device_id *id; + int retVal; + pdev = NULL; + + for(id = ck804xrom_pci_tbl; id->vendor; id++) { + pdev = pci_get_device(id->vendor, id->device, NULL); + if (pdev) + break; + } + if (pdev) { + retVal = ck804xrom_init_one(pdev, id); + pci_dev_put(pdev); + return retVal; + } + return -ENXIO; +#if 0 + return pci_register_driver(&ck804xrom_driver); +#endif +} + +static void __exit cleanup_ck804xrom(void) +{ + ck804xrom_remove_one(ck804xrom_window.pdev); +} + +module_init(init_ck804xrom); +module_exit(cleanup_ck804xrom); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Eric Biederman <ebiederman@lnxi.com>, Dave Olsen <dolsen@lnxi.com>"); +MODULE_DESCRIPTION("MTD map driver for BIOS chips on the Nvidia ck804 southbridge"); + diff --git a/drivers/mtd/maps/cstm_mips_ixx.c b/drivers/mtd/maps/cstm_mips_ixx.c deleted file mode 100644 index d6bef100d69..00000000000 --- a/drivers/mtd/maps/cstm_mips_ixx.c +++ /dev/null @@ -1,283 +0,0 @@ -/* - * $Id: cstm_mips_ixx.c,v 1.14 2005/11/07 11:14:26 gleixner Exp $ - * - * Mapping of a custom board with both AMD CFI and JEDEC flash in partitions. - * Config with both CFI and JEDEC device support. - * - * Basically physmap.c with the addition of partitions and - * an array of mapping info to accomodate more than one flash type per board. - * - * Copyright 2000 MontaVista Software Inc. - * - * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN - * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF - * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * 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/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <asm/io.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> -#include <linux/delay.h> - -#if defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR) -#define CC_GCR 0xB4013818 -#define CC_GPBCR 0xB401380A -#define CC_GPBDR 0xB4013808 -#define CC_M68K_DEVICE 1 -#define CC_M68K_FUNCTION 6 -#define CC_CONFADDR 0xB8004000 -#define CC_CONFDATA 0xB8004004 -#define CC_FC_FCR 0xB8002004 -#define CC_FC_DCR 0xB8002008 -#define CC_GPACR 0xB4013802 -#define CC_GPAICR 0xB4013804 -#endif /* defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR) */ - -#if defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR) -void cstm_mips_ixx_set_vpp(struct map_info *map,int vpp) -{ - static DEFINE_SPINLOCK(vpp_lock); - static int vpp_count = 0; - unsigned long flags; - - spin_lock_irqsave(&vpp_lock, flags); - - if (vpp) { - if (!vpp_count++) { - __u16 data; - __u8 data1; - static u8 first = 1; - - // Set GPIO port B pin3 to high - data = *(__u16 *)(CC_GPBCR); - data = (data & 0xff0f) | 0x0040; - *(__u16 *)CC_GPBCR = data; - *(__u8 *)CC_GPBDR = (*(__u8*)CC_GPBDR) | 0x08; - if (first) { - first = 0; - /* need to have this delay for first - enabling vpp after powerup */ - udelay(40); - } - } - } else { - if (!--vpp_count) { - __u16 data; - - // Set GPIO port B pin3 to high - data = *(__u16 *)(CC_GPBCR); - data = (data & 0xff3f) | 0x0040; - *(__u16 *)CC_GPBCR = data; - *(__u8 *)CC_GPBDR = (*(__u8*)CC_GPBDR) & 0xf7; - } - } - spin_unlock_irqrestore(&vpp_lock, flags); -} -#endif - -/* board and partition description */ - -#define MAX_PHYSMAP_PARTITIONS 8 -struct cstm_mips_ixx_info { - char *name; - unsigned long window_addr; - unsigned long window_size; - int bankwidth; - int num_partitions; -}; - -#if defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR) -#define PHYSMAP_NUMBER 1 // number of board desc structs needed, one per contiguous flash type -const struct cstm_mips_ixx_info cstm_mips_ixx_board_desc[PHYSMAP_NUMBER] = -{ - { // 28F128J3A in 2x16 configuration - "big flash", // name - 0x08000000, // window_addr - 0x02000000, // window_size - 4, // bankwidth - 1, // num_partitions - } - -}; -static struct mtd_partition cstm_mips_ixx_partitions[PHYSMAP_NUMBER][MAX_PHYSMAP_PARTITIONS] = { -{ // 28F128J3A in 2x16 configuration - { - .name = "main partition ", - .size = 0x02000000, // 128 x 2 x 128k byte sectors - .offset = 0, - }, -}, -}; -#else /* defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR) */ -#define PHYSMAP_NUMBER 1 // number of board desc structs needed, one per contiguous flash type -const struct cstm_mips_ixx_info cstm_mips_ixx_board_desc[PHYSMAP_NUMBER] = -{ - { - "MTD flash", // name - CONFIG_MTD_CSTM_MIPS_IXX_START, // window_addr - CONFIG_MTD_CSTM_MIPS_IXX_LEN, // window_size - CONFIG_MTD_CSTM_MIPS_IXX_BUSWIDTH, // bankwidth - 1, // num_partitions - }, - -}; -static struct mtd_partition cstm_mips_ixx_partitions[PHYSMAP_NUMBER][MAX_PHYSMAP_PARTITIONS] = { -{ - { - .name = "main partition", - .size = CONFIG_MTD_CSTM_MIPS_IXX_LEN, - .offset = 0, - }, -}, -}; -#endif /* defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR) */ - -struct map_info cstm_mips_ixx_map[PHYSMAP_NUMBER]; - -int __init init_cstm_mips_ixx(void) -{ - int i; - int jedec; - struct mtd_info *mymtd; - struct mtd_partition *parts; - - /* Initialize mapping */ - for (i=0;i<PHYSMAP_NUMBER;i++) { - printk(KERN_NOTICE "cstm_mips_ixx flash device: 0x%lx at 0x%lx\n", - cstm_mips_ixx_board_desc[i].window_size, cstm_mips_ixx_board_desc[i].window_addr); - - - cstm_mips_ixx_map[i].phys = cstm_mips_ixx_board_desc[i].window_addr; - cstm_mips_ixx_map[i].virt = ioremap(cstm_mips_ixx_board_desc[i].window_addr, cstm_mips_ixx_board_desc[i].window_size); - if (!cstm_mips_ixx_map[i].virt) { - int j = 0; - printk(KERN_WARNING "Failed to ioremap\n"); - for (j = 0; j < i; j++) { - if (cstm_mips_ixx_map[j].virt) { - iounmap((void *)cstm_mips_ixx_map[j].virt); - cstm_mips_ixx_map[j].virt = 0; - } - } - return -EIO; - } - cstm_mips_ixx_map[i].name = cstm_mips_ixx_board_desc[i].name; - cstm_mips_ixx_map[i].size = cstm_mips_ixx_board_desc[i].window_size; - cstm_mips_ixx_map[i].bankwidth = cstm_mips_ixx_board_desc[i].bankwidth; -#if defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR) - cstm_mips_ixx_map[i].set_vpp = cstm_mips_ixx_set_vpp; -#endif - simple_map_init(&cstm_mips_ixx_map[i]); - //printk(KERN_NOTICE "cstm_mips_ixx: ioremap is %x\n",(unsigned int)(cstm_mips_ixx_map[i].virt)); - } - -#if defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR) - setup_ITE_IVR_flash(); -#endif /* defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR) */ - - for (i=0;i<PHYSMAP_NUMBER;i++) { - parts = &cstm_mips_ixx_partitions[i][0]; - jedec = 0; - mymtd = (struct mtd_info *)do_map_probe("cfi_probe", &cstm_mips_ixx_map[i]); - //printk(KERN_NOTICE "phymap %d cfi_probe: mymtd is %x\n",i,(unsigned int)mymtd); - if (!mymtd) { - jedec = 1; - mymtd = (struct mtd_info *)do_map_probe("jedec", &cstm_mips_ixx_map[i]); - printk(KERN_NOTICE "cstm_mips_ixx %d jedec: mymtd is %x\n",i,(unsigned int)mymtd); - } - if (mymtd) { - mymtd->owner = THIS_MODULE; - - cstm_mips_ixx_map[i].map_priv_2 = (unsigned long)mymtd; - add_mtd_partitions(mymtd, parts, cstm_mips_ixx_board_desc[i].num_partitions); - } - else { - for (i = 0; i < PHYSMAP_NUMBER; i++) { - if (cstm_mips_ixx_map[i].virt) { - iounmap((void *)cstm_mips_ixx_map[i].virt); - cstm_mips_ixx_map[i].virt = 0; - } - } - return -ENXIO; - } - } - return 0; -} - -static void __exit cleanup_cstm_mips_ixx(void) -{ - int i; - struct mtd_info *mymtd; - - for (i=0;i<PHYSMAP_NUMBER;i++) { - mymtd = (struct mtd_info *)cstm_mips_ixx_map[i].map_priv_2; - if (mymtd) { - del_mtd_partitions(mymtd); - map_destroy(mymtd); - } - if (cstm_mips_ixx_map[i].virt) { - iounmap((void *)cstm_mips_ixx_map[i].virt); - cstm_mips_ixx_map[i].virt = 0; - } - } -} -#if defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR) -void PCISetULongByOffset(__u32 DevNumber, __u32 FuncNumber, __u32 Offset, __u32 data) -{ - __u32 offset; - - offset = ( unsigned long )( 0x80000000 | ( DevNumber << 11 ) + ( FuncNumber << 8 ) + Offset) ; - - *(__u32 *)CC_CONFADDR = offset; - *(__u32 *)CC_CONFDATA = data; -} -void setup_ITE_IVR_flash() -{ - __u32 size, base; - - size = 0x0e000000; // 32MiB - base = (0x08000000) >> 8 >>1; // Bug: we must shift one more bit - - /* need to set ITE flash to 32 bits instead of default 8 */ -#ifdef CONFIG_MIPS_IVR - *(__u32 *)CC_FC_FCR = 0x55; - *(__u32 *)CC_GPACR = 0xfffc; -#else - *(__u32 *)CC_FC_FCR = 0x77; -#endif - /* turn bursting off */ - *(__u32 *)CC_FC_DCR = 0x0; - - /* setup for one chip 4 byte PCI access */ - PCISetULongByOffset(CC_M68K_DEVICE, CC_M68K_FUNCTION, 0x60, size | base); - PCISetULongByOffset(CC_M68K_DEVICE, CC_M68K_FUNCTION, 0x64, 0x02); -} -#endif /* defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR) */ - -module_init(init_cstm_mips_ixx); -module_exit(cleanup_cstm_mips_ixx); - - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Alice Hennessy <ahennessy@mvista.com>"); -MODULE_DESCRIPTION("MTD map driver for ITE 8172G and Globespan IVR boards"); diff --git a/drivers/mtd/maps/dbox2-flash.c b/drivers/mtd/maps/dbox2-flash.c deleted file mode 100644 index 92a9c7fac99..00000000000 --- a/drivers/mtd/maps/dbox2-flash.c +++ /dev/null @@ -1,125 +0,0 @@ -/* - * $Id: dbox2-flash.c,v 1.14 2005/11/07 11:14:26 gleixner Exp $ - * - * D-Box 2 flash driver - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <asm/io.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> -#include <linux/errno.h> - -/* partition_info gives details on the logical partitions that the split the - * single flash device into. If the size if zero we use up to the end of the - * device. */ -static struct mtd_partition partition_info[]= { - { - .name = "BR bootloader", - .size = 128 * 1024, - .offset = 0, - .mask_flags = MTD_WRITEABLE - }, - { - .name = "FLFS (U-Boot)", - .size = 128 * 1024, - .offset = MTDPART_OFS_APPEND, - .mask_flags = 0 - }, - { - .name = "Root (SquashFS)", - .size = 7040 * 1024, - .offset = MTDPART_OFS_APPEND, - .mask_flags = 0 - }, - { - .name = "var (JFFS2)", - .size = 896 * 1024, - .offset = MTDPART_OFS_APPEND, - .mask_flags = 0 - }, - { - .name = "Flash without bootloader", - .size = MTDPART_SIZ_FULL, - .offset = 128 * 1024, - .mask_flags = 0 - }, - { - .name = "Complete Flash", - .size = MTDPART_SIZ_FULL, - .offset = 0, - .mask_flags = MTD_WRITEABLE - } -}; - -#define NUM_PARTITIONS ARRAY_SIZE(partition_info) - -#define WINDOW_ADDR 0x10000000 -#define WINDOW_SIZE 0x800000 - -static struct mtd_info *mymtd; - - -struct map_info dbox2_flash_map = { - .name = "D-Box 2 flash memory", - .size = WINDOW_SIZE, - .bankwidth = 4, - .phys = WINDOW_ADDR, -}; - -int __init init_dbox2_flash(void) -{ - printk(KERN_NOTICE "D-Box 2 flash driver (size->0x%X mem->0x%X)\n", WINDOW_SIZE, WINDOW_ADDR); - dbox2_flash_map.virt = ioremap(WINDOW_ADDR, WINDOW_SIZE); - - if (!dbox2_flash_map.virt) { - printk("Failed to ioremap\n"); - return -EIO; - } - simple_map_init(&dbox2_flash_map); - - // Probe for dual Intel 28F320 or dual AMD - mymtd = do_map_probe("cfi_probe", &dbox2_flash_map); - if (!mymtd) { - // Probe for single Intel 28F640 - dbox2_flash_map.bankwidth = 2; - - mymtd = do_map_probe("cfi_probe", &dbox2_flash_map); - } - - if (mymtd) { - mymtd->owner = THIS_MODULE; - - /* Create MTD devices for each partition. */ - add_mtd_partitions(mymtd, partition_info, NUM_PARTITIONS); - - return 0; - } - - iounmap((void *)dbox2_flash_map.virt); - return -ENXIO; -} - -static void __exit cleanup_dbox2_flash(void) -{ - if (mymtd) { - del_mtd_partitions(mymtd); - map_destroy(mymtd); - } - if (dbox2_flash_map.virt) { - iounmap((void *)dbox2_flash_map.virt); - dbox2_flash_map.virt = 0; - } -} - -module_init(init_dbox2_flash); -module_exit(cleanup_dbox2_flash); - - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Kári Davíðsson <kd@flaga.is>, Bastian Blank <waldi@tuxbox.org>, Alexander Wild <wild@te-elektronik.com>"); -MODULE_DESCRIPTION("MTD map driver for D-Box 2 board"); diff --git a/drivers/mtd/maps/dc21285.c b/drivers/mtd/maps/dc21285.c index b32bb9347d7..f8a7dd14cee 100644 --- a/drivers/mtd/maps/dc21285.c +++ b/drivers/mtd/maps/dc21285.c @@ -1,11 +1,9 @@ /* * MTD map driver for flash on the DC21285 (the StrongARM-110 companion chip) * - * (C) 2000 Nicolas Pitre <nico@cam.org> + * (C) 2000 Nicolas Pitre <nico@fluxnic.net> * * This code is GPL - * - * $Id: dc21285.c,v 1.24 2005/11/07 11:14:26 gleixner Exp $ */ #include <linux/module.h> #include <linux/types.h> @@ -34,16 +32,15 @@ static struct mtd_info *dc21285_mtd; */ static void nw_en_write(void) { - extern spinlock_t gpio_lock; unsigned long flags; /* * we want to write a bit pattern XXX1 to Xilinx to enable * the write gate, which will be open for about the next 2ms. */ - spin_lock_irqsave(&gpio_lock, flags); - cpld_modify(1, 1); - spin_unlock_irqrestore(&gpio_lock, flags); + spin_lock_irqsave(&nw_gpio_lock, flags); + nw_cpld_modify(CPLD_FLASH_WR_ENABLE, CPLD_FLASH_WR_ENABLE); + spin_unlock_irqrestore(&nw_gpio_lock, flags); /* * let the ISA bus to catch on... @@ -146,20 +143,11 @@ static struct map_info dc21285_map = { .copy_from = dc21285_copy_from, }; - /* Partition stuff */ -#ifdef CONFIG_MTD_PARTITIONS -static struct mtd_partition *dc21285_parts; -static const char *probes[] = { "RedBoot", "cmdlinepart", NULL }; -#endif +static const char * const probes[] = { "RedBoot", "cmdlinepart", NULL }; static int __init init_dc21285(void) { - -#ifdef CONFIG_MTD_PARTITIONS - int nrparts; -#endif - /* Determine bankwidth */ switch (*CSR_SA110_CNTL & (3<<14)) { case SA110_CNTL_ROMWIDTH_8: @@ -207,13 +195,7 @@ static int __init init_dc21285(void) dc21285_mtd->owner = THIS_MODULE; -#ifdef CONFIG_MTD_PARTITIONS - nrparts = parse_mtd_partitions(dc21285_mtd, probes, &dc21285_parts, 0); - if (nrparts > 0) - add_mtd_partitions(dc21285_mtd, dc21285_parts, nrparts); - else -#endif - add_mtd_device(dc21285_mtd); + mtd_device_parse_register(dc21285_mtd, probes, NULL, NULL, 0); if(machine_is_ebsa285()) { /* @@ -235,14 +217,7 @@ static int __init init_dc21285(void) static void __exit cleanup_dc21285(void) { -#ifdef CONFIG_MTD_PARTITIONS - if (dc21285_parts) { - del_mtd_partitions(dc21285_mtd); - kfree(dc21285_parts); - } else -#endif - del_mtd_device(dc21285_mtd); - + mtd_device_unregister(dc21285_mtd); map_destroy(dc21285_mtd); iounmap(dc21285_map.virt); } @@ -252,5 +227,5 @@ module_exit(cleanup_dc21285); MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Nicolas Pitre <nico@cam.org>"); +MODULE_AUTHOR("Nicolas Pitre <nico@fluxnic.net>"); MODULE_DESCRIPTION("MTD map driver for DC21285 boards"); diff --git a/drivers/mtd/maps/dilnetpc.c b/drivers/mtd/maps/dilnetpc.c deleted file mode 100644 index b1104fe1f20..00000000000 --- a/drivers/mtd/maps/dilnetpc.c +++ /dev/null @@ -1,497 +0,0 @@ -/* dilnetpc.c -- MTD map driver for SSV DIL/Net PC Boards "DNP" and "ADNP" - * - * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA - * - * $Id: dilnetpc.c,v 1.20 2005/11/07 11:14:26 gleixner Exp $ - * - * The DIL/Net PC is a tiny embedded PC board made by SSV Embedded Systems - * featuring the AMD Elan SC410 processor. There are two variants of this - * board: DNP/1486 and ADNP/1486. The DNP version has 2 megs of flash - * ROM (Intel 28F016S3) and 8 megs of DRAM, the ADNP version has 4 megs - * flash and 16 megs of RAM. - * For details, see http://www.ssv-embedded.de/ssv/pc104/p169.htm - * and http://www.ssv-embedded.de/ssv/pc104/p170.htm - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <linux/string.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> -#include <linux/mtd/concat.h> - -#include <asm/io.h> - -/* -** The DIL/NetPC keeps its BIOS in two distinct flash blocks. -** Destroying any of these blocks transforms the DNPC into -** a paperweight (albeit not a very useful one, considering -** it only weighs a few grams). -** -** Therefore, the BIOS blocks must never be erased or written to -** except by people who know exactly what they are doing (e.g. -** to install a BIOS update). These partitions are marked read-only -** by default, but can be made read/write by undefining -** DNPC_BIOS_BLOCKS_WRITEPROTECTED: -*/ -#define DNPC_BIOS_BLOCKS_WRITEPROTECTED - -/* -** The ID string (in ROM) is checked to determine whether we -** are running on a DNP/1486 or ADNP/1486 -*/ -#define BIOSID_BASE 0x000fe100 - -#define ID_DNPC "DNP1486" -#define ID_ADNP "ADNP1486" - -/* -** Address where the flash should appear in CPU space -*/ -#define FLASH_BASE 0x2000000 - -/* -** Chip Setup and Control (CSC) indexed register space -*/ -#define CSC_INDEX 0x22 -#define CSC_DATA 0x23 - -#define CSC_MMSWAR 0x30 /* MMS window C-F attributes register */ -#define CSC_MMSWDSR 0x31 /* MMS window C-F device select register */ - -#define CSC_RBWR 0xa7 /* GPIO Read-Back/Write Register B */ - -#define CSC_CR 0xd0 /* internal I/O device disable/Echo */ - /* Z-bus/configuration register */ - -#define CSC_PCCMDCR 0xf1 /* PC card mode and DMA control register */ - - -/* -** PC Card indexed register space: -*/ - -#define PCC_INDEX 0x3e0 -#define PCC_DATA 0x3e1 - -#define PCC_AWER_B 0x46 /* Socket B Address Window enable register */ -#define PCC_MWSAR_1_Lo 0x58 /* memory window 1 start address low register */ -#define PCC_MWSAR_1_Hi 0x59 /* memory window 1 start address high register */ -#define PCC_MWEAR_1_Lo 0x5A /* memory window 1 stop address low register */ -#define PCC_MWEAR_1_Hi 0x5B /* memory window 1 stop address high register */ -#define PCC_MWAOR_1_Lo 0x5C /* memory window 1 address offset low register */ -#define PCC_MWAOR_1_Hi 0x5D /* memory window 1 address offset high register */ - - -/* -** Access to SC4x0's Chip Setup and Control (CSC) -** and PC Card (PCC) indexed registers: -*/ -static inline void setcsc(int reg, unsigned char data) -{ - outb(reg, CSC_INDEX); - outb(data, CSC_DATA); -} - -static inline unsigned char getcsc(int reg) -{ - outb(reg, CSC_INDEX); - return(inb(CSC_DATA)); -} - -static inline void setpcc(int reg, unsigned char data) -{ - outb(reg, PCC_INDEX); - outb(data, PCC_DATA); -} - -static inline unsigned char getpcc(int reg) -{ - outb(reg, PCC_INDEX); - return(inb(PCC_DATA)); -} - - -/* -************************************************************ -** Enable access to DIL/NetPC's flash by mapping it into -** the SC4x0's MMS Window C. -************************************************************ -*/ -static void dnpc_map_flash(unsigned long flash_base, unsigned long flash_size) -{ - unsigned long flash_end = flash_base + flash_size - 1; - - /* - ** enable setup of MMS windows C-F: - */ - /* - enable PC Card indexed register space */ - setcsc(CSC_CR, getcsc(CSC_CR) | 0x2); - /* - set PC Card controller to operate in standard mode */ - setcsc(CSC_PCCMDCR, getcsc(CSC_PCCMDCR) & ~1); - - /* - ** Program base address and end address of window - ** where the flash ROM should appear in CPU address space - */ - setpcc(PCC_MWSAR_1_Lo, (flash_base >> 12) & 0xff); - setpcc(PCC_MWSAR_1_Hi, (flash_base >> 20) & 0x3f); - setpcc(PCC_MWEAR_1_Lo, (flash_end >> 12) & 0xff); - setpcc(PCC_MWEAR_1_Hi, (flash_end >> 20) & 0x3f); - - /* program offset of first flash location to appear in this window (0) */ - setpcc(PCC_MWAOR_1_Lo, ((0 - flash_base) >> 12) & 0xff); - setpcc(PCC_MWAOR_1_Hi, ((0 - flash_base)>> 20) & 0x3f); - - /* set attributes for MMS window C: non-cacheable, write-enabled */ - setcsc(CSC_MMSWAR, getcsc(CSC_MMSWAR) & ~0x11); - - /* select physical device ROMCS0 (i.e. flash) for MMS Window C */ - setcsc(CSC_MMSWDSR, getcsc(CSC_MMSWDSR) & ~0x03); - - /* enable memory window 1 */ - setpcc(PCC_AWER_B, getpcc(PCC_AWER_B) | 0x02); - - /* now disable PC Card indexed register space again */ - setcsc(CSC_CR, getcsc(CSC_CR) & ~0x2); -} - - -/* -************************************************************ -** Disable access to DIL/NetPC's flash by mapping it into -** the SC4x0's MMS Window C. -************************************************************ -*/ -static void dnpc_unmap_flash(void) -{ - /* - enable PC Card indexed register space */ - setcsc(CSC_CR, getcsc(CSC_CR) | 0x2); - - /* disable memory window 1 */ - setpcc(PCC_AWER_B, getpcc(PCC_AWER_B) & ~0x02); - - /* now disable PC Card indexed register space again */ - setcsc(CSC_CR, getcsc(CSC_CR) & ~0x2); -} - - - -/* -************************************************************ -** Enable/Disable VPP to write to flash -************************************************************ -*/ - -static DEFINE_SPINLOCK(dnpc_spin); -static int vpp_counter = 0; -/* -** This is what has to be done for the DNP board .. -*/ -static void dnp_set_vpp(struct map_info *not_used, int on) -{ - spin_lock_irq(&dnpc_spin); - - if (on) - { - if(++vpp_counter == 1) - setcsc(CSC_RBWR, getcsc(CSC_RBWR) & ~0x4); - } - else - { - if(--vpp_counter == 0) - setcsc(CSC_RBWR, getcsc(CSC_RBWR) | 0x4); - else - BUG_ON(vpp_counter < 0); - } - spin_unlock_irq(&dnpc_spin); -} - -/* -** .. and this the ADNP version: -*/ -static void adnp_set_vpp(struct map_info *not_used, int on) -{ - spin_lock_irq(&dnpc_spin); - - if (on) - { - if(++vpp_counter == 1) - setcsc(CSC_RBWR, getcsc(CSC_RBWR) & ~0x8); - } - else - { - if(--vpp_counter == 0) - setcsc(CSC_RBWR, getcsc(CSC_RBWR) | 0x8); - else - BUG_ON(vpp_counter < 0); - } - spin_unlock_irq(&dnpc_spin); -} - - - -#define DNP_WINDOW_SIZE 0x00200000 /* DNP flash size is 2MiB */ -#define ADNP_WINDOW_SIZE 0x00400000 /* ADNP flash size is 4MiB */ -#define WINDOW_ADDR FLASH_BASE - -static struct map_info dnpc_map = { - .name = "ADNP Flash Bank", - .size = ADNP_WINDOW_SIZE, - .bankwidth = 1, - .set_vpp = adnp_set_vpp, - .phys = WINDOW_ADDR -}; - -/* -** The layout of the flash is somewhat "strange": -** -** 1. 960 KiB (15 blocks) : Space for ROM Bootloader and user data -** 2. 64 KiB (1 block) : System BIOS -** 3. 960 KiB (15 blocks) : User Data (DNP model) or -** 3. 3008 KiB (47 blocks) : User Data (ADNP model) -** 4. 64 KiB (1 block) : System BIOS Entry -*/ - -static struct mtd_partition partition_info[]= -{ - { - .name = "ADNP boot", - .offset = 0, - .size = 0xf0000, - }, - { - .name = "ADNP system BIOS", - .offset = MTDPART_OFS_NXTBLK, - .size = 0x10000, -#ifdef DNPC_BIOS_BLOCKS_WRITEPROTECTED - .mask_flags = MTD_WRITEABLE, -#endif - }, - { - .name = "ADNP file system", - .offset = MTDPART_OFS_NXTBLK, - .size = 0x2f0000, - }, - { - .name = "ADNP system BIOS entry", - .offset = MTDPART_OFS_NXTBLK, - .size = MTDPART_SIZ_FULL, -#ifdef DNPC_BIOS_BLOCKS_WRITEPROTECTED - .mask_flags = MTD_WRITEABLE, -#endif - }, -}; - -#define NUM_PARTITIONS ARRAY_SIZE(partition_info) - -static struct mtd_info *mymtd; -static struct mtd_info *lowlvl_parts[NUM_PARTITIONS]; -static struct mtd_info *merged_mtd; - -/* -** "Highlevel" partition info: -** -** Using the MTD concat layer, we can re-arrange partitions to our -** liking: we construct a virtual MTD device by concatenating the -** partitions, specifying the sequence such that the boot block -** is immediately followed by the filesystem block (i.e. the stupid -** system BIOS block is mapped to a different place). When re-partitioning -** this concatenated MTD device, we can set the boot block size to -** an arbitrary (though erase block aligned) value i.e. not one that -** is dictated by the flash's physical layout. We can thus set the -** boot block to be e.g. 64 KB (which is fully sufficient if we want -** to boot an etherboot image) or to -say- 1.5 MB if we want to boot -** a large kernel image. In all cases, the remainder of the flash -** is available as file system space. -*/ - -static struct mtd_partition higlvl_partition_info[]= -{ - { - .name = "ADNP boot block", - .offset = 0, - .size = CONFIG_MTD_DILNETPC_BOOTSIZE, - }, - { - .name = "ADNP file system space", - .offset = MTDPART_OFS_NXTBLK, - .size = ADNP_WINDOW_SIZE-CONFIG_MTD_DILNETPC_BOOTSIZE-0x20000, - }, - { - .name = "ADNP system BIOS + BIOS Entry", - .offset = MTDPART_OFS_NXTBLK, - .size = MTDPART_SIZ_FULL, -#ifdef DNPC_BIOS_BLOCKS_WRITEPROTECTED - .mask_flags = MTD_WRITEABLE, -#endif - }, -}; - -#define NUM_HIGHLVL_PARTITIONS ARRAY_SIZE(higlvl_partition_info) - - -static int dnp_adnp_probe(void) -{ - char *biosid, rc = -1; - - biosid = (char*)ioremap(BIOSID_BASE, 16); - if(biosid) - { - if(!strcmp(biosid, ID_DNPC)) - rc = 1; /* this is a DNPC */ - else if(!strcmp(biosid, ID_ADNP)) - rc = 0; /* this is a ADNPC */ - } - iounmap((void *)biosid); - return(rc); -} - - -static int __init init_dnpc(void) -{ - int is_dnp; - - /* - ** determine hardware (DNP/ADNP/invalid) - */ - if((is_dnp = dnp_adnp_probe()) < 0) - return -ENXIO; - - /* - ** Things are set up for ADNP by default - ** -> modify all that needs to be different for DNP - */ - if(is_dnp) - { /* - ** Adjust window size, select correct set_vpp function. - ** The partitioning scheme is identical on both DNP - ** and ADNP except for the size of the third partition. - */ - int i; - dnpc_map.size = DNP_WINDOW_SIZE; - dnpc_map.set_vpp = dnp_set_vpp; - partition_info[2].size = 0xf0000; - - /* - ** increment all string pointers so the leading 'A' gets skipped, - ** thus turning all occurrences of "ADNP ..." into "DNP ..." - */ - ++dnpc_map.name; - for(i = 0; i < NUM_PARTITIONS; i++) - ++partition_info[i].name; - higlvl_partition_info[1].size = DNP_WINDOW_SIZE - - CONFIG_MTD_DILNETPC_BOOTSIZE - 0x20000; - for(i = 0; i < NUM_HIGHLVL_PARTITIONS; i++) - ++higlvl_partition_info[i].name; - } - - printk(KERN_NOTICE "DIL/Net %s flash: 0x%lx at 0x%lx\n", - is_dnp ? "DNPC" : "ADNP", dnpc_map.size, dnpc_map.phys); - - dnpc_map.virt = ioremap_nocache(dnpc_map.phys, dnpc_map.size); - - dnpc_map_flash(dnpc_map.phys, dnpc_map.size); - - if (!dnpc_map.virt) { - printk("Failed to ioremap_nocache\n"); - return -EIO; - } - simple_map_init(&dnpc_map); - - printk("FLASH virtual address: 0x%p\n", dnpc_map.virt); - - mymtd = do_map_probe("jedec_probe", &dnpc_map); - - if (!mymtd) - mymtd = do_map_probe("cfi_probe", &dnpc_map); - - /* - ** If flash probes fail, try to make flashes accessible - ** at least as ROM. Ajust erasesize in this case since - ** the default one (128M) will break our partitioning - */ - if (!mymtd) - if((mymtd = do_map_probe("map_rom", &dnpc_map))) - mymtd->erasesize = 0x10000; - - if (!mymtd) { - iounmap(dnpc_map.virt); - return -ENXIO; - } - - mymtd->owner = THIS_MODULE; - - /* - ** Supply pointers to lowlvl_parts[] array to add_mtd_partitions() - ** -> add_mtd_partitions() will _not_ register MTD devices for - ** the partitions, but will instead store pointers to the MTD - ** objects it creates into our lowlvl_parts[] array. - ** NOTE: we arrange the pointers such that the sequence of the - ** partitions gets re-arranged: partition #2 follows - ** partition #0. - */ - partition_info[0].mtdp = &lowlvl_parts[0]; - partition_info[1].mtdp = &lowlvl_parts[2]; - partition_info[2].mtdp = &lowlvl_parts[1]; - partition_info[3].mtdp = &lowlvl_parts[3]; - - add_mtd_partitions(mymtd, partition_info, NUM_PARTITIONS); - - /* - ** now create a virtual MTD device by concatenating the for partitions - ** (in the sequence given by the lowlvl_parts[] array. - */ - merged_mtd = mtd_concat_create(lowlvl_parts, NUM_PARTITIONS, "(A)DNP Flash Concatenated"); - if(merged_mtd) - { /* - ** now partition the new device the way we want it. This time, - ** we do not supply mtd pointers in higlvl_partition_info, so - ** add_mtd_partitions() will register the devices. - */ - add_mtd_partitions(merged_mtd, higlvl_partition_info, NUM_HIGHLVL_PARTITIONS); - } - - return 0; -} - -static void __exit cleanup_dnpc(void) -{ - if(merged_mtd) { - del_mtd_partitions(merged_mtd); - mtd_concat_destroy(merged_mtd); - } - - if (mymtd) { - del_mtd_partitions(mymtd); - map_destroy(mymtd); - } - if (dnpc_map.virt) { - iounmap(dnpc_map.virt); - dnpc_unmap_flash(); - dnpc_map.virt = NULL; - } -} - -module_init(init_dnpc); -module_exit(cleanup_dnpc); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Sysgo Real-Time Solutions GmbH"); -MODULE_DESCRIPTION("MTD map driver for SSV DIL/NetPC DNP & ADNP"); diff --git a/drivers/mtd/maps/dmv182.c b/drivers/mtd/maps/dmv182.c deleted file mode 100644 index a43c49905ca..00000000000 --- a/drivers/mtd/maps/dmv182.c +++ /dev/null @@ -1,148 +0,0 @@ - -/* - * drivers/mtd/maps/svme182.c - * - * Flash map driver for the Dy4 SVME182 board - * - * $Id: dmv182.c,v 1.6 2005/11/07 11:14:26 gleixner Exp $ - * - * Copyright 2003-2004, TimeSys Corporation - * - * Based on the SVME181 flash map, by Tom Nelson, Dot4, Inc. for TimeSys Corp. - * - * 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/module.h> -#include <linux/init.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <asm/io.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> -#include <linux/errno.h> - -/* - * This driver currently handles only the 16MiB user flash bank 1 on the - * board. It does not provide access to bank 0 (contains the Dy4 FFW), bank 2 - * (VxWorks boot), or the optional 48MiB expansion flash. - * - * scott.wood@timesys.com: On the newer boards with 128MiB flash, it - * now supports the first 96MiB (the boot flash bank containing FFW - * is excluded). The VxWorks loader is in partition 1. - */ - -#define FLASH_BASE_ADDR 0xf0000000 -#define FLASH_BANK_SIZE (128*1024*1024) - -MODULE_AUTHOR("Scott Wood, TimeSys Corporation <scott.wood@timesys.com>"); -MODULE_DESCRIPTION("User-programmable flash device on the Dy4 SVME182 board"); -MODULE_LICENSE("GPL"); - -static struct map_info svme182_map = { - .name = "Dy4 SVME182", - .bankwidth = 32, - .size = 128 * 1024 * 1024 -}; - -#define BOOTIMAGE_PART_SIZE ((6*1024*1024)-RESERVED_PART_SIZE) - -// Allow 6MiB for the kernel -#define NEW_BOOTIMAGE_PART_SIZE (6 * 1024 * 1024) -// Allow 1MiB for the bootloader -#define NEW_BOOTLOADER_PART_SIZE (1024 * 1024) -// Use the remaining 9MiB at the end of flash for the RFS -#define NEW_RFS_PART_SIZE (0x01000000 - NEW_BOOTLOADER_PART_SIZE - \ - NEW_BOOTIMAGE_PART_SIZE) - -static struct mtd_partition svme182_partitions[] = { - // The Lower PABS is only 128KiB, but the partition code doesn't - // like partitions that don't end on the largest erase block - // size of the device, even if all of the erase blocks in the - // partition are small ones. The hardware should prevent - // writes to the actual PABS areas. - { - name: "Lower PABS and CPU 0 bootloader or kernel", - size: 6*1024*1024, - offset: 0, - }, - { - name: "Root Filesystem", - size: 10*1024*1024, - offset: MTDPART_OFS_NXTBLK - }, - { - name: "CPU1 Bootloader", - size: 1024*1024, - offset: MTDPART_OFS_NXTBLK, - }, - { - name: "Extra", - size: 110*1024*1024, - offset: MTDPART_OFS_NXTBLK - }, - { - name: "Foundation Firmware and Upper PABS", - size: 1024*1024, - offset: MTDPART_OFS_NXTBLK, - mask_flags: MTD_WRITEABLE // read-only - } -}; - -static struct mtd_info *this_mtd; - -static int __init init_svme182(void) -{ - struct mtd_partition *partitions; - int num_parts = ARRAY_SIZE(svme182_partitions); - - partitions = svme182_partitions; - - svme182_map.virt = ioremap(FLASH_BASE_ADDR, svme182_map.size); - - if (svme182_map.virt == 0) { - printk("Failed to ioremap FLASH memory area.\n"); - return -EIO; - } - - simple_map_init(&svme182_map); - - this_mtd = do_map_probe("cfi_probe", &svme182_map); - if (!this_mtd) - { - iounmap((void *)svme182_map.virt); - return -ENXIO; - } - - printk(KERN_NOTICE "SVME182 flash device: %dMiB at 0x%08x\n", - this_mtd->size >> 20, FLASH_BASE_ADDR); - - this_mtd->owner = THIS_MODULE; - add_mtd_partitions(this_mtd, partitions, num_parts); - - return 0; -} - -static void __exit cleanup_svme182(void) -{ - if (this_mtd) - { - del_mtd_partitions(this_mtd); - map_destroy(this_mtd); - } - - if (svme182_map.virt) - { - iounmap((void *)svme182_map.virt); - svme182_map.virt = 0; - } - - return; -} - -module_init(init_svme182); -module_exit(cleanup_svme182); diff --git a/drivers/mtd/maps/ebony.c b/drivers/mtd/maps/ebony.c deleted file mode 100644 index 1488bb92f26..00000000000 --- a/drivers/mtd/maps/ebony.c +++ /dev/null @@ -1,165 +0,0 @@ -/* - * $Id: ebony.c,v 1.16 2005/11/07 11:14:26 gleixner Exp $ - * - * Mapping for Ebony user flash - * - * Matt Porter <mporter@kernel.crashing.org> - * - * Copyright 2002-2004 MontaVista Software Inc. - * - * 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/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> -#include <asm/io.h> -#include <asm/ibm44x.h> -#include <platforms/4xx/ebony.h> - -static struct mtd_info *flash; - -static struct map_info ebony_small_map = { - .name = "Ebony small flash", - .size = EBONY_SMALL_FLASH_SIZE, - .bankwidth = 1, -}; - -static struct map_info ebony_large_map = { - .name = "Ebony large flash", - .size = EBONY_LARGE_FLASH_SIZE, - .bankwidth = 1, -}; - -static struct mtd_partition ebony_small_partitions[] = { - { - .name = "OpenBIOS", - .offset = 0x0, - .size = 0x80000, - } -}; - -static struct mtd_partition ebony_large_partitions[] = { - { - .name = "fs", - .offset = 0, - .size = 0x380000, - }, - { - .name = "firmware", - .offset = 0x380000, - .size = 0x80000, - } -}; - -int __init init_ebony(void) -{ - u8 fpga0_reg; - u8 __iomem *fpga0_adr; - unsigned long long small_flash_base, large_flash_base; - - fpga0_adr = ioremap64(EBONY_FPGA_ADDR, 16); - if (!fpga0_adr) - return -ENOMEM; - - fpga0_reg = readb(fpga0_adr); - iounmap(fpga0_adr); - - if (EBONY_BOOT_SMALL_FLASH(fpga0_reg) && - !EBONY_FLASH_SEL(fpga0_reg)) - small_flash_base = EBONY_SMALL_FLASH_HIGH2; - else if (EBONY_BOOT_SMALL_FLASH(fpga0_reg) && - EBONY_FLASH_SEL(fpga0_reg)) - small_flash_base = EBONY_SMALL_FLASH_HIGH1; - else if (!EBONY_BOOT_SMALL_FLASH(fpga0_reg) && - !EBONY_FLASH_SEL(fpga0_reg)) - small_flash_base = EBONY_SMALL_FLASH_LOW2; - else - small_flash_base = EBONY_SMALL_FLASH_LOW1; - - if (EBONY_BOOT_SMALL_FLASH(fpga0_reg) && - !EBONY_ONBRD_FLASH_EN(fpga0_reg)) - large_flash_base = EBONY_LARGE_FLASH_LOW; - else - large_flash_base = EBONY_LARGE_FLASH_HIGH; - - ebony_small_map.phys = small_flash_base; - ebony_small_map.virt = ioremap64(small_flash_base, - ebony_small_map.size); - - if (!ebony_small_map.virt) { - printk("Failed to ioremap flash\n"); - return -EIO; - } - - simple_map_init(&ebony_small_map); - - flash = do_map_probe("jedec_probe", &ebony_small_map); - if (flash) { - flash->owner = THIS_MODULE; - add_mtd_partitions(flash, ebony_small_partitions, - ARRAY_SIZE(ebony_small_partitions)); - } else { - printk("map probe failed for flash\n"); - iounmap(ebony_small_map.virt); - return -ENXIO; - } - - ebony_large_map.phys = large_flash_base; - ebony_large_map.virt = ioremap64(large_flash_base, - ebony_large_map.size); - - if (!ebony_large_map.virt) { - printk("Failed to ioremap flash\n"); - iounmap(ebony_small_map.virt); - return -EIO; - } - - simple_map_init(&ebony_large_map); - - flash = do_map_probe("jedec_probe", &ebony_large_map); - if (flash) { - flash->owner = THIS_MODULE; - add_mtd_partitions(flash, ebony_large_partitions, - ARRAY_SIZE(ebony_large_partitions)); - } else { - printk("map probe failed for flash\n"); - iounmap(ebony_small_map.virt); - iounmap(ebony_large_map.virt); - return -ENXIO; - } - - return 0; -} - -static void __exit cleanup_ebony(void) -{ - if (flash) { - del_mtd_partitions(flash); - map_destroy(flash); - } - - if (ebony_small_map.virt) { - iounmap(ebony_small_map.virt); - ebony_small_map.virt = NULL; - } - - if (ebony_large_map.virt) { - iounmap(ebony_large_map.virt); - ebony_large_map.virt = NULL; - } -} - -module_init(init_ebony); -module_exit(cleanup_ebony); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Matt Porter <mporter@kernel.crashing.org>"); -MODULE_DESCRIPTION("MTD map and partitions for IBM 440GP Ebony boards"); diff --git a/drivers/mtd/maps/edb7312.c b/drivers/mtd/maps/edb7312.c deleted file mode 100644 index 1c5b97c8968..00000000000 --- a/drivers/mtd/maps/edb7312.c +++ /dev/null @@ -1,146 +0,0 @@ -/* - * $Id: edb7312.c,v 1.14 2005/11/07 11:14:27 gleixner Exp $ - * - * Handle mapping of the NOR flash on Cogent EDB7312 boards - * - * Copyright 2002 SYSGO Real-Time Solutions GmbH - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <asm/io.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> - -#ifdef CONFIG_MTD_PARTITIONS -#include <linux/mtd/partitions.h> -#endif - -#define WINDOW_ADDR 0x00000000 /* physical properties of flash */ -#define WINDOW_SIZE 0x01000000 -#define BUSWIDTH 2 -#define FLASH_BLOCKSIZE_MAIN 0x20000 -#define FLASH_NUMBLOCKS_MAIN 128 -/* can be "cfi_probe", "jedec_probe", "map_rom", NULL }; */ -#define PROBETYPES { "cfi_probe", NULL } - -#define MSG_PREFIX "EDB7312-NOR:" /* prefix for our printk()'s */ -#define MTDID "edb7312-nor" /* for mtdparts= partitioning */ - -static struct mtd_info *mymtd; - -struct map_info edb7312nor_map = { - .name = "NOR flash on EDB7312", - .size = WINDOW_SIZE, - .bankwidth = BUSWIDTH, - .phys = WINDOW_ADDR, -}; - -#ifdef CONFIG_MTD_PARTITIONS - -/* - * MTD partitioning stuff - */ -static struct mtd_partition static_partitions[3] = -{ - { - .name = "ARMboot", - .size = 0x40000, - .offset = 0 - }, - { - .name = "Kernel", - .size = 0x200000, - .offset = 0x40000 - }, - { - .name = "RootFS", - .size = 0xDC0000, - .offset = 0x240000 - }, -}; - -static const char *probes[] = { "RedBoot", "cmdlinepart", NULL }; - -#endif - -static int mtd_parts_nb = 0; -static struct mtd_partition *mtd_parts = 0; - -int __init init_edb7312nor(void) -{ - static const char *rom_probe_types[] = PROBETYPES; - const char **type; - const char *part_type = 0; - - printk(KERN_NOTICE MSG_PREFIX "0x%08x at 0x%08x\n", - WINDOW_SIZE, WINDOW_ADDR); - edb7312nor_map.virt = ioremap(WINDOW_ADDR, WINDOW_SIZE); - - if (!edb7312nor_map.virt) { - printk(MSG_PREFIX "failed to ioremap\n"); - return -EIO; - } - - simple_map_init(&edb7312nor_map); - - mymtd = 0; - type = rom_probe_types; - for(; !mymtd && *type; type++) { - mymtd = do_map_probe(*type, &edb7312nor_map); - } - if (mymtd) { - mymtd->owner = THIS_MODULE; - -#ifdef CONFIG_MTD_PARTITIONS - mtd_parts_nb = parse_mtd_partitions(mymtd, probes, &mtd_parts, MTDID); - if (mtd_parts_nb > 0) - part_type = "detected"; - - if (mtd_parts_nb == 0) - { - mtd_parts = static_partitions; - mtd_parts_nb = ARRAY_SIZE(static_partitions); - part_type = "static"; - } -#endif - add_mtd_device(mymtd); - if (mtd_parts_nb == 0) - printk(KERN_NOTICE MSG_PREFIX "no partition info available\n"); - else - { - printk(KERN_NOTICE MSG_PREFIX - "using %s partition definition\n", part_type); - add_mtd_partitions(mymtd, mtd_parts, mtd_parts_nb); - } - return 0; - } - - iounmap((void *)edb7312nor_map.virt); - return -ENXIO; -} - -static void __exit cleanup_edb7312nor(void) -{ - if (mymtd) { - del_mtd_device(mymtd); - map_destroy(mymtd); - } - if (edb7312nor_map.virt) { - iounmap((void *)edb7312nor_map.virt); - edb7312nor_map.virt = 0; - } -} - -module_init(init_edb7312nor); -module_exit(cleanup_edb7312nor); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Marius Groeger <mag@sysgo.de>"); -MODULE_DESCRIPTION("Generic configurable MTD map driver"); diff --git a/drivers/mtd/maps/esb2rom.c b/drivers/mtd/maps/esb2rom.c new file mode 100644 index 00000000000..f784cf0caa1 --- /dev/null +++ b/drivers/mtd/maps/esb2rom.c @@ -0,0 +1,452 @@ +/* + * esb2rom.c + * + * Normal mappings of flash chips in physical memory + * through the Intel ESB2 Southbridge. + * + * This was derived from ichxrom.c in May 2006 by + * Lew Glendenning <lglendenning@lnxi.com> + * + * Eric Biederman, of course, was a major help in this effort. + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <asm/io.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/cfi.h> +#include <linux/mtd/flashchip.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> +#include <linux/list.h> + +#define MOD_NAME KBUILD_BASENAME + +#define ADDRESS_NAME_LEN 18 + +#define ROM_PROBE_STEP_SIZE (64*1024) /* 64KiB */ + +#define BIOS_CNTL 0xDC +#define BIOS_LOCK_ENABLE 0x02 +#define BIOS_WRITE_ENABLE 0x01 + +/* This became a 16-bit register, and EN2 has disappeared */ +#define FWH_DEC_EN1 0xD8 +#define FWH_F8_EN 0x8000 +#define FWH_F0_EN 0x4000 +#define FWH_E8_EN 0x2000 +#define FWH_E0_EN 0x1000 +#define FWH_D8_EN 0x0800 +#define FWH_D0_EN 0x0400 +#define FWH_C8_EN 0x0200 +#define FWH_C0_EN 0x0100 +#define FWH_LEGACY_F_EN 0x0080 +#define FWH_LEGACY_E_EN 0x0040 +/* reserved 0x0020 and 0x0010 */ +#define FWH_70_EN 0x0008 +#define FWH_60_EN 0x0004 +#define FWH_50_EN 0x0002 +#define FWH_40_EN 0x0001 + +/* these are 32-bit values */ +#define FWH_SEL1 0xD0 +#define FWH_SEL2 0xD4 + +#define FWH_8MiB (FWH_F8_EN | FWH_F0_EN | FWH_E8_EN | FWH_E0_EN | \ + FWH_D8_EN | FWH_D0_EN | FWH_C8_EN | FWH_C0_EN | \ + FWH_70_EN | FWH_60_EN | FWH_50_EN | FWH_40_EN) + +#define FWH_7MiB (FWH_F8_EN | FWH_F0_EN | FWH_E8_EN | FWH_E0_EN | \ + FWH_D8_EN | FWH_D0_EN | FWH_C8_EN | FWH_C0_EN | \ + FWH_70_EN | FWH_60_EN | FWH_50_EN) + +#define FWH_6MiB (FWH_F8_EN | FWH_F0_EN | FWH_E8_EN | FWH_E0_EN | \ + FWH_D8_EN | FWH_D0_EN | FWH_C8_EN | FWH_C0_EN | \ + FWH_70_EN | FWH_60_EN) + +#define FWH_5MiB (FWH_F8_EN | FWH_F0_EN | FWH_E8_EN | FWH_E0_EN | \ + FWH_D8_EN | FWH_D0_EN | FWH_C8_EN | FWH_C0_EN | \ + FWH_70_EN) + +#define FWH_4MiB (FWH_F8_EN | FWH_F0_EN | FWH_E8_EN | FWH_E0_EN | \ + FWH_D8_EN | FWH_D0_EN | FWH_C8_EN | FWH_C0_EN) + +#define FWH_3_5MiB (FWH_F8_EN | FWH_F0_EN | FWH_E8_EN | FWH_E0_EN | \ + FWH_D8_EN | FWH_D0_EN | FWH_C8_EN) + +#define FWH_3MiB (FWH_F8_EN | FWH_F0_EN | FWH_E8_EN | FWH_E0_EN | \ + FWH_D8_EN | FWH_D0_EN) + +#define FWH_2_5MiB (FWH_F8_EN | FWH_F0_EN | FWH_E8_EN | FWH_E0_EN | \ + FWH_D8_EN) + +#define FWH_2MiB (FWH_F8_EN | FWH_F0_EN | FWH_E8_EN | FWH_E0_EN) + +#define FWH_1_5MiB (FWH_F8_EN | FWH_F0_EN | FWH_E8_EN) + +#define FWH_1MiB (FWH_F8_EN | FWH_F0_EN) + +#define FWH_0_5MiB (FWH_F8_EN) + + +struct esb2rom_window { + void __iomem* virt; + unsigned long phys; + unsigned long size; + struct list_head maps; + struct resource rsrc; + struct pci_dev *pdev; +}; + +struct esb2rom_map_info { + struct list_head list; + struct map_info map; + struct mtd_info *mtd; + struct resource rsrc; + char map_name[sizeof(MOD_NAME) + 2 + ADDRESS_NAME_LEN]; +}; + +static struct esb2rom_window esb2rom_window = { + .maps = LIST_HEAD_INIT(esb2rom_window.maps), +}; + +static void esb2rom_cleanup(struct esb2rom_window *window) +{ + struct esb2rom_map_info *map, *scratch; + u8 byte; + + /* Disable writes through the rom window */ + pci_read_config_byte(window->pdev, BIOS_CNTL, &byte); + pci_write_config_byte(window->pdev, BIOS_CNTL, + byte & ~BIOS_WRITE_ENABLE); + + /* Free all of the mtd devices */ + list_for_each_entry_safe(map, scratch, &window->maps, list) { + if (map->rsrc.parent) + release_resource(&map->rsrc); + mtd_device_unregister(map->mtd); + map_destroy(map->mtd); + list_del(&map->list); + kfree(map); + } + if (window->rsrc.parent) + release_resource(&window->rsrc); + if (window->virt) { + iounmap(window->virt); + window->virt = NULL; + window->phys = 0; + window->size = 0; + } + pci_dev_put(window->pdev); +} + +static int esb2rom_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + static char *rom_probe_types[] = { "cfi_probe", "jedec_probe", NULL }; + struct esb2rom_window *window = &esb2rom_window; + struct esb2rom_map_info *map = NULL; + unsigned long map_top; + u8 byte; + u16 word; + + /* For now I just handle the ecb2 and I assume there + * are not a lot of resources up at the top of the address + * space. It is possible to handle other devices in the + * top 16MiB but it is very painful. Also since + * you can only really attach a FWH to an ICHX there + * a number of simplifications you can make. + * + * Also you can page firmware hubs if an 8MiB window isn't enough + * but don't currently handle that case either. + */ + window->pdev = pci_dev_get(pdev); + + /* RLG: experiment 2. Force the window registers to the widest values */ + +/* + pci_read_config_word(pdev, FWH_DEC_EN1, &word); + printk(KERN_DEBUG "Original FWH_DEC_EN1 : %x\n", word); + pci_write_config_byte(pdev, FWH_DEC_EN1, 0xff); + pci_read_config_byte(pdev, FWH_DEC_EN1, &byte); + printk(KERN_DEBUG "New FWH_DEC_EN1 : %x\n", byte); + + pci_read_config_byte(pdev, FWH_DEC_EN2, &byte); + printk(KERN_DEBUG "Original FWH_DEC_EN2 : %x\n", byte); + pci_write_config_byte(pdev, FWH_DEC_EN2, 0x0f); + pci_read_config_byte(pdev, FWH_DEC_EN2, &byte); + printk(KERN_DEBUG "New FWH_DEC_EN2 : %x\n", byte); +*/ + + /* Find a region continuous to the end of the ROM window */ + window->phys = 0; + pci_read_config_word(pdev, FWH_DEC_EN1, &word); + printk(KERN_DEBUG "pci_read_config_word : %x\n", word); + + if ((word & FWH_8MiB) == FWH_8MiB) + window->phys = 0xff400000; + else if ((word & FWH_7MiB) == FWH_7MiB) + window->phys = 0xff500000; + else if ((word & FWH_6MiB) == FWH_6MiB) + window->phys = 0xff600000; + else if ((word & FWH_5MiB) == FWH_5MiB) + window->phys = 0xFF700000; + else if ((word & FWH_4MiB) == FWH_4MiB) + window->phys = 0xffc00000; + else if ((word & FWH_3_5MiB) == FWH_3_5MiB) + window->phys = 0xffc80000; + else if ((word & FWH_3MiB) == FWH_3MiB) + window->phys = 0xffd00000; + else if ((word & FWH_2_5MiB) == FWH_2_5MiB) + window->phys = 0xffd80000; + else if ((word & FWH_2MiB) == FWH_2MiB) + window->phys = 0xffe00000; + else if ((word & FWH_1_5MiB) == FWH_1_5MiB) + window->phys = 0xffe80000; + else if ((word & FWH_1MiB) == FWH_1MiB) + window->phys = 0xfff00000; + else if ((word & FWH_0_5MiB) == FWH_0_5MiB) + window->phys = 0xfff80000; + + if (window->phys == 0) { + printk(KERN_ERR MOD_NAME ": Rom window is closed\n"); + goto out; + } + + /* reserved 0x0020 and 0x0010 */ + window->phys -= 0x400000UL; + window->size = (0xffffffffUL - window->phys) + 1UL; + + /* Enable writes through the rom window */ + pci_read_config_byte(pdev, BIOS_CNTL, &byte); + if (!(byte & BIOS_WRITE_ENABLE) && (byte & (BIOS_LOCK_ENABLE))) { + /* The BIOS will generate an error if I enable + * this device, so don't even try. + */ + printk(KERN_ERR MOD_NAME ": firmware access control, I can't enable writes\n"); + goto out; + } + pci_write_config_byte(pdev, BIOS_CNTL, byte | BIOS_WRITE_ENABLE); + + /* + * Try to reserve the window mem region. If this fails then + * it is likely due to the window being "reseved" by the BIOS. + */ + window->rsrc.name = MOD_NAME; + window->rsrc.start = window->phys; + window->rsrc.end = window->phys + window->size - 1; + window->rsrc.flags = IORESOURCE_MEM | IORESOURCE_BUSY; + if (request_resource(&iomem_resource, &window->rsrc)) { + window->rsrc.parent = NULL; + printk(KERN_DEBUG MOD_NAME ": " + "%s(): Unable to register resource %pR - kernel bug?\n", + __func__, &window->rsrc); + } + + /* Map the firmware hub into my address space. */ + window->virt = ioremap_nocache(window->phys, window->size); + if (!window->virt) { + printk(KERN_ERR MOD_NAME ": ioremap(%08lx, %08lx) failed\n", + window->phys, window->size); + goto out; + } + + /* Get the first address to look for an rom chip at */ + map_top = window->phys; + if ((window->phys & 0x3fffff) != 0) { + /* if not aligned on 4MiB, look 4MiB lower in address space */ + map_top = window->phys + 0x400000; + } +#if 1 + /* The probe sequence run over the firmware hub lock + * registers sets them to 0x7 (no access). + * (Insane hardware design, but most copied Intel's.) + * ==> Probe at most the last 4M of the address space. + */ + if (map_top < 0xffc00000) + map_top = 0xffc00000; +#endif + /* Loop through and look for rom chips */ + while ((map_top - 1) < 0xffffffffUL) { + struct cfi_private *cfi; + unsigned long offset; + int i; + + if (!map) + map = kmalloc(sizeof(*map), GFP_KERNEL); + if (!map) { + printk(KERN_ERR MOD_NAME ": kmalloc failed"); + goto out; + } + memset(map, 0, sizeof(*map)); + INIT_LIST_HEAD(&map->list); + map->map.name = map->map_name; + map->map.phys = map_top; + offset = map_top - window->phys; + map->map.virt = (void __iomem *) + (((unsigned long)(window->virt)) + offset); + map->map.size = 0xffffffffUL - map_top + 1UL; + /* Set the name of the map to the address I am trying */ + sprintf(map->map_name, "%s @%08Lx", + MOD_NAME, (unsigned long long)map->map.phys); + + /* Firmware hubs only use vpp when being programmed + * in a factory setting. So in-place programming + * needs to use a different method. + */ + for(map->map.bankwidth = 32; map->map.bankwidth; + map->map.bankwidth >>= 1) { + char **probe_type; + /* Skip bankwidths that are not supported */ + if (!map_bankwidth_supported(map->map.bankwidth)) + continue; + + /* Setup the map methods */ + simple_map_init(&map->map); + + /* Try all of the probe methods */ + probe_type = rom_probe_types; + for(; *probe_type; probe_type++) { + map->mtd = do_map_probe(*probe_type, &map->map); + if (map->mtd) + goto found; + } + } + map_top += ROM_PROBE_STEP_SIZE; + continue; + found: + /* Trim the size if we are larger than the map */ + if (map->mtd->size > map->map.size) { + printk(KERN_WARNING MOD_NAME + " rom(%llu) larger than window(%lu). fixing...\n", + (unsigned long long)map->mtd->size, map->map.size); + map->mtd->size = map->map.size; + } + if (window->rsrc.parent) { + /* + * Registering the MTD device in iomem may not be possible + * if there is a BIOS "reserved" and BUSY range. If this + * fails then continue anyway. + */ + map->rsrc.name = map->map_name; + map->rsrc.start = map->map.phys; + map->rsrc.end = map->map.phys + map->mtd->size - 1; + map->rsrc.flags = IORESOURCE_MEM | IORESOURCE_BUSY; + if (request_resource(&window->rsrc, &map->rsrc)) { + printk(KERN_ERR MOD_NAME + ": cannot reserve MTD resource\n"); + map->rsrc.parent = NULL; + } + } + + /* Make the whole region visible in the map */ + map->map.virt = window->virt; + map->map.phys = window->phys; + cfi = map->map.fldrv_priv; + for(i = 0; i < cfi->numchips; i++) + cfi->chips[i].start += offset; + + /* Now that the mtd devices is complete claim and export it */ + map->mtd->owner = THIS_MODULE; + if (mtd_device_register(map->mtd, NULL, 0)) { + map_destroy(map->mtd); + map->mtd = NULL; + goto out; + } + + /* Calculate the new value of map_top */ + map_top += map->mtd->size; + + /* File away the map structure */ + list_add(&map->list, &window->maps); + map = NULL; + } + + out: + /* Free any left over map structures */ + kfree(map); + + /* See if I have any map structures */ + if (list_empty(&window->maps)) { + esb2rom_cleanup(window); + return -ENODEV; + } + return 0; +} + +static void esb2rom_remove_one(struct pci_dev *pdev) +{ + struct esb2rom_window *window = &esb2rom_window; + esb2rom_cleanup(window); +} + +static struct pci_device_id esb2rom_pci_tbl[] = { + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0, + PCI_ANY_ID, PCI_ANY_ID, }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0, + PCI_ANY_ID, PCI_ANY_ID, }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0, + PCI_ANY_ID, PCI_ANY_ID, }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0, + PCI_ANY_ID, PCI_ANY_ID, }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_1, + PCI_ANY_ID, PCI_ANY_ID, }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB2_0, + PCI_ANY_ID, PCI_ANY_ID, }, + { 0, }, +}; + +#if 0 +MODULE_DEVICE_TABLE(pci, esb2rom_pci_tbl); + +static struct pci_driver esb2rom_driver = { + .name = MOD_NAME, + .id_table = esb2rom_pci_tbl, + .probe = esb2rom_init_one, + .remove = esb2rom_remove_one, +}; +#endif + +static int __init init_esb2rom(void) +{ + struct pci_dev *pdev; + struct pci_device_id *id; + int retVal; + + pdev = NULL; + for (id = esb2rom_pci_tbl; id->vendor; id++) { + printk(KERN_DEBUG "device id = %x\n", id->device); + pdev = pci_get_device(id->vendor, id->device, NULL); + if (pdev) { + printk(KERN_DEBUG "matched device = %x\n", id->device); + break; + } + } + if (pdev) { + printk(KERN_DEBUG "matched device id %x\n", id->device); + retVal = esb2rom_init_one(pdev, &esb2rom_pci_tbl[0]); + pci_dev_put(pdev); + printk(KERN_DEBUG "retVal = %d\n", retVal); + return retVal; + } + return -ENXIO; +#if 0 + return pci_register_driver(&esb2rom_driver); +#endif +} + +static void __exit cleanup_esb2rom(void) +{ + esb2rom_remove_one(esb2rom_window.pdev); +} + +module_init(init_esb2rom); +module_exit(cleanup_esb2rom); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Lew Glendenning <lglendenning@lnxi.com>"); +MODULE_DESCRIPTION("MTD map driver for BIOS chips on the ESB2 southbridge"); diff --git a/drivers/mtd/maps/fortunet.c b/drivers/mtd/maps/fortunet.c deleted file mode 100644 index 7c50c271651..00000000000 --- a/drivers/mtd/maps/fortunet.c +++ /dev/null @@ -1,277 +0,0 @@ -/* fortunet.c memory map - * - * $Id: fortunet.c,v 1.11 2005/11/07 11:14:27 gleixner Exp $ - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <linux/string.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - -#include <asm/io.h> - -#define MAX_NUM_REGIONS 4 -#define MAX_NUM_PARTITIONS 8 - -#define DEF_WINDOW_ADDR_PHY 0x00000000 -#define DEF_WINDOW_SIZE 0x00800000 // 8 Mega Bytes - -#define MTD_FORTUNET_PK "MTD FortuNet: " - -#define MAX_NAME_SIZE 128 - -struct map_region -{ - int window_addr_physical; - int altbankwidth; - struct map_info map_info; - struct mtd_info *mymtd; - struct mtd_partition parts[MAX_NUM_PARTITIONS]; - char map_name[MAX_NAME_SIZE]; - char parts_name[MAX_NUM_PARTITIONS][MAX_NAME_SIZE]; -}; - -static struct map_region map_regions[MAX_NUM_REGIONS]; -static int map_regions_set[MAX_NUM_REGIONS] = {0,0,0,0}; -static int map_regions_parts[MAX_NUM_REGIONS] = {0,0,0,0}; - - - -struct map_info default_map = { - .size = DEF_WINDOW_SIZE, - .bankwidth = 4, -}; - -static char * __init get_string_option(char *dest,int dest_size,char *sor) -{ - if(!dest_size) - return sor; - dest_size--; - while(*sor) - { - if(*sor==',') - { - sor++; - break; - } - else if(*sor=='\"') - { - sor++; - while(*sor) - { - if(*sor=='\"') - { - sor++; - break; - } - *dest = *sor; - dest++; - sor++; - dest_size--; - if(!dest_size) - { - *dest = 0; - return sor; - } - } - } - else - { - *dest = *sor; - dest++; - sor++; - dest_size--; - if(!dest_size) - { - *dest = 0; - return sor; - } - } - } - *dest = 0; - return sor; -} - -static int __init MTD_New_Region(char *line) -{ - char string[MAX_NAME_SIZE]; - int params[6]; - get_options (get_string_option(string,sizeof(string),line),6,params); - if(params[0]<1) - { - printk(MTD_FORTUNET_PK "Bad parameters for MTD Region " - " name,region-number[,base,size,bankwidth,altbankwidth]\n"); - return 1; - } - if((params[1]<0)||(params[1]>=MAX_NUM_REGIONS)) - { - printk(MTD_FORTUNET_PK "Bad region index of %d only have 0..%u regions\n", - params[1],MAX_NUM_REGIONS-1); - return 1; - } - memset(&map_regions[params[1]],0,sizeof(map_regions[params[1]])); - memcpy(&map_regions[params[1]].map_info, - &default_map,sizeof(map_regions[params[1]].map_info)); - map_regions_set[params[1]] = 1; - map_regions[params[1]].window_addr_physical = DEF_WINDOW_ADDR_PHY; - map_regions[params[1]].altbankwidth = 2; - map_regions[params[1]].mymtd = NULL; - map_regions[params[1]].map_info.name = map_regions[params[1]].map_name; - strcpy(map_regions[params[1]].map_info.name,string); - if(params[0]>1) - { - map_regions[params[1]].window_addr_physical = params[2]; - } - if(params[0]>2) - { - map_regions[params[1]].map_info.size = params[3]; - } - if(params[0]>3) - { - map_regions[params[1]].map_info.bankwidth = params[4]; - } - if(params[0]>4) - { - map_regions[params[1]].altbankwidth = params[5]; - } - return 1; -} - -static int __init MTD_New_Partition(char *line) -{ - char string[MAX_NAME_SIZE]; - int params[4]; - get_options (get_string_option(string,sizeof(string),line),4,params); - if(params[0]<3) - { - printk(MTD_FORTUNET_PK "Bad parameters for MTD Partition " - " name,region-number,size,offset\n"); - return 1; - } - if((params[1]<0)||(params[1]>=MAX_NUM_REGIONS)) - { - printk(MTD_FORTUNET_PK "Bad region index of %d only have 0..%u regions\n", - params[1],MAX_NUM_REGIONS-1); - return 1; - } - if(map_regions_parts[params[1]]>=MAX_NUM_PARTITIONS) - { - printk(MTD_FORTUNET_PK "Out of space for partition in this region\n"); - return 1; - } - map_regions[params[1]].parts[map_regions_parts[params[1]]].name = - map_regions[params[1]]. parts_name[map_regions_parts[params[1]]]; - strcpy(map_regions[params[1]].parts[map_regions_parts[params[1]]].name,string); - map_regions[params[1]].parts[map_regions_parts[params[1]]].size = - params[2]; - map_regions[params[1]].parts[map_regions_parts[params[1]]].offset = - params[3]; - map_regions[params[1]].parts[map_regions_parts[params[1]]].mask_flags = 0; - map_regions_parts[params[1]]++; - return 1; -} - -__setup("MTD_Region=", MTD_New_Region); -__setup("MTD_Partition=", MTD_New_Partition); - -/* Backwards-spelling-compatibility */ -__setup("MTD_Partion=", MTD_New_Partition); - -int __init init_fortunet(void) -{ - int ix,iy; - for(iy=ix=0;ix<MAX_NUM_REGIONS;ix++) - { - if(map_regions_parts[ix]&&(!map_regions_set[ix])) - { - printk(MTD_FORTUNET_PK "Region %d is not setup (Setting to default)\n", - ix); - memset(&map_regions[ix],0,sizeof(map_regions[ix])); - memcpy(&map_regions[ix].map_info,&default_map, - sizeof(map_regions[ix].map_info)); - map_regions_set[ix] = 1; - map_regions[ix].window_addr_physical = DEF_WINDOW_ADDR_PHY; - map_regions[ix].altbankwidth = 2; - map_regions[ix].mymtd = NULL; - map_regions[ix].map_info.name = map_regions[ix].map_name; - strcpy(map_regions[ix].map_info.name,"FORTUNET"); - } - if(map_regions_set[ix]) - { - iy++; - printk(KERN_NOTICE MTD_FORTUNET_PK "%s flash device at physically " - " address %x size %x\n", - map_regions[ix].map_info.name, - map_regions[ix].window_addr_physical, - map_regions[ix].map_info.size); - - map_regions[ix].map_info.phys = map_regions[ix].window_addr_physical, - - map_regions[ix].map_info.virt = - ioremap_nocache( - map_regions[ix].window_addr_physical, - map_regions[ix].map_info.size); - if(!map_regions[ix].map_info.virt) - { - int j = 0; - printk(MTD_FORTUNET_PK "%s flash failed to ioremap!\n", - map_regions[ix].map_info.name); - for (j = 0 ; j < ix; j++) - iounmap(map_regions[j].map_info.virt); - return -ENXIO; - } - simple_map_init(&map_regions[ix].map_info); - - printk(KERN_NOTICE MTD_FORTUNET_PK "%s flash is virtually at: %x\n", - map_regions[ix].map_info.name, - map_regions[ix].map_info.virt); - map_regions[ix].mymtd = do_map_probe("cfi_probe", - &map_regions[ix].map_info); - if((!map_regions[ix].mymtd)&&( - map_regions[ix].altbankwidth!=map_regions[ix].map_info.bankwidth)) - { - printk(KERN_NOTICE MTD_FORTUNET_PK "Trying alternate bankwidth " - "for %s flash.\n", - map_regions[ix].map_info.name); - map_regions[ix].map_info.bankwidth = - map_regions[ix].altbankwidth; - map_regions[ix].mymtd = do_map_probe("cfi_probe", - &map_regions[ix].map_info); - } - map_regions[ix].mymtd->owner = THIS_MODULE; - add_mtd_partitions(map_regions[ix].mymtd, - map_regions[ix].parts,map_regions_parts[ix]); - } - } - if(iy) - return 0; - return -ENXIO; -} - -static void __exit cleanup_fortunet(void) -{ - int ix; - for(ix=0;ix<MAX_NUM_REGIONS;ix++) - { - if(map_regions_set[ix]) - { - if( map_regions[ix].mymtd ) - { - del_mtd_partitions( map_regions[ix].mymtd ); - map_destroy( map_regions[ix].mymtd ); - } - iounmap((void *)map_regions[ix].map_info.virt); - } - } -} - -module_init(init_fortunet); -module_exit(cleanup_fortunet); - -MODULE_AUTHOR("FortuNet, Inc."); -MODULE_DESCRIPTION("MTD map driver for FortuNet boards"); diff --git a/drivers/mtd/maps/gpio-addr-flash.c b/drivers/mtd/maps/gpio-addr-flash.c new file mode 100644 index 00000000000..a4c477b9fdd --- /dev/null +++ b/drivers/mtd/maps/gpio-addr-flash.c @@ -0,0 +1,288 @@ +/* + * drivers/mtd/maps/gpio-addr-flash.c + * + * Handle the case where a flash device is mostly addressed using physical + * line and supplemented by GPIOs. This way you can hook up say a 8MiB flash + * to a 2MiB memory range and use the GPIOs to select a particular range. + * + * Copyright © 2000 Nicolas Pitre <nico@cam.org> + * Copyright © 2005-2009 Analog Devices Inc. + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/gpio.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/physmap.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/types.h> + +#define pr_devinit(fmt, args...) \ + ({ static const char __fmt[] = fmt; printk(__fmt, ## args); }) + +#define DRIVER_NAME "gpio-addr-flash" +#define PFX DRIVER_NAME ": " + +/** + * struct async_state - keep GPIO flash state + * @mtd: MTD state for this mapping + * @map: MTD map state for this flash + * @gpio_count: number of GPIOs used to address + * @gpio_addrs: array of GPIOs to twiddle + * @gpio_values: cached GPIO values + * @win_size: dedicated memory size (if no GPIOs) + */ +struct async_state { + struct mtd_info *mtd; + struct map_info map; + size_t gpio_count; + unsigned *gpio_addrs; + int *gpio_values; + unsigned long win_size; +}; +#define gf_map_info_to_state(mi) ((struct async_state *)(mi)->map_priv_1) + +/** + * gf_set_gpios() - set GPIO address lines to access specified flash offset + * @state: GPIO flash state + * @ofs: desired offset to access + * + * Rather than call the GPIO framework every time, cache the last-programmed + * value. This speeds up sequential accesses (which are by far the most common + * type). We rely on the GPIO framework to treat non-zero value as high so + * that we don't have to normalize the bits. + */ +static void gf_set_gpios(struct async_state *state, unsigned long ofs) +{ + size_t i = 0; + int value; + ofs /= state->win_size; + do { + value = ofs & (1 << i); + if (state->gpio_values[i] != value) { + gpio_set_value(state->gpio_addrs[i], value); + state->gpio_values[i] = value; + } + } while (++i < state->gpio_count); +} + +/** + * gf_read() - read a word at the specified offset + * @map: MTD map state + * @ofs: desired offset to read + */ +static map_word gf_read(struct map_info *map, unsigned long ofs) +{ + struct async_state *state = gf_map_info_to_state(map); + uint16_t word; + map_word test; + + gf_set_gpios(state, ofs); + + word = readw(map->virt + (ofs % state->win_size)); + test.x[0] = word; + return test; +} + +/** + * gf_copy_from() - copy a chunk of data from the flash + * @map: MTD map state + * @to: memory to copy to + * @from: flash offset to copy from + * @len: how much to copy + * + * We rely on the MTD layer to chunk up copies such that a single request here + * will not cross a window size. This allows us to only wiggle the GPIOs once + * before falling back to a normal memcpy. Reading the higher layer code shows + * that this is indeed the case, but add a BUG_ON() to future proof. + */ +static void gf_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len) +{ + struct async_state *state = gf_map_info_to_state(map); + + gf_set_gpios(state, from); + + /* BUG if operation crosses the win_size */ + BUG_ON(!((from + len) % state->win_size <= (from + len))); + + /* operation does not cross the win_size, so one shot it */ + memcpy_fromio(to, map->virt + (from % state->win_size), len); +} + +/** + * gf_write() - write a word at the specified offset + * @map: MTD map state + * @ofs: desired offset to write + */ +static void gf_write(struct map_info *map, map_word d1, unsigned long ofs) +{ + struct async_state *state = gf_map_info_to_state(map); + uint16_t d; + + gf_set_gpios(state, ofs); + + d = d1.x[0]; + writew(d, map->virt + (ofs % state->win_size)); +} + +/** + * gf_copy_to() - copy a chunk of data to the flash + * @map: MTD map state + * @to: flash offset to copy to + * @from: memory to copy from + * @len: how much to copy + * + * See gf_copy_from() caveat. + */ +static void gf_copy_to(struct map_info *map, unsigned long to, + const void *from, ssize_t len) +{ + struct async_state *state = gf_map_info_to_state(map); + + gf_set_gpios(state, to); + + /* BUG if operation crosses the win_size */ + BUG_ON(!((to + len) % state->win_size <= (to + len))); + + /* operation does not cross the win_size, so one shot it */ + memcpy_toio(map->virt + (to % state->win_size), from, len); +} + +static const char * const part_probe_types[] = { + "cmdlinepart", "RedBoot", NULL }; + +/** + * gpio_flash_probe() - setup a mapping for a GPIO assisted flash + * @pdev: platform device + * + * The platform resource layout expected looks something like: + * struct mtd_partition partitions[] = { ... }; + * struct physmap_flash_data flash_data = { ... }; + * unsigned flash_gpios[] = { GPIO_XX, GPIO_XX, ... }; + * struct resource flash_resource[] = { + * { + * .name = "cfi_probe", + * .start = 0x20000000, + * .end = 0x201fffff, + * .flags = IORESOURCE_MEM, + * }, { + * .start = (unsigned long)flash_gpios, + * .end = ARRAY_SIZE(flash_gpios), + * .flags = IORESOURCE_IRQ, + * } + * }; + * struct platform_device flash_device = { + * .name = "gpio-addr-flash", + * .dev = { .platform_data = &flash_data, }, + * .num_resources = ARRAY_SIZE(flash_resource), + * .resource = flash_resource, + * ... + * }; + */ +static int gpio_flash_probe(struct platform_device *pdev) +{ + size_t i, arr_size; + struct physmap_flash_data *pdata; + struct resource *memory; + struct resource *gpios; + struct async_state *state; + + pdata = dev_get_platdata(&pdev->dev); + memory = platform_get_resource(pdev, IORESOURCE_MEM, 0); + gpios = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + + if (!memory || !gpios || !gpios->end) + return -EINVAL; + + arr_size = sizeof(int) * gpios->end; + state = kzalloc(sizeof(*state) + arr_size, GFP_KERNEL); + if (!state) + return -ENOMEM; + + /* + * We cast start/end to known types in the boards file, so cast + * away their pointer types here to the known types (gpios->xxx). + */ + state->gpio_count = gpios->end; + state->gpio_addrs = (void *)(unsigned long)gpios->start; + state->gpio_values = (void *)(state + 1); + state->win_size = resource_size(memory); + memset(state->gpio_values, 0xff, arr_size); + + state->map.name = DRIVER_NAME; + state->map.read = gf_read; + state->map.copy_from = gf_copy_from; + state->map.write = gf_write; + state->map.copy_to = gf_copy_to; + state->map.bankwidth = pdata->width; + state->map.size = state->win_size * (1 << state->gpio_count); + state->map.virt = ioremap_nocache(memory->start, state->map.size); + state->map.phys = NO_XIP; + state->map.map_priv_1 = (unsigned long)state; + + platform_set_drvdata(pdev, state); + + i = 0; + do { + if (gpio_request(state->gpio_addrs[i], DRIVER_NAME)) { + pr_devinit(KERN_ERR PFX "failed to request gpio %d\n", + state->gpio_addrs[i]); + while (i--) + gpio_free(state->gpio_addrs[i]); + kfree(state); + return -EBUSY; + } + gpio_direction_output(state->gpio_addrs[i], 0); + } while (++i < state->gpio_count); + + pr_devinit(KERN_NOTICE PFX "probing %d-bit flash bus\n", + state->map.bankwidth * 8); + state->mtd = do_map_probe(memory->name, &state->map); + if (!state->mtd) { + for (i = 0; i < state->gpio_count; ++i) + gpio_free(state->gpio_addrs[i]); + kfree(state); + return -ENXIO; + } + + + mtd_device_parse_register(state->mtd, part_probe_types, NULL, + pdata->parts, pdata->nr_parts); + + return 0; +} + +static int gpio_flash_remove(struct platform_device *pdev) +{ + struct async_state *state = platform_get_drvdata(pdev); + size_t i = 0; + do { + gpio_free(state->gpio_addrs[i]); + } while (++i < state->gpio_count); + mtd_device_unregister(state->mtd); + map_destroy(state->mtd); + kfree(state); + return 0; +} + +static struct platform_driver gpio_flash_driver = { + .probe = gpio_flash_probe, + .remove = gpio_flash_remove, + .driver = { + .name = DRIVER_NAME, + }, +}; + +module_platform_driver(gpio_flash_driver); + +MODULE_AUTHOR("Mike Frysinger <vapier@gentoo.org>"); +MODULE_DESCRIPTION("MTD map driver for flashes addressed physically and with gpios"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/maps/h720x-flash.c b/drivers/mtd/maps/h720x-flash.c deleted file mode 100644 index 6dde3182d64..00000000000 --- a/drivers/mtd/maps/h720x-flash.c +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Flash memory access on Hynix GMS30C7201/HMS30C7202 based - * evaluation boards - * - * $Id: h720x-flash.c,v 1.12 2005/11/07 11:14:27 gleixner Exp $ - * - * (C) 2002 Jungjun Kim <jungjun.kim@hynix.com> - * 2003 Thomas Gleixner <tglx@linutronix.de> - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <linux/errno.h> -#include <linux/slab.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> -#include <asm/hardware.h> -#include <asm/io.h> - -static struct mtd_info *mymtd; - -static struct map_info h720x_map = { - .name = "H720X", - .bankwidth = 4, - .size = FLASH_SIZE, - .phys = FLASH_PHYS, -}; - -static struct mtd_partition h720x_partitions[] = { - { - .name = "ArMon", - .size = 0x00080000, - .offset = 0, - .mask_flags = MTD_WRITEABLE - },{ - .name = "Env", - .size = 0x00040000, - .offset = 0x00080000, - .mask_flags = MTD_WRITEABLE - },{ - .name = "Kernel", - .size = 0x00180000, - .offset = 0x000c0000, - .mask_flags = MTD_WRITEABLE - },{ - .name = "Ramdisk", - .size = 0x00400000, - .offset = 0x00240000, - .mask_flags = MTD_WRITEABLE - },{ - .name = "jffs2", - .size = MTDPART_SIZ_FULL, - .offset = MTDPART_OFS_APPEND - } -}; - -#define NUM_PARTITIONS ARRAY_SIZE(h720x_partitions) - -static int nr_mtd_parts; -static struct mtd_partition *mtd_parts; -static const char *probes[] = { "cmdlinepart", NULL }; - -/* - * Initialize FLASH support - */ -int __init h720x_mtd_init(void) -{ - - char *part_type = NULL; - - h720x_map.virt = ioremap(FLASH_PHYS, FLASH_SIZE); - - if (!h720x_map.virt) { - printk(KERN_ERR "H720x-MTD: ioremap failed\n"); - return -EIO; - } - - simple_map_init(&h720x_map); - - // Probe for flash bankwidth 4 - printk (KERN_INFO "H720x-MTD probing 32bit FLASH\n"); - mymtd = do_map_probe("cfi_probe", &h720x_map); - if (!mymtd) { - printk (KERN_INFO "H720x-MTD probing 16bit FLASH\n"); - // Probe for bankwidth 2 - h720x_map.bankwidth = 2; - mymtd = do_map_probe("cfi_probe", &h720x_map); - } - - if (mymtd) { - mymtd->owner = THIS_MODULE; - -#ifdef CONFIG_MTD_PARTITIONS - nr_mtd_parts = parse_mtd_partitions(mymtd, probes, &mtd_parts, 0); - if (nr_mtd_parts > 0) - part_type = "command line"; -#endif - if (nr_mtd_parts <= 0) { - mtd_parts = h720x_partitions; - nr_mtd_parts = NUM_PARTITIONS; - part_type = "builtin"; - } - printk(KERN_INFO "Using %s partition table\n", part_type); - add_mtd_partitions(mymtd, mtd_parts, nr_mtd_parts); - return 0; - } - - iounmap((void *)h720x_map.virt); - return -ENXIO; -} - -/* - * Cleanup - */ -static void __exit h720x_mtd_cleanup(void) -{ - - if (mymtd) { - del_mtd_partitions(mymtd); - map_destroy(mymtd); - } - - /* Free partition info, if commandline partition was used */ - if (mtd_parts && (mtd_parts != h720x_partitions)) - kfree (mtd_parts); - - if (h720x_map.virt) { - iounmap((void *)h720x_map.virt); - h720x_map.virt = 0; - } -} - - -module_init(h720x_mtd_init); -module_exit(h720x_mtd_cleanup); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Thomas Gleixner <tglx@linutronix.de>"); -MODULE_DESCRIPTION("MTD map driver for Hynix evaluation boards"); diff --git a/drivers/mtd/maps/ichxrom.c b/drivers/mtd/maps/ichxrom.c index 2bb3e63606e..c7478e18f48 100644 --- a/drivers/mtd/maps/ichxrom.c +++ b/drivers/mtd/maps/ichxrom.c @@ -2,13 +2,13 @@ * ichxrom.c * * Normal mappings of chips in physical memory - * $Id: ichxrom.c,v 1.19 2005/11/07 11:14:27 gleixner Exp $ */ #include <linux/module.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/init.h> +#include <linux/slab.h> #include <asm/io.h> #include <linux/mtd/mtd.h> #include <linux/mtd/map.h> @@ -67,7 +67,7 @@ static void ichxrom_cleanup(struct ichxrom_window *window) list_for_each_entry_safe(map, scratch, &window->maps, list) { if (map->rsrc.parent) release_resource(&map->rsrc); - del_mtd_device(map->mtd); + mtd_device_unregister(map->mtd); map_destroy(map->mtd); list_del(&map->list); kfree(map); @@ -84,8 +84,8 @@ static void ichxrom_cleanup(struct ichxrom_window *window) } -static int __devinit ichxrom_init_one (struct pci_dev *pdev, - const struct pci_device_id *ent) +static int ichxrom_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent) { static char *rom_probe_types[] = { "cfi_probe", "jedec_probe", NULL }; struct ichxrom_window *window = &ichxrom_window; @@ -175,12 +175,9 @@ static int __devinit ichxrom_init_one (struct pci_dev *pdev, window->rsrc.flags = IORESOURCE_MEM | IORESOURCE_BUSY; if (request_resource(&iomem_resource, &window->rsrc)) { window->rsrc.parent = NULL; - printk(KERN_DEBUG MOD_NAME - ": %s(): Unable to register resource" - " 0x%.16llx-0x%.16llx - kernel bug?\n", - __func__, - (unsigned long long)window->rsrc.start, - (unsigned long long)window->rsrc.end); + printk(KERN_DEBUG MOD_NAME ": " + "%s(): Unable to register resource %pR - kernel bug?\n", + __func__, &window->rsrc); } /* Map the firmware hub into my address space. */ @@ -227,8 +224,8 @@ static int __devinit ichxrom_init_one (struct pci_dev *pdev, (((unsigned long)(window->virt)) + offset); map->map.size = 0xffffffffUL - map_top + 1UL; /* Set the name of the map to the address I am trying */ - sprintf(map->map_name, "%s @%08lx", - MOD_NAME, map->map.phys); + sprintf(map->map_name, "%s @%08Lx", + MOD_NAME, (unsigned long long)map->map.phys); /* Firmware hubs only use vpp when being programmed * in a factory setting. So in-place programming @@ -259,8 +256,8 @@ static int __devinit ichxrom_init_one (struct pci_dev *pdev, /* Trim the size if we are larger than the map */ if (map->mtd->size > map->map.size) { printk(KERN_WARNING MOD_NAME - " rom(%u) larger than window(%lu). fixing...\n", - map->mtd->size, map->map.size); + " rom(%llu) larger than window(%lu). fixing...\n", + (unsigned long long)map->mtd->size, map->map.size); map->mtd->size = map->map.size; } if (window->rsrc.parent) { @@ -290,7 +287,7 @@ static int __devinit ichxrom_init_one (struct pci_dev *pdev, /* Now that the mtd devices is complete claim and export it */ map->mtd->owner = THIS_MODULE; - if (add_mtd_device(map->mtd)) { + if (mtd_device_register(map->mtd, NULL, 0)) { map_destroy(map->mtd); map->mtd = NULL; goto out; @@ -318,13 +315,13 @@ static int __devinit ichxrom_init_one (struct pci_dev *pdev, } -static void __devexit ichxrom_remove_one (struct pci_dev *pdev) +static void ichxrom_remove_one(struct pci_dev *pdev) { struct ichxrom_window *window = &ichxrom_window; ichxrom_cleanup(window); } -static struct pci_device_id ichxrom_pci_tbl[] __devinitdata = { +static struct pci_device_id ichxrom_pci_tbl[] = { { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0, PCI_ANY_ID, PCI_ANY_ID, }, { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0, diff --git a/drivers/mtd/maps/impa7.c b/drivers/mtd/maps/impa7.c index a0b4dc7155d..15bbda03be6 100644 --- a/drivers/mtd/maps/impa7.c +++ b/drivers/mtd/maps/impa7.c @@ -1,6 +1,4 @@ /* - * $Id: impa7.c,v 1.14 2005/11/07 11:14:27 gleixner Exp $ - * * Handle mapping of the NOR flash on implementa A7 boards * * Copyright 2002 SYSGO Real-Time Solutions GmbH @@ -17,10 +15,7 @@ #include <asm/io.h> #include <linux/mtd/mtd.h> #include <linux/mtd/map.h> - -#ifdef CONFIG_MTD_PARTITIONS #include <linux/mtd/partitions.h> -#endif #define WINDOW_ADDR0 0x00000000 /* physical properties of flash */ #define WINDOW_SIZE0 0x00800000 @@ -29,14 +24,12 @@ #define NUM_FLASHBANKS 2 #define BUSWIDTH 4 -/* can be { "cfi_probe", "jedec_probe", "map_rom", NULL } */ -#define PROBETYPES { "jedec_probe", NULL } - #define MSG_PREFIX "impA7:" /* prefix for our printk()'s */ #define MTDID "impa7-%d" /* for mtdparts= partitioning */ static struct mtd_info *impa7_mtd[NUM_FLASHBANKS]; +static const char * const rom_probe_types[] = { "jedec_probe", NULL }; static struct map_info impa7_map[NUM_FLASHBANKS] = { { @@ -51,12 +44,10 @@ static struct map_info impa7_map[NUM_FLASHBANKS] = { }, }; -#ifdef CONFIG_MTD_PARTITIONS - /* * MTD partitioning stuff */ -static struct mtd_partition static_partitions[] = +static struct mtd_partition partitions[] = { { .name = "FileSystem", @@ -65,18 +56,9 @@ static struct mtd_partition static_partitions[] = }, }; -static int mtd_parts_nb[NUM_FLASHBANKS]; -static struct mtd_partition *mtd_parts[NUM_FLASHBANKS]; - -#endif - -static const char *probes[] = { "cmdlinepart", NULL }; - -int __init init_impa7(void) +static int __init init_impa7(void) { - static const char *rom_probe_types[] = PROBETYPES; - const char **type; - const char *part_type = 0; + const char * const *type; int i; static struct { u_long addr; u_long size; } pt[NUM_FLASHBANKS] = { { WINDOW_ADDR0, WINDOW_SIZE0 }, @@ -97,7 +79,7 @@ int __init init_impa7(void) } simple_map_init(&impa7_map[i]); - impa7_mtd[i] = 0; + impa7_mtd[i] = NULL; type = rom_probe_types; for(; !impa7_mtd[i] && *type; type++) { impa7_mtd[i] = do_map_probe(*type, &impa7_map[i]); @@ -106,31 +88,12 @@ int __init init_impa7(void) if (impa7_mtd[i]) { impa7_mtd[i]->owner = THIS_MODULE; devicesfound++; -#ifdef CONFIG_MTD_PARTITIONS - mtd_parts_nb[i] = parse_mtd_partitions(impa7_mtd[i], - probes, - &mtd_parts[i], - 0); - if (mtd_parts_nb[i] > 0) { - part_type = "command line"; - } else { - mtd_parts[i] = static_partitions; - mtd_parts_nb[i] = ARRAY_SIZE(static_partitions); - part_type = "static"; - } - - printk(KERN_NOTICE MSG_PREFIX - "using %s partition definition\n", - part_type); - add_mtd_partitions(impa7_mtd[i], - mtd_parts[i], mtd_parts_nb[i]); -#else - add_mtd_device(impa7_mtd[i]); - -#endif + mtd_device_parse_register(impa7_mtd[i], NULL, NULL, + partitions, + ARRAY_SIZE(partitions)); + } else { + iounmap((void __iomem *)impa7_map[i].virt); } - else - iounmap((void *)impa7_map[i].virt); } return devicesfound == 0 ? -ENXIO : 0; } @@ -140,14 +103,10 @@ static void __exit cleanup_impa7(void) int i; for (i=0; i<NUM_FLASHBANKS; i++) { if (impa7_mtd[i]) { -#ifdef CONFIG_MTD_PARTITIONS - del_mtd_partitions(impa7_mtd[i]); -#else - del_mtd_device(impa7_mtd[i]); -#endif + mtd_device_unregister(impa7_mtd[i]); map_destroy(impa7_mtd[i]); - iounmap((void *)impa7_map[i].virt); - impa7_map[i].virt = 0; + iounmap((void __iomem *)impa7_map[i].virt); + impa7_map[i].virt = NULL; } } } diff --git a/drivers/mtd/maps/integrator-flash.c b/drivers/mtd/maps/integrator-flash.c deleted file mode 100644 index c8db01b3e45..00000000000 --- a/drivers/mtd/maps/integrator-flash.c +++ /dev/null @@ -1,213 +0,0 @@ -/*====================================================================== - - drivers/mtd/maps/integrator-flash.c: ARM Integrator flash map driver - - Copyright (C) 2000 ARM Limited - Copyright (C) 2003 Deep Blue Solutions Ltd. - - 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - This is access code for flashes using ARM's flash partitioning - standards. - - $Id: integrator-flash.c,v 1.20 2005/11/07 11:14:27 gleixner Exp $ - -======================================================================*/ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/slab.h> -#include <linux/ioport.h> -#include <linux/platform_device.h> -#include <linux/init.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - -#include <asm/mach/flash.h> -#include <asm/hardware.h> -#include <asm/io.h> -#include <asm/system.h> - -#ifdef CONFIG_ARCH_P720T -#define FLASH_BASE (0x04000000) -#define FLASH_SIZE (64*1024*1024) -#endif - -struct armflash_info { - struct flash_platform_data *plat; - struct resource *res; - struct mtd_partition *parts; - struct mtd_info *mtd; - struct map_info map; -}; - -static void armflash_set_vpp(struct map_info *map, int on) -{ - struct armflash_info *info = container_of(map, struct armflash_info, map); - - if (info->plat && info->plat->set_vpp) - info->plat->set_vpp(on); -} - -static const char *probes[] = { "cmdlinepart", "RedBoot", "afs", NULL }; - -static int armflash_probe(struct platform_device *dev) -{ - struct flash_platform_data *plat = dev->dev.platform_data; - struct resource *res = dev->resource; - unsigned int size = res->end - res->start + 1; - struct armflash_info *info; - int err; - void __iomem *base; - - info = kmalloc(sizeof(struct armflash_info), GFP_KERNEL); - if (!info) { - err = -ENOMEM; - goto out; - } - - memset(info, 0, sizeof(struct armflash_info)); - - info->plat = plat; - if (plat && plat->init) { - err = plat->init(); - if (err) - goto no_resource; - } - - info->res = request_mem_region(res->start, size, "armflash"); - if (!info->res) { - err = -EBUSY; - goto no_resource; - } - - base = ioremap(res->start, size); - if (!base) { - err = -ENOMEM; - goto no_mem; - } - - /* - * look for CFI based flash parts fitted to this board - */ - info->map.size = size; - info->map.bankwidth = plat->width; - info->map.phys = res->start; - info->map.virt = base; - info->map.name = dev->dev.bus_id; - info->map.set_vpp = armflash_set_vpp; - - simple_map_init(&info->map); - - /* - * Also, the CFI layer automatically works out what size - * of chips we have, and does the necessary identification - * for us automatically. - */ - info->mtd = do_map_probe(plat->map_name, &info->map); - if (!info->mtd) { - err = -ENXIO; - goto no_device; - } - - info->mtd->owner = THIS_MODULE; - - err = parse_mtd_partitions(info->mtd, probes, &info->parts, 0); - if (err > 0) { - err = add_mtd_partitions(info->mtd, info->parts, err); - if (err) - printk(KERN_ERR - "mtd partition registration failed: %d\n", err); - } - - if (err == 0) - platform_set_drvdata(dev, info); - - /* - * If we got an error, free all resources. - */ - if (err < 0) { - if (info->mtd) { - del_mtd_partitions(info->mtd); - map_destroy(info->mtd); - } - kfree(info->parts); - - no_device: - iounmap(base); - no_mem: - release_mem_region(res->start, size); - no_resource: - if (plat && plat->exit) - plat->exit(); - kfree(info); - } - out: - return err; -} - -static int armflash_remove(struct platform_device *dev) -{ - struct armflash_info *info = platform_get_drvdata(dev); - - platform_set_drvdata(dev, NULL); - - if (info) { - if (info->mtd) { - del_mtd_partitions(info->mtd); - map_destroy(info->mtd); - } - kfree(info->parts); - - iounmap(info->map.virt); - release_resource(info->res); - kfree(info->res); - - if (info->plat && info->plat->exit) - info->plat->exit(); - - kfree(info); - } - - return 0; -} - -static struct platform_driver armflash_driver = { - .probe = armflash_probe, - .remove = armflash_remove, - .driver = { - .name = "armflash", - }, -}; - -static int __init armflash_init(void) -{ - return platform_driver_register(&armflash_driver); -} - -static void __exit armflash_exit(void) -{ - platform_driver_unregister(&armflash_driver); -} - -module_init(armflash_init); -module_exit(armflash_exit); - -MODULE_AUTHOR("ARM Ltd"); -MODULE_DESCRIPTION("ARM Integrator CFI map driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/maps/intel_vr_nor.c b/drivers/mtd/maps/intel_vr_nor.c new file mode 100644 index 00000000000..5ab71f0e1bc --- /dev/null +++ b/drivers/mtd/maps/intel_vr_nor.c @@ -0,0 +1,265 @@ +/* + * drivers/mtd/maps/intel_vr_nor.c + * + * An MTD map driver for a NOR flash bank on the Expansion Bus of the Intel + * Vermilion Range chipset. + * + * The Vermilion Range Expansion Bus supports four chip selects, each of which + * has 64MiB of address space. The 2nd BAR of the Expansion Bus PCI Device + * is a 256MiB memory region containing the address spaces for all four of the + * chip selects, with start addresses hardcoded on 64MiB boundaries. + * + * This map driver only supports NOR flash on chip select 0. The buswidth + * (either 8 bits or 16 bits) is determined by reading the Expansion Bus Timing + * and Control Register for Chip Select 0 (EXP_TIMING_CS0). This driver does + * not modify the value in the EXP_TIMING_CS0 register except to enable writing + * and disable boot acceleration. The timing parameters in the register are + * assumed to have been properly initialized by the BIOS. The reset default + * timing parameters are maximally conservative (slow), so access to the flash + * will be slower than it should be if the BIOS has not initialized the timing + * parameters. + * + * Author: Andy Lowe <alowe@mvista.com> + * + * 2006 (c) MontaVista Software, Inc. This file is licensed under + * the terms of the GNU General Public License version 2. This program + * is licensed "as is" without any warranty of any kind, whether express + * or implied. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/cfi.h> +#include <linux/mtd/flashchip.h> + +#define DRV_NAME "vr_nor" + +struct vr_nor_mtd { + void __iomem *csr_base; + struct map_info map; + struct mtd_info *info; + struct pci_dev *dev; +}; + +/* Expansion Bus Configuration and Status Registers are in BAR 0 */ +#define EXP_CSR_MBAR 0 +/* Expansion Bus Memory Window is BAR 1 */ +#define EXP_WIN_MBAR 1 +/* Maximum address space for Chip Select 0 is 64MiB */ +#define CS0_SIZE 0x04000000 +/* Chip Select 0 is at offset 0 in the Memory Window */ +#define CS0_START 0x0 +/* Chip Select 0 Timing Register is at offset 0 in CSR */ +#define EXP_TIMING_CS0 0x00 +#define TIMING_CS_EN (1 << 31) /* Chip Select Enable */ +#define TIMING_BOOT_ACCEL_DIS (1 << 8) /* Boot Acceleration Disable */ +#define TIMING_WR_EN (1 << 1) /* Write Enable */ +#define TIMING_BYTE_EN (1 << 0) /* 8-bit vs 16-bit bus */ +#define TIMING_MASK 0x3FFF0000 + +static void vr_nor_destroy_partitions(struct vr_nor_mtd *p) +{ + mtd_device_unregister(p->info); +} + +static int vr_nor_init_partitions(struct vr_nor_mtd *p) +{ + /* register the flash bank */ + /* partition the flash bank */ + return mtd_device_parse_register(p->info, NULL, NULL, NULL, 0); +} + +static void vr_nor_destroy_mtd_setup(struct vr_nor_mtd *p) +{ + map_destroy(p->info); +} + +static int vr_nor_mtd_setup(struct vr_nor_mtd *p) +{ + static const char * const probe_types[] = + { "cfi_probe", "jedec_probe", NULL }; + const char * const *type; + + for (type = probe_types; !p->info && *type; type++) + p->info = do_map_probe(*type, &p->map); + if (!p->info) + return -ENODEV; + + p->info->owner = THIS_MODULE; + + return 0; +} + +static void vr_nor_destroy_maps(struct vr_nor_mtd *p) +{ + unsigned int exp_timing_cs0; + + /* write-protect the flash bank */ + exp_timing_cs0 = readl(p->csr_base + EXP_TIMING_CS0); + exp_timing_cs0 &= ~TIMING_WR_EN; + writel(exp_timing_cs0, p->csr_base + EXP_TIMING_CS0); + + /* unmap the flash window */ + iounmap(p->map.virt); + + /* unmap the csr window */ + iounmap(p->csr_base); +} + +/* + * Initialize the map_info structure and map the flash. + * Returns 0 on success, nonzero otherwise. + */ +static int vr_nor_init_maps(struct vr_nor_mtd *p) +{ + unsigned long csr_phys, csr_len; + unsigned long win_phys, win_len; + unsigned int exp_timing_cs0; + int err; + + csr_phys = pci_resource_start(p->dev, EXP_CSR_MBAR); + csr_len = pci_resource_len(p->dev, EXP_CSR_MBAR); + win_phys = pci_resource_start(p->dev, EXP_WIN_MBAR); + win_len = pci_resource_len(p->dev, EXP_WIN_MBAR); + + if (!csr_phys || !csr_len || !win_phys || !win_len) + return -ENODEV; + + if (win_len < (CS0_START + CS0_SIZE)) + return -ENXIO; + + p->csr_base = ioremap_nocache(csr_phys, csr_len); + if (!p->csr_base) + return -ENOMEM; + + exp_timing_cs0 = readl(p->csr_base + EXP_TIMING_CS0); + if (!(exp_timing_cs0 & TIMING_CS_EN)) { + dev_warn(&p->dev->dev, "Expansion Bus Chip Select 0 " + "is disabled.\n"); + err = -ENODEV; + goto release; + } + if ((exp_timing_cs0 & TIMING_MASK) == TIMING_MASK) { + dev_warn(&p->dev->dev, "Expansion Bus Chip Select 0 " + "is configured for maximally slow access times.\n"); + } + p->map.name = DRV_NAME; + p->map.bankwidth = (exp_timing_cs0 & TIMING_BYTE_EN) ? 1 : 2; + p->map.phys = win_phys + CS0_START; + p->map.size = CS0_SIZE; + p->map.virt = ioremap_nocache(p->map.phys, p->map.size); + if (!p->map.virt) { + err = -ENOMEM; + goto release; + } + simple_map_init(&p->map); + + /* Enable writes to flash bank */ + exp_timing_cs0 |= TIMING_BOOT_ACCEL_DIS | TIMING_WR_EN; + writel(exp_timing_cs0, p->csr_base + EXP_TIMING_CS0); + + return 0; + + release: + iounmap(p->csr_base); + return err; +} + +static struct pci_device_id vr_nor_pci_ids[] = { + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x500D)}, + {0,} +}; + +static void vr_nor_pci_remove(struct pci_dev *dev) +{ + struct vr_nor_mtd *p = pci_get_drvdata(dev); + + vr_nor_destroy_partitions(p); + vr_nor_destroy_mtd_setup(p); + vr_nor_destroy_maps(p); + kfree(p); + pci_release_regions(dev); + pci_disable_device(dev); +} + +static int vr_nor_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct vr_nor_mtd *p = NULL; + unsigned int exp_timing_cs0; + int err; + + err = pci_enable_device(dev); + if (err) + goto out; + + err = pci_request_regions(dev, DRV_NAME); + if (err) + goto disable_dev; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + err = -ENOMEM; + if (!p) + goto release; + + p->dev = dev; + + err = vr_nor_init_maps(p); + if (err) + goto release; + + err = vr_nor_mtd_setup(p); + if (err) + goto destroy_maps; + + err = vr_nor_init_partitions(p); + if (err) + goto destroy_mtd_setup; + + pci_set_drvdata(dev, p); + + return 0; + + destroy_mtd_setup: + map_destroy(p->info); + + destroy_maps: + /* write-protect the flash bank */ + exp_timing_cs0 = readl(p->csr_base + EXP_TIMING_CS0); + exp_timing_cs0 &= ~TIMING_WR_EN; + writel(exp_timing_cs0, p->csr_base + EXP_TIMING_CS0); + + /* unmap the flash window */ + iounmap(p->map.virt); + + /* unmap the csr window */ + iounmap(p->csr_base); + + release: + kfree(p); + pci_release_regions(dev); + + disable_dev: + pci_disable_device(dev); + + out: + return err; +} + +static struct pci_driver vr_nor_pci_driver = { + .name = DRV_NAME, + .probe = vr_nor_pci_probe, + .remove = vr_nor_pci_remove, + .id_table = vr_nor_pci_ids, +}; + +module_pci_driver(vr_nor_pci_driver); + +MODULE_AUTHOR("Andy Lowe"); +MODULE_DESCRIPTION("MTD map driver for NOR flash on Intel Vermilion Range"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(pci, vr_nor_pci_ids); diff --git a/drivers/mtd/maps/ipaq-flash.c b/drivers/mtd/maps/ipaq-flash.c deleted file mode 100644 index f27c132794c..00000000000 --- a/drivers/mtd/maps/ipaq-flash.c +++ /dev/null @@ -1,462 +0,0 @@ -/* - * Flash memory access on iPAQ Handhelds (either SA1100 or PXA250 based) - * - * (C) 2000 Nicolas Pitre <nico@cam.org> - * (C) 2002 Hewlett-Packard Company <jamey.hicks@hp.com> - * (C) 2003 Christian Pellegrin <chri@ascensit.com>, <chri@infis.univ.ts.it>: concatenation of multiple flashes - * - * $Id: ipaq-flash.c,v 1.5 2005/11/07 11:14:27 gleixner Exp $ - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/spinlock.h> -#include <linux/init.h> -#include <linux/slab.h> -#include <asm/page.h> -#include <asm/mach-types.h> -#include <asm/system.h> -#include <asm/errno.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> -#ifdef CONFIG_MTD_CONCAT -#include <linux/mtd/concat.h> -#endif - -#include <asm/hardware.h> -#include <asm/arch-sa1100/h3600.h> -#include <asm/io.h> - - -#ifndef CONFIG_IPAQ_HANDHELD -#error This is for iPAQ Handhelds only -#endif -#ifdef CONFIG_SA1100_JORNADA56X - -static void jornada56x_set_vpp(struct map_info *map, int vpp) -{ - if (vpp) - GPSR = GPIO_GPIO26; - else - GPCR = GPIO_GPIO26; - GPDR |= GPIO_GPIO26; -} - -#endif - -#ifdef CONFIG_SA1100_JORNADA720 - -static void jornada720_set_vpp(struct map_info *map, int vpp) -{ - if (vpp) - PPSR |= 0x80; - else - PPSR &= ~0x80; - PPDR |= 0x80; -} - -#endif - -#define MAX_IPAQ_CS 2 /* Number of CS we are going to test */ - -#define IPAQ_MAP_INIT(X) \ - { \ - name: "IPAQ flash " X, \ - } - - -static struct map_info ipaq_map[MAX_IPAQ_CS] = { - IPAQ_MAP_INIT("bank 1"), - IPAQ_MAP_INIT("bank 2") -}; - -static struct mtd_info *my_sub_mtd[MAX_IPAQ_CS] = { - NULL, - NULL -}; - -/* - * Here are partition information for all known IPAQ-based devices. - * See include/linux/mtd/partitions.h for definition of the mtd_partition - * structure. - * - * The *_max_flash_size is the maximum possible mapped flash size which - * is not necessarily the actual flash size. It must be no more than - * the value specified in the "struct map_desc *_io_desc" mapping - * definition for the corresponding machine. - * - * Please keep these in alphabetical order, and formatted as per existing - * entries. Thanks. - */ - -#ifdef CONFIG_IPAQ_HANDHELD -static unsigned long h3xxx_max_flash_size = 0x04000000; -static struct mtd_partition h3xxx_partitions[] = { - { - name: "H3XXX boot firmware", -#ifndef CONFIG_LAB - size: 0x00040000, -#else - size: 0x00080000, -#endif - offset: 0, -#ifndef CONFIG_LAB - mask_flags: MTD_WRITEABLE, /* force read-only */ -#endif - }, - { - name: "H3XXX root jffs2", -#ifndef CONFIG_LAB - size: 0x2000000 - 2*0x40000, /* Warning, this is fixed later */ - offset: 0x00040000, -#else - size: 0x2000000 - 0x40000 - 0x80000, /* Warning, this is fixed later */ - offset: 0x00080000, -#endif - }, - { - name: "asset", - size: 0x40000, - offset: 0x2000000 - 0x40000, /* Warning, this is fixed later */ - mask_flags: MTD_WRITEABLE, /* force read-only */ - } -}; - -#ifndef CONFIG_MTD_CONCAT -static struct mtd_partition h3xxx_partitions_bank2[] = { - /* this is used only on 2 CS machines when concat is not present */ - { - name: "second H3XXX root jffs2", - size: 0x1000000 - 0x40000, /* Warning, this is fixed later */ - offset: 0x00000000, - }, - { - name: "second asset", - size: 0x40000, - offset: 0x1000000 - 0x40000, /* Warning, this is fixed later */ - mask_flags: MTD_WRITEABLE, /* force read-only */ - } -}; -#endif - -static DEFINE_SPINLOCK(ipaq_vpp_lock); - -static void h3xxx_set_vpp(struct map_info *map, int vpp) -{ - static int nest = 0; - - spin_lock(&ipaq_vpp_lock); - if (vpp) - nest++; - else - nest--; - if (nest) - assign_h3600_egpio(IPAQ_EGPIO_VPP_ON, 1); - else - assign_h3600_egpio(IPAQ_EGPIO_VPP_ON, 0); - spin_unlock(&ipaq_vpp_lock); -} - -#endif - -#if defined(CONFIG_SA1100_JORNADA56X) || defined(CONFIG_SA1100_JORNADA720) -static unsigned long jornada_max_flash_size = 0x02000000; -static struct mtd_partition jornada_partitions[] = { - { - name: "Jornada boot firmware", - size: 0x00040000, - offset: 0, - mask_flags: MTD_WRITEABLE, /* force read-only */ - }, { - name: "Jornada root jffs2", - size: MTDPART_SIZ_FULL, - offset: 0x00040000, - } -}; -#endif - - -static struct mtd_partition *parsed_parts; -static struct mtd_info *mymtd; - -static unsigned long cs_phys[] = { -#ifdef CONFIG_ARCH_SA1100 - SA1100_CS0_PHYS, - SA1100_CS1_PHYS, - SA1100_CS2_PHYS, - SA1100_CS3_PHYS, - SA1100_CS4_PHYS, - SA1100_CS5_PHYS, -#else - PXA_CS0_PHYS, - PXA_CS1_PHYS, - PXA_CS2_PHYS, - PXA_CS3_PHYS, - PXA_CS4_PHYS, - PXA_CS5_PHYS, -#endif -}; - -static const char *part_probes[] = { "cmdlinepart", "RedBoot", NULL }; - -static int __init h1900_special_case(void); - -int __init ipaq_mtd_init(void) -{ - struct mtd_partition *parts = NULL; - int nb_parts = 0; - int parsed_nr_parts = 0; - const char *part_type; - int i; /* used when we have >1 flash chips */ - unsigned long tot_flashsize = 0; /* used when we have >1 flash chips */ - - /* Default flash bankwidth */ - // ipaq_map.bankwidth = (MSC0 & MSC_RBW) ? 2 : 4; - - if (machine_is_h1900()) - { - /* For our intents, the h1900 is not a real iPAQ, so we special-case it. */ - return h1900_special_case(); - } - - if (machine_is_h3100() || machine_is_h1900()) - for(i=0; i<MAX_IPAQ_CS; i++) - ipaq_map[i].bankwidth = 2; - else - for(i=0; i<MAX_IPAQ_CS; i++) - ipaq_map[i].bankwidth = 4; - - /* - * Static partition definition selection - */ - part_type = "static"; - - simple_map_init(&ipaq_map[0]); - simple_map_init(&ipaq_map[1]); - -#ifdef CONFIG_IPAQ_HANDHELD - if (machine_is_ipaq()) { - parts = h3xxx_partitions; - nb_parts = ARRAY_SIZE(h3xxx_partitions); - for(i=0; i<MAX_IPAQ_CS; i++) { - ipaq_map[i].size = h3xxx_max_flash_size; - ipaq_map[i].set_vpp = h3xxx_set_vpp; - ipaq_map[i].phys = cs_phys[i]; - ipaq_map[i].virt = ioremap(cs_phys[i], 0x04000000); - if (machine_is_h3100 () || machine_is_h1900()) - ipaq_map[i].bankwidth = 2; - } - if (machine_is_h3600()) { - /* No asset partition here */ - h3xxx_partitions[1].size += 0x40000; - nb_parts--; - } - } -#endif -#ifdef CONFIG_ARCH_H5400 - if (machine_is_h5400()) { - ipaq_map[0].size = 0x02000000; - ipaq_map[1].size = 0x02000000; - ipaq_map[1].phys = 0x02000000; - ipaq_map[1].virt = ipaq_map[0].virt + 0x02000000; - } -#endif -#ifdef CONFIG_ARCH_H1900 - if (machine_is_h1900()) { - ipaq_map[0].size = 0x00400000; - ipaq_map[1].size = 0x02000000; - ipaq_map[1].phys = 0x00080000; - ipaq_map[1].virt = ipaq_map[0].virt + 0x00080000; - } -#endif - -#ifdef CONFIG_SA1100_JORNADA56X - if (machine_is_jornada56x()) { - parts = jornada_partitions; - nb_parts = ARRAY_SIZE(jornada_partitions); - ipaq_map[0].size = jornada_max_flash_size; - ipaq_map[0].set_vpp = jornada56x_set_vpp; - ipaq_map[0].virt = (__u32)ioremap(0x0, 0x04000000); - } -#endif -#ifdef CONFIG_SA1100_JORNADA720 - if (machine_is_jornada720()) { - parts = jornada_partitions; - nb_parts = ARRAY_SIZE(jornada_partitions); - ipaq_map[0].size = jornada_max_flash_size; - ipaq_map[0].set_vpp = jornada720_set_vpp; - } -#endif - - - if (machine_is_ipaq()) { /* for iPAQs only */ - for(i=0; i<MAX_IPAQ_CS; i++) { - printk(KERN_NOTICE "iPAQ flash: probing %d-bit flash bus, window=%lx with CFI.\n", ipaq_map[i].bankwidth*8, ipaq_map[i].virt); - my_sub_mtd[i] = do_map_probe("cfi_probe", &ipaq_map[i]); - if (!my_sub_mtd[i]) { - printk(KERN_NOTICE "iPAQ flash: probing %d-bit flash bus, window=%lx with JEDEC.\n", ipaq_map[i].bankwidth*8, ipaq_map[i].virt); - my_sub_mtd[i] = do_map_probe("jedec_probe", &ipaq_map[i]); - } - if (!my_sub_mtd[i]) { - printk(KERN_NOTICE "iPAQ flash: failed to find flash.\n"); - if (i) - break; - else - return -ENXIO; - } else - printk(KERN_NOTICE "iPAQ flash: found %d bytes\n", my_sub_mtd[i]->size); - - /* do we really need this debugging? --joshua 20030703 */ - // printk("my_sub_mtd[%d]=%p\n", i, my_sub_mtd[i]); - my_sub_mtd[i]->owner = THIS_MODULE; - tot_flashsize += my_sub_mtd[i]->size; - } -#ifdef CONFIG_MTD_CONCAT - /* fix the asset location */ -# ifdef CONFIG_LAB - h3xxx_partitions[1].size = tot_flashsize - 0x40000 - 0x80000 /* extra big boot block */; -# else - h3xxx_partitions[1].size = tot_flashsize - 2 * 0x40000; -# endif - h3xxx_partitions[2].offset = tot_flashsize - 0x40000; - /* and concat the devices */ - mymtd = mtd_concat_create(&my_sub_mtd[0], i, - "ipaq"); - if (!mymtd) { - printk("Cannot create iPAQ concat device\n"); - return -ENXIO; - } -#else - mymtd = my_sub_mtd[0]; - - /* - *In the very near future, command line partition parsing - * will use the device name as 'mtd-id' instead of a value - * passed to the parse_cmdline_partitions() routine. Since - * the bootldr says 'ipaq', make sure it continues to work. - */ - mymtd->name = "ipaq"; - - if ((machine_is_h3600())) { -# ifdef CONFIG_LAB - h3xxx_partitions[1].size = my_sub_mtd[0]->size - 0x80000; -# else - h3xxx_partitions[1].size = my_sub_mtd[0]->size - 0x40000; -# endif - nb_parts = 2; - } else { -# ifdef CONFIG_LAB - h3xxx_partitions[1].size = my_sub_mtd[0]->size - 0x40000 - 0x80000; /* extra big boot block */ -# else - h3xxx_partitions[1].size = my_sub_mtd[0]->size - 2*0x40000; -# endif - h3xxx_partitions[2].offset = my_sub_mtd[0]->size - 0x40000; - } - - if (my_sub_mtd[1]) { -# ifdef CONFIG_LAB - h3xxx_partitions_bank2[0].size = my_sub_mtd[1]->size - 0x80000; -# else - h3xxx_partitions_bank2[0].size = my_sub_mtd[1]->size - 0x40000; -# endif - h3xxx_partitions_bank2[1].offset = my_sub_mtd[1]->size - 0x40000; - } -#endif - } - else { - /* - * Now let's probe for the actual flash. Do it here since - * specific machine settings might have been set above. - */ - printk(KERN_NOTICE "IPAQ flash: probing %d-bit flash bus, window=%lx\n", ipaq_map[0].bankwidth*8, ipaq_map[0].virt); - mymtd = do_map_probe("cfi_probe", &ipaq_map[0]); - if (!mymtd) - return -ENXIO; - mymtd->owner = THIS_MODULE; - } - - - /* - * Dynamic partition selection stuff (might override the static ones) - */ - - i = parse_mtd_partitions(mymtd, part_probes, &parsed_parts, 0); - - if (i > 0) { - nb_parts = parsed_nr_parts = i; - parts = parsed_parts; - part_type = "dynamic"; - } - - if (!parts) { - printk(KERN_NOTICE "IPAQ flash: no partition info available, registering whole flash at once\n"); - add_mtd_device(mymtd); -#ifndef CONFIG_MTD_CONCAT - if (my_sub_mtd[1]) - add_mtd_device(my_sub_mtd[1]); -#endif - } else { - printk(KERN_NOTICE "Using %s partition definition\n", part_type); - add_mtd_partitions(mymtd, parts, nb_parts); -#ifndef CONFIG_MTD_CONCAT - if (my_sub_mtd[1]) - add_mtd_partitions(my_sub_mtd[1], h3xxx_partitions_bank2, ARRAY_SIZE(h3xxx_partitions_bank2)); -#endif - } - - return 0; -} - -static void __exit ipaq_mtd_cleanup(void) -{ - int i; - - if (mymtd) { - del_mtd_partitions(mymtd); -#ifndef CONFIG_MTD_CONCAT - if (my_sub_mtd[1]) - del_mtd_partitions(my_sub_mtd[1]); -#endif - map_destroy(mymtd); -#ifdef CONFIG_MTD_CONCAT - for(i=0; i<MAX_IPAQ_CS; i++) -#else - for(i=1; i<MAX_IPAQ_CS; i++) -#endif - { - if (my_sub_mtd[i]) - map_destroy(my_sub_mtd[i]); - } - kfree(parsed_parts); - } -} - -static int __init h1900_special_case(void) -{ - /* The iPAQ h1900 is a special case - it has weird ROM. */ - simple_map_init(&ipaq_map[0]); - ipaq_map[0].size = 0x80000; - ipaq_map[0].set_vpp = h3xxx_set_vpp; - ipaq_map[0].phys = 0x0; - ipaq_map[0].virt = ioremap(0x0, 0x04000000); - ipaq_map[0].bankwidth = 2; - - printk(KERN_NOTICE "iPAQ flash: probing %d-bit flash bus, window=%lx with JEDEC.\n", ipaq_map[0].bankwidth*8, ipaq_map[0].virt); - mymtd = do_map_probe("jedec_probe", &ipaq_map[0]); - if (!mymtd) - return -ENODEV; - add_mtd_device(mymtd); - printk(KERN_NOTICE "iPAQ flash: registered h1910 flash\n"); - - return 0; -} - -module_init(ipaq_mtd_init); -module_exit(ipaq_mtd_cleanup); - -MODULE_AUTHOR("Jamey Hicks"); -MODULE_DESCRIPTION("IPAQ CFI map driver"); -MODULE_LICENSE("MIT"); diff --git a/drivers/mtd/maps/ixp2000.c b/drivers/mtd/maps/ixp2000.c deleted file mode 100644 index c26488a1793..00000000000 --- a/drivers/mtd/maps/ixp2000.c +++ /dev/null @@ -1,273 +0,0 @@ -/* - * $Id: ixp2000.c,v 1.9 2005/11/07 11:14:27 gleixner Exp $ - * - * drivers/mtd/maps/ixp2000.c - * - * Mapping for the Intel XScale IXP2000 based systems - * - * Copyright (C) 2002 Intel Corp. - * Copyright (C) 2003-2004 MontaVista Software, Inc. - * - * Original Author: Naeem M Afzal <naeem.m.afzal@intel.com> - * Maintainer: Deepak Saxena <dsaxena@plexity.net> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/init.h> -#include <linux/kernel.h> -#include <linux/string.h> -#include <linux/slab.h> -#include <linux/ioport.h> -#include <linux/device.h> -#include <linux/platform_device.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - -#include <asm/io.h> -#include <asm/hardware.h> -#include <asm/mach/flash.h> - -#include <linux/reboot.h> - -struct ixp2000_flash_info { - struct mtd_info *mtd; - struct map_info map; - struct mtd_partition *partitions; - struct resource *res; -}; - -static inline unsigned long flash_bank_setup(struct map_info *map, unsigned long ofs) -{ - unsigned long (*set_bank)(unsigned long) = - (unsigned long(*)(unsigned long))map->map_priv_2; - - return (set_bank ? set_bank(ofs) : ofs); -} - -#ifdef __ARMEB__ -/* - * Rev A0 and A1 of IXP2400 silicon have a broken addressing unit which - * causes the lower address bits to be XORed with 0x11 on 8 bit accesses - * and XORed with 0x10 on 16 bit accesses. See the spec update, erratum 44. - */ -static int erratum44_workaround = 0; - -static inline unsigned long address_fix8_write(unsigned long addr) -{ - if (erratum44_workaround) { - return (addr ^ 3); - } - return addr; -} -#else - -#define address_fix8_write(x) (x) -#endif - -static map_word ixp2000_flash_read8(struct map_info *map, unsigned long ofs) -{ - map_word val; - - val.x[0] = *((u8 *)(map->map_priv_1 + flash_bank_setup(map, ofs))); - return val; -} - -/* - * We can't use the standard memcpy due to the broken SlowPort - * address translation on rev A0 and A1 silicon and the fact that - * we have banked flash. - */ -static void ixp2000_flash_copy_from(struct map_info *map, void *to, - unsigned long from, ssize_t len) -{ - from = flash_bank_setup(map, from); - while(len--) - *(__u8 *) to++ = *(__u8 *)(map->map_priv_1 + from++); -} - -static void ixp2000_flash_write8(struct map_info *map, map_word d, unsigned long ofs) -{ - *(__u8 *) (address_fix8_write(map->map_priv_1 + - flash_bank_setup(map, ofs))) = d.x[0]; -} - -static void ixp2000_flash_copy_to(struct map_info *map, unsigned long to, - const void *from, ssize_t len) -{ - to = flash_bank_setup(map, to); - while(len--) { - unsigned long tmp = address_fix8_write(map->map_priv_1 + to++); - *(__u8 *)(tmp) = *(__u8 *)(from++); - } -} - - -static int ixp2000_flash_remove(struct platform_device *dev) -{ - struct flash_platform_data *plat = dev->dev.platform_data; - struct ixp2000_flash_info *info = platform_get_drvdata(dev); - - platform_set_drvdata(dev, NULL); - - if(!info) - return 0; - - if (info->mtd) { - del_mtd_partitions(info->mtd); - map_destroy(info->mtd); - } - if (info->map.map_priv_1) - iounmap((void *) info->map.map_priv_1); - - kfree(info->partitions); - - if (info->res) { - release_resource(info->res); - kfree(info->res); - } - - if (plat->exit) - plat->exit(); - - return 0; -} - - -static int ixp2000_flash_probe(struct platform_device *dev) -{ - static const char *probes[] = { "RedBoot", "cmdlinepart", NULL }; - struct ixp2000_flash_data *ixp_data = dev->dev.platform_data; - struct flash_platform_data *plat; - struct ixp2000_flash_info *info; - unsigned long window_size; - int err = -1; - - if (!ixp_data) - return -ENODEV; - - plat = ixp_data->platform_data; - if (!plat) - return -ENODEV; - - window_size = dev->resource->end - dev->resource->start + 1; - dev_info(&dev->dev, "Probe of IXP2000 flash(%d banks x %dMiB)\n", - ixp_data->nr_banks, ((u32)window_size >> 20)); - - if (plat->width != 1) { - dev_err(&dev->dev, "IXP2000 MTD map only supports 8-bit mode, asking for %d\n", - plat->width * 8); - return -EIO; - } - - info = kmalloc(sizeof(struct ixp2000_flash_info), GFP_KERNEL); - if(!info) { - err = -ENOMEM; - goto Error; - } - memzero(info, sizeof(struct ixp2000_flash_info)); - - platform_set_drvdata(dev, info); - - /* - * Tell the MTD layer we're not 1:1 mapped so that it does - * not attempt to do a direct access on us. - */ - info->map.phys = NO_XIP; - - info->map.size = ixp_data->nr_banks * window_size; - info->map.bankwidth = 1; - - /* - * map_priv_2 is used to store a ptr to to the bank_setup routine - */ - info->map.map_priv_2 = (unsigned long) ixp_data->bank_setup; - - info->map.name = dev->dev.bus_id; - info->map.read = ixp2000_flash_read8; - info->map.write = ixp2000_flash_write8; - info->map.copy_from = ixp2000_flash_copy_from; - info->map.copy_to = ixp2000_flash_copy_to; - - info->res = request_mem_region(dev->resource->start, - dev->resource->end - dev->resource->start + 1, - dev->dev.bus_id); - if (!info->res) { - dev_err(&dev->dev, "Could not reserve memory region\n"); - err = -ENOMEM; - goto Error; - } - - info->map.map_priv_1 = (unsigned long) ioremap(dev->resource->start, - dev->resource->end - dev->resource->start + 1); - if (!info->map.map_priv_1) { - dev_err(&dev->dev, "Failed to ioremap flash region\n"); - err = -EIO; - goto Error; - } - -#if defined(__ARMEB__) - /* - * Enable erratum 44 workaround for NPUs with broken slowport - */ - - erratum44_workaround = ixp2000_has_broken_slowport(); - dev_info(&dev->dev, "Erratum 44 workaround %s\n", - erratum44_workaround ? "enabled" : "disabled"); -#endif - - info->mtd = do_map_probe(plat->map_name, &info->map); - if (!info->mtd) { - dev_err(&dev->dev, "map_probe failed\n"); - err = -ENXIO; - goto Error; - } - info->mtd->owner = THIS_MODULE; - - err = parse_mtd_partitions(info->mtd, probes, &info->partitions, 0); - if (err > 0) { - err = add_mtd_partitions(info->mtd, info->partitions, err); - if(err) - dev_err(&dev->dev, "Could not parse partitions\n"); - } - - if (err) - goto Error; - - return 0; - -Error: - ixp2000_flash_remove(dev); - return err; -} - -static struct platform_driver ixp2000_flash_driver = { - .probe = ixp2000_flash_probe, - .remove = ixp2000_flash_remove, - .driver = { - .name = "IXP2000-Flash", - }, -}; - -static int __init ixp2000_flash_init(void) -{ - return platform_driver_register(&ixp2000_flash_driver); -} - -static void __exit ixp2000_flash_exit(void) -{ - platform_driver_unregister(&ixp2000_flash_driver); -} - -module_init(ixp2000_flash_init); -module_exit(ixp2000_flash_exit); -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net>"); - diff --git a/drivers/mtd/maps/ixp4xx.c b/drivers/mtd/maps/ixp4xx.c index 7a828e3e644..6a589f1e288 100644 --- a/drivers/mtd/maps/ixp4xx.c +++ b/drivers/mtd/maps/ixp4xx.c @@ -1,6 +1,4 @@ /* - * $Id: ixp4xx.c,v 1.13 2005/11/16 16:23:21 dvrabel Exp $ - * * drivers/mtd/maps/ixp4xx.c * * MTD Map file for IXP4XX based systems. Please do not make per-board @@ -15,9 +13,9 @@ * */ +#include <linux/err.h> #include <linux/module.h> #include <linux/types.h> -#include <linux/init.h> #include <linux/kernel.h> #include <linux/string.h> #include <linux/slab.h> @@ -109,8 +107,8 @@ static void ixp4xx_copy_from(struct map_info *map, void *to, return; if (from & 1) { - *dest++ = BYTE1(flash_read16(src)); - src++; + *dest++ = BYTE1(flash_read16(src-1)); + src++; --len; } @@ -120,7 +118,7 @@ static void ixp4xx_copy_from(struct map_info *map, void *to, *dest++ = BYTE1(data); src += 2; len -= 2; - } + } if (len > 0) *dest++ = BYTE0(flash_read16(src)); @@ -147,35 +145,23 @@ static void ixp4xx_write16(struct map_info *map, map_word d, unsigned long adr) struct ixp4xx_flash_info { struct mtd_info *mtd; struct map_info map; - struct mtd_partition *partitions; struct resource *res; }; -static const char *probes[] = { "RedBoot", "cmdlinepart", NULL }; +static const char * const probes[] = { "RedBoot", "cmdlinepart", NULL }; static int ixp4xx_flash_remove(struct platform_device *dev) { - struct flash_platform_data *plat = dev->dev.platform_data; + struct flash_platform_data *plat = dev_get_platdata(&dev->dev); struct ixp4xx_flash_info *info = platform_get_drvdata(dev); - platform_set_drvdata(dev, NULL); - if(!info) return 0; if (info->mtd) { - del_mtd_partitions(info->mtd); + mtd_device_unregister(info->mtd); map_destroy(info->mtd); } - if (info->map.virt) - iounmap(info->map.virt); - - kfree(info->partitions); - - if (info->res) { - release_resource(info->res); - kfree(info->res); - } if (plat->exit) plat->exit(); @@ -185,8 +171,11 @@ static int ixp4xx_flash_remove(struct platform_device *dev) static int ixp4xx_flash_probe(struct platform_device *dev) { - struct flash_platform_data *plat = dev->dev.platform_data; + struct flash_platform_data *plat = dev_get_platdata(&dev->dev); struct ixp4xx_flash_info *info; + struct mtd_part_parser_data ppdata = { + .origin = dev->resource->start, + }; int err = -1; if (!plat) @@ -198,12 +187,12 @@ static int ixp4xx_flash_probe(struct platform_device *dev) return err; } - info = kmalloc(sizeof(struct ixp4xx_flash_info), GFP_KERNEL); + info = devm_kzalloc(&dev->dev, sizeof(struct ixp4xx_flash_info), + GFP_KERNEL); if(!info) { err = -ENOMEM; goto Error; } - memzero(info, sizeof(struct ixp4xx_flash_info)); platform_set_drvdata(dev, info); @@ -212,7 +201,7 @@ static int ixp4xx_flash_probe(struct platform_device *dev) * not attempt to do a direct access on us. */ info->map.phys = NO_XIP; - info->map.size = dev->resource->end - dev->resource->start + 1; + info->map.size = resource_size(dev->resource); /* * We only support 16-bit accesses for now. If and when @@ -220,25 +209,14 @@ static int ixp4xx_flash_probe(struct platform_device *dev) * handle that. */ info->map.bankwidth = 2; - info->map.name = dev->dev.bus_id; - info->map.read = ixp4xx_read16, - info->map.write = ixp4xx_probe_write16, - info->map.copy_from = ixp4xx_copy_from, - - info->res = request_mem_region(dev->resource->start, - dev->resource->end - dev->resource->start + 1, - "IXP4XXFlash"); - if (!info->res) { - printk(KERN_ERR "IXP4XXFlash: Could not reserve memory region\n"); - err = -ENOMEM; - goto Error; - } - - info->map.virt = ioremap(dev->resource->start, - dev->resource->end - dev->resource->start + 1); - if (!info->map.virt) { - printk(KERN_ERR "IXP4XXFlash: Failed to ioremap region\n"); - err = -EIO; + info->map.name = dev_name(&dev->dev); + info->map.read = ixp4xx_read16; + info->map.write = ixp4xx_probe_write16; + info->map.copy_from = ixp4xx_copy_from; + + info->map.virt = devm_ioremap_resource(&dev->dev, dev->resource); + if (IS_ERR(info->map.virt)) { + err = PTR_ERR(info->map.virt); goto Error; } @@ -251,17 +229,14 @@ static int ixp4xx_flash_probe(struct platform_device *dev) info->mtd->owner = THIS_MODULE; /* Use the fast version */ - info->map.write = ixp4xx_write16, + info->map.write = ixp4xx_write16; - err = parse_mtd_partitions(info->mtd, probes, &info->partitions, dev->resource->start); - if (err > 0) { - err = add_mtd_partitions(info->mtd, info->partitions, err); - if(err) - printk(KERN_ERR "Could not parse partitions\n"); - } - - if (err) + err = mtd_device_parse_register(info->mtd, probes, &ppdata, + plat->parts, plat->nr_parts); + if (err) { + printk(KERN_ERR "Could not parse partitions\n"); goto Error; + } return 0; @@ -275,23 +250,13 @@ static struct platform_driver ixp4xx_flash_driver = { .remove = ixp4xx_flash_remove, .driver = { .name = "IXP4XX-Flash", + .owner = THIS_MODULE, }, }; -static int __init ixp4xx_flash_init(void) -{ - return platform_driver_register(&ixp4xx_flash_driver); -} - -static void __exit ixp4xx_flash_exit(void) -{ - platform_driver_unregister(&ixp4xx_flash_driver); -} - - -module_init(ixp4xx_flash_init); -module_exit(ixp4xx_flash_exit); +module_platform_driver(ixp4xx_flash_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("MTD map driver for Intel IXP4xx systems"); MODULE_AUTHOR("Deepak Saxena"); +MODULE_ALIAS("platform:IXP4XX-Flash"); diff --git a/drivers/mtd/maps/l440gx.c b/drivers/mtd/maps/l440gx.c index 67620adf481..74bd98ee635 100644 --- a/drivers/mtd/maps/l440gx.c +++ b/drivers/mtd/maps/l440gx.c @@ -1,6 +1,4 @@ /* - * $Id: l440gx.c,v 1.18 2005/11/07 11:14:27 gleixner Exp $ - * * BIOS Flash chip on Intel 440GX board. * * Bugs this currently does not work under linuxBIOS. @@ -29,17 +27,21 @@ static struct mtd_info *mymtd; /* Is this really the vpp port? */ +static DEFINE_SPINLOCK(l440gx_vpp_lock); +static int l440gx_vpp_refcnt; static void l440gx_set_vpp(struct map_info *map, int vpp) { - unsigned long l; + unsigned long flags; - l = inl(VPP_PORT); + spin_lock_irqsave(&l440gx_vpp_lock, flags); if (vpp) { - l |= 1; + if (++l440gx_vpp_refcnt == 1) /* first nested 'on' */ + outl(inl(VPP_PORT) | 1, VPP_PORT); } else { - l &= ~1; + if (--l440gx_vpp_refcnt == 0) /* last nested 'off' */ + outl(inl(VPP_PORT) & ~1, VPP_PORT); } - outl(l, VPP_PORT); + spin_unlock_irqrestore(&l440gx_vpp_lock, flags); } static struct map_info l440gx_map = { @@ -140,7 +142,7 @@ static int __init init_l440gx(void) if (mymtd) { mymtd->owner = THIS_MODULE; - add_mtd_device(mymtd); + mtd_device_register(mymtd, NULL, 0); return 0; } @@ -150,7 +152,7 @@ static int __init init_l440gx(void) static void __exit cleanup_l440gx(void) { - del_mtd_device(mymtd); + mtd_device_unregister(mymtd); map_destroy(mymtd); iounmap(l440gx_map.virt); diff --git a/drivers/mtd/maps/lantiq-flash.c b/drivers/mtd/maps/lantiq-flash.c new file mode 100644 index 00000000000..7aa682cd4d7 --- /dev/null +++ b/drivers/mtd/maps/lantiq-flash.c @@ -0,0 +1,217 @@ +/* + * 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) 2004 Liu Peng Infineon IFAP DC COM CPE + * Copyright (C) 2010 John Crispin <blogic@openwrt.org> + */ + +#include <linux/err.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/cfi.h> +#include <linux/platform_device.h> +#include <linux/mtd/physmap.h> +#include <linux/of.h> + +#include <lantiq_soc.h> + +/* + * The NOR flash is connected to the same external bus unit (EBU) as PCI. + * To make PCI work we need to enable the endianness swapping for the address + * written to the EBU. This endianness swapping works for PCI correctly but + * fails for attached NOR devices. To workaround this we need to use a complex + * map. The workaround involves swapping all addresses whilst probing the chip. + * Once probing is complete we stop swapping the addresses but swizzle the + * unlock addresses to ensure that access to the NOR device works correctly. + */ + +enum { + LTQ_NOR_PROBING, + LTQ_NOR_NORMAL +}; + +struct ltq_mtd { + struct resource *res; + struct mtd_info *mtd; + struct map_info *map; +}; + +static const char ltq_map_name[] = "ltq_nor"; +static const char * const ltq_probe_types[] = { "cmdlinepart", "ofpart", NULL }; + +static map_word +ltq_read16(struct map_info *map, unsigned long adr) +{ + unsigned long flags; + map_word temp; + + if (map->map_priv_1 == LTQ_NOR_PROBING) + adr ^= 2; + spin_lock_irqsave(&ebu_lock, flags); + temp.x[0] = *(u16 *)(map->virt + adr); + spin_unlock_irqrestore(&ebu_lock, flags); + return temp; +} + +static void +ltq_write16(struct map_info *map, map_word d, unsigned long adr) +{ + unsigned long flags; + + if (map->map_priv_1 == LTQ_NOR_PROBING) + adr ^= 2; + spin_lock_irqsave(&ebu_lock, flags); + *(u16 *)(map->virt + adr) = d.x[0]; + spin_unlock_irqrestore(&ebu_lock, flags); +} + +/* + * The following 2 functions copy data between iomem and a cached memory + * section. As memcpy() makes use of pre-fetching we cannot use it here. + * The normal alternative of using memcpy_{to,from}io also makes use of + * memcpy() on MIPS so it is not applicable either. We are therefore stuck + * with having to use our own loop. + */ +static void +ltq_copy_from(struct map_info *map, void *to, + unsigned long from, ssize_t len) +{ + unsigned char *f = (unsigned char *)map->virt + from; + unsigned char *t = (unsigned char *)to; + unsigned long flags; + + spin_lock_irqsave(&ebu_lock, flags); + while (len--) + *t++ = *f++; + spin_unlock_irqrestore(&ebu_lock, flags); +} + +static void +ltq_copy_to(struct map_info *map, unsigned long to, + const void *from, ssize_t len) +{ + unsigned char *f = (unsigned char *)from; + unsigned char *t = (unsigned char *)map->virt + to; + unsigned long flags; + + spin_lock_irqsave(&ebu_lock, flags); + while (len--) + *t++ = *f++; + spin_unlock_irqrestore(&ebu_lock, flags); +} + +static int +ltq_mtd_probe(struct platform_device *pdev) +{ + struct mtd_part_parser_data ppdata; + struct ltq_mtd *ltq_mtd; + struct cfi_private *cfi; + int err; + + if (of_machine_is_compatible("lantiq,falcon") && + (ltq_boot_select() != BS_FLASH)) { + dev_err(&pdev->dev, "invalid bootstrap options\n"); + return -ENODEV; + } + + ltq_mtd = devm_kzalloc(&pdev->dev, sizeof(struct ltq_mtd), GFP_KERNEL); + if (!ltq_mtd) + return -ENOMEM; + + platform_set_drvdata(pdev, ltq_mtd); + + ltq_mtd->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!ltq_mtd->res) { + dev_err(&pdev->dev, "failed to get memory resource\n"); + return -ENOENT; + } + + ltq_mtd->map = devm_kzalloc(&pdev->dev, sizeof(struct map_info), + GFP_KERNEL); + if (!ltq_mtd->map) + return -ENOMEM; + + ltq_mtd->map->phys = ltq_mtd->res->start; + ltq_mtd->map->size = resource_size(ltq_mtd->res); + ltq_mtd->map->virt = devm_ioremap_resource(&pdev->dev, ltq_mtd->res); + if (IS_ERR(ltq_mtd->map->virt)) + return PTR_ERR(ltq_mtd->map->virt); + + ltq_mtd->map->name = ltq_map_name; + ltq_mtd->map->bankwidth = 2; + ltq_mtd->map->read = ltq_read16; + ltq_mtd->map->write = ltq_write16; + ltq_mtd->map->copy_from = ltq_copy_from; + ltq_mtd->map->copy_to = ltq_copy_to; + + ltq_mtd->map->map_priv_1 = LTQ_NOR_PROBING; + ltq_mtd->mtd = do_map_probe("cfi_probe", ltq_mtd->map); + ltq_mtd->map->map_priv_1 = LTQ_NOR_NORMAL; + + if (!ltq_mtd->mtd) { + dev_err(&pdev->dev, "probing failed\n"); + return -ENXIO; + } + + ltq_mtd->mtd->owner = THIS_MODULE; + + cfi = ltq_mtd->map->fldrv_priv; + cfi->addr_unlock1 ^= 1; + cfi->addr_unlock2 ^= 1; + + ppdata.of_node = pdev->dev.of_node; + err = mtd_device_parse_register(ltq_mtd->mtd, ltq_probe_types, + &ppdata, NULL, 0); + if (err) { + dev_err(&pdev->dev, "failed to add partitions\n"); + goto err_destroy; + } + + return 0; + +err_destroy: + map_destroy(ltq_mtd->mtd); + return err; +} + +static int +ltq_mtd_remove(struct platform_device *pdev) +{ + struct ltq_mtd *ltq_mtd = platform_get_drvdata(pdev); + + if (ltq_mtd && ltq_mtd->mtd) { + mtd_device_unregister(ltq_mtd->mtd); + map_destroy(ltq_mtd->mtd); + } + return 0; +} + +static const struct of_device_id ltq_mtd_match[] = { + { .compatible = "lantiq,nor" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ltq_mtd_match); + +static struct platform_driver ltq_mtd_driver = { + .probe = ltq_mtd_probe, + .remove = ltq_mtd_remove, + .driver = { + .name = "ltq-nor", + .owner = THIS_MODULE, + .of_match_table = ltq_mtd_match, + }, +}; + +module_platform_driver(ltq_mtd_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("John Crispin <blogic@openwrt.org>"); +MODULE_DESCRIPTION("Lantiq SoC NOR"); diff --git a/drivers/mtd/maps/lasat.c b/drivers/mtd/maps/lasat.c deleted file mode 100644 index e3437632105..00000000000 --- a/drivers/mtd/maps/lasat.c +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Flash device on Lasat 100 and 200 boards - * - * (C) 2002 Brian Murphy <brian@murphy.dk> - * - * 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. - * - * $Id: lasat.c,v 1.9 2004/11/04 13:24:15 gleixner Exp $ - * - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <asm/io.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> -#include <asm/lasat/lasat.h> - -static struct mtd_info *lasat_mtd; - -static struct mtd_partition partition_info[LASAT_MTD_LAST]; -static char *lasat_mtd_partnames[] = {"Bootloader", "Service", "Normal", "Filesystem", "Config"}; - -static void lasat_set_vpp(struct map_info *map, int vpp) -{ - if (vpp) - *lasat_misc->flash_wp_reg |= 1 << lasat_misc->flash_wp_bit; - else - *lasat_misc->flash_wp_reg &= ~(1 << lasat_misc->flash_wp_bit); -} - -static struct map_info lasat_map = { - .name = "LASAT flash", - .bankwidth = 4, - .set_vpp = lasat_set_vpp -}; - -static int __init init_lasat(void) -{ - int i; - /* since we use AMD chips and set_vpp is not implimented - * for these (yet) we still have to permanently enable flash write */ - printk(KERN_NOTICE "Unprotecting flash\n"); - ENABLE_VPP((&lasat_map)); - - lasat_map.phys = lasat_flash_partition_start(LASAT_MTD_BOOTLOADER); - lasat_map.virt = ioremap_nocache( - lasat_map.phys, lasat_board_info.li_flash_size); - lasat_map.size = lasat_board_info.li_flash_size; - - simple_map_init(&lasat_map); - - for (i=0; i < LASAT_MTD_LAST; i++) - partition_info[i].name = lasat_mtd_partnames[i]; - - lasat_mtd = do_map_probe("cfi_probe", &lasat_map); - - if (!lasat_mtd) - lasat_mtd = do_map_probe("jedec_probe", &lasat_map); - - if (lasat_mtd) { - u32 size, offset = 0; - - lasat_mtd->owner = THIS_MODULE; - - for (i=0; i < LASAT_MTD_LAST; i++) { - size = lasat_flash_partition_size(i); - partition_info[i].size = size; - partition_info[i].offset = offset; - offset += size; - } - - add_mtd_partitions( lasat_mtd, partition_info, LASAT_MTD_LAST ); - return 0; - } - - iounmap(lasat_map.virt); - return -ENXIO; -} - -static void __exit cleanup_lasat(void) -{ - if (lasat_mtd) { - del_mtd_partitions(lasat_mtd); - map_destroy(lasat_mtd); - } - if (lasat_map.virt) { - iounmap(lasat_map.virt); - lasat_map.virt = 0; - } -} - -module_init(init_lasat); -module_exit(cleanup_lasat); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Brian Murphy <brian@murphy.dk>"); -MODULE_DESCRIPTION("Lasat Safepipe/Masquerade MTD map driver"); diff --git a/drivers/mtd/maps/latch-addr-flash.c b/drivers/mtd/maps/latch-addr-flash.c new file mode 100644 index 00000000000..cadfbe05187 --- /dev/null +++ b/drivers/mtd/maps/latch-addr-flash.c @@ -0,0 +1,230 @@ +/* + * Interface for NOR flash driver whose high address lines are latched + * + * Copyright © 2000 Nicolas Pitre <nico@cam.org> + * Copyright © 2005-2008 Analog Devices Inc. + * Copyright © 2008 MontaVista Software, Inc. <source@mvista.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> +#include <linux/platform_device.h> +#include <linux/mtd/latch-addr-flash.h> +#include <linux/slab.h> + +#define DRIVER_NAME "latch-addr-flash" + +struct latch_addr_flash_info { + struct mtd_info *mtd; + struct map_info map; + struct resource *res; + + void (*set_window)(unsigned long offset, void *data); + void *data; + + /* cache; could be found out of res */ + unsigned long win_mask; + + spinlock_t lock; +}; + +static map_word lf_read(struct map_info *map, unsigned long ofs) +{ + struct latch_addr_flash_info *info; + map_word datum; + + info = (struct latch_addr_flash_info *)map->map_priv_1; + + spin_lock(&info->lock); + + info->set_window(ofs, info->data); + datum = inline_map_read(map, info->win_mask & ofs); + + spin_unlock(&info->lock); + + return datum; +} + +static void lf_write(struct map_info *map, map_word datum, unsigned long ofs) +{ + struct latch_addr_flash_info *info; + + info = (struct latch_addr_flash_info *)map->map_priv_1; + + spin_lock(&info->lock); + + info->set_window(ofs, info->data); + inline_map_write(map, datum, info->win_mask & ofs); + + spin_unlock(&info->lock); +} + +static void lf_copy_from(struct map_info *map, void *to, + unsigned long from, ssize_t len) +{ + struct latch_addr_flash_info *info = + (struct latch_addr_flash_info *) map->map_priv_1; + unsigned n; + + while (len > 0) { + n = info->win_mask + 1 - (from & info->win_mask); + if (n > len) + n = len; + + spin_lock(&info->lock); + + info->set_window(from, info->data); + memcpy_fromio(to, map->virt + (from & info->win_mask), n); + + spin_unlock(&info->lock); + + to += n; + from += n; + len -= n; + } +} + +static char *rom_probe_types[] = { "cfi_probe", NULL }; + +static int latch_addr_flash_remove(struct platform_device *dev) +{ + struct latch_addr_flash_info *info; + struct latch_addr_flash_data *latch_addr_data; + + info = platform_get_drvdata(dev); + if (info == NULL) + return 0; + + latch_addr_data = dev_get_platdata(&dev->dev); + + if (info->mtd != NULL) { + mtd_device_unregister(info->mtd); + map_destroy(info->mtd); + } + + if (info->map.virt != NULL) + iounmap(info->map.virt); + + if (info->res != NULL) + release_mem_region(info->res->start, resource_size(info->res)); + + kfree(info); + + if (latch_addr_data->done) + latch_addr_data->done(latch_addr_data->data); + + return 0; +} + +static int latch_addr_flash_probe(struct platform_device *dev) +{ + struct latch_addr_flash_data *latch_addr_data; + struct latch_addr_flash_info *info; + resource_size_t win_base = dev->resource->start; + resource_size_t win_size = resource_size(dev->resource); + char **probe_type; + int chipsel; + int err; + + latch_addr_data = dev_get_platdata(&dev->dev); + if (latch_addr_data == NULL) + return -ENODEV; + + pr_notice("latch-addr platform flash device: %#llx byte " + "window at %#.8llx\n", + (unsigned long long)win_size, (unsigned long long)win_base); + + chipsel = dev->id; + + if (latch_addr_data->init) { + err = latch_addr_data->init(latch_addr_data->data, chipsel); + if (err != 0) + return err; + } + + info = kzalloc(sizeof(struct latch_addr_flash_info), GFP_KERNEL); + if (info == NULL) { + err = -ENOMEM; + goto done; + } + + platform_set_drvdata(dev, info); + + info->res = request_mem_region(win_base, win_size, DRIVER_NAME); + if (info->res == NULL) { + dev_err(&dev->dev, "Could not reserve memory region\n"); + err = -EBUSY; + goto free_info; + } + + info->map.name = DRIVER_NAME; + info->map.size = latch_addr_data->size; + info->map.bankwidth = latch_addr_data->width; + + info->map.phys = NO_XIP; + info->map.virt = ioremap(win_base, win_size); + if (!info->map.virt) { + err = -ENOMEM; + goto free_res; + } + + info->map.map_priv_1 = (unsigned long)info; + + info->map.read = lf_read; + info->map.copy_from = lf_copy_from; + info->map.write = lf_write; + info->set_window = latch_addr_data->set_window; + info->data = latch_addr_data->data; + info->win_mask = win_size - 1; + + spin_lock_init(&info->lock); + + for (probe_type = rom_probe_types; !info->mtd && *probe_type; + probe_type++) + info->mtd = do_map_probe(*probe_type, &info->map); + + if (info->mtd == NULL) { + dev_err(&dev->dev, "map_probe failed\n"); + err = -ENODEV; + goto iounmap; + } + info->mtd->owner = THIS_MODULE; + + mtd_device_parse_register(info->mtd, NULL, NULL, + latch_addr_data->parts, + latch_addr_data->nr_parts); + return 0; + +iounmap: + iounmap(info->map.virt); +free_res: + release_mem_region(info->res->start, resource_size(info->res)); +free_info: + kfree(info); +done: + if (latch_addr_data->done) + latch_addr_data->done(latch_addr_data->data); + return err; +} + +static struct platform_driver latch_addr_flash_driver = { + .probe = latch_addr_flash_probe, + .remove = latch_addr_flash_remove, + .driver = { + .name = DRIVER_NAME, + }, +}; + +module_platform_driver(latch_addr_flash_driver); + +MODULE_AUTHOR("David Griego <dgriego@mvista.com>"); +MODULE_DESCRIPTION("MTD map driver for flashes addressed physically with upper " + "address lines being set board specifically"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/maps/lubbock-flash.c b/drivers/mtd/maps/lubbock-flash.c deleted file mode 100644 index 1aa0447c5e6..00000000000 --- a/drivers/mtd/maps/lubbock-flash.c +++ /dev/null @@ -1,170 +0,0 @@ -/* - * $Id: lubbock-flash.c,v 1.21 2005/11/07 11:14:27 gleixner Exp $ - * - * Map driver for the Lubbock developer platform. - * - * Author: Nicolas Pitre - * Copyright: (C) 2001 MontaVista Software Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <linux/slab.h> - -#include <linux/dma-mapping.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - -#include <asm/io.h> -#include <asm/hardware.h> -#include <asm/arch/pxa-regs.h> -#include <asm/arch/lubbock.h> - - -#define ROM_ADDR 0x00000000 -#define FLASH_ADDR 0x04000000 - -#define WINDOW_SIZE 64*1024*1024 - -static void lubbock_map_inval_cache(struct map_info *map, unsigned long from, ssize_t len) -{ - consistent_sync((char *)map->cached + from, len, DMA_FROM_DEVICE); -} - -static struct map_info lubbock_maps[2] = { { - .size = WINDOW_SIZE, - .phys = 0x00000000, - .inval_cache = lubbock_map_inval_cache, -}, { - .size = WINDOW_SIZE, - .phys = 0x04000000, - .inval_cache = lubbock_map_inval_cache, -} }; - -static struct mtd_partition lubbock_partitions[] = { - { - .name = "Bootloader", - .size = 0x00040000, - .offset = 0, - .mask_flags = MTD_WRITEABLE /* force read-only */ - },{ - .name = "Kernel", - .size = 0x00100000, - .offset = 0x00040000, - },{ - .name = "Filesystem", - .size = MTDPART_SIZ_FULL, - .offset = 0x00140000 - } -}; - -static struct mtd_info *mymtds[2]; -static struct mtd_partition *parsed_parts[2]; -static int nr_parsed_parts[2]; - -static const char *probes[] = { "RedBoot", "cmdlinepart", NULL }; - -static int __init init_lubbock(void) -{ - int flashboot = (LUB_CONF_SWITCHES & 1); - int ret = 0, i; - - lubbock_maps[0].bankwidth = lubbock_maps[1].bankwidth = - (BOOT_DEF & 1) ? 2 : 4; - - /* Compensate for the nROMBT switch which swaps the flash banks */ - printk(KERN_NOTICE "Lubbock configured to boot from %s (bank %d)\n", - flashboot?"Flash":"ROM", flashboot); - - lubbock_maps[flashboot^1].name = "Lubbock Application Flash"; - lubbock_maps[flashboot].name = "Lubbock Boot ROM"; - - for (i = 0; i < 2; i++) { - lubbock_maps[i].virt = ioremap(lubbock_maps[i].phys, WINDOW_SIZE); - if (!lubbock_maps[i].virt) { - printk(KERN_WARNING "Failed to ioremap %s\n", lubbock_maps[i].name); - if (!ret) - ret = -ENOMEM; - continue; - } - lubbock_maps[i].cached = ioremap_cached(lubbock_maps[i].phys, WINDOW_SIZE); - if (!lubbock_maps[i].cached) - printk(KERN_WARNING "Failed to ioremap cached %s\n", lubbock_maps[i].name); - simple_map_init(&lubbock_maps[i]); - - printk(KERN_NOTICE "Probing %s at physical address 0x%08lx (%d-bit bankwidth)\n", - lubbock_maps[i].name, lubbock_maps[i].phys, - lubbock_maps[i].bankwidth * 8); - - mymtds[i] = do_map_probe("cfi_probe", &lubbock_maps[i]); - - if (!mymtds[i]) { - iounmap((void *)lubbock_maps[i].virt); - if (lubbock_maps[i].cached) - iounmap(lubbock_maps[i].cached); - if (!ret) - ret = -EIO; - continue; - } - mymtds[i]->owner = THIS_MODULE; - - ret = parse_mtd_partitions(mymtds[i], probes, - &parsed_parts[i], 0); - - if (ret > 0) - nr_parsed_parts[i] = ret; - } - - if (!mymtds[0] && !mymtds[1]) - return ret; - - for (i = 0; i < 2; i++) { - if (!mymtds[i]) { - printk(KERN_WARNING "%s is absent. Skipping\n", lubbock_maps[i].name); - } else if (nr_parsed_parts[i]) { - add_mtd_partitions(mymtds[i], parsed_parts[i], nr_parsed_parts[i]); - } else if (!i) { - printk("Using static partitions on %s\n", lubbock_maps[i].name); - add_mtd_partitions(mymtds[i], lubbock_partitions, ARRAY_SIZE(lubbock_partitions)); - } else { - printk("Registering %s as whole device\n", lubbock_maps[i].name); - add_mtd_device(mymtds[i]); - } - } - return 0; -} - -static void __exit cleanup_lubbock(void) -{ - int i; - for (i = 0; i < 2; i++) { - if (!mymtds[i]) - continue; - - if (nr_parsed_parts[i] || !i) - del_mtd_partitions(mymtds[i]); - else - del_mtd_device(mymtds[i]); - - map_destroy(mymtds[i]); - iounmap((void *)lubbock_maps[i].virt); - if (lubbock_maps[i].cached) - iounmap(lubbock_maps[i].cached); - - kfree(parsed_parts[i]); - } -} - -module_init(init_lubbock); -module_exit(cleanup_lubbock); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Nicolas Pitre <nico@cam.org>"); -MODULE_DESCRIPTION("MTD map driver for Intel Lubbock"); diff --git a/drivers/mtd/maps/mainstone-flash.c b/drivers/mtd/maps/mainstone-flash.c deleted file mode 100644 index eaa4bbb868a..00000000000 --- a/drivers/mtd/maps/mainstone-flash.c +++ /dev/null @@ -1,181 +0,0 @@ -/* - * $Id: $ - * - * Map driver for the Mainstone developer platform. - * - * Author: Nicolas Pitre - * Copyright: (C) 2001 MontaVista Software Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <linux/dma-mapping.h> -#include <linux/slab.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - -#include <asm/io.h> -#include <asm/hardware.h> -#include <asm/arch/pxa-regs.h> -#include <asm/arch/mainstone.h> - - -#define ROM_ADDR 0x00000000 -#define FLASH_ADDR 0x04000000 - -#define WINDOW_SIZE 0x04000000 - -static void mainstone_map_inval_cache(struct map_info *map, unsigned long from, - ssize_t len) -{ - consistent_sync((char *)map->cached + from, len, DMA_FROM_DEVICE); -} - -static struct map_info mainstone_maps[2] = { { - .size = WINDOW_SIZE, - .phys = PXA_CS0_PHYS, - .inval_cache = mainstone_map_inval_cache, -}, { - .size = WINDOW_SIZE, - .phys = PXA_CS1_PHYS, - .inval_cache = mainstone_map_inval_cache, -} }; - -static struct mtd_partition mainstone_partitions[] = { - { - .name = "Bootloader", - .size = 0x00040000, - .offset = 0, - .mask_flags = MTD_WRITEABLE /* force read-only */ - },{ - .name = "Kernel", - .size = 0x00400000, - .offset = 0x00040000, - },{ - .name = "Filesystem", - .size = MTDPART_SIZ_FULL, - .offset = 0x00440000 - } -}; - -static struct mtd_info *mymtds[2]; -static struct mtd_partition *parsed_parts[2]; -static int nr_parsed_parts[2]; - -static const char *probes[] = { "RedBoot", "cmdlinepart", NULL }; - -static int __init init_mainstone(void) -{ - int SW7 = 0; /* FIXME: get from SCR (Mst doc section 3.2.1.1) */ - int ret = 0, i; - - mainstone_maps[0].bankwidth = (BOOT_DEF & 1) ? 2 : 4; - mainstone_maps[1].bankwidth = 4; - - /* Compensate for SW7 which swaps the flash banks */ - mainstone_maps[SW7].name = "processor flash"; - mainstone_maps[SW7 ^ 1].name = "main board flash"; - - printk(KERN_NOTICE "Mainstone configured to boot from %s\n", - mainstone_maps[0].name); - - for (i = 0; i < 2; i++) { - mainstone_maps[i].virt = ioremap(mainstone_maps[i].phys, - WINDOW_SIZE); - if (!mainstone_maps[i].virt) { - printk(KERN_WARNING "Failed to ioremap %s\n", - mainstone_maps[i].name); - if (!ret) - ret = -ENOMEM; - continue; - } - mainstone_maps[i].cached = - ioremap_cached(mainstone_maps[i].phys, WINDOW_SIZE); - if (!mainstone_maps[i].cached) - printk(KERN_WARNING "Failed to ioremap cached %s\n", - mainstone_maps[i].name); - simple_map_init(&mainstone_maps[i]); - - printk(KERN_NOTICE - "Probing %s at physical address 0x%08lx" - " (%d-bit bankwidth)\n", - mainstone_maps[i].name, mainstone_maps[i].phys, - mainstone_maps[i].bankwidth * 8); - - mymtds[i] = do_map_probe("cfi_probe", &mainstone_maps[i]); - - if (!mymtds[i]) { - iounmap((void *)mainstone_maps[i].virt); - if (mainstone_maps[i].cached) - iounmap(mainstone_maps[i].cached); - if (!ret) - ret = -EIO; - continue; - } - mymtds[i]->owner = THIS_MODULE; - - ret = parse_mtd_partitions(mymtds[i], probes, - &parsed_parts[i], 0); - - if (ret > 0) - nr_parsed_parts[i] = ret; - } - - if (!mymtds[0] && !mymtds[1]) - return ret; - - for (i = 0; i < 2; i++) { - if (!mymtds[i]) { - printk(KERN_WARNING "%s is absent. Skipping\n", - mainstone_maps[i].name); - } else if (nr_parsed_parts[i]) { - add_mtd_partitions(mymtds[i], parsed_parts[i], - nr_parsed_parts[i]); - } else if (!i) { - printk("Using static partitions on %s\n", - mainstone_maps[i].name); - add_mtd_partitions(mymtds[i], mainstone_partitions, - ARRAY_SIZE(mainstone_partitions)); - } else { - printk("Registering %s as whole device\n", - mainstone_maps[i].name); - add_mtd_device(mymtds[i]); - } - } - return 0; -} - -static void __exit cleanup_mainstone(void) -{ - int i; - for (i = 0; i < 2; i++) { - if (!mymtds[i]) - continue; - - if (nr_parsed_parts[i] || !i) - del_mtd_partitions(mymtds[i]); - else - del_mtd_device(mymtds[i]); - - map_destroy(mymtds[i]); - iounmap((void *)mainstone_maps[i].virt); - if (mainstone_maps[i].cached) - iounmap(mainstone_maps[i].cached); - kfree(parsed_parts[i]); - } -} - -module_init(init_mainstone); -module_exit(cleanup_mainstone); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Nicolas Pitre <nico@cam.org>"); -MODULE_DESCRIPTION("MTD map driver for Intel Mainstone"); diff --git a/drivers/mtd/maps/map_funcs.c b/drivers/mtd/maps/map_funcs.c index 9105e6ca0aa..3f268370eec 100644 --- a/drivers/mtd/maps/map_funcs.c +++ b/drivers/mtd/maps/map_funcs.c @@ -1,6 +1,4 @@ /* - * $Id: map_funcs.c,v 1.10 2005/06/06 23:04:36 tpoynor Exp $ - * * Out-of-line map I/O functions for simple maps when CONFIG_COMPLEX_MAPPINGS * is enabled. */ diff --git a/drivers/mtd/maps/mbx860.c b/drivers/mtd/maps/mbx860.c deleted file mode 100644 index 06b11872784..00000000000 --- a/drivers/mtd/maps/mbx860.c +++ /dev/null @@ -1,100 +0,0 @@ -/* - * $Id: mbx860.c,v 1.9 2005/11/07 11:14:27 gleixner Exp $ - * - * Handle mapping of the flash on MBX860 boards - * - * Author: Anton Todorov - * Copyright: (C) 2001 Emness Technology - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <asm/io.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - - -#define WINDOW_ADDR 0xfe000000 -#define WINDOW_SIZE 0x00200000 - -/* Flash / Partition sizing */ -#define MAX_SIZE_KiB 8192 -#define BOOT_PARTITION_SIZE_KiB 512 -#define KERNEL_PARTITION_SIZE_KiB 5632 -#define APP_PARTITION_SIZE_KiB 2048 - -#define NUM_PARTITIONS 3 - -/* partition_info gives details on the logical partitions that the split the - * single flash device into. If the size if zero we use up to the end of the - * device. */ -static struct mtd_partition partition_info[]={ - { .name = "MBX flash BOOT partition", - .offset = 0, - .size = BOOT_PARTITION_SIZE_KiB*1024 }, - { .name = "MBX flash DATA partition", - .offset = BOOT_PARTITION_SIZE_KiB*1024, - .size = (KERNEL_PARTITION_SIZE_KiB)*1024 }, - { .name = "MBX flash APPLICATION partition", - .offset = (BOOT_PARTITION_SIZE_KiB+KERNEL_PARTITION_SIZE_KiB)*1024 } -}; - - -static struct mtd_info *mymtd; - -struct map_info mbx_map = { - .name = "MBX flash", - .size = WINDOW_SIZE, - .phys = WINDOW_ADDR, - .bankwidth = 4, -}; - -int __init init_mbx(void) -{ - printk(KERN_NOTICE "Motorola MBX flash device: 0x%x at 0x%x\n", WINDOW_SIZE*4, WINDOW_ADDR); - mbx_map.virt = ioremap(WINDOW_ADDR, WINDOW_SIZE * 4); - - if (!mbx_map.virt) { - printk("Failed to ioremap\n"); - return -EIO; - } - simple_map_init(&mbx_map); - - mymtd = do_map_probe("jedec_probe", &mbx_map); - if (mymtd) { - mymtd->owner = THIS_MODULE; - add_mtd_device(mymtd); - add_mtd_partitions(mymtd, partition_info, NUM_PARTITIONS); - return 0; - } - - iounmap((void *)mbx_map.virt); - return -ENXIO; -} - -static void __exit cleanup_mbx(void) -{ - if (mymtd) { - del_mtd_device(mymtd); - map_destroy(mymtd); - } - if (mbx_map.virt) { - iounmap((void *)mbx_map.virt); - mbx_map.virt = 0; - } -} - -module_init(init_mbx); -module_exit(cleanup_mbx); - -MODULE_AUTHOR("Anton Todorov <a.todorov@emness.com>"); -MODULE_DESCRIPTION("MTD map driver for Motorola MBX860 board"); -MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/maps/mpc1211.c b/drivers/mtd/maps/mpc1211.c deleted file mode 100644 index 45a00fac88a..00000000000 --- a/drivers/mtd/maps/mpc1211.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Flash on MPC-1211 - * - * $Id: mpc1211.c,v 1.4 2004/09/16 23:27:13 gleixner Exp $ - * - * (C) 2002 Interface, Saito.K & Jeanne - * - * GPL'd - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <asm/io.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - -static struct mtd_info *flash_mtd; -static struct mtd_partition *parsed_parts; - -struct map_info mpc1211_flash_map = { - .name = "MPC-1211 FLASH", - .size = 0x80000, - .bankwidth = 1, -}; - -static struct mtd_partition mpc1211_partitions[] = { - { - .name = "IPL & ETH-BOOT", - .offset = 0x00000000, - .size = 0x10000, - }, - { - .name = "Flash FS", - .offset = 0x00010000, - .size = MTDPART_SIZ_FULL, - } -}; - -static int __init init_mpc1211_maps(void) -{ - int nr_parts; - - mpc1211_flash_map.phys = 0; - mpc1211_flash_map.virt = (void __iomem *)P2SEGADDR(0); - - simple_map_init(&mpc1211_flash_map); - - printk(KERN_NOTICE "Probing for flash chips at 0x00000000:\n"); - flash_mtd = do_map_probe("jedec_probe", &mpc1211_flash_map); - if (!flash_mtd) { - printk(KERN_NOTICE "Flash chips not detected at either possible location.\n"); - return -ENXIO; - } - printk(KERN_NOTICE "MPC-1211: Flash at 0x%08lx\n", mpc1211_flash_map.virt & 0x1fffffff); - flash_mtd->module = THIS_MODULE; - - parsed_parts = mpc1211_partitions; - nr_parts = ARRAY_SIZE(mpc1211_partitions); - - add_mtd_partitions(flash_mtd, parsed_parts, nr_parts); - return 0; -} - -static void __exit cleanup_mpc1211_maps(void) -{ - if (parsed_parts) - del_mtd_partitions(flash_mtd); - else - del_mtd_device(flash_mtd); - map_destroy(flash_mtd); -} - -module_init(init_mpc1211_maps); -module_exit(cleanup_mpc1211_maps); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Saito.K & Jeanne <ksaito@interface.co.jp>"); -MODULE_DESCRIPTION("MTD map driver for MPC-1211 boards. Interface"); diff --git a/drivers/mtd/maps/mtx-1_flash.c b/drivers/mtd/maps/mtx-1_flash.c deleted file mode 100644 index d884f2be28f..00000000000 --- a/drivers/mtd/maps/mtx-1_flash.c +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Flash memory access on 4G Systems MTX-1 boards - * - * $Id: mtx-1_flash.c,v 1.2 2005/11/07 11:14:27 gleixner Exp $ - * - * (C) 2005 Bruno Randolf <bruno.randolf@4g-systems.biz> - * (C) 2005 Jörn Engel <joern@wohnheim.fh-wedel.de> - * - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/init.h> -#include <linux/kernel.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - -#include <asm/io.h> - -static struct map_info mtx1_map = { - .name = "MTX-1 flash", - .bankwidth = 4, - .size = 0x2000000, - .phys = 0x1E000000, -}; - -static struct mtd_partition mtx1_partitions[] = { - { - .name = "filesystem", - .size = 0x01C00000, - .offset = 0, - },{ - .name = "yamon", - .size = 0x00100000, - .offset = MTDPART_OFS_APPEND, - .mask_flags = MTD_WRITEABLE, - },{ - .name = "kernel", - .size = 0x002c0000, - .offset = MTDPART_OFS_APPEND, - },{ - .name = "yamon env", - .size = 0x00040000, - .offset = MTDPART_OFS_APPEND, - } -}; - -static struct mtd_info *mtx1_mtd; - -int __init mtx1_mtd_init(void) -{ - int ret = -ENXIO; - - simple_map_init(&mtx1_map); - - mtx1_map.virt = ioremap(mtx1_map.phys, mtx1_map.size); - if (!mtx1_map.virt) - return -EIO; - - mtx1_mtd = do_map_probe("cfi_probe", &mtx1_map); - if (!mtx1_mtd) - goto err; - - mtx1_mtd->owner = THIS_MODULE; - - ret = add_mtd_partitions(mtx1_mtd, mtx1_partitions, - ARRAY_SIZE(mtx1_partitions)); - if (ret) - goto err; - - return 0; - -err: - iounmap(mtx1_map.virt); - return ret; -} - -static void __exit mtx1_mtd_cleanup(void) -{ - if (mtx1_mtd) { - del_mtd_partitions(mtx1_mtd); - map_destroy(mtx1_mtd); - } - if (mtx1_map.virt) - iounmap(mtx1_map.virt); -} - -module_init(mtx1_mtd_init); -module_exit(mtx1_mtd_cleanup); - -MODULE_AUTHOR("Bruno Randolf <bruno.randolf@4g-systems.biz>"); -MODULE_DESCRIPTION("MTX-1 flash map"); -MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/maps/netsc520.c b/drivers/mtd/maps/netsc520.c index ed215470158..81dc2598bc0 100644 --- a/drivers/mtd/maps/netsc520.c +++ b/drivers/mtd/maps/netsc520.c @@ -3,8 +3,6 @@ * Copyright (C) 2001 Mark Langsdorf (mark.langsdorf@amd.com) * based on sc520cdp.c by Sysgo Real-Time Solutions GmbH * - * $Id: netsc520.c,v 1.14 2005/11/07 11:14:27 gleixner Exp $ - * * 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 @@ -94,7 +92,9 @@ static struct mtd_info *mymtd; static int __init init_netsc520(void) { - printk(KERN_NOTICE "NetSc520 flash device: 0x%lx at 0x%lx\n", netsc520_map.size, netsc520_map.phys); + printk(KERN_NOTICE "NetSc520 flash device: 0x%Lx at 0x%Lx\n", + (unsigned long long)netsc520_map.size, + (unsigned long long)netsc520_map.phys); netsc520_map.virt = ioremap_nocache(netsc520_map.phys, netsc520_map.size); if (!netsc520_map.virt) { @@ -116,14 +116,14 @@ static int __init init_netsc520(void) } mymtd->owner = THIS_MODULE; - add_mtd_partitions( mymtd, partition_info, NUM_PARTITIONS ); + mtd_device_register(mymtd, partition_info, NUM_PARTITIONS); return 0; } static void __exit cleanup_netsc520(void) { if (mymtd) { - del_mtd_partitions(mymtd); + mtd_device_unregister(mymtd); map_destroy(mymtd); } if (netsc520_map.virt) { diff --git a/drivers/mtd/maps/nettel.c b/drivers/mtd/maps/nettel.c index 198e840ff6d..eadcfffc4f9 100644 --- a/drivers/mtd/maps/nettel.c +++ b/drivers/mtd/maps/nettel.c @@ -5,8 +5,6 @@ * * (C) Copyright 2000-2001, Greg Ungerer (gerg@snapgear.com) * (C) Copyright 2001-2002, SnapGear (www.snapgear.com) - * - * $Id: nettel.c,v 1.12 2005/11/29 14:30:00 gleixner Exp $ */ /****************************************************************************/ @@ -20,6 +18,7 @@ #include <linux/mtd/partitions.h> #include <linux/mtd/cfi.h> #include <linux/reboot.h> +#include <linux/err.h> #include <linux/kdev_t.h> #include <linux/root_dev.h> #include <asm/io.h> @@ -157,68 +156,11 @@ static struct notifier_block nettel_notifier_block = { nettel_reboot_notifier, NULL, 0 }; -/* - * Erase the configuration file system. - * Used to support the software reset button. - */ -static void nettel_erasecallback(struct erase_info *done) -{ - wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv; - wake_up(wait_q); -} - -static struct erase_info nettel_erase; - -int nettel_eraseconfig(void) -{ - struct mtd_info *mtd; - DECLARE_WAITQUEUE(wait, current); - wait_queue_head_t wait_q; - int ret; - - init_waitqueue_head(&wait_q); - mtd = get_mtd_device(NULL, 2); - if (mtd) { - nettel_erase.mtd = mtd; - nettel_erase.callback = nettel_erasecallback; - nettel_erase.callback = NULL; - nettel_erase.addr = 0; - nettel_erase.len = mtd->size; - nettel_erase.priv = (u_long) &wait_q; - nettel_erase.priv = 0; - - set_current_state(TASK_INTERRUPTIBLE); - add_wait_queue(&wait_q, &wait); - - ret = mtd->erase(mtd, &nettel_erase); - if (ret) { - set_current_state(TASK_RUNNING); - remove_wait_queue(&wait_q, &wait); - put_mtd_device(mtd); - return(ret); - } - - schedule(); /* Wait for erase to finish. */ - remove_wait_queue(&wait_q, &wait); - - put_mtd_device(mtd); - } - - return(0); -} - -#else - -int nettel_eraseconfig(void) -{ - return(0); -} - #endif /****************************************************************************/ -int __init nettel_init(void) +static int __init nettel_init(void) { volatile unsigned long *amdpar; unsigned long amdaddr, maxsize; @@ -284,7 +226,7 @@ int __init nettel_init(void) if ((amd_mtd = do_map_probe("jedec_probe", &nettel_amd_map))) { printk(KERN_NOTICE "SNAPGEAR: AMD flash device size = %dK\n", - amd_mtd->size>>10); + (int)(amd_mtd->size>>10)); amd_mtd->owner = THIS_MODULE; @@ -357,7 +299,7 @@ int __init nettel_init(void) /* Turn other PAR off so the first probe doesn't find it */ *intel1par = 0; - /* Probe for the the size of the first Intel flash */ + /* Probe for the size of the first Intel flash */ nettel_intel_map.size = maxsize; nettel_intel_map.phys = intel0addr; nettel_intel_map.virt = ioremap_nocache(intel0addr, maxsize); @@ -415,17 +357,12 @@ int __init nettel_init(void) *intel1par = 0; } - printk(KERN_NOTICE "SNAPGEAR: Intel flash device size = %dK\n", - (intel_mtd->size >> 10)); + printk(KERN_NOTICE "SNAPGEAR: Intel flash device size = %lldKiB\n", + (unsigned long long)(intel_mtd->size >> 10)); intel_mtd->owner = THIS_MODULE; -#ifndef CONFIG_BLK_DEV_INITRD - ROOT_DEV = MKDEV(MTD_BLOCK_MAJOR, 1); -#endif - - num_intel_partitions = sizeof(nettel_intel_partitions) / - sizeof(nettel_intel_partitions[0]); + num_intel_partitions = ARRAY_SIZE(nettel_intel_partitions); if (intelboot) { /* @@ -446,13 +383,13 @@ int __init nettel_init(void) /* No BIOS regions when AMD boot */ num_intel_partitions -= 2; } - rc = add_mtd_partitions(intel_mtd, nettel_intel_partitions, - num_intel_partitions); + rc = mtd_device_register(intel_mtd, nettel_intel_partitions, + num_intel_partitions); #endif if (amd_mtd) { - rc = add_mtd_partitions(amd_mtd, nettel_amd_partitions, - num_amd_partitions); + rc = mtd_device_register(amd_mtd, nettel_amd_partitions, + num_amd_partitions); } #ifdef CONFIG_MTD_CFI_INTELEXT @@ -463,7 +400,7 @@ int __init nettel_init(void) #ifdef CONFIG_MTD_CFI_INTELEXT out_unmap1: - iounmap((void *) nettel_intel_map.virt); + iounmap(nettel_intel_map.virt); #endif out_unmap2: @@ -471,18 +408,18 @@ out_unmap2: iounmap(nettel_amd_map.virt); return(rc); - + } /****************************************************************************/ -void __exit nettel_cleanup(void) +static void __exit nettel_cleanup(void) { #ifdef CONFIG_MTD_CFI_INTELEXT unregister_reboot_notifier(&nettel_notifier_block); #endif if (amd_mtd) { - del_mtd_partitions(amd_mtd); + mtd_device_unregister(amd_mtd); map_destroy(amd_mtd); } if (nettel_mmcrp) { @@ -495,7 +432,7 @@ void __exit nettel_cleanup(void) } #ifdef CONFIG_MTD_CFI_INTELEXT if (intel_mtd) { - del_mtd_partitions(intel_mtd); + mtd_device_unregister(intel_mtd); map_destroy(intel_mtd); } if (nettel_intel_map.virt) { diff --git a/drivers/mtd/maps/ocelot.c b/drivers/mtd/maps/ocelot.c deleted file mode 100644 index 6977963d789..00000000000 --- a/drivers/mtd/maps/ocelot.c +++ /dev/null @@ -1,175 +0,0 @@ -/* - * $Id: ocelot.c,v 1.17 2005/11/07 11:14:27 gleixner Exp $ - * - * Flash on Momenco Ocelot - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <asm/io.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - -#define OCELOT_PLD 0x2c000000 -#define FLASH_WINDOW_ADDR 0x2fc00000 -#define FLASH_WINDOW_SIZE 0x00080000 -#define FLASH_BUSWIDTH 1 -#define NVRAM_WINDOW_ADDR 0x2c800000 -#define NVRAM_WINDOW_SIZE 0x00007FF0 -#define NVRAM_BUSWIDTH 1 - -static unsigned int cacheflush = 0; - -static struct mtd_info *flash_mtd; -static struct mtd_info *nvram_mtd; - -static void ocelot_ram_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) -{ - struct map_info *map = mtd->priv; - size_t done = 0; - - /* If we use memcpy, it does word-wide writes. Even though we told the - GT64120A that it's an 8-bit wide region, word-wide writes don't work. - We end up just writing the first byte of the four to all four bytes. - So we have this loop instead */ - *retlen = len; - while(len) { - __raw_writeb(*(unsigned char *) from, map->virt + to); - from++; - to++; - len--; - } -} - -static struct mtd_partition *parsed_parts; - -struct map_info ocelot_flash_map = { - .name = "Ocelot boot flash", - .size = FLASH_WINDOW_SIZE, - .bankwidth = FLASH_BUSWIDTH, - .phys = FLASH_WINDOW_ADDR, -}; - -struct map_info ocelot_nvram_map = { - .name = "Ocelot NVRAM", - .size = NVRAM_WINDOW_SIZE, - .bankwidth = NVRAM_BUSWIDTH, - .phys = NVRAM_WINDOW_ADDR, -}; - -static const char *probes[] = { "RedBoot", NULL }; - -static int __init init_ocelot_maps(void) -{ - void *pld; - int nr_parts; - unsigned char brd_status; - - printk(KERN_INFO "Momenco Ocelot MTD mappings: Flash 0x%x at 0x%x, NVRAM 0x%x at 0x%x\n", - FLASH_WINDOW_SIZE, FLASH_WINDOW_ADDR, NVRAM_WINDOW_SIZE, NVRAM_WINDOW_ADDR); - - /* First check whether the flash jumper is present */ - pld = ioremap(OCELOT_PLD, 0x10); - if (!pld) { - printk(KERN_NOTICE "Failed to ioremap Ocelot PLD\n"); - return -EIO; - } - brd_status = readb(pld+4); - iounmap(pld); - - /* Now ioremap the NVRAM space */ - ocelot_nvram_map.virt = ioremap_nocache(NVRAM_WINDOW_ADDR, NVRAM_WINDOW_SIZE); - if (!ocelot_nvram_map.virt) { - printk(KERN_NOTICE "Failed to ioremap Ocelot NVRAM space\n"); - return -EIO; - } - - simple_map_init(&ocelot_nvram_map); - - /* And do the RAM probe on it to get an MTD device */ - nvram_mtd = do_map_probe("map_ram", &ocelot_nvram_map); - if (!nvram_mtd) { - printk("NVRAM probe failed\n"); - goto fail_1; - } - nvram_mtd->owner = THIS_MODULE; - nvram_mtd->erasesize = 16; - /* Override the write() method */ - nvram_mtd->write = ocelot_ram_write; - - /* Now map the flash space */ - ocelot_flash_map.virt = ioremap_nocache(FLASH_WINDOW_ADDR, FLASH_WINDOW_SIZE); - if (!ocelot_flash_map.virt) { - printk(KERN_NOTICE "Failed to ioremap Ocelot flash space\n"); - goto fail_2; - } - /* Now the cached version */ - ocelot_flash_map.cached = (unsigned long)__ioremap(FLASH_WINDOW_ADDR, FLASH_WINDOW_SIZE, 0); - - simple_map_init(&ocelot_flash_map); - - /* Only probe for flash if the write jumper is present */ - if (brd_status & 0x40) { - flash_mtd = do_map_probe("jedec", &ocelot_flash_map); - } else { - printk(KERN_NOTICE "Ocelot flash write jumper not present. Treating as ROM\n"); - } - /* If that failed or the jumper's absent, pretend it's ROM */ - if (!flash_mtd) { - flash_mtd = do_map_probe("map_rom", &ocelot_flash_map); - /* If we're treating it as ROM, set the erase size */ - if (flash_mtd) - flash_mtd->erasesize = 0x10000; - } - if (!flash_mtd) - goto fail3; - - add_mtd_device(nvram_mtd); - - flash_mtd->owner = THIS_MODULE; - nr_parts = parse_mtd_partitions(flash_mtd, probes, &parsed_parts, 0); - - if (nr_parts > 0) - add_mtd_partitions(flash_mtd, parsed_parts, nr_parts); - else - add_mtd_device(flash_mtd); - - return 0; - - fail3: - iounmap((void *)ocelot_flash_map.virt); - if (ocelot_flash_map.cached) - iounmap((void *)ocelot_flash_map.cached); - fail_2: - map_destroy(nvram_mtd); - fail_1: - iounmap((void *)ocelot_nvram_map.virt); - - return -ENXIO; -} - -static void __exit cleanup_ocelot_maps(void) -{ - del_mtd_device(nvram_mtd); - map_destroy(nvram_mtd); - iounmap((void *)ocelot_nvram_map.virt); - - if (parsed_parts) - del_mtd_partitions(flash_mtd); - else - del_mtd_device(flash_mtd); - map_destroy(flash_mtd); - iounmap((void *)ocelot_flash_map.virt); - if (ocelot_flash_map.cached) - iounmap((void *)ocelot_flash_map.cached); -} - -module_init(init_ocelot_maps); -module_exit(cleanup_ocelot_maps); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Red Hat, Inc. - David Woodhouse <dwmw2@cambridge.redhat.com>"); -MODULE_DESCRIPTION("MTD map driver for Momenco Ocelot board"); diff --git a/drivers/mtd/maps/ocotea.c b/drivers/mtd/maps/ocotea.c deleted file mode 100644 index 5522eac8c98..00000000000 --- a/drivers/mtd/maps/ocotea.c +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Mapping for Ocotea user flash - * - * Matt Porter <mporter@kernel.crashing.org> - * - * Copyright 2002-2004 MontaVista Software Inc. - * - * 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/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> -#include <asm/io.h> -#include <asm/ibm44x.h> -#include <platforms/4xx/ocotea.h> - -static struct mtd_info *flash; - -static struct map_info ocotea_small_map = { - .name = "Ocotea small flash", - .size = OCOTEA_SMALL_FLASH_SIZE, - .buswidth = 1, -}; - -static struct map_info ocotea_large_map = { - .name = "Ocotea large flash", - .size = OCOTEA_LARGE_FLASH_SIZE, - .buswidth = 1, -}; - -static struct mtd_partition ocotea_small_partitions[] = { - { - .name = "pibs", - .offset = 0x0, - .size = 0x100000, - } -}; - -static struct mtd_partition ocotea_large_partitions[] = { - { - .name = "fs", - .offset = 0, - .size = 0x300000, - }, - { - .name = "firmware", - .offset = 0x300000, - .size = 0x100000, - } -}; - -int __init init_ocotea(void) -{ - u8 fpga0_reg; - u8 *fpga0_adr; - unsigned long long small_flash_base, large_flash_base; - - fpga0_adr = ioremap64(OCOTEA_FPGA_ADDR, 16); - if (!fpga0_adr) - return -ENOMEM; - - fpga0_reg = readb((unsigned long)fpga0_adr); - iounmap(fpga0_adr); - - if (OCOTEA_BOOT_LARGE_FLASH(fpga0_reg)) { - small_flash_base = OCOTEA_SMALL_FLASH_HIGH; - large_flash_base = OCOTEA_LARGE_FLASH_LOW; - } - else { - small_flash_base = OCOTEA_SMALL_FLASH_LOW; - large_flash_base = OCOTEA_LARGE_FLASH_HIGH; - } - - ocotea_small_map.phys = small_flash_base; - ocotea_small_map.virt = ioremap64(small_flash_base, - ocotea_small_map.size); - - if (!ocotea_small_map.virt) { - printk("Failed to ioremap flash\n"); - return -EIO; - } - - simple_map_init(&ocotea_small_map); - - flash = do_map_probe("map_rom", &ocotea_small_map); - if (flash) { - flash->owner = THIS_MODULE; - add_mtd_partitions(flash, ocotea_small_partitions, - ARRAY_SIZE(ocotea_small_partitions)); - } else { - printk("map probe failed for flash\n"); - iounmap(ocotea_small_map.virt); - return -ENXIO; - } - - ocotea_large_map.phys = large_flash_base; - ocotea_large_map.virt = ioremap64(large_flash_base, - ocotea_large_map.size); - - if (!ocotea_large_map.virt) { - printk("Failed to ioremap flash\n"); - iounmap(ocotea_small_map.virt); - return -EIO; - } - - simple_map_init(&ocotea_large_map); - - flash = do_map_probe("cfi_probe", &ocotea_large_map); - if (flash) { - flash->owner = THIS_MODULE; - add_mtd_partitions(flash, ocotea_large_partitions, - ARRAY_SIZE(ocotea_large_partitions)); - } else { - printk("map probe failed for flash\n"); - iounmap(ocotea_small_map.virt); - iounmap(ocotea_large_map.virt); - return -ENXIO; - } - - return 0; -} - -static void __exit cleanup_ocotea(void) -{ - if (flash) { - del_mtd_partitions(flash); - map_destroy(flash); - } - - if (ocotea_small_map.virt) { - iounmap((void *)ocotea_small_map.virt); - ocotea_small_map.virt = 0; - } - - if (ocotea_large_map.virt) { - iounmap((void *)ocotea_large_map.virt); - ocotea_large_map.virt = 0; - } -} - -module_init(init_ocotea); -module_exit(cleanup_ocotea); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Matt Porter <mporter@kernel.crashing.org>"); -MODULE_DESCRIPTION("MTD map and partitions for IBM 440GX Ocotea boards"); diff --git a/drivers/mtd/maps/octagon-5066.c b/drivers/mtd/maps/octagon-5066.c deleted file mode 100644 index a6642db3d32..00000000000 --- a/drivers/mtd/maps/octagon-5066.c +++ /dev/null @@ -1,248 +0,0 @@ -// $Id: octagon-5066.c,v 1.28 2005/11/07 11:14:27 gleixner Exp $ -/* ###################################################################### - - Octagon 5066 MTD Driver. - - The Octagon 5066 is a SBC based on AMD's 586-WB running at 133 MHZ. It - comes with a builtin AMD 29F016 flash chip and a socketed EEPROM that - is replacable by flash. Both units are mapped through a multiplexer - into a 32k memory window at 0xe8000. The control register for the - multiplexing unit is located at IO 0x208 with a bit map of - 0-5 Page Selection in 32k increments - 6-7 Device selection: - 00 SSD off - 01 SSD 0 (Socket) - 10 SSD 1 (Flash chip) - 11 undefined - - On each SSD, the first 128k is reserved for use by the bios - (actually it IS the bios..) This only matters if you are booting off the - flash, you must not put a file system starting there. - - The driver tries to do a detection algorithm to guess what sort of devices - are plugged into the sockets. - - ##################################################################### */ - -#include <linux/module.h> -#include <linux/slab.h> -#include <linux/ioport.h> -#include <linux/init.h> -#include <asm/io.h> - -#include <linux/mtd/map.h> -#include <linux/mtd/mtd.h> - -#define WINDOW_START 0xe8000 -#define WINDOW_LENGTH 0x8000 -#define WINDOW_SHIFT 27 -#define WINDOW_MASK 0x7FFF -#define PAGE_IO 0x208 - -static volatile char page_n_dev = 0; -static unsigned long iomapadr; -static DEFINE_SPINLOCK(oct5066_spin); - -/* - * We use map_priv_1 to identify which device we are. - */ - -static void __oct5066_page(struct map_info *map, __u8 byte) -{ - outb(byte,PAGE_IO); - page_n_dev = byte; -} - -static inline void oct5066_page(struct map_info *map, unsigned long ofs) -{ - __u8 byte = map->map_priv_1 | (ofs >> WINDOW_SHIFT); - - if (page_n_dev != byte) - __oct5066_page(map, byte); -} - - -static map_word oct5066_read8(struct map_info *map, unsigned long ofs) -{ - map_word ret; - spin_lock(&oct5066_spin); - oct5066_page(map, ofs); - ret.x[0] = readb(iomapadr + (ofs & WINDOW_MASK)); - spin_unlock(&oct5066_spin); - return ret; -} - -static void oct5066_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len) -{ - while(len) { - unsigned long thislen = len; - if (len > (WINDOW_LENGTH - (from & WINDOW_MASK))) - thislen = WINDOW_LENGTH-(from & WINDOW_MASK); - - spin_lock(&oct5066_spin); - oct5066_page(map, from); - memcpy_fromio(to, iomapadr + from, thislen); - spin_unlock(&oct5066_spin); - to += thislen; - from += thislen; - len -= thislen; - } -} - -static void oct5066_write8(struct map_info *map, map_word d, unsigned long adr) -{ - spin_lock(&oct5066_spin); - oct5066_page(map, adr); - writeb(d.x[0], iomapadr + (adr & WINDOW_MASK)); - spin_unlock(&oct5066_spin); -} - -static void oct5066_copy_to(struct map_info *map, unsigned long to, const void *from, ssize_t len) -{ - while(len) { - unsigned long thislen = len; - if (len > (WINDOW_LENGTH - (to & WINDOW_MASK))) - thislen = WINDOW_LENGTH-(to & WINDOW_MASK); - - spin_lock(&oct5066_spin); - oct5066_page(map, to); - memcpy_toio(iomapadr + to, from, thislen); - spin_unlock(&oct5066_spin); - to += thislen; - from += thislen; - len -= thislen; - } -} - -static struct map_info oct5066_map[2] = { - { - .name = "Octagon 5066 Socket", - .phys = NO_XIP, - .size = 512 * 1024, - .bankwidth = 1, - .read = oct5066_read8, - .copy_from = oct5066_copy_from, - .write = oct5066_write8, - .copy_to = oct5066_copy_to, - .map_priv_1 = 1<<6 - }, - { - .name = "Octagon 5066 Internal Flash", - .phys = NO_XIP, - .size = 2 * 1024 * 1024, - .bankwidth = 1, - .read = oct5066_read8, - .copy_from = oct5066_copy_from, - .write = oct5066_write8, - .copy_to = oct5066_copy_to, - .map_priv_1 = 2<<6 - } -}; - -static struct mtd_info *oct5066_mtd[2] = {NULL, NULL}; - -// OctProbe - Sense if this is an octagon card -// --------------------------------------------------------------------- -/* Perform a simple validity test, we map the window select SSD0 and - change pages while monitoring the window. A change in the window, - controlled by the PAGE_IO port is a functioning 5066 board. This will - fail if the thing in the socket is set to a uniform value. */ -static int __init OctProbe(void) -{ - unsigned int Base = (1 << 6); - unsigned long I; - unsigned long Values[10]; - for (I = 0; I != 20; I++) - { - outb(Base + (I%10),PAGE_IO); - if (I < 10) - { - // Record the value and check for uniqueness - Values[I%10] = readl(iomapadr); - if (I > 0 && Values[I%10] == Values[0]) - return -EAGAIN; - } - else - { - // Make sure we get the same values on the second pass - if (Values[I%10] != readl(iomapadr)) - return -EAGAIN; - } - } - return 0; -} - -void cleanup_oct5066(void) -{ - int i; - for (i=0; i<2; i++) { - if (oct5066_mtd[i]) { - del_mtd_device(oct5066_mtd[i]); - map_destroy(oct5066_mtd[i]); - } - } - iounmap((void *)iomapadr); - release_region(PAGE_IO, 1); -} - -int __init init_oct5066(void) -{ - int i; - int ret = 0; - - // Do an autoprobe sequence - if (!request_region(PAGE_IO,1,"Octagon SSD")) { - printk(KERN_NOTICE "5066: Page Register in Use\n"); - return -EAGAIN; - } - iomapadr = (unsigned long)ioremap(WINDOW_START, WINDOW_LENGTH); - if (!iomapadr) { - printk(KERN_NOTICE "Failed to ioremap memory region\n"); - ret = -EIO; - goto out_rel; - } - if (OctProbe() != 0) { - printk(KERN_NOTICE "5066: Octagon Probe Failed, is this an Octagon 5066 SBC?\n"); - iounmap((void *)iomapadr); - ret = -EAGAIN; - goto out_unmap; - } - - // Print out our little header.. - printk("Octagon 5066 SSD IO:0x%x MEM:0x%x-0x%x\n",PAGE_IO,WINDOW_START, - WINDOW_START+WINDOW_LENGTH); - - for (i=0; i<2; i++) { - oct5066_mtd[i] = do_map_probe("cfi_probe", &oct5066_map[i]); - if (!oct5066_mtd[i]) - oct5066_mtd[i] = do_map_probe("jedec", &oct5066_map[i]); - if (!oct5066_mtd[i]) - oct5066_mtd[i] = do_map_probe("map_ram", &oct5066_map[i]); - if (!oct5066_mtd[i]) - oct5066_mtd[i] = do_map_probe("map_rom", &oct5066_map[i]); - if (oct5066_mtd[i]) { - oct5066_mtd[i]->owner = THIS_MODULE; - add_mtd_device(oct5066_mtd[i]); - } - } - - if (!oct5066_mtd[0] && !oct5066_mtd[1]) { - cleanup_oct5066(); - return -ENXIO; - } - - return 0; - - out_unmap: - iounmap((void *)iomapadr); - out_rel: - release_region(PAGE_IO, 1); - return ret; -} - -module_init(init_oct5066); -module_exit(cleanup_oct5066); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Jason Gunthorpe <jgg@deltatee.com>, David Woodhouse <dwmw2@infradead.org>"); -MODULE_DESCRIPTION("MTD map driver for Octagon 5066 Single Board Computer"); diff --git a/drivers/mtd/maps/omap-toto-flash.c b/drivers/mtd/maps/omap-toto-flash.c deleted file mode 100644 index e6e391efbeb..00000000000 --- a/drivers/mtd/maps/omap-toto-flash.c +++ /dev/null @@ -1,135 +0,0 @@ -/* - * NOR Flash memory access on TI Toto board - * - * jzhang@ti.com (C) 2003 Texas Instruments. - * - * (C) 2002 MontVista Software, Inc. - * - * $Id: omap-toto-flash.c,v 1.5 2005/11/07 11:14:27 gleixner Exp $ - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/errno.h> -#include <linux/init.h> -#include <linux/slab.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - -#include <asm/hardware.h> -#include <asm/io.h> - - -#ifndef CONFIG_ARCH_OMAP -#error This is for OMAP architecture only -#endif - -//these lines need be moved to a hardware header file -#define OMAP_TOTO_FLASH_BASE 0xd8000000 -#define OMAP_TOTO_FLASH_SIZE 0x80000 - -static struct map_info omap_toto_map_flash = { - .name = "OMAP Toto flash", - .bankwidth = 2, - .virt = (void __iomem *)OMAP_TOTO_FLASH_BASE, -}; - - -static struct mtd_partition toto_flash_partitions[] = { - { - .name = "BootLoader", - .size = 0x00040000, /* hopefully u-boot will stay 128k + 128*/ - .offset = 0, - .mask_flags = MTD_WRITEABLE, /* force read-only */ - }, { - .name = "ReservedSpace", - .size = 0x00030000, - .offset = MTDPART_OFS_APPEND, - //mask_flags: MTD_WRITEABLE, /* force read-only */ - }, { - .name = "EnvArea", /* bottom 64KiB for env vars */ - .size = MTDPART_SIZ_FULL, - .offset = MTDPART_OFS_APPEND, - } -}; - -static struct mtd_partition *parsed_parts; - -static struct mtd_info *flash_mtd; - -static int __init init_flash (void) -{ - - struct mtd_partition *parts; - int nb_parts = 0; - int parsed_nr_parts = 0; - const char *part_type; - - /* - * Static partition definition selection - */ - part_type = "static"; - - parts = toto_flash_partitions; - nb_parts = ARRAY_SIZE(toto_flash_partitions); - omap_toto_map_flash.size = OMAP_TOTO_FLASH_SIZE; - omap_toto_map_flash.phys = virt_to_phys(OMAP_TOTO_FLASH_BASE); - - simple_map_init(&omap_toto_map_flash); - /* - * Now let's probe for the actual flash. Do it here since - * specific machine settings might have been set above. - */ - printk(KERN_NOTICE "OMAP toto flash: probing %d-bit flash bus\n", - omap_toto_map_flash.bankwidth*8); - flash_mtd = do_map_probe("jedec_probe", &omap_toto_map_flash); - if (!flash_mtd) - return -ENXIO; - - if (parsed_nr_parts > 0) { - parts = parsed_parts; - nb_parts = parsed_nr_parts; - } - - if (nb_parts == 0) { - printk(KERN_NOTICE "OMAP toto flash: no partition info available," - "registering whole flash at once\n"); - if (add_mtd_device(flash_mtd)){ - return -ENXIO; - } - } else { - printk(KERN_NOTICE "Using %s partition definition\n", - part_type); - return add_mtd_partitions(flash_mtd, parts, nb_parts); - } - return 0; -} - -int __init omap_toto_mtd_init(void) -{ - int status; - - if (status = init_flash()) { - printk(KERN_ERR "OMAP Toto Flash: unable to init map for toto flash\n"); - } - return status; -} - -static void __exit omap_toto_mtd_cleanup(void) -{ - if (flash_mtd) { - del_mtd_partitions(flash_mtd); - map_destroy(flash_mtd); - kfree(parsed_parts); - } -} - -module_init(omap_toto_mtd_init); -module_exit(omap_toto_mtd_cleanup); - -MODULE_AUTHOR("Jian Zhang"); -MODULE_DESCRIPTION("OMAP Toto board map driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/maps/omap_nor.c b/drivers/mtd/maps/omap_nor.c deleted file mode 100644 index 418afffb2d8..00000000000 --- a/drivers/mtd/maps/omap_nor.c +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Flash memory support for various TI OMAP boards - * - * Copyright (C) 2001-2002 MontaVista Software Inc. - * Copyright (C) 2003-2004 Texas Instruments - * Copyright (C) 2004 Nokia Corporation - * - * Assembled using driver code copyright the companies above - * and written by David Brownell, Jian Zhang <jzhang@ti.com>, - * Tony Lindgren <tony@atomide.com> and others. - * - * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN - * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF - * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * 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/platform_device.h> -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <linux/ioport.h> -#include <linux/slab.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - -#include <asm/io.h> -#include <asm/hardware.h> -#include <asm/mach/flash.h> -#include <asm/arch/tc.h> - -#ifdef CONFIG_MTD_PARTITIONS -static const char *part_probes[] = { /* "RedBoot", */ "cmdlinepart", NULL }; -#endif - -struct omapflash_info { - struct mtd_partition *parts; - struct mtd_info *mtd; - struct map_info map; -}; - -static void omap_set_vpp(struct map_info *map, int enable) -{ - static int count; - - if (enable) { - if (count++ == 0) - OMAP_EMIFS_CONFIG_REG |= OMAP_EMIFS_CONFIG_WP; - } else { - if (count && (--count == 0)) - OMAP_EMIFS_CONFIG_REG &= ~OMAP_EMIFS_CONFIG_WP; - } -} - -static int __devinit omapflash_probe(struct platform_device *pdev) -{ - int err; - struct omapflash_info *info; - struct flash_platform_data *pdata = pdev->dev.platform_data; - struct resource *res = pdev->resource; - unsigned long size = res->end - res->start + 1; - - info = kmalloc(sizeof(struct omapflash_info), GFP_KERNEL); - if (!info) - return -ENOMEM; - - memset(info, 0, sizeof(struct omapflash_info)); - - if (!request_mem_region(res->start, size, "flash")) { - err = -EBUSY; - goto out_free_info; - } - - info->map.virt = ioremap(res->start, size); - if (!info->map.virt) { - err = -ENOMEM; - goto out_release_mem_region; - } - info->map.name = pdev->dev.bus_id; - info->map.phys = res->start; - info->map.size = size; - info->map.bankwidth = pdata->width; - info->map.set_vpp = omap_set_vpp; - - simple_map_init(&info->map); - info->mtd = do_map_probe(pdata->map_name, &info->map); - if (!info->mtd) { - err = -EIO; - goto out_iounmap; - } - info->mtd->owner = THIS_MODULE; - -#ifdef CONFIG_MTD_PARTITIONS - err = parse_mtd_partitions(info->mtd, part_probes, &info->parts, 0); - if (err > 0) - add_mtd_partitions(info->mtd, info->parts, err); - else if (err < 0 && pdata->parts) - add_mtd_partitions(info->mtd, pdata->parts, pdata->nr_parts); - else -#endif - add_mtd_device(info->mtd); - - platform_set_drvdata(pdev, info); - - return 0; - -out_iounmap: - iounmap(info->map.virt); -out_release_mem_region: - release_mem_region(res->start, size); -out_free_info: - kfree(info); - - return err; -} - -static int __devexit omapflash_remove(struct platform_device *pdev) -{ - struct omapflash_info *info = platform_get_drvdata(pdev); - - platform_set_drvdata(pdev, NULL); - - if (info) { - if (info->parts) { - del_mtd_partitions(info->mtd); - kfree(info->parts); - } else - del_mtd_device(info->mtd); - map_destroy(info->mtd); - release_mem_region(info->map.phys, info->map.size); - iounmap((void __iomem *) info->map.virt); - kfree(info); - } - - return 0; -} - -static struct platform_driver omapflash_driver = { - .probe = omapflash_probe, - .remove = __devexit_p(omapflash_remove), - .driver = { - .name = "omapflash", - }, -}; - -static int __init omapflash_init(void) -{ - return platform_driver_register(&omapflash_driver); -} - -static void __exit omapflash_exit(void) -{ - platform_driver_unregister(&omapflash_driver); -} - -module_init(omapflash_init); -module_exit(omapflash_exit); - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("MTD NOR map driver for TI OMAP boards"); - diff --git a/drivers/mtd/maps/pci.c b/drivers/mtd/maps/pci.c index d2ab1bae9c3..eb0242e0b2d 100644 --- a/drivers/mtd/maps/pci.c +++ b/drivers/mtd/maps/pci.c @@ -7,8 +7,6 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * - * $Id: pci.c,v 1.14 2005/11/17 08:20:27 dwmw2 Exp $ - * * Generic PCI memory map driver. We support the following boards: * - Intel IQ80310 ATU. * - Intel EBSA285 (blank rom programming mode). Tested working 27/09/2001 @@ -16,7 +14,6 @@ #include <linux/module.h> #include <linux/kernel.h> #include <linux/pci.h> -#include <linux/init.h> #include <linux/slab.h> #include <linux/mtd/mtd.h> @@ -45,26 +42,14 @@ static map_word mtd_pci_read8(struct map_info *_map, unsigned long ofs) struct map_pci_info *map = (struct map_pci_info *)_map; map_word val; val.x[0]= readb(map->base + map->translate(map, ofs)); -// printk("read8 : %08lx => %02x\n", ofs, val.x[0]); return val; } -#if 0 -static map_word mtd_pci_read16(struct map_info *_map, unsigned long ofs) -{ - struct map_pci_info *map = (struct map_pci_info *)_map; - map_word val; - val.x[0] = readw(map->base + map->translate(map, ofs)); -// printk("read16: %08lx => %04x\n", ofs, val.x[0]); - return val; -} -#endif static map_word mtd_pci_read32(struct map_info *_map, unsigned long ofs) { struct map_pci_info *map = (struct map_pci_info *)_map; map_word val; val.x[0] = readl(map->base + map->translate(map, ofs)); -// printk("read32: %08lx => %08x\n", ofs, val.x[0]); return val; } @@ -77,22 +62,12 @@ static void mtd_pci_copyfrom(struct map_info *_map, void *to, unsigned long from static void mtd_pci_write8(struct map_info *_map, map_word val, unsigned long ofs) { struct map_pci_info *map = (struct map_pci_info *)_map; -// printk("write8 : %08lx <= %02x\n", ofs, val.x[0]); writeb(val.x[0], map->base + map->translate(map, ofs)); } -#if 0 -static void mtd_pci_write16(struct map_info *_map, map_word val, unsigned long ofs) -{ - struct map_pci_info *map = (struct map_pci_info *)_map; -// printk("write16: %08lx <= %04x\n", ofs, val.x[0]); - writew(val.x[0], map->base + map->translate(map, ofs)); -} -#endif static void mtd_pci_write32(struct map_info *_map, map_word val, unsigned long ofs) { struct map_pci_info *map = (struct map_pci_info *)_map; -// printk("write32: %08lx <= %08x\n", ofs, val.x[0]); writel(val.x[0], map->base + map->translate(map, ofs)); } @@ -205,15 +180,8 @@ intel_dc21285_init(struct pci_dev *dev, struct map_pci_info *map) * not enabled, should we be allocating a new resource for it * or simply enabling it? */ - if (!(pci_resource_flags(dev, PCI_ROM_RESOURCE) & - IORESOURCE_ROM_ENABLE)) { - u32 val; - pci_resource_flags(dev, PCI_ROM_RESOURCE) |= IORESOURCE_ROM_ENABLE; - pci_read_config_dword(dev, PCI_ROM_ADDRESS, &val); - val |= PCI_ROM_ADDRESS_ENABLE; - pci_write_config_dword(dev, PCI_ROM_ADDRESS, val); - printk("%s: enabling expansion ROM\n", pci_name(dev)); - } + pci_enable_rom(dev); + printk("%s: enabling expansion ROM\n", pci_name(dev)); } if (!len || !base) @@ -234,18 +202,13 @@ intel_dc21285_init(struct pci_dev *dev, struct map_pci_info *map) static void intel_dc21285_exit(struct pci_dev *dev, struct map_pci_info *map) { - u32 val; - if (map->base) iounmap(map->base); /* * We need to undo the PCI BAR2/PCI ROM BAR address alteration. */ - pci_resource_flags(dev, PCI_ROM_RESOURCE) &= ~IORESOURCE_ROM_ENABLE; - pci_read_config_dword(dev, PCI_ROM_ADDRESS, &val); - val &= ~PCI_ROM_ADDRESS_ENABLE; - pci_write_config_dword(dev, PCI_ROM_ADDRESS, val); + pci_disable_rom(dev); } static unsigned long @@ -289,8 +252,7 @@ static struct pci_device_id mtd_pci_ids[] = { * Generic code follows. */ -static int __devinit -mtd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +static int mtd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { struct mtd_pci_info *info = (struct mtd_pci_info *)id->driver_data; struct map_pci_info *map = NULL; @@ -320,14 +282,13 @@ mtd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) if (err) goto release; - /* tsk - do_map_probe should take const char * */ - mtd = do_map_probe((char *)info->map_name, &map->map); + mtd = do_map_probe(info->map_name, &map->map); err = -ENODEV; if (!mtd) goto release; mtd->owner = THIS_MODULE; - add_mtd_device(mtd); + mtd_device_register(mtd, NULL, 0); pci_set_drvdata(dev, mtd); @@ -344,43 +305,29 @@ out: return err; } -static void __devexit -mtd_pci_remove(struct pci_dev *dev) +static void mtd_pci_remove(struct pci_dev *dev) { struct mtd_info *mtd = pci_get_drvdata(dev); struct map_pci_info *map = mtd->priv; - del_mtd_device(mtd); + mtd_device_unregister(mtd); map_destroy(mtd); map->exit(dev, map); kfree(map); - pci_set_drvdata(dev, NULL); pci_release_regions(dev); } static struct pci_driver mtd_pci_driver = { .name = "MTD PCI", .probe = mtd_pci_probe, - .remove = __devexit_p(mtd_pci_remove), + .remove = mtd_pci_remove, .id_table = mtd_pci_ids, }; -static int __init mtd_pci_maps_init(void) -{ - return pci_register_driver(&mtd_pci_driver); -} - -static void __exit mtd_pci_maps_exit(void) -{ - pci_unregister_driver(&mtd_pci_driver); -} - -module_init(mtd_pci_maps_init); -module_exit(mtd_pci_maps_exit); +module_pci_driver(mtd_pci_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); MODULE_DESCRIPTION("Generic PCI map driver"); MODULE_DEVICE_TABLE(pci, mtd_pci_ids); - diff --git a/drivers/mtd/maps/pcmciamtd.c b/drivers/mtd/maps/pcmciamtd.c index 995347b1beb..a3cfad392ed 100644 --- a/drivers/mtd/maps/pcmciamtd.c +++ b/drivers/mtd/maps/pcmciamtd.c @@ -1,6 +1,4 @@ /* - * $Id: pcmciamtd.c,v 1.55 2005/11/07 11:14:28 gleixner Exp $ - * * pcmciamtd.c - MTD driver for PCMCIA flash memory cards * * Author: Simon Evans <spse@secret.org.uk> @@ -16,46 +14,22 @@ #include <linux/timer.h> #include <linux/init.h> #include <asm/io.h> -#include <asm/system.h> -#include <pcmcia/cs_types.h> -#include <pcmcia/cs.h> #include <pcmcia/cistpl.h> #include <pcmcia/ds.h> #include <linux/mtd/map.h> #include <linux/mtd/mtd.h> -#ifdef CONFIG_MTD_DEBUG -static int debug = CONFIG_MTD_DEBUG_VERBOSE; -module_param(debug, int, 0); -MODULE_PARM_DESC(debug, "Set Debug Level 0=quiet, 5=noisy"); -#undef DEBUG -#define DEBUG(n, format, arg...) \ - if (n <= debug) { \ - printk(KERN_DEBUG __FILE__ ":%s(): " format "\n", __FUNCTION__ , ## arg); \ - } - -#else -#undef DEBUG -#define DEBUG(n, arg...) -static const int debug = 0; -#endif - -#define err(format, arg...) printk(KERN_ERR "pcmciamtd: " format "\n" , ## arg) #define info(format, arg...) printk(KERN_INFO "pcmciamtd: " format "\n" , ## arg) -#define warn(format, arg...) printk(KERN_WARNING "pcmciamtd: " format "\n" , ## arg) - #define DRIVER_DESC "PCMCIA Flash memory card driver" -#define DRIVER_VERSION "$Revision: 1.55 $" /* Size of the PCMCIA address space: 26 bits = 64 MB */ #define MAX_PCMCIA_ADDR 0x4000000 struct pcmciamtd_dev { struct pcmcia_device *p_dev; - dev_node_t node; /* device node */ caddr_t win_base; /* ioremapped address of PCMCIA window */ unsigned int win_size; /* size of window */ unsigned int offset; /* offset into card the window currently points at */ @@ -103,29 +77,29 @@ module_param(mem_type, int, 0); MODULE_PARM_DESC(mem_type, "Set Memory type (0=Flash, 1=RAM, 2=ROM, default=0)"); -/* read/write{8,16} copy_{from,to} routines with window remapping to access whole card */ +/* read/write{8,16} copy_{from,to} routines with window remapping + * to access whole card + */ static caddr_t remap_window(struct map_info *map, unsigned long to) { struct pcmciamtd_dev *dev = (struct pcmciamtd_dev *)map->map_priv_1; - window_handle_t win = (window_handle_t)map->map_priv_2; - memreq_t mrq; + struct resource *win = (struct resource *) map->map_priv_2; + unsigned int offset; int ret; if (!pcmcia_dev_present(dev->p_dev)) { - DEBUG(1, "device removed"); + pr_debug("device removed\n"); return 0; } - mrq.CardOffset = to & ~(dev->win_size-1); - if(mrq.CardOffset != dev->offset) { - DEBUG(2, "Remapping window from 0x%8.8x to 0x%8.8x", - dev->offset, mrq.CardOffset); - mrq.Page = 0; - if( (ret = pcmcia_map_mem_page(win, &mrq)) != CS_SUCCESS) { - cs_error(dev->p_dev, MapMemPage, ret); + offset = to & ~(dev->win_size-1); + if (offset != dev->offset) { + pr_debug("Remapping window from 0x%8.8x to 0x%8.8x\n", + dev->offset, offset); + ret = pcmcia_map_mem_page(dev->p_dev, win, offset); + if (ret != 0) return NULL; - } - dev->offset = mrq.CardOffset; + dev->offset = offset; } return dev->win_base + (to & (dev->win_size-1)); } @@ -141,7 +115,7 @@ static map_word pcmcia_read8_remap(struct map_info *map, unsigned long ofs) return d; d.x[0] = readb(addr); - DEBUG(3, "ofs = 0x%08lx (%p) data = 0x%02x", ofs, addr, d.x[0]); + pr_debug("ofs = 0x%08lx (%p) data = 0x%02lx\n", ofs, addr, d.x[0]); return d; } @@ -156,7 +130,7 @@ static map_word pcmcia_read16_remap(struct map_info *map, unsigned long ofs) return d; d.x[0] = readw(addr); - DEBUG(3, "ofs = 0x%08lx (%p) data = 0x%04x", ofs, addr, d.x[0]); + pr_debug("ofs = 0x%08lx (%p) data = 0x%04lx\n", ofs, addr, d.x[0]); return d; } @@ -166,7 +140,7 @@ static void pcmcia_copy_from_remap(struct map_info *map, void *to, unsigned long struct pcmciamtd_dev *dev = (struct pcmciamtd_dev *)map->map_priv_1; unsigned long win_size = dev->win_size; - DEBUG(3, "to = %p from = %lu len = %u", to, from, len); + pr_debug("to = %p from = %lu len = %zd\n", to, from, len); while(len) { int toread = win_size - (from & (win_size-1)); caddr_t addr; @@ -178,7 +152,7 @@ static void pcmcia_copy_from_remap(struct map_info *map, void *to, unsigned long if(!addr) return; - DEBUG(4, "memcpy from %p to %p len = %d", addr, to, toread); + pr_debug("memcpy from %p to %p len = %d\n", addr, to, toread); memcpy_fromio(to, addr, toread); len -= toread; to += toread; @@ -194,7 +168,7 @@ static void pcmcia_write8_remap(struct map_info *map, map_word d, unsigned long if(!addr) return; - DEBUG(3, "adr = 0x%08lx (%p) data = 0x%02x", adr, addr, d.x[0]); + pr_debug("adr = 0x%08lx (%p) data = 0x%02lx\n", adr, addr, d.x[0]); writeb(d.x[0], addr); } @@ -205,7 +179,7 @@ static void pcmcia_write16_remap(struct map_info *map, map_word d, unsigned long if(!addr) return; - DEBUG(3, "adr = 0x%08lx (%p) data = 0x%04x", adr, addr, d.x[0]); + pr_debug("adr = 0x%08lx (%p) data = 0x%04lx\n", adr, addr, d.x[0]); writew(d.x[0], addr); } @@ -215,7 +189,7 @@ static void pcmcia_copy_to_remap(struct map_info *map, unsigned long to, const v struct pcmciamtd_dev *dev = (struct pcmciamtd_dev *)map->map_priv_1; unsigned long win_size = dev->win_size; - DEBUG(3, "to = %lu from = %p len = %u", to, from, len); + pr_debug("to = %lu from = %p len = %zd\n", to, from, len); while(len) { int towrite = win_size - (to & (win_size-1)); caddr_t addr; @@ -227,7 +201,7 @@ static void pcmcia_copy_to_remap(struct map_info *map, unsigned long to, const v if(!addr) return; - DEBUG(4, "memcpy from %p to %p len = %d", from, addr, towrite); + pr_debug("memcpy from %p to %p len = %d\n", from, addr, towrite); memcpy_toio(addr, from, towrite); len -= towrite; to += towrite; @@ -249,7 +223,8 @@ static map_word pcmcia_read8(struct map_info *map, unsigned long ofs) return d; d.x[0] = readb(win_base + ofs); - DEBUG(3, "ofs = 0x%08lx (%p) data = 0x%02x", ofs, win_base + ofs, d.x[0]); + pr_debug("ofs = 0x%08lx (%p) data = 0x%02lx\n", + ofs, win_base + ofs, d.x[0]); return d; } @@ -263,7 +238,8 @@ static map_word pcmcia_read16(struct map_info *map, unsigned long ofs) return d; d.x[0] = readw(win_base + ofs); - DEBUG(3, "ofs = 0x%08lx (%p) data = 0x%04x", ofs, win_base + ofs, d.x[0]); + pr_debug("ofs = 0x%08lx (%p) data = 0x%04lx\n", + ofs, win_base + ofs, d.x[0]); return d; } @@ -275,32 +251,34 @@ static void pcmcia_copy_from(struct map_info *map, void *to, unsigned long from, if(DEV_REMOVED(map)) return; - DEBUG(3, "to = %p from = %lu len = %u", to, from, len); + pr_debug("to = %p from = %lu len = %zd\n", to, from, len); memcpy_fromio(to, win_base + from, len); } -static void pcmcia_write8(struct map_info *map, u8 d, unsigned long adr) +static void pcmcia_write8(struct map_info *map, map_word d, unsigned long adr) { caddr_t win_base = (caddr_t)map->map_priv_2; if(DEV_REMOVED(map)) return; - DEBUG(3, "adr = 0x%08lx (%p) data = 0x%02x", adr, win_base + adr, d); - writeb(d, win_base + adr); + pr_debug("adr = 0x%08lx (%p) data = 0x%02lx\n", + adr, win_base + adr, d.x[0]); + writeb(d.x[0], win_base + adr); } -static void pcmcia_write16(struct map_info *map, u16 d, unsigned long adr) +static void pcmcia_write16(struct map_info *map, map_word d, unsigned long adr) { caddr_t win_base = (caddr_t)map->map_priv_2; if(DEV_REMOVED(map)) return; - DEBUG(3, "adr = 0x%08lx (%p) data = 0x%04x", adr, win_base + adr, d); - writew(d, win_base + adr); + pr_debug("adr = 0x%08lx (%p) data = 0x%04lx\n", + adr, win_base + adr, d.x[0]); + writew(d.x[0], win_base + adr); } @@ -311,147 +289,149 @@ static void pcmcia_copy_to(struct map_info *map, unsigned long to, const void *f if(DEV_REMOVED(map)) return; - DEBUG(3, "to = %lu from = %p len = %u", to, from, len); + pr_debug("to = %lu from = %p len = %zd\n", to, from, len); memcpy_toio(win_base + to, from, len); } +static DEFINE_SPINLOCK(pcmcia_vpp_lock); +static int pcmcia_vpp_refcnt; static void pcmciamtd_set_vpp(struct map_info *map, int on) { struct pcmciamtd_dev *dev = (struct pcmciamtd_dev *)map->map_priv_1; struct pcmcia_device *link = dev->p_dev; - modconf_t mod; - int ret; + unsigned long flags; - mod.Attributes = CONF_VPP1_CHANGE_VALID | CONF_VPP2_CHANGE_VALID; - mod.Vcc = 0; - mod.Vpp1 = mod.Vpp2 = on ? dev->vpp : 0; - - DEBUG(2, "dev = %p on = %d vpp = %d\n", dev, on, dev->vpp); - ret = pcmcia_modify_configuration(link, &mod); - if(ret != CS_SUCCESS) { - cs_error(link, ModifyConfiguration, ret); + pr_debug("dev = %p on = %d vpp = %d\n\n", dev, on, dev->vpp); + spin_lock_irqsave(&pcmcia_vpp_lock, flags); + if (on) { + if (++pcmcia_vpp_refcnt == 1) /* first nested 'on' */ + pcmcia_fixup_vpp(link, dev->vpp); + } else { + if (--pcmcia_vpp_refcnt == 0) /* last nested 'off' */ + pcmcia_fixup_vpp(link, 0); } + spin_unlock_irqrestore(&pcmcia_vpp_lock, flags); } -/* After a card is removed, pcmciamtd_release() will unregister the - * device, and release the PCMCIA configuration. If the device is - * still open, this will be postponed until it is closed. - */ - static void pcmciamtd_release(struct pcmcia_device *link) { struct pcmciamtd_dev *dev = link->priv; - DEBUG(3, "link = 0x%p", link); + pr_debug("link = 0x%p\n", link); - if (link->win) { + if (link->resource[2]->end) { if(dev->win_base) { iounmap(dev->win_base); dev->win_base = NULL; } - pcmcia_release_window(link->win); } pcmcia_disable_device(link); } -static void card_settings(struct pcmciamtd_dev *dev, struct pcmcia_device *link, int *new_name) +static int pcmciamtd_cistpl_format(struct pcmcia_device *p_dev, + tuple_t *tuple, + void *priv_data) { - int rc; - tuple_t tuple; cisparse_t parse; - u_char buf[64]; - - tuple.Attributes = 0; - tuple.TupleData = (cisdata_t *)buf; - tuple.TupleDataMax = sizeof(buf); - tuple.TupleOffset = 0; - tuple.DesiredTuple = RETURN_FIRST_TUPLE; - - rc = pcmcia_get_first_tuple(link, &tuple); - while(rc == CS_SUCCESS) { - rc = pcmcia_get_tuple_data(link, &tuple); - if(rc != CS_SUCCESS) { - cs_error(link, GetTupleData, rc); - break; - } - rc = pcmcia_parse_tuple(link, &tuple, &parse); - if(rc != CS_SUCCESS) { - cs_error(link, ParseTuple, rc); - break; - } - switch(tuple.TupleCode) { - case CISTPL_FORMAT: { - cistpl_format_t *t = &parse.format; - (void)t; /* Shut up, gcc */ - DEBUG(2, "Format type: %u, Error Detection: %u, offset = %u, length =%u", - t->type, t->edc, t->offset, t->length); - break; + if (!pcmcia_parse_tuple(tuple, &parse)) { + cistpl_format_t *t = &parse.format; + (void)t; /* Shut up, gcc */ + pr_debug("Format type: %u, Error Detection: %u, offset = %u, length =%u\n", + t->type, t->edc, t->offset, t->length); + } + return -ENOSPC; +} - } +static int pcmciamtd_cistpl_jedec(struct pcmcia_device *p_dev, + tuple_t *tuple, + void *priv_data) +{ + cisparse_t parse; + int i; - case CISTPL_DEVICE: { - cistpl_device_t *t = &parse.device; - int i; - DEBUG(2, "Common memory:"); - dev->pcmcia_map.size = t->dev[0].size; - for(i = 0; i < t->ndev; i++) { - DEBUG(2, "Region %d, type = %u", i, t->dev[i].type); - DEBUG(2, "Region %d, wp = %u", i, t->dev[i].wp); - DEBUG(2, "Region %d, speed = %u ns", i, t->dev[i].speed); - DEBUG(2, "Region %d, size = %u bytes", i, t->dev[i].size); - } - break; - } + if (!pcmcia_parse_tuple(tuple, &parse)) { + cistpl_jedec_t *t = &parse.jedec; + for (i = 0; i < t->nid; i++) + pr_debug("JEDEC: 0x%02x 0x%02x\n", + t->id[i].mfr, t->id[i].info); + } + return -ENOSPC; +} - case CISTPL_VERS_1: { - cistpl_vers_1_t *t = &parse.version_1; - int i; - if(t->ns) { - dev->mtd_name[0] = '\0'; - for(i = 0; i < t->ns; i++) { - if(i) - strcat(dev->mtd_name, " "); - strcat(dev->mtd_name, t->str+t->ofs[i]); - } - } - DEBUG(2, "Found name: %s", dev->mtd_name); - break; - } +static int pcmciamtd_cistpl_device(struct pcmcia_device *p_dev, + tuple_t *tuple, + void *priv_data) +{ + struct pcmciamtd_dev *dev = priv_data; + cisparse_t parse; + cistpl_device_t *t = &parse.device; + int i; - case CISTPL_JEDEC_C: { - cistpl_jedec_t *t = &parse.jedec; - int i; - for(i = 0; i < t->nid; i++) { - DEBUG(2, "JEDEC: 0x%02x 0x%02x", t->id[i].mfr, t->id[i].info); - } - break; - } + if (pcmcia_parse_tuple(tuple, &parse)) + return -EINVAL; + + pr_debug("Common memory:\n"); + dev->pcmcia_map.size = t->dev[0].size; + /* from here on: DEBUG only */ + for (i = 0; i < t->ndev; i++) { + pr_debug("Region %d, type = %u\n", i, t->dev[i].type); + pr_debug("Region %d, wp = %u\n", i, t->dev[i].wp); + pr_debug("Region %d, speed = %u ns\n", i, t->dev[i].speed); + pr_debug("Region %d, size = %u bytes\n", i, t->dev[i].size); + } + return 0; +} - case CISTPL_DEVICE_GEO: { - cistpl_device_geo_t *t = &parse.device_geo; - int i; - dev->pcmcia_map.bankwidth = t->geo[0].buswidth; - for(i = 0; i < t->ngeo; i++) { - DEBUG(2, "region: %d bankwidth = %u", i, t->geo[i].buswidth); - DEBUG(2, "region: %d erase_block = %u", i, t->geo[i].erase_block); - DEBUG(2, "region: %d read_block = %u", i, t->geo[i].read_block); - DEBUG(2, "region: %d write_block = %u", i, t->geo[i].write_block); - DEBUG(2, "region: %d partition = %u", i, t->geo[i].partition); - DEBUG(2, "region: %d interleave = %u", i, t->geo[i].interleave); - } - break; - } +static int pcmciamtd_cistpl_geo(struct pcmcia_device *p_dev, + tuple_t *tuple, + void *priv_data) +{ + struct pcmciamtd_dev *dev = priv_data; + cisparse_t parse; + cistpl_device_geo_t *t = &parse.device_geo; + int i; - default: - DEBUG(2, "Unknown tuple code %d", tuple.TupleCode); - } + if (pcmcia_parse_tuple(tuple, &parse)) + return -EINVAL; + + dev->pcmcia_map.bankwidth = t->geo[0].buswidth; + /* from here on: DEBUG only */ + for (i = 0; i < t->ngeo; i++) { + pr_debug("region: %d bankwidth = %u\n", i, t->geo[i].buswidth); + pr_debug("region: %d erase_block = %u\n", i, t->geo[i].erase_block); + pr_debug("region: %d read_block = %u\n", i, t->geo[i].read_block); + pr_debug("region: %d write_block = %u\n", i, t->geo[i].write_block); + pr_debug("region: %d partition = %u\n", i, t->geo[i].partition); + pr_debug("region: %d interleave = %u\n", i, t->geo[i].interleave); + } + return 0; +} - rc = pcmcia_get_next_tuple(link, &tuple); + +static void card_settings(struct pcmciamtd_dev *dev, struct pcmcia_device *p_dev, int *new_name) +{ + int i; + + if (p_dev->prod_id[0]) { + dev->mtd_name[0] = '\0'; + for (i = 0; i < 4; i++) { + if (i) + strcat(dev->mtd_name, " "); + if (p_dev->prod_id[i]) + strcat(dev->mtd_name, p_dev->prod_id[i]); + } + pr_debug("Found name: %s\n", dev->mtd_name); } + + pcmcia_loop_tuple(p_dev, CISTPL_FORMAT, pcmciamtd_cistpl_format, NULL); + pcmcia_loop_tuple(p_dev, CISTPL_JEDEC_C, pcmciamtd_cistpl_jedec, NULL); + pcmcia_loop_tuple(p_dev, CISTPL_DEVICE, pcmciamtd_cistpl_device, dev); + pcmcia_loop_tuple(p_dev, CISTPL_DEVICE_GEO, pcmciamtd_cistpl_geo, dev); + if(!dev->pcmcia_map.size) dev->pcmcia_map.size = MAX_PCMCIA_ADDR; @@ -460,12 +440,12 @@ static void card_settings(struct pcmciamtd_dev *dev, struct pcmcia_device *link, if(force_size) { dev->pcmcia_map.size = force_size << 20; - DEBUG(2, "size forced to %dM", force_size); + pr_debug("size forced to %dM\n", force_size); } if(bankwidth) { dev->pcmcia_map.bankwidth = bankwidth; - DEBUG(2, "bankwidth forced to %d", bankwidth); + pr_debug("bankwidth forced to %d\n", bankwidth); } dev->pcmcia_map.name = dev->mtd_name; @@ -474,42 +454,22 @@ static void card_settings(struct pcmciamtd_dev *dev, struct pcmcia_device *link, *new_name = 1; } - DEBUG(1, "Device: Size: %lu Width:%d Name: %s", - dev->pcmcia_map.size, dev->pcmcia_map.bankwidth << 3, dev->mtd_name); + pr_debug("Device: Size: %lu Width:%d Name: %s\n", + dev->pcmcia_map.size, + dev->pcmcia_map.bankwidth << 3, dev->mtd_name); } -/* pcmciamtd_config() is scheduled to run after a CARD_INSERTION event - * is received, to configure the PCMCIA socket, and to make the - * MTD device available to the system. - */ - -#define CS_CHECK(fn, ret) \ -do { last_fn = (fn); if ((last_ret = (ret)) != 0) goto cs_failed; } while (0) - static int pcmciamtd_config(struct pcmcia_device *link) { struct pcmciamtd_dev *dev = link->priv; struct mtd_info *mtd = NULL; - cs_status_t status; - win_req_t req; - int last_ret = 0, last_fn = 0; int ret; - int i; - config_info_t t; + int i, j = 0; static char *probes[] = { "jedec_probe", "cfi_probe" }; - cisinfo_t cisinfo; int new_name = 0; - DEBUG(3, "link=0x%p", link); - - DEBUG(2, "Validating CIS"); - ret = pcmcia_validate_cis(link, &cisinfo); - if(ret != CS_SUCCESS) { - cs_error(link, GetTupleData, ret); - } else { - DEBUG(2, "ValidateCIS found %d chains", cisinfo.Chains); - } + pr_debug("link=0x%p\n", link); card_settings(dev, link, &new_name); @@ -526,82 +486,77 @@ static int pcmciamtd_config(struct pcmcia_device *link) if(setvpp == 1) dev->pcmcia_map.set_vpp = pcmciamtd_set_vpp; - /* Request a memory window for PCMCIA. Some architeures can map windows upto the maximum - that PCMCIA can support (64MiB) - this is ideal and we aim for a window the size of the - whole card - otherwise we try smaller windows until we succeed */ - - req.Attributes = WIN_MEMORY_TYPE_CM | WIN_ENABLE; - req.Attributes |= (dev->pcmcia_map.bankwidth == 1) ? WIN_DATA_WIDTH_8 : WIN_DATA_WIDTH_16; - req.Base = 0; - req.AccessSpeed = mem_speed; - link->win = (window_handle_t)link; - req.Size = (force_size) ? force_size << 20 : MAX_PCMCIA_ADDR; + /* Request a memory window for PCMCIA. Some architeures can map windows + * up to the maximum that PCMCIA can support (64MiB) - this is ideal and + * we aim for a window the size of the whole card - otherwise we try + * smaller windows until we succeed + */ + + link->resource[2]->flags |= WIN_MEMORY_TYPE_CM | WIN_ENABLE; + link->resource[2]->flags |= (dev->pcmcia_map.bankwidth == 1) ? + WIN_DATA_WIDTH_8 : WIN_DATA_WIDTH_16; + link->resource[2]->start = 0; + link->resource[2]->end = (force_size) ? force_size << 20 : + MAX_PCMCIA_ADDR; dev->win_size = 0; do { int ret; - DEBUG(2, "requesting window with size = %dKiB memspeed = %d", - req.Size >> 10, req.AccessSpeed); - ret = pcmcia_request_window(&link, &req, &link->win); - DEBUG(2, "ret = %d dev->win_size = %d", ret, dev->win_size); + pr_debug("requesting window with size = %luKiB memspeed = %d\n", + (unsigned long) resource_size(link->resource[2]) >> 10, + mem_speed); + ret = pcmcia_request_window(link, link->resource[2], mem_speed); + pr_debug("ret = %d dev->win_size = %d\n", ret, dev->win_size); if(ret) { - req.Size >>= 1; + j++; + link->resource[2]->start = 0; + link->resource[2]->end = (force_size) ? + force_size << 20 : MAX_PCMCIA_ADDR; + link->resource[2]->end >>= j; } else { - DEBUG(2, "Got window of size %dKiB", req.Size >> 10); - dev->win_size = req.Size; + pr_debug("Got window of size %luKiB\n", (unsigned long) + resource_size(link->resource[2]) >> 10); + dev->win_size = resource_size(link->resource[2]); break; } - } while(req.Size >= 0x1000); + } while (link->resource[2]->end >= 0x1000); - DEBUG(2, "dev->win_size = %d", dev->win_size); + pr_debug("dev->win_size = %d\n", dev->win_size); if(!dev->win_size) { - err("Cant allocate memory window"); + dev_err(&dev->p_dev->dev, "Cannot allocate memory window\n"); pcmciamtd_release(link); return -ENODEV; } - DEBUG(1, "Allocated a window of %dKiB", dev->win_size >> 10); + pr_debug("Allocated a window of %dKiB\n", dev->win_size >> 10); /* Get write protect status */ - CS_CHECK(GetStatus, pcmcia_get_status(link, &status)); - DEBUG(2, "status value: 0x%x window handle = 0x%8.8lx", - status.CardState, (unsigned long)link->win); - dev->win_base = ioremap(req.Base, req.Size); + dev->win_base = ioremap(link->resource[2]->start, + resource_size(link->resource[2])); if(!dev->win_base) { - err("ioremap(%lu, %u) failed", req.Base, req.Size); + dev_err(&dev->p_dev->dev, "ioremap(%pR) failed\n", + link->resource[2]); pcmciamtd_release(link); return -ENODEV; } - DEBUG(1, "mapped window dev = %p req.base = 0x%lx base = %p size = 0x%x", - dev, req.Base, dev->win_base, req.Size); + pr_debug("mapped window dev = %p @ %pR, base = %p\n", + dev, link->resource[2], dev->win_base); dev->offset = 0; dev->pcmcia_map.map_priv_1 = (unsigned long)dev; - dev->pcmcia_map.map_priv_2 = (unsigned long)link->win; + dev->pcmcia_map.map_priv_2 = (unsigned long)link->resource[2]; - DEBUG(2, "Getting configuration"); - CS_CHECK(GetConfigurationInfo, pcmcia_get_configuration_info(link, &t)); - DEBUG(2, "Vcc = %d Vpp1 = %d Vpp2 = %d", t.Vcc, t.Vpp1, t.Vpp2); - dev->vpp = (vpp) ? vpp : t.Vpp1; - link->conf.Attributes = 0; + dev->vpp = (vpp) ? vpp : link->socket->socket.Vpp; if(setvpp == 2) { - link->conf.Vpp = dev->vpp; + link->vpp = dev->vpp; } else { - link->conf.Vpp = 0; + link->vpp = 0; } - link->conf.IntType = INT_MEMORY; - link->conf.ConfigBase = t.ConfigBase; - link->conf.Status = t.Status; - link->conf.Pin = t.Pin; - link->conf.Copy = t.Copy; - link->conf.ExtStatus = t.ExtStatus; - link->conf.ConfigIndex = 0; - link->conf.Present = t.Present; - DEBUG(2, "Setting Configuration"); - ret = pcmcia_request_configuration(link, &link->conf); - if(ret != CS_SUCCESS) { - cs_error(link, RequestConfiguration, ret); + link->config_index = 0; + pr_debug("Setting Configuration\n"); + ret = pcmcia_enable_device(link); + if (ret != 0) { if (dev->win_base) { iounmap(dev->win_base); dev->win_base = NULL; @@ -615,17 +570,17 @@ static int pcmciamtd_config(struct pcmcia_device *link) mtd = do_map_probe("map_rom", &dev->pcmcia_map); } else { for(i = 0; i < ARRAY_SIZE(probes); i++) { - DEBUG(1, "Trying %s", probes[i]); + pr_debug("Trying %s\n", probes[i]); mtd = do_map_probe(probes[i], &dev->pcmcia_map); if(mtd) break; - DEBUG(1, "FAILED: %s", probes[i]); + pr_debug("FAILED: %s\n", probes[i]); } } if(!mtd) { - DEBUG(1, "Cant find an MTD"); + pr_debug("Can not find an MTD\n"); pcmciamtd_release(link); return -ENODEV; } @@ -636,8 +591,9 @@ static int pcmciamtd_config(struct pcmcia_device *link) if(new_name) { int size = 0; char unit = ' '; - /* Since we are using a default name, make it better by adding in the - size */ + /* Since we are using a default name, make it better by adding + * in the size + */ if(mtd->size < 1048576) { /* <1MiB in size, show size in KiB */ size = mtd->size >> 10; unit = 'K'; @@ -651,7 +607,7 @@ static int pcmciamtd_config(struct pcmcia_device *link) /* If the memory found is fits completely into the mapped PCMCIA window, use the faster non-remapping read/write functions */ if(mtd->size <= dev->win_size) { - DEBUG(1, "Using non remapping memory functions"); + pr_debug("Using non remapping memory functions\n"); dev->pcmcia_map.map_priv_2 = (unsigned long)dev->win_base; if (dev->pcmcia_map.bankwidth == 1) { dev->pcmcia_map.read = pcmcia_read8; @@ -664,29 +620,22 @@ static int pcmciamtd_config(struct pcmcia_device *link) dev->pcmcia_map.copy_to = pcmcia_copy_to; } - if(add_mtd_device(mtd)) { + if (mtd_device_register(mtd, NULL, 0)) { map_destroy(mtd); dev->mtd_info = NULL; - err("Couldnt register MTD device"); + dev_err(&dev->p_dev->dev, + "Could not register the MTD device\n"); pcmciamtd_release(link); return -ENODEV; } - snprintf(dev->node.dev_name, sizeof(dev->node.dev_name), "mtd%d", mtd->index); - info("mtd%d: %s", mtd->index, mtd->name); - link->dev_node = &dev->node; + dev_info(&dev->p_dev->dev, "mtd%d: %s\n", mtd->index, mtd->name); return 0; - - cs_failed: - cs_error(link, last_fn, last_ret); - err("CS Error, exiting"); - pcmciamtd_release(link); - return -ENODEV; } static int pcmciamtd_suspend(struct pcmcia_device *dev) { - DEBUG(2, "EVENT_PM_RESUME"); + pr_debug("EVENT_PM_RESUME\n"); /* get_lock(link); */ @@ -695,7 +644,7 @@ static int pcmciamtd_suspend(struct pcmcia_device *dev) static int pcmciamtd_resume(struct pcmcia_device *dev) { - DEBUG(2, "EVENT_PM_SUSPEND"); + pr_debug("EVENT_PM_SUSPEND\n"); /* free_lock(link); */ @@ -703,53 +652,39 @@ static int pcmciamtd_resume(struct pcmcia_device *dev) } -/* This deletes a driver "instance". The device is de-registered - * with Card Services. If it has been released, all local data - * structures are freed. Otherwise, the structures will be freed - * when the device is released. - */ - static void pcmciamtd_detach(struct pcmcia_device *link) { struct pcmciamtd_dev *dev = link->priv; - DEBUG(3, "link=0x%p", link); + pr_debug("link=0x%p\n", link); if(dev->mtd_info) { - del_mtd_device(dev->mtd_info); + mtd_device_unregister(dev->mtd_info); + dev_info(&dev->p_dev->dev, "mtd%d: Removing\n", + dev->mtd_info->index); map_destroy(dev->mtd_info); - info("mtd%d: Removed", dev->mtd_info->index); } pcmciamtd_release(link); } -/* pcmciamtd_attach() creates an "instance" of the driver, allocating - * local data structures for one device. The device is registered - * with Card Services. - */ - static int pcmciamtd_probe(struct pcmcia_device *link) { struct pcmciamtd_dev *dev; /* Create new memory card device */ - dev = kmalloc(sizeof(*dev), GFP_KERNEL); + dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; - DEBUG(1, "dev=0x%p", dev); + pr_debug("dev=0x%p\n", dev); - memset(dev, 0, sizeof(*dev)); dev->p_dev = link; link->priv = dev; - link->conf.Attributes = 0; - link->conf.IntType = INT_MEMORY; - return pcmciamtd_config(link); } -static struct pcmcia_device_id pcmciamtd_ids[] = { +static const struct pcmcia_device_id pcmciamtd_ids[] = { PCMCIA_DEVICE_FUNC_ID(1), PCMCIA_DEVICE_PROD_ID123("IO DATA", "PCS-2M", "2MB SRAM", 0x547e66dc, 0x1fed36cd, 0x36eadd21), PCMCIA_DEVICE_PROD_ID12("IBM", "2MB SRAM", 0xb569a6e5, 0x36eadd21), @@ -763,8 +698,11 @@ static struct pcmcia_device_id pcmciamtd_ids[] = { PCMCIA_DEVICE_PROD_ID12("intel", "VALUE SERIES 100 ", 0x40ade711, 0xdf8506d8), PCMCIA_DEVICE_PROD_ID12("KINGMAX TECHNOLOGY INC.", "SRAM 256K Bytes", 0x54d0c69c, 0xad12c29c), PCMCIA_DEVICE_PROD_ID12("Maxtor", "MAXFL MobileMax Flash Memory Card", 0xb68968c8, 0x2dfb47b0), + PCMCIA_DEVICE_PROD_ID123("M-Systems", "M-SYS Flash Memory Card", "(c) M-Systems", 0x7ed2ad87, 0x675dc3fb, 0x7aef3965), + PCMCIA_DEVICE_PROD_ID12("PRETEC", " 2MB SRAM CARD", 0xebf91155, 0x805360ca), PCMCIA_DEVICE_PROD_ID12("SEIKO EPSON", "WWB101EN20", 0xf9876baf, 0xad0b207b), PCMCIA_DEVICE_PROD_ID12("SEIKO EPSON", "WWB513EN20", 0xf9876baf, 0xe8d884ad), + PCMCIA_DEVICE_PROD_ID12("SMART Modular Technologies", " 4MB FLASH Card", 0x96fd8277, 0x737a5b05), PCMCIA_DEVICE_PROD_ID12("Starfish, Inc.", "REX-3000", 0x05ddca47, 0xe7d67bca), PCMCIA_DEVICE_PROD_ID12("Starfish, Inc.", "REX-4100", 0x05ddca47, 0x7bc32944), /* the following was commented out in pcmcia-cs-3.2.7 */ @@ -777,9 +715,7 @@ static struct pcmcia_device_id pcmciamtd_ids[] = { MODULE_DEVICE_TABLE(pcmcia, pcmciamtd_ids); static struct pcmcia_driver pcmciamtd_driver = { - .drv = { - .name = "pcmciamtd" - }, + .name = "pcmciamtd", .probe = pcmciamtd_probe, .remove = pcmciamtd_detach, .owner = THIS_MODULE, @@ -791,8 +727,6 @@ static struct pcmcia_driver pcmciamtd_driver = { static int __init init_pcmciamtd(void) { - info(DRIVER_DESC " " DRIVER_VERSION); - if(bankwidth && bankwidth != 1 && bankwidth != 2) { info("bad bankwidth (%d), using default", bankwidth); bankwidth = 2; @@ -811,7 +745,7 @@ static int __init init_pcmciamtd(void) static void __exit exit_pcmciamtd(void) { - DEBUG(1, DRIVER_DESC " unloading"); + pr_debug(DRIVER_DESC " unloading"); pcmcia_unregister_driver(&pcmciamtd_driver); } diff --git a/drivers/mtd/maps/physmap.c b/drivers/mtd/maps/physmap.c index bc7cc71788b..f73cd461257 100644 --- a/drivers/mtd/maps/physmap.c +++ b/drivers/mtd/maps/physmap.c @@ -1,6 +1,4 @@ /* - * $Id: physmap.c,v 1.39 2005/11/29 14:49:36 gleixner Exp $ - * * Normal mappings of chips in physical memory * * Copyright (C) 2003 MontaVista Software Inc. @@ -20,137 +18,178 @@ #include <linux/mtd/map.h> #include <linux/mtd/partitions.h> #include <linux/mtd/physmap.h> -#include <asm/io.h> +#include <linux/mtd/concat.h> +#include <linux/io.h> + +#define MAX_RESOURCES 4 struct physmap_flash_info { - struct mtd_info *mtd; - struct map_info map; - struct resource *res; -#ifdef CONFIG_MTD_PARTITIONS - int nr_parts; - struct mtd_partition *parts; -#endif + struct mtd_info *mtd[MAX_RESOURCES]; + struct mtd_info *cmtd; + struct map_info map[MAX_RESOURCES]; + spinlock_t vpp_lock; + int vpp_refcnt; }; - static int physmap_flash_remove(struct platform_device *dev) { struct physmap_flash_info *info; struct physmap_flash_data *physmap_data; + int i; info = platform_get_drvdata(dev); if (info == NULL) return 0; - platform_set_drvdata(dev, NULL); - physmap_data = dev->dev.platform_data; + physmap_data = dev_get_platdata(&dev->dev); - if (info->mtd != NULL) { -#ifdef CONFIG_MTD_PARTITIONS - if (info->nr_parts) { - del_mtd_partitions(info->mtd); - kfree(info->parts); - } else if (physmap_data->nr_parts) { - del_mtd_partitions(info->mtd); - } else { - del_mtd_device(info->mtd); - } -#else - del_mtd_device(info->mtd); -#endif - map_destroy(info->mtd); + if (info->cmtd) { + mtd_device_unregister(info->cmtd); + if (info->cmtd != info->mtd[0]) + mtd_concat_destroy(info->cmtd); } - if (info->map.virt != NULL) - iounmap((void *)info->map.virt); - - if (info->res != NULL) { - release_resource(info->res); - kfree(info->res); + for (i = 0; i < MAX_RESOURCES; i++) { + if (info->mtd[i] != NULL) + map_destroy(info->mtd[i]); } + if (physmap_data->exit) + physmap_data->exit(dev); + return 0; } -static const char *rom_probe_types[] = { "cfi_probe", "jedec_probe", "map_rom", NULL }; -#ifdef CONFIG_MTD_PARTITIONS -static const char *part_probe_types[] = { "cmdlinepart", "RedBoot", NULL }; -#endif +static void physmap_set_vpp(struct map_info *map, int state) +{ + struct platform_device *pdev; + struct physmap_flash_data *physmap_data; + struct physmap_flash_info *info; + unsigned long flags; + + pdev = (struct platform_device *)map->map_priv_1; + physmap_data = dev_get_platdata(&pdev->dev); + + if (!physmap_data->set_vpp) + return; + + info = platform_get_drvdata(pdev); + + spin_lock_irqsave(&info->vpp_lock, flags); + if (state) { + if (++info->vpp_refcnt == 1) /* first nested 'on' */ + physmap_data->set_vpp(pdev, 1); + } else { + if (--info->vpp_refcnt == 0) /* last nested 'off' */ + physmap_data->set_vpp(pdev, 0); + } + spin_unlock_irqrestore(&info->vpp_lock, flags); +} + +static const char * const rom_probe_types[] = { + "cfi_probe", "jedec_probe", "qinfo_probe", "map_rom", NULL }; + +static const char * const part_probe_types[] = { + "cmdlinepart", "RedBoot", "afs", NULL }; static int physmap_flash_probe(struct platform_device *dev) { struct physmap_flash_data *physmap_data; struct physmap_flash_info *info; - const char **probe_type; - int err; + const char * const *probe_type; + const char * const *part_types; + int err = 0; + int i; + int devices_found = 0; - physmap_data = dev->dev.platform_data; + physmap_data = dev_get_platdata(&dev->dev); if (physmap_data == NULL) return -ENODEV; - printk(KERN_NOTICE "physmap platform flash device: %.8llx at %.8llx\n", - (unsigned long long)dev->resource->end - dev->resource->start + 1, - (unsigned long long)dev->resource->start); - - info = kmalloc(sizeof(struct physmap_flash_info), GFP_KERNEL); + info = devm_kzalloc(&dev->dev, sizeof(struct physmap_flash_info), + GFP_KERNEL); if (info == NULL) { err = -ENOMEM; goto err_out; } - memset(info, 0, sizeof(*info)); + + if (physmap_data->init) { + err = physmap_data->init(dev); + if (err) + goto err_out; + } platform_set_drvdata(dev, info); - info->res = request_mem_region(dev->resource->start, - dev->resource->end - dev->resource->start + 1, - dev->dev.bus_id); - if (info->res == NULL) { - dev_err(&dev->dev, "Could not reserve memory region\n"); - err = -ENOMEM; - goto err_out; - } + for (i = 0; i < dev->num_resources; i++) { + printk(KERN_NOTICE "physmap platform flash device: %.8llx at %.8llx\n", + (unsigned long long)resource_size(&dev->resource[i]), + (unsigned long long)dev->resource[i].start); + + if (!devm_request_mem_region(&dev->dev, + dev->resource[i].start, + resource_size(&dev->resource[i]), + dev_name(&dev->dev))) { + dev_err(&dev->dev, "Could not reserve memory region\n"); + err = -ENOMEM; + goto err_out; + } - info->map.name = dev->dev.bus_id; - info->map.phys = dev->resource->start; - info->map.size = dev->resource->end - dev->resource->start + 1; - info->map.bankwidth = physmap_data->width; - info->map.set_vpp = physmap_data->set_vpp; + info->map[i].name = dev_name(&dev->dev); + info->map[i].phys = dev->resource[i].start; + info->map[i].size = resource_size(&dev->resource[i]); + info->map[i].bankwidth = physmap_data->width; + info->map[i].set_vpp = physmap_set_vpp; + info->map[i].pfow_base = physmap_data->pfow_base; + info->map[i].map_priv_1 = (unsigned long)dev; + + info->map[i].virt = devm_ioremap(&dev->dev, info->map[i].phys, + info->map[i].size); + if (info->map[i].virt == NULL) { + dev_err(&dev->dev, "Failed to ioremap flash region\n"); + err = -EIO; + goto err_out; + } - info->map.virt = ioremap(info->map.phys, info->map.size); - if (info->map.virt == NULL) { - dev_err(&dev->dev, "Failed to ioremap flash region\n"); - err = EIO; - goto err_out; - } + simple_map_init(&info->map[i]); - simple_map_init(&info->map); + probe_type = rom_probe_types; + if (physmap_data->probe_type == NULL) { + for (; info->mtd[i] == NULL && *probe_type != NULL; probe_type++) + info->mtd[i] = do_map_probe(*probe_type, &info->map[i]); + } else + info->mtd[i] = do_map_probe(physmap_data->probe_type, &info->map[i]); - probe_type = rom_probe_types; - for (; info->mtd == NULL && *probe_type != NULL; probe_type++) - info->mtd = do_map_probe(*probe_type, &info->map); - if (info->mtd == NULL) { - dev_err(&dev->dev, "map_probe failed\n"); - err = -ENXIO; - goto err_out; + if (info->mtd[i] == NULL) { + dev_err(&dev->dev, "map_probe failed\n"); + err = -ENXIO; + goto err_out; + } else { + devices_found++; + } + info->mtd[i]->owner = THIS_MODULE; + info->mtd[i]->dev.parent = &dev->dev; } - info->mtd->owner = THIS_MODULE; -#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); - return 0; + if (devices_found == 1) { + info->cmtd = info->mtd[0]; + } else if (devices_found > 1) { + /* + * We detected multiple devices. Concatenate them together. + */ + info->cmtd = mtd_concat_create(info->mtd, devices_found, dev_name(&dev->dev)); + if (info->cmtd == NULL) + err = -ENXIO; } + if (err) + goto err_out; - if (physmap_data->nr_parts) { - printk(KERN_NOTICE "Using physmap partition information\n"); - add_mtd_partitions(info->mtd, physmap_data->parts, - physmap_data->nr_parts); - return 0; - } -#endif + spin_lock_init(&info->vpp_lock); + + part_types = physmap_data->part_probe_types ? : part_probe_types; - add_mtd_device(info->mtd); + mtd_device_parse_register(info->cmtd, part_types, NULL, + physmap_data->parts, physmap_data->nr_parts); return 0; err_out: @@ -159,55 +198,31 @@ err_out: } #ifdef CONFIG_PM -static int physmap_flash_suspend(struct platform_device *dev, pm_message_t state) -{ - struct physmap_flash_info *info = platform_get_drvdata(dev); - int ret = 0; - - if (info) - ret = info->mtd->suspend(info->mtd); - - return ret; -} - -static int physmap_flash_resume(struct platform_device *dev) -{ - struct physmap_flash_info *info = platform_get_drvdata(dev); - if (info) - info->mtd->resume(info->mtd); - return 0; -} - static void physmap_flash_shutdown(struct platform_device *dev) { struct physmap_flash_info *info = platform_get_drvdata(dev); - if (info && info->mtd->suspend(info->mtd) == 0) - info->mtd->resume(info->mtd); + int i; + + for (i = 0; i < MAX_RESOURCES && info->mtd[i]; i++) + if (mtd_suspend(info->mtd[i]) == 0) + mtd_resume(info->mtd[i]); } +#else +#define physmap_flash_shutdown NULL #endif static struct platform_driver physmap_flash_driver = { .probe = physmap_flash_probe, .remove = physmap_flash_remove, -#ifdef CONFIG_PM - .suspend = physmap_flash_suspend, - .resume = physmap_flash_resume, .shutdown = physmap_flash_shutdown, -#endif .driver = { .name = "physmap-flash", + .owner = THIS_MODULE, }, }; -#ifdef CONFIG_MTD_PHYSMAP_LEN -#if CONFIG_MTD_PHYSMAP_LEN != 0 -#warning using PHYSMAP compat code -#define PHYSMAP_COMPAT -#endif -#endif - -#ifdef PHYSMAP_COMPAT +#ifdef CONFIG_MTD_PHYSMAP_COMPAT static struct physmap_flash_data physmap_flash_data = { .width = CONFIG_MTD_PHYSMAP_BANKWIDTH, }; @@ -227,23 +242,6 @@ static struct platform_device physmap_flash = { .num_resources = 1, .resource = &physmap_flash_resource, }; - -void physmap_configure(unsigned long addr, unsigned long size, - int bankwidth, void (*set_vpp)(struct map_info *, int)) -{ - physmap_flash_resource.start = addr; - physmap_flash_resource.end = addr + size - 1; - physmap_flash_data.width = bankwidth; - physmap_flash_data.set_vpp = set_vpp; -} - -#ifdef CONFIG_MTD_PARTITIONS -void physmap_set_partitions(struct mtd_partition *parts, int num_parts) -{ - physmap_flash_data.nr_parts = num_parts; - physmap_flash_data.parts = parts; -} -#endif #endif static int __init physmap_init(void) @@ -251,9 +249,12 @@ static int __init physmap_init(void) int err; err = platform_driver_register(&physmap_flash_driver); -#ifdef PHYSMAP_COMPAT - if (err == 0) - platform_device_register(&physmap_flash); +#ifdef CONFIG_MTD_PHYSMAP_COMPAT + if (err == 0) { + err = platform_device_register(&physmap_flash); + if (err) + platform_driver_unregister(&physmap_flash_driver); + } #endif return err; @@ -261,7 +262,7 @@ static int __init physmap_init(void) static void __exit physmap_exit(void) { -#ifdef PHYSMAP_COMPAT +#ifdef CONFIG_MTD_PHYSMAP_COMPAT platform_device_unregister(&physmap_flash); #endif platform_driver_unregister(&physmap_flash_driver); @@ -273,3 +274,9 @@ module_exit(physmap_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); MODULE_DESCRIPTION("Generic configurable MTD map driver"); + +/* legacy platform drivers can't hotplug or coldplg */ +#ifndef CONFIG_MTD_PHYSMAP_COMPAT +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:physmap-flash"); +#endif diff --git a/drivers/mtd/maps/physmap_of.c b/drivers/mtd/maps/physmap_of.c new file mode 100644 index 00000000000..217c25d7381 --- /dev/null +++ b/drivers/mtd/maps/physmap_of.c @@ -0,0 +1,364 @@ +/* + * Flash mappings described by the OF (or flattened) device tree + * + * Copyright (C) 2006 MontaVista Software Inc. + * Author: Vitaly Wool <vwool@ru.mvista.com> + * + * Revised to handle newer style flash binding by: + * Copyright (C) 2007 David Gibson, IBM Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/concat.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/slab.h> + +struct of_flash_list { + struct mtd_info *mtd; + struct map_info map; + struct resource *res; +}; + +struct of_flash { + struct mtd_info *cmtd; + int list_size; /* number of elements in of_flash_list */ + struct of_flash_list list[0]; +}; + +static int of_flash_remove(struct platform_device *dev) +{ + struct of_flash *info; + int i; + + info = dev_get_drvdata(&dev->dev); + if (!info) + return 0; + dev_set_drvdata(&dev->dev, NULL); + + if (info->cmtd != info->list[0].mtd) { + mtd_device_unregister(info->cmtd); + mtd_concat_destroy(info->cmtd); + } + + if (info->cmtd) + mtd_device_unregister(info->cmtd); + + for (i = 0; i < info->list_size; i++) { + if (info->list[i].mtd) + map_destroy(info->list[i].mtd); + + if (info->list[i].map.virt) + iounmap(info->list[i].map.virt); + + if (info->list[i].res) { + release_resource(info->list[i].res); + kfree(info->list[i].res); + } + } + return 0; +} + +static const char * const rom_probe_types[] = { + "cfi_probe", "jedec_probe", "map_rom" }; + +/* Helper function to handle probing of the obsolete "direct-mapped" + * compatible binding, which has an extra "probe-type" property + * describing the type of flash probe necessary. */ +static struct mtd_info *obsolete_probe(struct platform_device *dev, + struct map_info *map) +{ + struct device_node *dp = dev->dev.of_node; + const char *of_probe; + struct mtd_info *mtd; + int i; + + dev_warn(&dev->dev, "Device tree uses obsolete \"direct-mapped\" " + "flash binding\n"); + + of_probe = of_get_property(dp, "probe-type", NULL); + if (!of_probe) { + for (i = 0; i < ARRAY_SIZE(rom_probe_types); i++) { + mtd = do_map_probe(rom_probe_types[i], map); + if (mtd) + return mtd; + } + return NULL; + } else if (strcmp(of_probe, "CFI") == 0) { + return do_map_probe("cfi_probe", map); + } else if (strcmp(of_probe, "JEDEC") == 0) { + return do_map_probe("jedec_probe", map); + } else { + if (strcmp(of_probe, "ROM") != 0) + dev_warn(&dev->dev, "obsolete_probe: don't know probe " + "type '%s', mapping as rom\n", of_probe); + return do_map_probe("mtd_rom", map); + } +} + +/* When partitions are set we look for a linux,part-probe property which + specifies the list of partition probers to use. If none is given then the + default is use. These take precedence over other device tree + information. */ +static const char * const part_probe_types_def[] = { + "cmdlinepart", "RedBoot", "ofpart", "ofoldpart", NULL }; + +static const char * const *of_get_probes(struct device_node *dp) +{ + const char *cp; + int cplen; + unsigned int l; + unsigned int count; + const char **res; + + cp = of_get_property(dp, "linux,part-probe", &cplen); + if (cp == NULL) + return part_probe_types_def; + + count = 0; + for (l = 0; l != cplen; l++) + if (cp[l] == 0) + count++; + + res = kzalloc((count + 1)*sizeof(*res), GFP_KERNEL); + count = 0; + while (cplen > 0) { + res[count] = cp; + l = strlen(cp) + 1; + cp += l; + cplen -= l; + count++; + } + return res; +} + +static void of_free_probes(const char * const *probes) +{ + if (probes != part_probe_types_def) + kfree(probes); +} + +static struct of_device_id of_flash_match[]; +static int of_flash_probe(struct platform_device *dev) +{ + const char * const *part_probe_types; + const struct of_device_id *match; + struct device_node *dp = dev->dev.of_node; + struct resource res; + struct of_flash *info; + const char *probe_type; + const __be32 *width; + int err; + int i; + int count; + const __be32 *p; + int reg_tuple_size; + struct mtd_info **mtd_list = NULL; + resource_size_t res_size; + struct mtd_part_parser_data ppdata; + bool map_indirect; + const char *mtd_name = NULL; + + match = of_match_device(of_flash_match, &dev->dev); + if (!match) + return -EINVAL; + probe_type = match->data; + + reg_tuple_size = (of_n_addr_cells(dp) + of_n_size_cells(dp)) * sizeof(u32); + + of_property_read_string(dp, "linux,mtd-name", &mtd_name); + + /* + * Get number of "reg" tuples. Scan for MTD devices on area's + * described by each "reg" region. This makes it possible (including + * the concat support) to support the Intel P30 48F4400 chips which + * consists internally of 2 non-identical NOR chips on one die. + */ + p = of_get_property(dp, "reg", &count); + if (count % reg_tuple_size != 0) { + dev_err(&dev->dev, "Malformed reg property on %s\n", + dev->dev.of_node->full_name); + err = -EINVAL; + goto err_flash_remove; + } + count /= reg_tuple_size; + + map_indirect = of_property_read_bool(dp, "no-unaligned-direct-access"); + + err = -ENOMEM; + info = devm_kzalloc(&dev->dev, + sizeof(struct of_flash) + + sizeof(struct of_flash_list) * count, GFP_KERNEL); + if (!info) + goto err_flash_remove; + + dev_set_drvdata(&dev->dev, info); + + mtd_list = kzalloc(sizeof(*mtd_list) * count, GFP_KERNEL); + if (!mtd_list) + goto err_flash_remove; + + for (i = 0; i < count; i++) { + err = -ENXIO; + if (of_address_to_resource(dp, i, &res)) { + /* + * Continue with next register tuple if this + * one is not mappable + */ + continue; + } + + dev_dbg(&dev->dev, "of_flash device: %pR\n", &res); + + err = -EBUSY; + res_size = resource_size(&res); + info->list[i].res = request_mem_region(res.start, res_size, + dev_name(&dev->dev)); + if (!info->list[i].res) + goto err_out; + + err = -ENXIO; + width = of_get_property(dp, "bank-width", NULL); + if (!width) { + dev_err(&dev->dev, "Can't get bank width from device" + " tree\n"); + goto err_out; + } + + info->list[i].map.name = mtd_name ?: dev_name(&dev->dev); + info->list[i].map.phys = res.start; + info->list[i].map.size = res_size; + info->list[i].map.bankwidth = be32_to_cpup(width); + info->list[i].map.device_node = dp; + + err = -ENOMEM; + info->list[i].map.virt = ioremap(info->list[i].map.phys, + info->list[i].map.size); + if (!info->list[i].map.virt) { + dev_err(&dev->dev, "Failed to ioremap() flash" + " region\n"); + goto err_out; + } + + simple_map_init(&info->list[i].map); + + /* + * On some platforms (e.g. MPC5200) a direct 1:1 mapping + * may cause problems with JFFS2 usage, as the local bus (LPB) + * doesn't support unaligned accesses as implemented in the + * JFFS2 code via memcpy(). By setting NO_XIP, the + * flash will not be exposed directly to the MTD users + * (e.g. JFFS2) any more. + */ + if (map_indirect) + info->list[i].map.phys = NO_XIP; + + if (probe_type) { + info->list[i].mtd = do_map_probe(probe_type, + &info->list[i].map); + } else { + info->list[i].mtd = obsolete_probe(dev, + &info->list[i].map); + } + mtd_list[i] = info->list[i].mtd; + + err = -ENXIO; + if (!info->list[i].mtd) { + dev_err(&dev->dev, "do_map_probe() failed\n"); + goto err_out; + } else { + info->list_size++; + } + info->list[i].mtd->owner = THIS_MODULE; + info->list[i].mtd->dev.parent = &dev->dev; + } + + err = 0; + info->cmtd = NULL; + if (info->list_size == 1) { + info->cmtd = info->list[0].mtd; + } else if (info->list_size > 1) { + /* + * We detected multiple devices. Concatenate them together. + */ + info->cmtd = mtd_concat_create(mtd_list, info->list_size, + dev_name(&dev->dev)); + } + if (info->cmtd == NULL) + err = -ENXIO; + + if (err) + goto err_out; + + ppdata.of_node = dp; + part_probe_types = of_get_probes(dp); + mtd_device_parse_register(info->cmtd, part_probe_types, &ppdata, + NULL, 0); + of_free_probes(part_probe_types); + + kfree(mtd_list); + + return 0; + +err_out: + kfree(mtd_list); +err_flash_remove: + of_flash_remove(dev); + + return err; +} + +static struct of_device_id of_flash_match[] = { + { + .compatible = "cfi-flash", + .data = (void *)"cfi_probe", + }, + { + /* FIXME: JEDEC chips can't be safely and reliably + * probed, although the mtd code gets it right in + * practice most of the time. We should use the + * vendor and device ids specified by the binding to + * bypass the heuristic probe code, but the mtd layer + * provides, at present, no interface for doing so + * :(. */ + .compatible = "jedec-flash", + .data = (void *)"jedec_probe", + }, + { + .compatible = "mtd-ram", + .data = (void *)"map_ram", + }, + { + .type = "rom", + .compatible = "direct-mapped" + }, + { }, +}; +MODULE_DEVICE_TABLE(of, of_flash_match); + +static struct platform_driver of_flash_driver = { + .driver = { + .name = "of-flash", + .owner = THIS_MODULE, + .of_match_table = of_flash_match, + }, + .probe = of_flash_probe, + .remove = of_flash_remove, +}; + +module_platform_driver(of_flash_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Vitaly Wool <vwool@ru.mvista.com>"); +MODULE_DESCRIPTION("Device tree based MTD map driver"); diff --git a/drivers/mtd/maps/pismo.c b/drivers/mtd/maps/pismo.c new file mode 100644 index 00000000000..dc6df9abea0 --- /dev/null +++ b/drivers/mtd/maps/pismo.c @@ -0,0 +1,292 @@ +/* + * PISMO memory driver - http://www.pismoworld.org/ + * + * For ARM Realview and Versatile platforms + * + * 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. + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/mutex.h> +#include <linux/mtd/physmap.h> +#include <linux/mtd/plat-ram.h> +#include <linux/mtd/pismo.h> + +#define PISMO_NUM_CS 5 + +struct pismo_cs_block { + u8 type; + u8 width; + __le16 access; + __le32 size; + u32 reserved[2]; + char device[32]; +} __packed; + +struct pismo_eeprom { + struct pismo_cs_block cs[PISMO_NUM_CS]; + char board[15]; + u8 sum; +} __packed; + +struct pismo_mem { + phys_addr_t base; + u32 size; + u16 access; + u8 width; + u8 type; +}; + +struct pismo_data { + struct i2c_client *client; + void (*vpp)(void *, int); + void *vpp_data; + struct platform_device *dev[PISMO_NUM_CS]; +}; + +static void pismo_set_vpp(struct platform_device *pdev, int on) +{ + struct i2c_client *client = to_i2c_client(pdev->dev.parent); + struct pismo_data *pismo = i2c_get_clientdata(client); + + pismo->vpp(pismo->vpp_data, on); +} + +static unsigned int pismo_width_to_bytes(unsigned int width) +{ + width &= 15; + if (width > 2) + return 0; + return 1 << width; +} + +static int pismo_eeprom_read(struct i2c_client *client, void *buf, u8 addr, + size_t size) +{ + int ret; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .len = sizeof(addr), + .buf = &addr, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .len = size, + .buf = buf, + }, + }; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + + return ret == ARRAY_SIZE(msg) ? size : -EIO; +} + +static int pismo_add_device(struct pismo_data *pismo, int i, + struct pismo_mem *region, const char *name, + void *pdata, size_t psize) +{ + struct platform_device *dev; + struct resource res = { }; + phys_addr_t base = region->base; + int ret; + + if (base == ~0) + return -ENXIO; + + res.start = base; + res.end = base + region->size - 1; + res.flags = IORESOURCE_MEM; + + dev = platform_device_alloc(name, i); + if (!dev) + return -ENOMEM; + dev->dev.parent = &pismo->client->dev; + + do { + ret = platform_device_add_resources(dev, &res, 1); + if (ret) + break; + + ret = platform_device_add_data(dev, pdata, psize); + if (ret) + break; + + ret = platform_device_add(dev); + if (ret) + break; + + pismo->dev[i] = dev; + return 0; + } while (0); + + platform_device_put(dev); + return ret; +} + +static int pismo_add_nor(struct pismo_data *pismo, int i, + struct pismo_mem *region) +{ + struct physmap_flash_data data = { + .width = region->width, + }; + + if (pismo->vpp) + data.set_vpp = pismo_set_vpp; + + return pismo_add_device(pismo, i, region, "physmap-flash", + &data, sizeof(data)); +} + +static int pismo_add_sram(struct pismo_data *pismo, int i, + struct pismo_mem *region) +{ + struct platdata_mtd_ram data = { + .bankwidth = region->width, + }; + + return pismo_add_device(pismo, i, region, "mtd-ram", + &data, sizeof(data)); +} + +static void pismo_add_one(struct pismo_data *pismo, int i, + const struct pismo_cs_block *cs, phys_addr_t base) +{ + struct device *dev = &pismo->client->dev; + struct pismo_mem region; + + region.base = base; + region.type = cs->type; + region.width = pismo_width_to_bytes(cs->width); + region.access = le16_to_cpu(cs->access); + region.size = le32_to_cpu(cs->size); + + if (region.width == 0) { + dev_err(dev, "cs%u: bad width: %02x, ignoring\n", i, cs->width); + return; + } + + /* + * FIXME: may need to the platforms memory controller here, but at + * the moment we assume that it has already been correctly setup. + * The memory controller can also tell us the base address as well. + */ + + dev_info(dev, "cs%u: %.32s: type %02x access %u00ps size %uK\n", + i, cs->device, region.type, region.access, region.size / 1024); + + switch (region.type) { + case 0: + break; + case 1: + /* static DOC */ + break; + case 2: + /* static NOR */ + pismo_add_nor(pismo, i, ®ion); + break; + case 3: + /* static RAM */ + pismo_add_sram(pismo, i, ®ion); + break; + } +} + +static int pismo_remove(struct i2c_client *client) +{ + struct pismo_data *pismo = i2c_get_clientdata(client); + int i; + + for (i = 0; i < ARRAY_SIZE(pismo->dev); i++) + platform_device_unregister(pismo->dev[i]); + + kfree(pismo); + + return 0; +} + +static int pismo_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct pismo_pdata *pdata = client->dev.platform_data; + struct pismo_eeprom eeprom; + struct pismo_data *pismo; + int ret, i; + + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "functionality mismatch\n"); + return -EIO; + } + + pismo = kzalloc(sizeof(*pismo), GFP_KERNEL); + if (!pismo) + return -ENOMEM; + + pismo->client = client; + if (pdata) { + pismo->vpp = pdata->set_vpp; + pismo->vpp_data = pdata->vpp_data; + } + i2c_set_clientdata(client, pismo); + + ret = pismo_eeprom_read(client, &eeprom, 0, sizeof(eeprom)); + if (ret < 0) { + dev_err(&client->dev, "error reading EEPROM: %d\n", ret); + goto exit_free; + } + + dev_info(&client->dev, "%.15s board found\n", eeprom.board); + + for (i = 0; i < ARRAY_SIZE(eeprom.cs); i++) + if (eeprom.cs[i].type != 0xff) + pismo_add_one(pismo, i, &eeprom.cs[i], + pdata->cs_addrs[i]); + + return 0; + + exit_free: + kfree(pismo); + return ret; +} + +static const struct i2c_device_id pismo_id[] = { + { "pismo" }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, pismo_id); + +static struct i2c_driver pismo_driver = { + .driver = { + .name = "pismo", + .owner = THIS_MODULE, + }, + .probe = pismo_probe, + .remove = pismo_remove, + .id_table = pismo_id, +}; + +static int __init pismo_init(void) +{ + BUILD_BUG_ON(sizeof(struct pismo_cs_block) != 48); + BUILD_BUG_ON(sizeof(struct pismo_eeprom) != 256); + + return i2c_add_driver(&pismo_driver); +} +module_init(pismo_init); + +static void __exit pismo_exit(void) +{ + i2c_del_driver(&pismo_driver); +} +module_exit(pismo_exit); + +MODULE_AUTHOR("Russell King <linux@arm.linux.org.uk>"); +MODULE_DESCRIPTION("PISMO memory driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/maps/plat-ram.c b/drivers/mtd/maps/plat-ram.c index 5d3c75451ca..d597e89f269 100644 --- a/drivers/mtd/maps/plat-ram.c +++ b/drivers/mtd/maps/plat-ram.c @@ -4,9 +4,7 @@ * http://www.simtec.co.uk/products/SWLINUX/ * Ben Dooks <ben@simtec.co.uk> * - * Generic platfrom device based RAM map - * - * $Id: plat-ram.c,v 1.7 2005/11/07 11:14:28 gleixner Exp $ + * Generic platform device based RAM map * * 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 @@ -25,7 +23,6 @@ #include <linux/module.h> #include <linux/types.h> -#include <linux/init.h> #include <linux/kernel.h> #include <linux/string.h> #include <linux/ioport.h> @@ -46,7 +43,6 @@ struct platram_info { struct device *dev; struct mtd_info *mtd; struct map_info map; - struct mtd_partition *partitions; struct resource *area; struct platdata_mtd_ram *pdata; }; @@ -58,7 +54,7 @@ struct platram_info { static inline struct platram_info *to_platram_info(struct platform_device *dev) { - return (struct platram_info *)platform_get_drvdata(dev); + return platform_get_drvdata(dev); } /* platram_setrw @@ -87,21 +83,13 @@ static int platram_remove(struct platform_device *pdev) { struct platram_info *info = to_platram_info(pdev); - platform_set_drvdata(pdev, NULL); - dev_dbg(&pdev->dev, "removing device\n"); if (info == NULL) return 0; if (info->mtd) { -#ifdef CONFIG_MTD_PARTITIONS - if (info->partitions) { - del_mtd_partitions(info->mtd); - kfree(info->partitions); - } -#endif - del_mtd_device(info->mtd); + mtd_device_unregister(info->mtd); map_destroy(info->mtd); } @@ -139,22 +127,20 @@ static int platram_probe(struct platform_device *pdev) dev_dbg(&pdev->dev, "probe entered\n"); - if (pdev->dev.platform_data == NULL) { + if (dev_get_platdata(&pdev->dev) == NULL) { dev_err(&pdev->dev, "no platform data supplied\n"); err = -ENOENT; goto exit_error; } - pdata = pdev->dev.platform_data; + pdata = dev_get_platdata(&pdev->dev); - info = kmalloc(sizeof(*info), GFP_KERNEL); + info = kzalloc(sizeof(*info), GFP_KERNEL); if (info == NULL) { - dev_err(&pdev->dev, "no memory for flash info\n"); err = -ENOMEM; goto exit_error; } - memset(info, 0, sizeof(*info)); platform_set_drvdata(pdev, info); info->dev = &pdev->dev; @@ -170,13 +156,15 @@ static int platram_probe(struct platform_device *pdev) goto exit_free; } - dev_dbg(&pdev->dev, "got platform resource %p (0x%lx)\n", res, res->start); + dev_dbg(&pdev->dev, "got platform resource %p (0x%llx)\n", res, + (unsigned long long)res->start); /* setup map parameters */ info->map.phys = res->start; - info->map.size = (res->end - res->start) + 1; - info->map.name = pdata->mapname != NULL ? pdata->mapname : (char *)pdev->name; + info->map.size = resource_size(res); + info->map.name = pdata->mapname != NULL ? + (char *)pdata->mapname : (char *)pdev->name; info->map.bankwidth = pdata->bankwidth; /* register our usage of the memory area */ @@ -203,9 +191,19 @@ static int platram_probe(struct platform_device *pdev) dev_dbg(&pdev->dev, "initialised map, probing for mtd\n"); - /* probe for the right mtd map driver */ + /* probe for the right mtd map driver + * supplied by the platform_data struct */ + + if (pdata->map_probes) { + const char * const *map_probes = pdata->map_probes; + + for ( ; !info->mtd && *map_probes; map_probes++) + info->mtd = do_map_probe(*map_probes , &info->map); + } + /* fallback to map_ram */ + else + info->mtd = do_map_probe("map_ram", &info->map); - info->mtd = do_map_probe("map_ram" , &info->map); if (info->mtd == NULL) { dev_err(&pdev->dev, "failed to probe for map_ram\n"); err = -ENOMEM; @@ -213,34 +211,28 @@ static int platram_probe(struct platform_device *pdev) } info->mtd->owner = THIS_MODULE; + info->mtd->dev.parent = &pdev->dev; platram_setrw(info, PLATRAM_RW); - /* check to see if there are any available partitions, or wether + /* check to see if there are any available partitions, or whether * to add this device whole */ -#ifdef CONFIG_MTD_PARTITIONS - if (pdata->nr_partitions > 0) { - const char **probes = { NULL }; - - if (pdata->probes) - probes = (const char **)pdata->probes; - - err = parse_mtd_partitions(info->mtd, probes, - &info->partitions, 0); - if (err > 0) { - err = add_mtd_partitions(info->mtd, info->partitions, - err); + err = mtd_device_parse_register(info->mtd, pdata->probes, NULL, + pdata->partitions, + pdata->nr_partitions); + if (!err) + dev_info(&pdev->dev, "registered mtd device\n"); + + if (pdata->nr_partitions) { + /* add the whole device. */ + err = mtd_device_register(info->mtd, NULL, 0); + if (err) { + dev_err(&pdev->dev, + "failed to register the entire device\n"); } } -#endif /* CONFIG_MTD_PARTITIONS */ - if (add_mtd_device(info->mtd)) { - dev_err(&pdev->dev, "add_mtd_device() failed\n"); - err = -ENOMEM; - } - - dev_info(&pdev->dev, "registered mtd device\n"); return err; exit_free: @@ -251,6 +243,9 @@ static int platram_probe(struct platform_device *pdev) /* device driver info */ +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:mtd-ram"); + static struct platform_driver platram_driver = { .probe = platram_probe, .remove = platram_remove, @@ -260,21 +255,7 @@ static struct platform_driver platram_driver = { }, }; -/* module init/exit */ - -static int __init platram_init(void) -{ - printk("Generic platform RAM MTD, (c) 2004 Simtec Electronics\n"); - return platform_driver_register(&platram_driver); -} - -static void __exit platram_exit(void) -{ - platform_driver_unregister(&platram_driver); -} - -module_init(platram_init); -module_exit(platram_exit); +module_platform_driver(platram_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); diff --git a/drivers/mtd/maps/pmcmsp-flash.c b/drivers/mtd/maps/pmcmsp-flash.c new file mode 100644 index 00000000000..744ca5cacc9 --- /dev/null +++ b/drivers/mtd/maps/pmcmsp-flash.c @@ -0,0 +1,229 @@ +/* + * Mapping of a custom board with both AMD CFI and JEDEC flash in partitions. + * Config with both CFI and JEDEC device support. + * + * Basically physmap.c with the addition of partitions and + * an array of mapping info to accommodate more than one flash type per board. + * + * Copyright 2005-2007 PMC-Sierra, Inc. + * + * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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/slab.h> +#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 <msp_prom.h> +#include <msp_regs.h> + + +static struct mtd_info **msp_flash; +static struct mtd_partition **msp_parts; +static struct map_info *msp_maps; +static int fcnt; + +#define DEBUG_MARKER printk(KERN_NOTICE "%s[%d]\n", __func__, __LINE__) + +static int __init init_msp_flash(void) +{ + int i, j, ret = -ENOMEM; + int offset, coff; + char *env; + int pcnt; + char flash_name[] = "flash0"; + char part_name[] = "flash0_0"; + unsigned addr, size; + + /* If ELB is disabled by "ful-mux" mode, we can't get at flash */ + if ((*DEV_ID_REG & DEV_ID_SINGLE_PC) && + (*ELB_1PC_EN_REG & SINGLE_PCCARD)) { + printk(KERN_NOTICE "Single PC Card mode: no flash access\n"); + return -ENXIO; + } + + /* examine the prom environment for flash devices */ + for (fcnt = 0; (env = prom_getenv(flash_name)); fcnt++) + flash_name[5] = '0' + fcnt + 1; + + if (fcnt < 1) + return -ENXIO; + + printk(KERN_NOTICE "Found %d PMC flash devices\n", fcnt); + + msp_flash = kmalloc(fcnt * sizeof(struct map_info *), GFP_KERNEL); + if (!msp_flash) + return -ENOMEM; + + msp_parts = kmalloc(fcnt * sizeof(struct mtd_partition *), GFP_KERNEL); + if (!msp_parts) + goto free_msp_flash; + + msp_maps = kcalloc(fcnt, sizeof(struct mtd_info), GFP_KERNEL); + if (!msp_maps) + goto free_msp_parts; + + /* loop over the flash devices, initializing each */ + for (i = 0; i < fcnt; i++) { + /* examine the prom environment for flash partititions */ + part_name[5] = '0' + i; + part_name[7] = '0'; + for (pcnt = 0; (env = prom_getenv(part_name)); pcnt++) + part_name[7] = '0' + pcnt + 1; + + if (pcnt == 0) { + printk(KERN_NOTICE "Skipping flash device %d " + "(no partitions defined)\n", i); + continue; + } + + msp_parts[i] = kcalloc(pcnt, sizeof(struct mtd_partition), + GFP_KERNEL); + if (!msp_parts[i]) + goto cleanup_loop; + + /* now initialize the devices proper */ + flash_name[5] = '0' + i; + env = prom_getenv(flash_name); + + if (sscanf(env, "%x:%x", &addr, &size) < 2) { + ret = -ENXIO; + kfree(msp_parts[i]); + goto cleanup_loop; + } + addr = CPHYSADDR(addr); + + printk(KERN_NOTICE + "MSP flash device \"%s\": 0x%08x at 0x%08x\n", + flash_name, size, addr); + /* This must matchs the actual size of the flash chip */ + msp_maps[i].size = size; + msp_maps[i].phys = addr; + + /* + * Platforms have a specific limit of the size of memory + * which may be mapped for flash: + */ + if (size > CONFIG_MSP_FLASH_MAP_LIMIT) + size = CONFIG_MSP_FLASH_MAP_LIMIT; + + msp_maps[i].virt = ioremap(addr, size); + if (msp_maps[i].virt == NULL) { + ret = -ENXIO; + kfree(msp_parts[i]); + goto cleanup_loop; + } + + msp_maps[i].bankwidth = 1; + msp_maps[i].name = kmalloc(7, GFP_KERNEL); + if (!msp_maps[i].name) { + iounmap(msp_maps[i].virt); + kfree(msp_parts[i]); + goto cleanup_loop; + } + + msp_maps[i].name = strncpy(msp_maps[i].name, flash_name, 7); + + for (j = 0; j < pcnt; j++) { + part_name[5] = '0' + i; + part_name[7] = '0' + j; + + env = prom_getenv(part_name); + + if (sscanf(env, "%x:%x:%n", &offset, &size, + &coff) < 2) { + ret = -ENXIO; + kfree(msp_maps[i].name); + iounmap(msp_maps[i].virt); + kfree(msp_parts[i]); + goto cleanup_loop; + } + + msp_parts[i][j].size = size; + msp_parts[i][j].offset = offset; + msp_parts[i][j].name = env + coff; + } + + /* now probe and add the device */ + simple_map_init(&msp_maps[i]); + msp_flash[i] = do_map_probe("cfi_probe", &msp_maps[i]); + if (msp_flash[i]) { + msp_flash[i]->owner = THIS_MODULE; + mtd_device_register(msp_flash[i], msp_parts[i], pcnt); + } else { + printk(KERN_ERR "map probe failed for flash\n"); + ret = -ENXIO; + kfree(msp_maps[i].name); + iounmap(msp_maps[i].virt); + kfree(msp_parts[i]); + goto cleanup_loop; + } + } + + return 0; + +cleanup_loop: + while (i--) { + mtd_device_unregister(msp_flash[i]); + map_destroy(msp_flash[i]); + kfree(msp_maps[i].name); + iounmap(msp_maps[i].virt); + kfree(msp_parts[i]); + } + kfree(msp_maps); +free_msp_parts: + kfree(msp_parts); +free_msp_flash: + kfree(msp_flash); + return ret; +} + +static void __exit cleanup_msp_flash(void) +{ + int i; + + for (i = 0; i < fcnt; i++) { + mtd_device_unregister(msp_flash[i]); + map_destroy(msp_flash[i]); + iounmap((void *)msp_maps[i].virt); + + /* free the memory */ + kfree(msp_maps[i].name); + kfree(msp_parts[i]); + } + + kfree(msp_flash); + kfree(msp_parts); + kfree(msp_maps); +} + +MODULE_AUTHOR("PMC-Sierra, Inc"); +MODULE_DESCRIPTION("MTD map driver for PMC-Sierra MSP boards"); +MODULE_LICENSE("GPL"); + +module_init(init_msp_flash); +module_exit(cleanup_msp_flash); diff --git a/drivers/mtd/maps/pnc2000.c b/drivers/mtd/maps/pnc2000.c deleted file mode 100644 index d7e16c2d5c4..00000000000 --- a/drivers/mtd/maps/pnc2000.c +++ /dev/null @@ -1,93 +0,0 @@ -/* - * pnc2000.c - mapper for Photron PNC-2000 board. - * - * Copyright (C) 2000 Crossnet Co. <info@crossnet.co.jp> - * - * This code is GPL - * - * $Id: pnc2000.c,v 1.18 2005/11/07 11:14:28 gleixner Exp $ - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - - -#define WINDOW_ADDR 0xbf000000 -#define WINDOW_SIZE 0x00400000 - -/* - * MAP DRIVER STUFF - */ - - -static struct map_info pnc_map = { - .name = "PNC-2000", - .size = WINDOW_SIZE, - .bankwidth = 4, - .phys = 0xFFFFFFFF, - .virt = (void __iomem *)WINDOW_ADDR, -}; - - -/* - * MTD 'PARTITIONING' STUFF - */ -static struct mtd_partition pnc_partitions[3] = { - { - .name = "PNC-2000 boot firmware", - .size = 0x20000, - .offset = 0 - }, - { - .name = "PNC-2000 kernel", - .size = 0x1a0000, - .offset = 0x20000 - }, - { - .name = "PNC-2000 filesystem", - .size = 0x240000, - .offset = 0x1c0000 - } -}; - -/* - * This is the master MTD device for which all the others are just - * auto-relocating aliases. - */ -static struct mtd_info *mymtd; - -static int __init init_pnc2000(void) -{ - printk(KERN_NOTICE "Photron PNC-2000 flash mapping: %x at %x\n", WINDOW_SIZE, WINDOW_ADDR); - - simple_map_init(&pnc_map); - - mymtd = do_map_probe("cfi_probe", &pnc_map); - if (mymtd) { - mymtd->owner = THIS_MODULE; - return add_mtd_partitions(mymtd, pnc_partitions, 3); - } - - return -ENXIO; -} - -static void __exit cleanup_pnc2000(void) -{ - if (mymtd) { - del_mtd_partitions(mymtd); - map_destroy(mymtd); - } -} - -module_init(init_pnc2000); -module_exit(cleanup_pnc2000); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Crossnet Co. <info@crossnet.co.jp>"); -MODULE_DESCRIPTION("MTD map driver for Photron PNC-2000 board"); diff --git a/drivers/mtd/maps/pq2fads.c b/drivers/mtd/maps/pq2fads.c deleted file mode 100644 index fb78d87cc13..00000000000 --- a/drivers/mtd/maps/pq2fads.c +++ /dev/null @@ -1,88 +0,0 @@ -/* - * drivers/mtd/maps/pq2fads.c - * - * Mapping for the flash SIMM on 8272ADS and PQ2FADS board - * - * Author: Vitaly Bordug <vbordug@ru.mvista.com> - * - * 2005 (c) MontaVista Software, Inc. This file is licensed under - * the terms of the GNU General Public License version 2. This program - * is licensed "as is" without any warranty of any kind, whether express - * or implied. - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <asm/io.h> -#include <asm/ppcboot.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> -#include <linux/mtd/physmap.h> - -/* - NOTE: bank width and interleave relative to the installed flash - should have been chosen within MTD_CFI_GEOMETRY options. - */ -#define PQ2FADS_BANK_WIDTH 4 - -static struct mtd_partition pq2fads_partitions[] = { - { -#ifdef CONFIG_ADS8272 - .name = "HRCW", - .size = 0x40000, - .offset = 0, - .mask_flags = MTD_WRITEABLE, /* force read-only */ - }, { - .name = "User FS", - .size = 0x5c0000, - .offset = 0x40000, -#else - .name = "User FS", - .size = 0x600000, - .offset = 0, -#endif - }, { - .name = "uImage", - .size = 0x100000, - .offset = 0x600000, - .mask_flags = MTD_WRITEABLE, /* force read-only */ - }, { - .name = "bootloader", - .size = 0x40000, - .offset = 0x700000, - .mask_flags = MTD_WRITEABLE, /* force read-only */ - }, { - .name = "bootloader env", - .size = 0x40000, - .offset = 0x740000, - .mask_flags = MTD_WRITEABLE, /* force read-only */ - } -}; - - -/* pointer to MPC885ADS board info data */ -extern unsigned char __res[]; - -static int __init init_pq2fads_mtd(void) -{ - bd_t *bd = (bd_t *)__res; - physmap_configure(bd->bi_flashstart, bd->bi_flashsize, PQ2FADS_BANK_WIDTH, NULL); - - physmap_set_partitions(pq2fads_partitions, - sizeof (pq2fads_partitions) / - sizeof (pq2fads_partitions[0])); - return 0; -} - -static void __exit cleanup_pq2fads_mtd(void) -{ -} - -module_init(init_pq2fads_mtd); -module_exit(cleanup_pq2fads_mtd); - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("MTD map and partitions for MPC8272ADS boards"); diff --git a/drivers/mtd/maps/pxa2xx-flash.c b/drivers/mtd/maps/pxa2xx-flash.c new file mode 100644 index 00000000000..cb4d92eea9f --- /dev/null +++ b/drivers/mtd/maps/pxa2xx-flash.c @@ -0,0 +1,145 @@ +/* + * Map driver for Intel XScale PXA2xx platforms. + * + * Author: Nicolas Pitre + * Copyright: (C) 2001 MontaVista Software Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> + +#include <asm/io.h> +#include <mach/hardware.h> + +#include <asm/mach/flash.h> + +#define CACHELINESIZE 32 + +static void pxa2xx_map_inval_cache(struct map_info *map, unsigned long from, + ssize_t len) +{ + unsigned long start = (unsigned long)map->cached + from; + unsigned long end = start + len; + + start &= ~(CACHELINESIZE - 1); + while (start < end) { + /* invalidate D cache line */ + asm volatile ("mcr p15, 0, %0, c7, c6, 1" : : "r" (start)); + start += CACHELINESIZE; + } +} + +struct pxa2xx_flash_info { + struct mtd_info *mtd; + struct map_info map; +}; + +static const char * const probes[] = { "RedBoot", "cmdlinepart", NULL }; + +static int pxa2xx_flash_probe(struct platform_device *pdev) +{ + struct flash_platform_data *flash = dev_get_platdata(&pdev->dev); + struct pxa2xx_flash_info *info; + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + info = kzalloc(sizeof(struct pxa2xx_flash_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->map.name = flash->name; + info->map.bankwidth = flash->width; + info->map.phys = res->start; + info->map.size = resource_size(res); + + info->map.virt = ioremap(info->map.phys, info->map.size); + if (!info->map.virt) { + printk(KERN_WARNING "Failed to ioremap %s\n", + info->map.name); + return -ENOMEM; + } + info->map.cached = + ioremap_cache(info->map.phys, info->map.size); + if (!info->map.cached) + printk(KERN_WARNING "Failed to ioremap cached %s\n", + info->map.name); + info->map.inval_cache = pxa2xx_map_inval_cache; + simple_map_init(&info->map); + + printk(KERN_NOTICE + "Probing %s at physical address 0x%08lx" + " (%d-bit bankwidth)\n", + info->map.name, (unsigned long)info->map.phys, + info->map.bankwidth * 8); + + info->mtd = do_map_probe(flash->map_name, &info->map); + + if (!info->mtd) { + iounmap((void *)info->map.virt); + if (info->map.cached) + iounmap(info->map.cached); + return -EIO; + } + info->mtd->owner = THIS_MODULE; + + mtd_device_parse_register(info->mtd, probes, NULL, flash->parts, + flash->nr_parts); + + platform_set_drvdata(pdev, info); + return 0; +} + +static int pxa2xx_flash_remove(struct platform_device *dev) +{ + struct pxa2xx_flash_info *info = platform_get_drvdata(dev); + + mtd_device_unregister(info->mtd); + + map_destroy(info->mtd); + iounmap(info->map.virt); + if (info->map.cached) + iounmap(info->map.cached); + kfree(info); + return 0; +} + +#ifdef CONFIG_PM +static void pxa2xx_flash_shutdown(struct platform_device *dev) +{ + struct pxa2xx_flash_info *info = platform_get_drvdata(dev); + + if (info && mtd_suspend(info->mtd) == 0) + mtd_resume(info->mtd); +} +#else +#define pxa2xx_flash_shutdown NULL +#endif + +static struct platform_driver pxa2xx_flash_driver = { + .driver = { + .name = "pxa2xx-flash", + .owner = THIS_MODULE, + }, + .probe = pxa2xx_flash_probe, + .remove = pxa2xx_flash_remove, + .shutdown = pxa2xx_flash_shutdown, +}; + +module_platform_driver(pxa2xx_flash_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Nicolas Pitre <nico@fluxnic.net>"); +MODULE_DESCRIPTION("MTD map driver for Intel XScale PXA2xx"); diff --git a/drivers/mtd/maps/rbtx4939-flash.c b/drivers/mtd/maps/rbtx4939-flash.c new file mode 100644 index 00000000000..146b6047ed2 --- /dev/null +++ b/drivers/mtd/maps/rbtx4939-flash.c @@ -0,0 +1,140 @@ +/* + * 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/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; +}; + +static int rbtx4939_flash_remove(struct platform_device *dev) +{ + struct rbtx4939_flash_info *info; + + info = platform_get_drvdata(dev); + if (!info) + return 0; + + if (info->mtd) { + struct rbtx4939_flash_data *pdata = dev_get_platdata(&dev->dev); + + mtd_device_unregister(info->mtd); + map_destroy(info->mtd); + } + return 0; +} + +static const char * const rom_probe_types[] = { + "cfi_probe", "jedec_probe", NULL }; + +static int rbtx4939_flash_probe(struct platform_device *dev) +{ + struct rbtx4939_flash_data *pdata; + struct rbtx4939_flash_info *info; + struct resource *res; + const char * const *probe_type; + int err = 0; + unsigned long size; + + pdata = dev_get_platdata(&dev->dev); + 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; + err = mtd_device_parse_register(info->mtd, NULL, NULL, pdata->parts, + pdata->nr_parts); + + if (err) + goto err_out; + return 0; + +err_out: + rbtx4939_flash_remove(dev); + return err; +} + +#ifdef CONFIG_PM +static void rbtx4939_flash_shutdown(struct platform_device *dev) +{ + struct rbtx4939_flash_info *info = platform_get_drvdata(dev); + + if (mtd_suspend(info->mtd) == 0) + mtd_resume(info->mtd); +} +#else +#define rbtx4939_flash_shutdown NULL +#endif + +static struct platform_driver rbtx4939_flash_driver = { + .probe = rbtx4939_flash_probe, + .remove = rbtx4939_flash_remove, + .shutdown = rbtx4939_flash_shutdown, + .driver = { + .name = "rbtx4939-flash", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(rbtx4939_flash_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("RBTX4939 MTD map driver"); +MODULE_ALIAS("platform:rbtx4939-flash"); diff --git a/drivers/mtd/maps/redwood.c b/drivers/mtd/maps/redwood.c deleted file mode 100644 index 2257d2b500c..00000000000 --- a/drivers/mtd/maps/redwood.c +++ /dev/null @@ -1,176 +0,0 @@ -/* - * $Id: redwood.c,v 1.11 2005/11/07 11:14:28 gleixner Exp $ - * - * drivers/mtd/maps/redwood.c - * - * FLASH map for the IBM Redwood 4/5/6 boards. - * - * Author: MontaVista Software, Inc. <source@mvista.com> - * - * 2001-2003 (c) MontaVista, Software, Inc. This file is licensed under - * the terms of the GNU General Public License version 2. This program - * is licensed "as is" without any warranty of any kind, whether express - * or implied. - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - -#include <asm/io.h> - -#if !defined (CONFIG_REDWOOD_6) - -#define WINDOW_ADDR 0xffc00000 -#define WINDOW_SIZE 0x00400000 - -#define RW_PART0_OF 0 -#define RW_PART0_SZ 0x10000 -#define RW_PART1_OF RW_PART0_SZ -#define RW_PART1_SZ 0x200000 - 0x10000 -#define RW_PART2_OF 0x200000 -#define RW_PART2_SZ 0x10000 -#define RW_PART3_OF 0x210000 -#define RW_PART3_SZ 0x200000 - (0x10000 + 0x20000) -#define RW_PART4_OF 0x3e0000 -#define RW_PART4_SZ 0x20000 - -static struct mtd_partition redwood_flash_partitions[] = { - { - .name = "Redwood OpenBIOS Vital Product Data", - .offset = RW_PART0_OF, - .size = RW_PART0_SZ, - .mask_flags = MTD_WRITEABLE /* force read-only */ - }, - { - .name = "Redwood kernel", - .offset = RW_PART1_OF, - .size = RW_PART1_SZ - }, - { - .name = "Redwood OpenBIOS non-volatile storage", - .offset = RW_PART2_OF, - .size = RW_PART2_SZ, - .mask_flags = MTD_WRITEABLE /* force read-only */ - }, - { - .name = "Redwood filesystem", - .offset = RW_PART3_OF, - .size = RW_PART3_SZ - }, - { - .name = "Redwood OpenBIOS", - .offset = RW_PART4_OF, - .size = RW_PART4_SZ, - .mask_flags = MTD_WRITEABLE /* force read-only */ - } -}; - -#else /* CONFIG_REDWOOD_6 */ -/* FIXME: the window is bigger - armin */ -#define WINDOW_ADDR 0xff800000 -#define WINDOW_SIZE 0x00800000 - -#define RW_PART0_OF 0 -#define RW_PART0_SZ 0x400000 /* 4 MiB data */ -#define RW_PART1_OF RW_PART0_OF + RW_PART0_SZ -#define RW_PART1_SZ 0x10000 /* 64K VPD */ -#define RW_PART2_OF RW_PART1_OF + RW_PART1_SZ -#define RW_PART2_SZ 0x400000 - (0x10000 + 0x20000) -#define RW_PART3_OF RW_PART2_OF + RW_PART2_SZ -#define RW_PART3_SZ 0x20000 - -static struct mtd_partition redwood_flash_partitions[] = { - { - .name = "Redwood filesystem", - .offset = RW_PART0_OF, - .size = RW_PART0_SZ - }, - { - .name = "Redwood OpenBIOS Vital Product Data", - .offset = RW_PART1_OF, - .size = RW_PART1_SZ, - .mask_flags = MTD_WRITEABLE /* force read-only */ - }, - { - .name = "Redwood kernel", - .offset = RW_PART2_OF, - .size = RW_PART2_SZ - }, - { - .name = "Redwood OpenBIOS", - .offset = RW_PART3_OF, - .size = RW_PART3_SZ, - .mask_flags = MTD_WRITEABLE /* force read-only */ - } -}; - -#endif /* CONFIG_REDWOOD_6 */ - -struct map_info redwood_flash_map = { - .name = "IBM Redwood", - .size = WINDOW_SIZE, - .bankwidth = 2, - .phys = WINDOW_ADDR, -}; - - -#define NUM_REDWOOD_FLASH_PARTITIONS ARRAY_SIZE(redwood_flash_partitions) - -static struct mtd_info *redwood_mtd; - -int __init init_redwood_flash(void) -{ - int err = 0; - - printk(KERN_NOTICE "redwood: flash mapping: %x at %x\n", - WINDOW_SIZE, WINDOW_ADDR); - - redwood_flash_map.virt = ioremap(WINDOW_ADDR, WINDOW_SIZE); - - if (!redwood_flash_map.virt) { - printk("init_redwood_flash: failed to ioremap\n"); - return -EIO; - } - simple_map_init(&redwood_flash_map); - - redwood_mtd = do_map_probe("cfi_probe",&redwood_flash_map); - - if (redwood_mtd) { - redwood_mtd->owner = THIS_MODULE; - err = add_mtd_partitions(redwood_mtd, - redwood_flash_partitions, - NUM_REDWOOD_FLASH_PARTITIONS); - if (err) { - printk("init_redwood_flash: add_mtd_partitions failed\n"); - iounmap(redwood_flash_map.virt); - } - return err; - - } - - iounmap(redwood_flash_map.virt); - return -ENXIO; -} - -static void __exit cleanup_redwood_flash(void) -{ - if (redwood_mtd) { - del_mtd_partitions(redwood_mtd); - /* moved iounmap after map_destroy - armin */ - map_destroy(redwood_mtd); - iounmap((void *)redwood_flash_map.virt); - } -} - -module_init(init_redwood_flash); -module_exit(cleanup_redwood_flash); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("MontaVista Software <source@mvista.com>"); -MODULE_DESCRIPTION("MTD map driver for the IBM Redwood reference boards"); diff --git a/drivers/mtd/maps/rpxlite.c b/drivers/mtd/maps/rpxlite.c deleted file mode 100644 index 809a0c8e7aa..00000000000 --- a/drivers/mtd/maps/rpxlite.c +++ /dev/null @@ -1,66 +0,0 @@ -/* - * $Id: rpxlite.c,v 1.22 2004/11/04 13:24:15 gleixner Exp $ - * - * Handle mapping of the flash on the RPX Lite and CLLF boards - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <asm/io.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> - - -#define WINDOW_ADDR 0xfe000000 -#define WINDOW_SIZE 0x800000 - -static struct mtd_info *mymtd; - -static struct map_info rpxlite_map = { - .name = "RPX", - .size = WINDOW_SIZE, - .bankwidth = 4, - .phys = WINDOW_ADDR, -}; - -int __init init_rpxlite(void) -{ - printk(KERN_NOTICE "RPX Lite or CLLF flash device: %x at %x\n", WINDOW_SIZE*4, WINDOW_ADDR); - rpxlite_map.virt = ioremap(WINDOW_ADDR, WINDOW_SIZE * 4); - - if (!rpxlite_map.virt) { - printk("Failed to ioremap\n"); - return -EIO; - } - simple_map_init(&rpxlite_map); - mymtd = do_map_probe("cfi_probe", &rpxlite_map); - if (mymtd) { - mymtd->owner = THIS_MODULE; - add_mtd_device(mymtd); - return 0; - } - - iounmap((void *)rpxlite_map.virt); - return -ENXIO; -} - -static void __exit cleanup_rpxlite(void) -{ - if (mymtd) { - del_mtd_device(mymtd); - map_destroy(mymtd); - } - if (rpxlite_map.virt) { - iounmap((void *)rpxlite_map.virt); - rpxlite_map.virt = 0; - } -} - -module_init(init_rpxlite); -module_exit(cleanup_rpxlite); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Arnold Christensen <AKC@pel.dk>"); -MODULE_DESCRIPTION("MTD map driver for RPX Lite and CLLF boards"); diff --git a/drivers/mtd/maps/sa1100-flash.c b/drivers/mtd/maps/sa1100-flash.c index 950bf1c5784..8fc06bf111c 100644 --- a/drivers/mtd/maps/sa1100-flash.c +++ b/drivers/mtd/maps/sa1100-flash.c @@ -1,9 +1,7 @@ /* * Flash memory access on SA11x0 based devices * - * (C) 2000 Nicolas Pitre <nico@cam.org> - * - * $Id: sa1100-flash.c,v 1.51 2005/11/07 11:14:28 gleixner Exp $ + * (C) 2000 Nicolas Pitre <nico@fluxnic.net> */ #include <linux/module.h> #include <linux/types.h> @@ -14,117 +12,17 @@ #include <linux/slab.h> #include <linux/platform_device.h> #include <linux/err.h> +#include <linux/io.h> #include <linux/mtd/mtd.h> #include <linux/mtd/map.h> #include <linux/mtd/partitions.h> #include <linux/mtd/concat.h> -#include <asm/hardware.h> -#include <asm/io.h> +#include <mach/hardware.h> #include <asm/sizes.h> #include <asm/mach/flash.h> -#if 0 -/* - * This is here for documentation purposes only - until these people - * submit their machine types. It will be gone January 2005. - */ -static struct mtd_partition consus_partitions[] = { - { - .name = "Consus boot firmware", - .offset = 0, - .size = 0x00040000, - .mask_flags = MTD_WRITABLE, /* force read-only */ - }, { - .name = "Consus kernel", - .offset = 0x00040000, - .size = 0x00100000, - .mask_flags = 0, - }, { - .name = "Consus disk", - .offset = 0x00140000, - /* The rest (up to 16M) for jffs. We could put 0 and - make it find the size automatically, but right now - i have 32 megs. jffs will use all 32 megs if given - the chance, and this leads to horrible problems - when you try to re-flash the image because blob - won't erase the whole partition. */ - .size = 0x01000000 - 0x00140000, - .mask_flags = 0, - }, { - /* this disk is a secondary disk, which can be used as - needed, for simplicity, make it the size of the other - consus partition, although realistically it could be - the remainder of the disk (depending on the file - system used) */ - .name = "Consus disk2", - .offset = 0x01000000, - .size = 0x01000000 - 0x00140000, - .mask_flags = 0, - } -}; - -/* Frodo has 2 x 16M 28F128J3A flash chips in bank 0: */ -static struct mtd_partition frodo_partitions[] = -{ - { - .name = "bootloader", - .size = 0x00040000, - .offset = 0x00000000, - .mask_flags = MTD_WRITEABLE - }, { - .name = "bootloader params", - .size = 0x00040000, - .offset = MTDPART_OFS_APPEND, - .mask_flags = MTD_WRITEABLE - }, { - .name = "kernel", - .size = 0x00100000, - .offset = MTDPART_OFS_APPEND, - .mask_flags = MTD_WRITEABLE - }, { - .name = "ramdisk", - .size = 0x00400000, - .offset = MTDPART_OFS_APPEND, - .mask_flags = MTD_WRITEABLE - }, { - .name = "file system", - .size = MTDPART_SIZ_FULL, - .offset = MTDPART_OFS_APPEND - } -}; - -static struct mtd_partition jornada56x_partitions[] = { - { - .name = "bootldr", - .size = 0x00040000, - .offset = 0, - .mask_flags = MTD_WRITEABLE, - }, { - .name = "rootfs", - .size = MTDPART_SIZ_FULL, - .offset = MTDPART_OFS_APPEND, - } -}; - -static void jornada56x_set_vpp(int vpp) -{ - if (vpp) - GPSR = GPIO_GPIO26; - else - GPCR = GPIO_GPIO26; - GPDR |= GPIO_GPIO26; -} - -/* - * Machine Phys Size set_vpp - * Consus : SA1100_CS0_PHYS SZ_32M - * Frodo : SA1100_CS0_PHYS SZ_32M - * Jornada56x: SA1100_CS0_PHYS SZ_32M jornada56x_set_vpp - */ -#endif - struct sa_subdev_info { char name[16]; struct map_info map; @@ -133,17 +31,27 @@ struct sa_subdev_info { }; struct sa_info { - struct mtd_partition *parts; struct mtd_info *mtd; int num_subdev; - unsigned int nr_parts; struct sa_subdev_info subdev[0]; }; +static DEFINE_SPINLOCK(sa1100_vpp_lock); +static int sa1100_vpp_refcnt; static void sa1100_set_vpp(struct map_info *map, int on) { struct sa_subdev_info *subdev = container_of(map, struct sa_subdev_info, map); - subdev->plat->set_vpp(on); + unsigned long flags; + + spin_lock_irqsave(&sa1100_vpp_lock, flags); + if (on) { + if (++sa1100_vpp_refcnt == 1) /* first nested 'on' */ + subdev->plat->set_vpp(1); + } else { + if (--sa1100_vpp_refcnt == 0) /* last nested 'off' */ + subdev->plat->set_vpp(0); + } + spin_unlock_irqrestore(&sa1100_vpp_lock, flags); } static void sa1100_destroy_subdev(struct sa_subdev_info *subdev) @@ -211,8 +119,8 @@ static int sa1100_probe_subdev(struct sa_subdev_info *subdev, struct resource *r } subdev->mtd->owner = THIS_MODULE; - printk(KERN_INFO "SA1100 flash: CFI device at 0x%08lx, %dMiB, " - "%d-bit\n", phys, subdev->mtd->size >> 20, + printk(KERN_INFO "SA1100 flash: CFI device at 0x%08lx, %uMiB, %d-bit\n", + phys, (unsigned)(subdev->mtd->size >> 20), subdev->map.bankwidth * 8); return 0; @@ -228,20 +136,11 @@ static void sa1100_destroy(struct sa_info *info, struct flash_platform_data *pla int i; if (info->mtd) { - if (info->nr_parts == 0) - del_mtd_device(info->mtd); -#ifdef CONFIG_MTD_PARTITIONS - else - del_mtd_partitions(info->mtd); -#endif -#ifdef CONFIG_MTD_CONCAT + mtd_device_unregister(info->mtd); if (info->mtd != info->subdev[0].mtd) mtd_concat_destroy(info->mtd); -#endif } - kfree(info->parts); - for (i = info->num_subdev - 1; i >= 0; i--) sa1100_destroy_subdev(&info->subdev[i]); kfree(info); @@ -250,8 +149,8 @@ static void sa1100_destroy(struct sa_info *info, struct flash_platform_data *pla plat->exit(); } -static struct sa_info *__init -sa1100_setup_mtd(struct platform_device *pdev, struct flash_platform_data *plat) +static struct sa_info *sa1100_setup_mtd(struct platform_device *pdev, + struct flash_platform_data *plat) { struct sa_info *info; int nr, size, i, ret = 0; @@ -273,14 +172,12 @@ sa1100_setup_mtd(struct platform_device *pdev, struct flash_platform_data *plat) /* * Allocate the map_info structs in one go. */ - info = kmalloc(size, GFP_KERNEL); + info = kzalloc(size, GFP_KERNEL); if (!info) { ret = -ENOMEM; goto out; } - memset(info, 0, size); - if (plat->init) { ret = plat->init(); if (ret) @@ -325,7 +222,6 @@ sa1100_setup_mtd(struct platform_device *pdev, struct flash_platform_data *plat) info->mtd = info->subdev[0].mtd; ret = 0; } else if (info->num_subdev > 1) { -#ifdef CONFIG_MTD_CONCAT struct mtd_info *cdev[nr]; /* * We detected multiple devices. Concatenate them together. @@ -337,11 +233,6 @@ sa1100_setup_mtd(struct platform_device *pdev, struct flash_platform_data *plat) plat->name); if (info->mtd == NULL) ret = -ENXIO; -#else - printk(KERN_ERR "SA1100 flash: multiple devices " - "found but MTD concat support disabled.\n"); - ret = -ENXIO; -#endif } if (ret == 0) @@ -353,15 +244,13 @@ sa1100_setup_mtd(struct platform_device *pdev, struct flash_platform_data *plat) return ERR_PTR(ret); } -static const char *part_probes[] = { "cmdlinepart", "RedBoot", NULL }; +static const char * const part_probes[] = { "cmdlinepart", "RedBoot", NULL }; -static int __init sa1100_mtd_probe(struct platform_device *pdev) +static int sa1100_mtd_probe(struct platform_device *pdev) { - struct flash_platform_data *plat = pdev->dev.platform_data; - struct mtd_partition *parts; - const char *part_type = NULL; + struct flash_platform_data *plat = dev_get_platdata(&pdev->dev); struct sa_info *info; - int err, nr_parts = 0; + int err; if (!plat) return -ENODEV; @@ -375,30 +264,8 @@ static int __init sa1100_mtd_probe(struct platform_device *pdev) /* * Partition selection stuff. */ -#ifdef CONFIG_MTD_PARTITIONS - nr_parts = parse_mtd_partitions(info->mtd, part_probes, &parts, 0); - if (nr_parts > 0) { - info->parts = parts; - part_type = "dynamic"; - } else -#endif - { - parts = plat->parts; - nr_parts = plat->nr_parts; - part_type = "static"; - } - - if (nr_parts == 0) { - printk(KERN_NOTICE "SA1100 flash: no partition info " - "available, registering whole flash\n"); - add_mtd_device(info->mtd); - } else { - printk(KERN_NOTICE "SA1100 flash: using %s partition " - "definition\n", part_type); - add_mtd_partitions(info->mtd, parts, nr_parts); - } - - info->nr_parts = nr_parts; + mtd_device_parse_register(info->mtd, part_probes, NULL, plat->parts, + plat->nr_parts); platform_set_drvdata(pdev, info); err = 0; @@ -410,70 +277,25 @@ static int __init sa1100_mtd_probe(struct platform_device *pdev) static int __exit sa1100_mtd_remove(struct platform_device *pdev) { struct sa_info *info = platform_get_drvdata(pdev); - struct flash_platform_data *plat = pdev->dev.platform_data; + struct flash_platform_data *plat = dev_get_platdata(&pdev->dev); - platform_set_drvdata(pdev, NULL); sa1100_destroy(info, plat); return 0; } -#ifdef CONFIG_PM -static int sa1100_mtd_suspend(struct platform_device *dev, pm_message_t state) -{ - struct sa_info *info = platform_get_drvdata(dev); - int ret = 0; - - if (info) - ret = info->mtd->suspend(info->mtd); - - return ret; -} - -static int sa1100_mtd_resume(struct platform_device *dev) -{ - struct sa_info *info = platform_get_drvdata(dev); - if (info) - info->mtd->resume(info->mtd); - return 0; -} - -static void sa1100_mtd_shutdown(struct platform_device *dev) -{ - struct sa_info *info = platform_get_drvdata(dev); - if (info && info->mtd->suspend(info->mtd) == 0) - info->mtd->resume(info->mtd); -} -#else -#define sa1100_mtd_suspend NULL -#define sa1100_mtd_resume NULL -#define sa1100_mtd_shutdown NULL -#endif - static struct platform_driver sa1100_mtd_driver = { .probe = sa1100_mtd_probe, .remove = __exit_p(sa1100_mtd_remove), - .suspend = sa1100_mtd_suspend, - .resume = sa1100_mtd_resume, - .shutdown = sa1100_mtd_shutdown, .driver = { - .name = "flash", + .name = "sa1100-mtd", + .owner = THIS_MODULE, }, }; -static int __init sa1100_mtd_init(void) -{ - return platform_driver_register(&sa1100_mtd_driver); -} - -static void __exit sa1100_mtd_exit(void) -{ - platform_driver_unregister(&sa1100_mtd_driver); -} - -module_init(sa1100_mtd_init); -module_exit(sa1100_mtd_exit); +module_platform_driver(sa1100_mtd_driver); MODULE_AUTHOR("Nicolas Pitre"); MODULE_DESCRIPTION("SA1100 CFI map driver"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sa1100-mtd"); diff --git a/drivers/mtd/maps/sbc8240.c b/drivers/mtd/maps/sbc8240.c deleted file mode 100644 index b8c1331b7a0..00000000000 --- a/drivers/mtd/maps/sbc8240.c +++ /dev/null @@ -1,253 +0,0 @@ -/* - * Handle mapping of the flash memory access routines on the SBC8240 board. - * - * Carolyn Smith, Tektronix, Inc. - * - * This code is GPLed - * - * $Id: sbc8240.c,v 1.5 2005/11/07 11:14:28 gleixner Exp $ - * - */ - -/* - * The SBC8240 has 2 flash banks. - * Bank 0 is a 512 KiB AMD AM29F040B; 8 x 64 KiB sectors. - * It contains the U-Boot code (7 sectors) and the environment (1 sector). - * Bank 1 is 4 x 1 MiB AMD AM29LV800BT; 15 x 64 KiB sectors, 1 x 32 KiB sector, - * 2 x 8 KiB sectors, 1 x 16 KiB sectors. - * Both parts are JEDEC compatible. - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <asm/io.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/cfi.h> - -#ifdef CONFIG_MTD_PARTITIONS -#include <linux/mtd/partitions.h> -#endif - -#define DEBUG - -#ifdef DEBUG -# define debugk(fmt,args...) printk(fmt ,##args) -#else -# define debugk(fmt,args...) -#endif - - -#define WINDOW_ADDR0 0xFFF00000 /* 512 KiB */ -#define WINDOW_SIZE0 0x00080000 -#define BUSWIDTH0 1 - -#define WINDOW_ADDR1 0xFF000000 /* 4 MiB */ -#define WINDOW_SIZE1 0x00400000 -#define BUSWIDTH1 8 - -#define MSG_PREFIX "sbc8240:" /* prefix for our printk()'s */ -#define MTDID "sbc8240-%d" /* for mtdparts= partitioning */ - - -static struct map_info sbc8240_map[2] = { - { - .name = "sbc8240 Flash Bank #0", - .size = WINDOW_SIZE0, - .bankwidth = BUSWIDTH0, - }, - { - .name = "sbc8240 Flash Bank #1", - .size = WINDOW_SIZE1, - .bankwidth = BUSWIDTH1, - } -}; - -#define NUM_FLASH_BANKS ARRAY_SIZE(sbc8240_map) - -/* - * The following defines the partition layout of SBC8240 boards. - * - * See include/linux/mtd/partitions.h for definition of the - * mtd_partition structure. - * - * The *_max_flash_size is the maximum possible mapped flash size - * which is not necessarily the actual flash size. It must correspond - * to the value specified in the mapping definition defined by the - * "struct map_desc *_io_desc" for the corresponding machine. - */ - -#ifdef CONFIG_MTD_PARTITIONS - -static struct mtd_partition sbc8240_uboot_partitions [] = { - /* Bank 0 */ - { - .name = "U-boot", /* U-Boot Firmware */ - .offset = 0, - .size = 0x00070000, /* 7 x 64 KiB sectors */ - .mask_flags = MTD_WRITEABLE, /* force read-only */ - }, - { - .name = "environment", /* U-Boot environment */ - .offset = 0x00070000, - .size = 0x00010000, /* 1 x 64 KiB sector */ - }, -}; - -static struct mtd_partition sbc8240_fs_partitions [] = { - { - .name = "jffs", /* JFFS filesystem */ - .offset = 0, - .size = 0x003C0000, /* 4 * 15 * 64KiB */ - }, - { - .name = "tmp32", - .offset = 0x003C0000, - .size = 0x00020000, /* 4 * 32KiB */ - }, - { - .name = "tmp8a", - .offset = 0x003E0000, - .size = 0x00008000, /* 4 * 8KiB */ - }, - { - .name = "tmp8b", - .offset = 0x003E8000, - .size = 0x00008000, /* 4 * 8KiB */ - }, - { - .name = "tmp16", - .offset = 0x003F0000, - .size = 0x00010000, /* 4 * 16KiB */ - } -}; - -/* trivial struct to describe partition information */ -struct mtd_part_def -{ - int nums; - unsigned char *type; - struct mtd_partition* mtd_part; -}; - -static struct mtd_info *sbc8240_mtd[NUM_FLASH_BANKS]; -static struct mtd_part_def sbc8240_part_banks[NUM_FLASH_BANKS]; - - -#endif /* CONFIG_MTD_PARTITIONS */ - - -int __init init_sbc8240_mtd (void) -{ - static struct _cjs { - u_long addr; - u_long size; - } pt[NUM_FLASH_BANKS] = { - { - .addr = WINDOW_ADDR0, - .size = WINDOW_SIZE0 - }, - { - .addr = WINDOW_ADDR1, - .size = WINDOW_SIZE1 - }, - }; - - int devicesfound = 0; - int i,j; - - for (i = 0; i < NUM_FLASH_BANKS; i++) { - printk (KERN_NOTICE MSG_PREFIX - "Probing 0x%08lx at 0x%08lx\n", pt[i].size, pt[i].addr); - - sbc8240_map[i].map_priv_1 = - (unsigned long) ioremap (pt[i].addr, pt[i].size); - if (!sbc8240_map[i].map_priv_1) { - printk (MSG_PREFIX "failed to ioremap\n"); - for (j = 0; j < i; j++) { - iounmap((void *) sbc8240_map[j].map_priv_1); - sbc8240_map[j].map_priv_1 = 0; - } - return -EIO; - } - simple_map_init(&sbc8240_mtd[i]); - - sbc8240_mtd[i] = do_map_probe("jedec_probe", &sbc8240_map[i]); - - if (sbc8240_mtd[i]) { - sbc8240_mtd[i]->module = THIS_MODULE; - devicesfound++; - } else { - if (sbc8240_map[i].map_priv_1) { - iounmap((void *) sbc8240_map[i].map_priv_1); - sbc8240_map[i].map_priv_1 = 0; - } - } - } - - if (!devicesfound) { - printk(KERN_NOTICE MSG_PREFIX - "No suppported flash chips found!\n"); - return -ENXIO; - } - -#ifdef CONFIG_MTD_PARTITIONS - sbc8240_part_banks[0].mtd_part = sbc8240_uboot_partitions; - sbc8240_part_banks[0].type = "static image"; - sbc8240_part_banks[0].nums = ARRAY_SIZE(sbc8240_uboot_partitions); - sbc8240_part_banks[1].mtd_part = sbc8240_fs_partitions; - sbc8240_part_banks[1].type = "static file system"; - sbc8240_part_banks[1].nums = ARRAY_SIZE(sbc8240_fs_partitions); - - for (i = 0; i < NUM_FLASH_BANKS; i++) { - - if (!sbc8240_mtd[i]) continue; - if (sbc8240_part_banks[i].nums == 0) { - printk (KERN_NOTICE MSG_PREFIX - "No partition info available, registering whole device\n"); - add_mtd_device(sbc8240_mtd[i]); - } else { - printk (KERN_NOTICE MSG_PREFIX - "Using %s partition definition\n", sbc8240_part_banks[i].mtd_part->name); - add_mtd_partitions (sbc8240_mtd[i], - sbc8240_part_banks[i].mtd_part, - sbc8240_part_banks[i].nums); - } - } -#else - printk(KERN_NOTICE MSG_PREFIX - "Registering %d flash banks at once\n", devicesfound); - - for (i = 0; i < devicesfound; i++) { - add_mtd_device(sbc8240_mtd[i]); - } -#endif /* CONFIG_MTD_PARTITIONS */ - - return devicesfound == 0 ? -ENXIO : 0; -} - -static void __exit cleanup_sbc8240_mtd (void) -{ - int i; - - for (i = 0; i < NUM_FLASH_BANKS; i++) { - if (sbc8240_mtd[i]) { - del_mtd_device (sbc8240_mtd[i]); - map_destroy (sbc8240_mtd[i]); - } - if (sbc8240_map[i].map_priv_1) { - iounmap ((void *) sbc8240_map[i].map_priv_1); - sbc8240_map[i].map_priv_1 = 0; - } - } -} - -module_init (init_sbc8240_mtd); -module_exit (cleanup_sbc8240_mtd); - -MODULE_LICENSE ("GPL"); -MODULE_AUTHOR ("Carolyn Smith <carolyn.smith@tektronix.com>"); -MODULE_DESCRIPTION ("MTD map driver for SBC8240 boards"); - diff --git a/drivers/mtd/maps/sbc_gxx.c b/drivers/mtd/maps/sbc_gxx.c index 7cc4041d096..556a2dfe94c 100644 --- a/drivers/mtd/maps/sbc_gxx.c +++ b/drivers/mtd/maps/sbc_gxx.c @@ -17,8 +17,6 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA - $Id: sbc_gxx.c,v 1.35 2005/11/07 11:14:28 gleixner Exp $ - The SBC-MediaGX / SBC-GXx has up to 16 MiB of Intel StrataFlash (28F320/28F640) in x8 mode. @@ -47,7 +45,6 @@ separate MTD devices. // Includes #include <linux/module.h> -#include <linux/slab.h> #include <linux/ioport.h> #include <linux/init.h> #include <asm/io.h> @@ -185,7 +182,7 @@ static struct mtd_info *all_mtd; static void cleanup_sbc_gxx(void) { if( all_mtd ) { - del_mtd_partitions( all_mtd ); + mtd_device_unregister(all_mtd); map_destroy( all_mtd ); } @@ -226,7 +223,7 @@ static int __init init_sbc_gxx(void) all_mtd->owner = THIS_MODULE; /* Create MTD devices for each partition. */ - add_mtd_partitions(all_mtd, partition_info, NUM_PARTITIONS ); + mtd_device_register(all_mtd, partition_info, NUM_PARTITIONS); return 0; } diff --git a/drivers/mtd/maps/sc520cdp.c b/drivers/mtd/maps/sc520cdp.c index 9b50cfc355b..093edd51bdc 100644 --- a/drivers/mtd/maps/sc520cdp.c +++ b/drivers/mtd/maps/sc520cdp.c @@ -16,8 +16,6 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA * - * $Id: sc520cdp.c,v 1.23 2005/11/17 08:20:27 dwmw2 Exp $ - * * * The SC520CDP is an evaluation board for the Elan SC520 processor available * from AMD. It has two banks of 32-bit Flash ROM, each 8 Megabytes in size, @@ -185,7 +183,7 @@ static const struct sc520_par_table par_table[NUM_FLASH_BANKS] = static void sc520cdp_setup_par(void) { - volatile unsigned long __iomem *mmcr; + unsigned long __iomem *mmcr; unsigned long mmcr_val; int i, j; @@ -199,17 +197,17 @@ static void sc520cdp_setup_par(void) } /* - ** Find the PARxx registers that are reponsible for activating + ** Find the PARxx registers that are responsible for activating ** ROMCS0, ROMCS1 and BOOTCS. Reprogram each of these with a ** new value from the table. */ for(i = 0; i < NUM_FLASH_BANKS; i++) { /* for each par_table entry */ for(j = 0; j < NUM_SC520_PAR; j++) { /* for each PAR register */ - mmcr_val = mmcr[SC520_PAR(j)]; + mmcr_val = readl(&mmcr[SC520_PAR(j)]); /* if target device field matches, reprogram the PAR */ if((mmcr_val & SC520_PAR_TRGDEV) == par_table[i].trgdev) { - mmcr[SC520_PAR(j)] = par_table[i].new_par; + writel(par_table[i].new_par, &mmcr[SC520_PAR(j)]); break; } } @@ -237,8 +235,9 @@ static int __init init_sc520cdp(void) #endif for (i = 0; i < NUM_FLASH_BANKS; i++) { - printk(KERN_NOTICE "SC520 CDP flash device: 0x%lx at 0x%lx\n", - sc520cdp_map[i].size, sc520cdp_map[i].phys); + printk(KERN_NOTICE "SC520 CDP flash device: 0x%Lx at 0x%Lx\n", + (unsigned long long)sc520cdp_map[i].size, + (unsigned long long)sc520cdp_map[i].phys); sc520cdp_map[i].virt = ioremap_nocache(sc520cdp_map[i].phys, sc520cdp_map[i].size); @@ -267,10 +266,10 @@ static int __init init_sc520cdp(void) /* Combine the two flash banks into a single MTD device & register it: */ merged_mtd = mtd_concat_create(mymtd, 2, "SC520CDP Flash Banks #0 and #1"); if(merged_mtd) - add_mtd_device(merged_mtd); + mtd_device_register(merged_mtd, NULL, 0); } if(devices_found == 3) /* register the third (DIL-Flash) device */ - add_mtd_device(mymtd[2]); + mtd_device_register(mymtd[2], NULL, 0); return(devices_found ? 0 : -ENXIO); } @@ -279,11 +278,11 @@ static void __exit cleanup_sc520cdp(void) int i; if (merged_mtd) { - del_mtd_device(merged_mtd); + mtd_device_unregister(merged_mtd); mtd_concat_destroy(merged_mtd); } if (mymtd[2]) - del_mtd_device(mymtd[2]); + mtd_device_unregister(mymtd[2]); for (i = 0; i < NUM_FLASH_BANKS; i++) { if (mymtd[i]) diff --git a/drivers/mtd/maps/scb2_flash.c b/drivers/mtd/maps/scb2_flash.c index dcfb85840d1..b7a22a612a4 100644 --- a/drivers/mtd/maps/scb2_flash.c +++ b/drivers/mtd/maps/scb2_flash.c @@ -1,6 +1,5 @@ /* * MTD map driver for BIOS Flash on Intel SCB2 boards - * $Id: scb2_flash.c,v 1.12 2005/03/18 14:04:35 gleixner Exp $ * Copyright (C) 2002 Sun Microsystems, Inc. * Tim Hockin <thockin@sun.com> * @@ -48,7 +47,6 @@ #include <linux/module.h> #include <linux/types.h> #include <linux/kernel.h> -#include <linux/init.h> #include <asm/io.h> #include <linux/mtd/mtd.h> #include <linux/mtd/map.h> @@ -70,8 +68,7 @@ static struct map_info scb2_map = { }; static int region_fail; -static int __devinit -scb2_fixup_mtd(struct mtd_info *mtd) +static int scb2_fixup_mtd(struct mtd_info *mtd) { int i; int done = 0; @@ -79,7 +76,7 @@ scb2_fixup_mtd(struct mtd_info *mtd) struct cfi_private *cfi = map->fldrv_priv; /* barf if this doesn't look right */ - if (cfi->cfiq->InterfaceDesc != 1) { + if (cfi->cfiq->InterfaceDesc != CFI_INTERFACE_X16_ASYNC) { printk(KERN_ERR MODNAME ": unsupported InterfaceDesc: %#x\n", cfi->cfiq->InterfaceDesc); return -1; @@ -119,7 +116,8 @@ scb2_fixup_mtd(struct mtd_info *mtd) struct mtd_erase_region_info *region = &mtd->eraseregions[i]; if (region->numblocks * region->erasesize > mtd->size) { - region->numblocks = (mtd->size / region->erasesize); + region->numblocks = ((unsigned long)mtd->size / + region->erasesize); done = 1; } else { region->numblocks = 0; @@ -133,8 +131,8 @@ scb2_fixup_mtd(struct mtd_info *mtd) /* CSB5's 'Function Control Register' has bits for decoding @ >= 0xffc00000 */ #define CSB5_FCR 0x41 #define CSB5_FCR_DECODE_ALL 0x0e -static int __devinit -scb2_flash_probe(struct pci_dev *dev, const struct pci_device_id *ent) +static int scb2_flash_probe(struct pci_dev *dev, + const struct pci_device_id *ent) { u8 reg; @@ -180,7 +178,7 @@ scb2_flash_probe(struct pci_dev *dev, const struct pci_device_id *ent) scb2_mtd->owner = THIS_MODULE; if (scb2_fixup_mtd(scb2_mtd) < 0) { - del_mtd_device(scb2_mtd); + mtd_device_unregister(scb2_mtd); map_destroy(scb2_mtd); iounmap(scb2_ioaddr); if (!region_fail) @@ -188,25 +186,24 @@ scb2_flash_probe(struct pci_dev *dev, const struct pci_device_id *ent) return -ENODEV; } - printk(KERN_NOTICE MODNAME ": chip size 0x%x at offset 0x%x\n", - scb2_mtd->size, SCB2_WINDOW - scb2_mtd->size); + printk(KERN_NOTICE MODNAME ": chip size 0x%llx at offset 0x%llx\n", + (unsigned long long)scb2_mtd->size, + (unsigned long long)(SCB2_WINDOW - scb2_mtd->size)); - add_mtd_device(scb2_mtd); + mtd_device_register(scb2_mtd, NULL, 0); return 0; } -static void __devexit -scb2_flash_remove(struct pci_dev *dev) +static void scb2_flash_remove(struct pci_dev *dev) { if (!scb2_mtd) return; /* disable flash writes */ - if (scb2_mtd->lock) - scb2_mtd->lock(scb2_mtd, 0, scb2_mtd->size); + mtd_lock(scb2_mtd, 0, scb2_mtd->size); - del_mtd_device(scb2_mtd); + mtd_device_unregister(scb2_mtd); map_destroy(scb2_mtd); iounmap(scb2_ioaddr); @@ -214,7 +211,6 @@ scb2_flash_remove(struct pci_dev *dev) if (!region_fail) release_mem_region(SCB2_ADDR, SCB2_WINDOW); - pci_set_drvdata(dev, NULL); } static struct pci_device_id scb2_flash_pci_ids[] = { @@ -231,23 +227,10 @@ static struct pci_driver scb2_flash_driver = { .name = "Intel SCB2 BIOS Flash", .id_table = scb2_flash_pci_ids, .probe = scb2_flash_probe, - .remove = __devexit_p(scb2_flash_remove), + .remove = scb2_flash_remove, }; -static int __init -scb2_flash_init(void) -{ - return pci_register_driver(&scb2_flash_driver); -} - -static void __exit -scb2_flash_exit(void) -{ - pci_unregister_driver(&scb2_flash_driver); -} - -module_init(scb2_flash_init); -module_exit(scb2_flash_exit); +module_pci_driver(scb2_flash_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Tim Hockin <thockin@sun.com>"); diff --git a/drivers/mtd/maps/scx200_docflash.c b/drivers/mtd/maps/scx200_docflash.c index 5e2bce22f37..f1c1f737d0d 100644 --- a/drivers/mtd/maps/scx200_docflash.c +++ b/drivers/mtd/maps/scx200_docflash.c @@ -2,8 +2,6 @@ Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com> - $Id: scx200_docflash.c,v 1.12 2005/11/07 11:14:28 gleixner Exp $ - National Semiconductor SCx200 flash mapped with DOCCS */ @@ -46,7 +44,6 @@ static struct resource docmem = { static struct mtd_info *mymtd; -#ifdef CONFIG_MTD_PARTITIONS static struct mtd_partition partition_info[] = { { .name = "DOCCS Boot kernel", @@ -70,8 +67,6 @@ static struct mtd_partition partition_info[] = { }, }; #define NUM_PARTITIONS ARRAY_SIZE(partition_info) -#endif - static struct map_info scx200_docflash_map = { .name = "NatSemi SCx200 DOCCS Flash", @@ -168,9 +163,8 @@ static int __init init_scx200_docflash(void) outl(pmr, scx200_cb_base + SCx200_PMR); } - printk(KERN_INFO NAME ": DOCCS mapped at 0x%llx-0x%llx, width %d\n", - (unsigned long long)docmem.start, - (unsigned long long)docmem.end, width); + printk(KERN_INFO NAME ": DOCCS mapped at %pR, width %d\n", + &docmem, width); scx200_docflash_map.size = size; if (width == 8) @@ -201,24 +195,17 @@ static int __init init_scx200_docflash(void) mymtd->owner = THIS_MODULE; -#ifdef CONFIG_MTD_PARTITIONS partition_info[3].offset = mymtd->size-partition_info[3].size; partition_info[2].size = partition_info[3].offset-partition_info[2].offset; - add_mtd_partitions(mymtd, partition_info, NUM_PARTITIONS); -#else - add_mtd_device(mymtd); -#endif + mtd_device_register(mymtd, partition_info, NUM_PARTITIONS); + return 0; } static void __exit cleanup_scx200_docflash(void) { if (mymtd) { -#ifdef CONFIG_MTD_PARTITIONS - del_mtd_partitions(mymtd); -#else - del_mtd_device(mymtd); -#endif + mtd_device_unregister(mymtd); map_destroy(mymtd); } if (scx200_docflash_map.virt) { diff --git a/drivers/mtd/maps/sharpsl-flash.c b/drivers/mtd/maps/sharpsl-flash.c deleted file mode 100644 index 12fe53c0d2f..00000000000 --- a/drivers/mtd/maps/sharpsl-flash.c +++ /dev/null @@ -1,118 +0,0 @@ -/* - * sharpsl-flash.c - * - * Copyright (C) 2001 Lineo Japan, Inc. - * Copyright (C) 2002 SHARP - * - * $Id: sharpsl-flash.c,v 1.7 2005/11/07 11:14:28 gleixner Exp $ - * - * 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", - } -}; - -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 partision 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/maps/solutionengine.c b/drivers/mtd/maps/solutionengine.c index d76ceef453c..bb580bc1644 100644 --- a/drivers/mtd/maps/solutionengine.c +++ b/drivers/mtd/maps/solutionengine.c @@ -1,6 +1,4 @@ /* - * $Id: solutionengine.c,v 1.15 2005/11/07 11:14:28 gleixner Exp $ - * * Flash and EPROM on Hitachi Solution Engine and similar boards. * * (C) 2001 Red Hat, Inc. @@ -21,8 +19,6 @@ static struct mtd_info *flash_mtd; static struct mtd_info *eprom_mtd; -static struct mtd_partition *parsed_parts; - struct map_info soleng_eprom_map = { .name = "Solution Engine EPROM", .size = 0x400000, @@ -35,30 +31,10 @@ struct map_info soleng_flash_map = { .bankwidth = 4, }; -static const char *probes[] = { "RedBoot", "cmdlinepart", NULL }; - -#ifdef CONFIG_MTD_SUPERH_RESERVE -static struct mtd_partition superh_se_partitions[] = { - /* Reserved for boot code, read-only */ - { - .name = "flash_boot", - .offset = 0x00000000, - .size = CONFIG_MTD_SUPERH_RESERVE, - .mask_flags = MTD_WRITEABLE, - }, - /* All else is writable (e.g. JFFS) */ - { - .name = "Flash FS", - .offset = MTDPART_OFS_NXTBLK, - .size = MTDPART_SIZ_FULL, - } -}; -#endif /* CONFIG_MTD_SUPERH_RESERVE */ +static const char * const probes[] = { "RedBoot", "cmdlinepart", NULL }; static int __init init_soleng_maps(void) { - int nr_parts = 0; - /* First probe at offset 0 */ soleng_flash_map.phys = 0; soleng_flash_map.virt = (void __iomem *)P2SEGADDR(0); @@ -91,24 +67,10 @@ static int __init init_soleng_maps(void) eprom_mtd = do_map_probe("map_rom", &soleng_eprom_map); if (eprom_mtd) { eprom_mtd->owner = THIS_MODULE; - add_mtd_device(eprom_mtd); - } - - nr_parts = parse_mtd_partitions(flash_mtd, probes, &parsed_parts, 0); - -#ifdef CONFIG_MTD_SUPERH_RESERVE - if (nr_parts <= 0) { - printk(KERN_NOTICE "Using configured partition at 0x%08x.\n", - CONFIG_MTD_SUPERH_RESERVE); - parsed_parts = superh_se_partitions; - nr_parts = sizeof(superh_se_partitions)/sizeof(*parsed_parts); + mtd_device_register(eprom_mtd, NULL, 0); } -#endif /* CONFIG_MTD_SUPERH_RESERVE */ - if (nr_parts > 0) - add_mtd_partitions(flash_mtd, parsed_parts, nr_parts); - else - add_mtd_device(flash_mtd); + mtd_device_parse_register(flash_mtd, probes, NULL, NULL, 0); return 0; } @@ -116,14 +78,11 @@ static int __init init_soleng_maps(void) static void __exit cleanup_soleng_maps(void) { if (eprom_mtd) { - del_mtd_device(eprom_mtd); + mtd_device_unregister(eprom_mtd); map_destroy(eprom_mtd); } - if (parsed_parts) - del_mtd_partitions(flash_mtd); - else - del_mtd_device(flash_mtd); + mtd_device_unregister(flash_mtd); map_destroy(flash_mtd); } diff --git a/drivers/mtd/maps/sun_uflash.c b/drivers/mtd/maps/sun_uflash.c index 4db2055cee3..b6f1aac3510 100644 --- a/drivers/mtd/maps/sun_uflash.c +++ b/drivers/mtd/maps/sun_uflash.c @@ -1,23 +1,20 @@ -/* $Id: sun_uflash.c,v 1.13 2005/11/07 11:14:28 gleixner Exp $ - * - * sun_uflash - Driver implementation for user-programmable flash - * present on many Sun Microsystems SME boardsets. +/* sun_uflash.c - Driver for user-programmable flash on + * Sun Microsystems SME boardsets. * * This driver does NOT provide access to the OBP-flash for * safety reasons-- use <linux>/drivers/sbus/char/flash.c instead. * * Copyright (c) 2001 Eric Brower (ebrower@usa.net) - * */ #include <linux/kernel.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/errno.h> -#include <linux/init.h> #include <linux/ioport.h> -#include <asm/ebus.h> -#include <asm/oplib.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/slab.h> #include <asm/prom.h> #include <asm/uaccess.h> #include <asm/io.h> @@ -26,67 +23,65 @@ #include <linux/mtd/map.h> #define UFLASH_OBPNAME "flashprom" -#define UFLASH_DEVNAME "userflash" +#define DRIVER_NAME "sun_uflash" +#define PFX DRIVER_NAME ": " #define UFLASH_WINDOW_SIZE 0x200000 #define UFLASH_BUSWIDTH 1 /* EBus is 8-bit */ MODULE_AUTHOR("Eric Brower <ebrower@usa.net>"); MODULE_DESCRIPTION("User-programmable flash device on Sun Microsystems boardsets"); -MODULE_SUPPORTED_DEVICE("userflash"); +MODULE_SUPPORTED_DEVICE(DRIVER_NAME); MODULE_LICENSE("GPL"); -MODULE_VERSION("2.0"); +MODULE_VERSION("2.1"); -static LIST_HEAD(device_list); struct uflash_dev { - char *name; /* device name */ + const char *name; /* device name */ struct map_info map; /* mtd map info */ struct mtd_info *mtd; /* mtd info */ }; - struct map_info uflash_map_templ = { .name = "SUNW,???-????", .size = UFLASH_WINDOW_SIZE, .bankwidth = UFLASH_BUSWIDTH, }; -int uflash_devinit(struct linux_ebus_device *edev, struct device_node *dp) +int uflash_devinit(struct platform_device *op, struct device_node *dp) { struct uflash_dev *up; - struct resource *res; - - res = &edev->resource[0]; - if (edev->num_addrs != 1) { + if (op->resource[1].flags) { /* Non-CFI userflash device-- once I find one we * can work on supporting it. */ - printk("%s: unsupported device at 0x%llx (%d regs): " \ - "email ebrower@usa.net\n", - dp->full_name, (unsigned long long)res->start, - edev->num_addrs); + printk(KERN_ERR PFX "Unsupported device at %s, 0x%llx\n", + dp->full_name, (unsigned long long)op->resource[0].start); return -ENODEV; } up = kzalloc(sizeof(struct uflash_dev), GFP_KERNEL); - if (!up) + if (!up) { + printk(KERN_ERR PFX "Cannot allocate struct uflash_dev\n"); return -ENOMEM; + } /* copy defaults and tweak parameters */ memcpy(&up->map, &uflash_map_templ, sizeof(uflash_map_templ)); - up->map.size = (res->end - res->start) + 1UL; + + up->map.size = resource_size(&op->resource[0]); up->name = of_get_property(dp, "model", NULL); if (up->name && 0 < strlen(up->name)) up->map.name = up->name; - up->map.phys = res->start; + up->map.phys = op->resource[0].start; - up->map.virt = ioremap_nocache(res->start, up->map.size); + up->map.virt = of_ioremap(&op->resource[0], 0, up->map.size, + DRIVER_NAME); if (!up->map.virt) { - printk("%s: Failed to map device.\n", dp->full_name); + printk(KERN_ERR PFX "Failed to map device.\n"); kfree(up); return -EINVAL; @@ -97,7 +92,7 @@ int uflash_devinit(struct linux_ebus_device *edev, struct device_node *dp) /* MTD registration */ up->mtd = do_map_probe("cfi_probe", &up->map); if (!up->mtd) { - iounmap(up->map.virt); + of_iounmap(&op->resource[0], up->map.virt, up->map.size); kfree(up); return -ENXIO; @@ -105,34 +100,36 @@ int uflash_devinit(struct linux_ebus_device *edev, struct device_node *dp) up->mtd->owner = THIS_MODULE; - add_mtd_device(up->mtd); + mtd_device_register(up->mtd, NULL, 0); - dev_set_drvdata(&edev->ofdev.dev, up); + dev_set_drvdata(&op->dev, up); return 0; } -static int __devinit uflash_probe(struct of_device *dev, const struct of_device_id *match) +static int uflash_probe(struct platform_device *op) { - struct linux_ebus_device *edev = to_ebus_device(&dev->dev); - struct device_node *dp = dev->node; + struct device_node *dp = op->dev.of_node; - if (of_find_property(dp, "user", NULL)) + /* Flashprom must have the "user" property in order to + * be used by this driver. + */ + if (!of_find_property(dp, "user", NULL)) return -ENODEV; - return uflash_devinit(edev, dp); + return uflash_devinit(op, dp); } -static int __devexit uflash_remove(struct of_device *dev) +static int uflash_remove(struct platform_device *op) { - struct uflash_dev *up = dev_get_drvdata(&dev->dev); + struct uflash_dev *up = dev_get_drvdata(&op->dev); if (up->mtd) { - del_mtd_device(up->mtd); + mtd_device_unregister(up->mtd); map_destroy(up->mtd); } if (up->map.virt) { - iounmap(up->map.virt); + of_iounmap(&op->resource[0], up->map.virt, up->map.size); up->map.virt = NULL; } @@ -141,7 +138,7 @@ static int __devexit uflash_remove(struct of_device *dev) return 0; } -static struct of_device_id uflash_match[] = { +static const struct of_device_id uflash_match[] = { { .name = UFLASH_OBPNAME, }, @@ -150,22 +147,14 @@ static struct of_device_id uflash_match[] = { MODULE_DEVICE_TABLE(of, uflash_match); -static struct of_platform_driver uflash_driver = { - .name = UFLASH_DEVNAME, - .match_table = uflash_match, +static struct platform_driver uflash_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = uflash_match, + }, .probe = uflash_probe, - .remove = __devexit_p(uflash_remove), + .remove = uflash_remove, }; -static int __init uflash_init(void) -{ - return of_register_driver(&uflash_driver, &ebus_bus_type); -} - -static void __exit uflash_exit(void) -{ - of_unregister_driver(&uflash_driver); -} - -module_init(uflash_init); -module_exit(uflash_exit); +module_platform_driver(uflash_driver); diff --git a/drivers/mtd/maps/tqm834x.c b/drivers/mtd/maps/tqm834x.c deleted file mode 100644 index 58e5912bd38..00000000000 --- a/drivers/mtd/maps/tqm834x.c +++ /dev/null @@ -1,290 +0,0 @@ -/* - * drivers/mtd/maps/tqm834x.c - * - * MTD mapping driver for TQM834x boards - * - * Copyright 2005 Wolfgang Denk, DENX Software Engineering, <wd@denx.de>. - * - * This file is licensed under the terms of the GNU General Public License - * version 2. This program is licensed "as is" without any warranty of any - * kind, whether express or implied. - * - */ - -#include <linux/init.h> -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/slab.h> -#include <asm/io.h> -#include <asm/ppcboot.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - -#define FLASH_BANK_MAX 2 - -extern unsigned char __res[]; - -/* trivial struct to describe partition information */ -struct mtd_part_def -{ - int nums; - unsigned char *type; - struct mtd_partition* mtd_part; -}; - -static struct mtd_info* mtd_banks[FLASH_BANK_MAX]; -static struct map_info* map_banks[FLASH_BANK_MAX]; -static struct mtd_part_def part_banks[FLASH_BANK_MAX]; - -static unsigned long num_banks; -static unsigned long start_scan_addr; - -#ifdef CONFIG_MTD_PARTITIONS -/* - * The following defines the partition layout of TQM834x boards. - * - * See include/linux/mtd/partitions.h for definition of the - * mtd_partition structure. - * - * Assume minimal initial size of 4 MiB per bank, will be updated - * later in init_tqm834x_mtd() routine. - */ - -/* Partition definition for the first flash bank which is always present. */ -static struct mtd_partition tqm834x_partitions_bank1[] = { - { - .name = "u-boot", /* u-boot firmware */ - .offset = 0x00000000, - .size = 0x00040000, /* 256 KiB */ - /*mask_flags: MTD_WRITEABLE, * force read-only */ - }, - { - .name = "env", /* u-boot environment */ - .offset = 0x00040000, - .size = 0x00020000, /* 128 KiB */ - /*mask_flags: MTD_WRITEABLE, * force read-only */ - }, - { - .name = "kernel", /* linux kernel image */ - .offset = 0x00060000, - .size = 0x00100000, /* 1 MiB */ - /*mask_flags: MTD_WRITEABLE, * force read-only */ - }, - { - .name = "initrd", /* ramdisk image */ - .offset = 0x00160000, - .size = 0x00200000, /* 2 MiB */ - }, - { - .name = "user", /* user data */ - .offset = 0x00360000, - .size = 0x000a0000, /* remaining space */ - /* NOTE: this parttion size is re-calcated in */ - /* init_tqm834x_mtd() to cover actual remaining space. */ - }, -}; - -/* Partition definition for the second flash bank which may be present on some - * TQM834x boards. - */ -static struct mtd_partition tqm834x_partitions_bank2[] = { - { - .name = "jffs2", /* jffs2 filesystem */ - .offset = 0x00000000, - .size = 0x00400000, /* whole device */ - /* NOTE: this parttion size is re-calcated in */ - /* init_tqm834x_mtd() to cover actual device size. */ - }, -}; - -#endif /* CONFIG_MTD_PARTITIONS */ - -static int __init init_tqm834x_mtd(void) -{ - int idx = 0, ret = 0; - unsigned long flash_addr, flash_size, mtd_size = 0; - - /* pointer to TQM834x board info data */ - bd_t *bd = (bd_t *)__res; -#ifdef CONFIG_MTD_CMDLINE_PARTS - int n; - char mtdid[4]; - const char *part_probes[] = { "cmdlinepart", NULL }; -#endif - - flash_addr = bd->bi_flashstart; - flash_size = bd->bi_flashsize; - - /* request maximum flash size address space */ - start_scan_addr = (unsigned long)ioremap(flash_addr, flash_size); - if (!start_scan_addr) { - printk("%s: Failed to ioremap address: 0x%lx\n", - __FUNCTION__, flash_addr); - return -EIO; - } - - for(idx = 0 ; idx < FLASH_BANK_MAX ; idx++) { - if (mtd_size >= flash_size) - break; - - pr_debug("%s: chip probing count %d\n", __FUNCTION__, idx); - - map_banks[idx] = - (struct map_info *)kmalloc(sizeof(struct map_info), - GFP_KERNEL); - if (map_banks[idx] == NULL) { - ret = -ENOMEM; - goto error_mem; - } - memset((void *)map_banks[idx], 0, sizeof(struct map_info)); - map_banks[idx]->name = (char *)kmalloc(16, GFP_KERNEL); - if (map_banks[idx]->name == NULL) { - ret = -ENOMEM; - goto error_mem; - } - memset((void *)map_banks[idx]->name, 0, 16); - - sprintf(map_banks[idx]->name, "TQM834x-%d", idx); - map_banks[idx]->size = flash_size; - map_banks[idx]->bankwidth = 4; - - simple_map_init(map_banks[idx]); - - map_banks[idx]->virt = (void __iomem *) - (start_scan_addr + ((idx > 0) ? - (mtd_banks[idx-1] ? mtd_banks[idx-1]->size : 0) : 0)); - map_banks[idx]->phys = - flash_addr + ((idx > 0) ? - (mtd_banks[idx-1] ? mtd_banks[idx-1]->size : 0) : 0); - - /* start to probe flash chips */ - mtd_banks[idx] = do_map_probe("cfi_probe", map_banks[idx]); - if (mtd_banks[idx]) { - mtd_banks[idx]->owner = THIS_MODULE; - mtd_size += mtd_banks[idx]->size; - num_banks++; - pr_debug("%s: bank %ld, name: %s, size: %d bytes \n", - __FUNCTION__, num_banks, - mtd_banks[idx]->name, mtd_banks[idx]->size); - } - } - - /* no supported flash chips found */ - if (!num_banks) { - printk("TQM834x: No supported flash chips found!\n"); - ret = -ENXIO; - goto error_mem; - } - -#ifdef CONFIG_MTD_PARTITIONS - /* - * Select static partition definitions - */ - n = ARRAY_SIZE(tqm834x_partitions_bank1); - part_banks[0].mtd_part = tqm834x_partitions_bank1; - part_banks[0].type = "static image bank1"; - part_banks[0].nums = n; - - /* update last partition size to cover actual remaining space */ - tqm834x_partitions_bank1[n - 1].size = - mtd_banks[0]->size - - tqm834x_partitions_bank1[n - 1].offset; - - /* check if we have second bank? */ - if (num_banks == 2) { - n = ARRAY_SIZE(tqm834x_partitions_bank2); - part_banks[1].mtd_part = tqm834x_partitions_bank2; - part_banks[1].type = "static image bank2"; - part_banks[1].nums = n; - - /* update last partition size to cover actual remaining space */ - tqm834x_partitions_bank2[n - 1].size = - mtd_banks[1]->size - - tqm834x_partitions_bank2[n - 1].offset; - } - - for(idx = 0; idx < num_banks ; idx++) { -#ifdef CONFIG_MTD_CMDLINE_PARTS - sprintf(mtdid, "%d", idx); - n = parse_mtd_partitions(mtd_banks[idx], - part_probes, - &part_banks[idx].mtd_part, - 0); - pr_debug("%s: %d command line partitions on bank %s\n", - __FUNCTION__, n, mtdid); - if (n > 0) { - part_banks[idx].type = "command line"; - part_banks[idx].nums = n; - } -#endif /* CONFIG_MTD_CMDLINE_PARTS */ - if (part_banks[idx].nums == 0) { - printk(KERN_NOTICE - "TQM834x flash bank %d: no partition info " - "available, registering whole device\n", idx); - add_mtd_device(mtd_banks[idx]); - } else { - printk(KERN_NOTICE - "TQM834x flash bank %d: Using %s partition " - "definition\n", idx, part_banks[idx].type); - add_mtd_partitions(mtd_banks[idx], - part_banks[idx].mtd_part, - part_banks[idx].nums); - } - } -#else /* ! CONFIG_MTD_PARTITIONS */ - printk(KERN_NOTICE "TQM834x flash: registering %d flash banks " - "at once\n", num_banks); - - for(idx = 0 ; idx < num_banks ; idx++) - add_mtd_device(mtd_banks[idx]); - -#endif /* CONFIG_MTD_PARTITIONS */ - - return 0; -error_mem: - for (idx = 0 ; idx < FLASH_BANK_MAX ; idx++) { - if (map_banks[idx] != NULL) { - if (map_banks[idx]->name != NULL) { - kfree(map_banks[idx]->name); - map_banks[idx]->name = NULL; - } - kfree(map_banks[idx]); - map_banks[idx] = NULL; - } - } - - iounmap((void *)start_scan_addr); - - return ret; -} - -static void __exit cleanup_tqm834x_mtd(void) -{ - unsigned int idx = 0; - for(idx = 0 ; idx < num_banks ; idx++) { - /* destroy mtd_info previously allocated */ - if (mtd_banks[idx]) { - del_mtd_partitions(mtd_banks[idx]); - map_destroy(mtd_banks[idx]); - } - - /* release map_info not used anymore */ - kfree(map_banks[idx]->name); - kfree(map_banks[idx]); - } - - if (start_scan_addr) { - iounmap((void *)start_scan_addr); - start_scan_addr = 0; - } -} - -module_init(init_tqm834x_mtd); -module_exit(cleanup_tqm834x_mtd); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Wolfgang Denk <wd@denx.de>"); -MODULE_DESCRIPTION("MTD map driver for TQM834x boards"); diff --git a/drivers/mtd/maps/tqm8xxl.c b/drivers/mtd/maps/tqm8xxl.c deleted file mode 100644 index 19578ba84ee..00000000000 --- a/drivers/mtd/maps/tqm8xxl.c +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Handle mapping of the flash memory access routines - * on TQM8xxL based devices. - * - * $Id: tqm8xxl.c,v 1.15 2005/11/07 11:14:28 gleixner Exp $ - * - * based on rpxlite.c - * - * Copyright(C) 2001 Kirk Lee <kirk@hpc.ee.ntu.edu.tw> - * - * This code is GPLed - * - */ - -/* - * According to TQM8xxL hardware manual, TQM8xxL series have - * following flash memory organisations: - * | capacity | | chip type | | bank0 | | bank1 | - * 2MiB 512Kx16 2MiB 0 - * 4MiB 1Mx16 4MiB 0 - * 8MiB 1Mx16 4MiB 4MiB - * Thus, we choose CONFIG_MTD_CFI_I2 & CONFIG_MTD_CFI_B4 at - * kernel configuration. - */ -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <linux/slab.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - -#include <asm/io.h> - -#define FLASH_ADDR 0x40000000 -#define FLASH_SIZE 0x00800000 -#define FLASH_BANK_MAX 4 - -// trivial struct to describe partition information -struct mtd_part_def -{ - int nums; - unsigned char *type; - struct mtd_partition* mtd_part; -}; - -//static struct mtd_info *mymtd; -static struct mtd_info* mtd_banks[FLASH_BANK_MAX]; -static struct map_info* map_banks[FLASH_BANK_MAX]; -static struct mtd_part_def part_banks[FLASH_BANK_MAX]; -static unsigned long num_banks; -static void __iomem *start_scan_addr; - -/* - * Here are partition information for all known TQM8xxL series devices. - * See include/linux/mtd/partitions.h for definition of the mtd_partition - * structure. - * - * The *_max_flash_size is the maximum possible mapped flash size which - * is not necessarily the actual flash size. It must correspond to the - * value specified in the mapping definition defined by the - * "struct map_desc *_io_desc" for the corresponding machine. - */ - -#ifdef CONFIG_MTD_PARTITIONS -/* Currently, TQM8xxL has upto 8MiB flash */ -static unsigned long tqm8xxl_max_flash_size = 0x00800000; - -/* partition definition for first flash bank - * (cf. "drivers/char/flash_config.c") - */ -static struct mtd_partition tqm8xxl_partitions[] = { - { - .name = "ppcboot", - .offset = 0x00000000, - .size = 0x00020000, /* 128KB */ - .mask_flags = MTD_WRITEABLE, /* force read-only */ - }, - { - .name = "kernel", /* default kernel image */ - .offset = 0x00020000, - .size = 0x000e0000, - .mask_flags = MTD_WRITEABLE, /* force read-only */ - }, - { - .name = "user", - .offset = 0x00100000, - .size = 0x00100000, - }, - { - .name = "initrd", - .offset = 0x00200000, - .size = 0x00200000, - } -}; -/* partition definition for second flash bank */ -static struct mtd_partition tqm8xxl_fs_partitions[] = { - { - .name = "cramfs", - .offset = 0x00000000, - .size = 0x00200000, - }, - { - .name = "jffs", - .offset = 0x00200000, - .size = 0x00200000, - //.size = MTDPART_SIZ_FULL, - } -}; -#endif - -int __init init_tqm_mtd(void) -{ - int idx = 0, ret = 0; - unsigned long flash_addr, flash_size, mtd_size = 0; - /* pointer to TQM8xxL board info data */ - bd_t *bd = (bd_t *)__res; - - flash_addr = bd->bi_flashstart; - flash_size = bd->bi_flashsize; - - //request maximum flash size address space - start_scan_addr = ioremap(flash_addr, flash_size); - if (!start_scan_addr) { - printk(KERN_WARNING "%s:Failed to ioremap address:0x%x\n", __FUNCTION__, flash_addr); - return -EIO; - } - - for (idx = 0 ; idx < FLASH_BANK_MAX ; idx++) { - if(mtd_size >= flash_size) - break; - - printk(KERN_INFO "%s: chip probing count %d\n", __FUNCTION__, idx); - - map_banks[idx] = (struct map_info *)kmalloc(sizeof(struct map_info), GFP_KERNEL); - if(map_banks[idx] == NULL) { - ret = -ENOMEM; - /* FIXME: What if some MTD devices were probed already? */ - goto error_mem; - } - - memset((void *)map_banks[idx], 0, sizeof(struct map_info)); - map_banks[idx]->name = (char *)kmalloc(16, GFP_KERNEL); - - if (!map_banks[idx]->name) { - ret = -ENOMEM; - /* FIXME: What if some MTD devices were probed already? */ - goto error_mem; - } - sprintf(map_banks[idx]->name, "TQM8xxL%d", idx); - - map_banks[idx]->size = flash_size; - map_banks[idx]->bankwidth = 4; - - simple_map_init(map_banks[idx]); - - map_banks[idx]->virt = start_scan_addr; - map_banks[idx]->phys = flash_addr; - /* FIXME: This looks utterly bogus, but I'm trying to - preserve the behaviour of the original (shown here)... - - map_banks[idx]->map_priv_1 = - start_scan_addr + ((idx > 0) ? - (mtd_banks[idx-1] ? mtd_banks[idx-1]->size : 0) : 0); - */ - - if (idx && mtd_banks[idx-1]) { - map_banks[idx]->virt += mtd_banks[idx-1]->size; - map_banks[idx]->phys += mtd_banks[idx-1]->size; - } - - //start to probe flash chips - mtd_banks[idx] = do_map_probe("cfi_probe", map_banks[idx]); - - if (mtd_banks[idx]) { - mtd_banks[idx]->owner = THIS_MODULE; - mtd_size += mtd_banks[idx]->size; - num_banks++; - - printk(KERN_INFO "%s: bank%d, name:%s, size:%dbytes \n", __FUNCTION__, num_banks, - mtd_banks[idx]->name, mtd_banks[idx]->size); - } - } - - /* no supported flash chips found */ - if (!num_banks) { - printk(KERN_NOTICE "TQM8xxL: No support flash chips found!\n"); - ret = -ENXIO; - goto error_mem; - } - -#ifdef CONFIG_MTD_PARTITIONS - /* - * Select Static partition definitions - */ - part_banks[0].mtd_part = tqm8xxl_partitions; - part_banks[0].type = "Static image"; - part_banks[0].nums = ARRAY_SIZE(tqm8xxl_partitions); - - part_banks[1].mtd_part = tqm8xxl_fs_partitions; - part_banks[1].type = "Static file system"; - part_banks[1].nums = ARRAY_SIZE(tqm8xxl_fs_partitions); - - for(idx = 0; idx < num_banks ; idx++) { - if (part_banks[idx].nums == 0) { - printk(KERN_NOTICE "TQM flash%d: no partition info available, registering whole flash at once\n", idx); - add_mtd_device(mtd_banks[idx]); - } else { - printk(KERN_NOTICE "TQM flash%d: Using %s partition definition\n", - idx, part_banks[idx].type); - add_mtd_partitions(mtd_banks[idx], part_banks[idx].mtd_part, - part_banks[idx].nums); - } - } -#else - printk(KERN_NOTICE "TQM flash: registering %d whole flash banks at once\n", num_banks); - for(idx = 0 ; idx < num_banks ; idx++) - add_mtd_device(mtd_banks[idx]); -#endif - return 0; -error_mem: - for(idx = 0 ; idx < FLASH_BANK_MAX ; idx++) { - if(map_banks[idx] != NULL) { - kfree(map_banks[idx]->name); - map_banks[idx]->name = NULL; - kfree(map_banks[idx]); - map_banks[idx] = NULL; - } - } -error: - iounmap(start_scan_addr); - return ret; -} - -static void __exit cleanup_tqm_mtd(void) -{ - unsigned int idx = 0; - for(idx = 0 ; idx < num_banks ; idx++) { - /* destroy mtd_info previously allocated */ - if (mtd_banks[idx]) { - del_mtd_partitions(mtd_banks[idx]); - map_destroy(mtd_banks[idx]); - } - /* release map_info not used anymore */ - kfree(map_banks[idx]->name); - kfree(map_banks[idx]); - } - - if (start_scan_addr) { - iounmap(start_scan_addr); - start_scan_addr = 0; - } -} - -module_init(init_tqm_mtd); -module_exit(cleanup_tqm_mtd); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Kirk Lee <kirk@hpc.ee.ntu.edu.tw>"); -MODULE_DESCRIPTION("MTD map driver for TQM8xxL boards"); diff --git a/drivers/mtd/maps/ts5500_flash.c b/drivers/mtd/maps/ts5500_flash.c index b47270e850b..d1d671daf23 100644 --- a/drivers/mtd/maps/ts5500_flash.c +++ b/drivers/mtd/maps/ts5500_flash.c @@ -22,8 +22,6 @@ * - Drive A and B use the resident flash disk (RFD) flash translation layer. * - If you have created your own jffs file system and the bios overwrites * it during boot, try disabling Drive A: and B: in the boot order. - * - * $Id: ts5500_flash.c,v 1.5 2005/11/07 11:14:28 gleixner Exp $ */ #include <linux/init.h> @@ -91,12 +89,11 @@ static int __init init_ts5500_map(void) } mymtd->owner = THIS_MODULE; - add_mtd_partitions(mymtd, ts5500_partitions, NUM_PARTITIONS); + mtd_device_register(mymtd, ts5500_partitions, NUM_PARTITIONS); return 0; err1: - map_destroy(mymtd); iounmap(ts5500_map.virt); err2: return rc; @@ -105,7 +102,7 @@ err2: static void __exit cleanup_ts5500_map(void) { if (mymtd) { - del_mtd_partitions(mymtd); + mtd_device_unregister(mymtd); map_destroy(mymtd); } diff --git a/drivers/mtd/maps/tsunami_flash.c b/drivers/mtd/maps/tsunami_flash.c index 0f915ac3102..da2cdb5fd6d 100644 --- a/drivers/mtd/maps/tsunami_flash.c +++ b/drivers/mtd/maps/tsunami_flash.c @@ -2,7 +2,6 @@ * tsunami_flash.c * * flash chip on alpha ds10... - * $Id: tsunami_flash.c,v 1.10 2005/11/07 11:14:29 gleixner Exp $ */ #include <asm/io.h> #include <asm/core_tsunami.h> @@ -77,17 +76,18 @@ static void __exit cleanup_tsunami_flash(void) struct mtd_info *mtd; mtd = tsunami_flash_mtd; if (mtd) { - del_mtd_device(mtd); + mtd_device_unregister(mtd); map_destroy(mtd); } tsunami_flash_mtd = 0; } +static const char * const rom_probe_types[] = { + "cfi_probe", "jedec_probe", "map_rom", NULL }; static int __init init_tsunami_flash(void) { - static const char *rom_probe_types[] = { "cfi_probe", "jedec_probe", "map_rom", NULL }; - char **type; + const char * const *type; tsunami_tig_writeb(FLASH_ENABLE_BYTE, FLASH_ENABLE_PORT); @@ -98,7 +98,7 @@ static int __init init_tsunami_flash(void) } if (tsunami_flash_mtd) { tsunami_flash_mtd->owner = THIS_MODULE; - add_mtd_device(tsunami_flash_mtd); + mtd_device_register(tsunami_flash_mtd, NULL, 0); return 0; } return -ENXIO; diff --git a/drivers/mtd/maps/uclinux.c b/drivers/mtd/maps/uclinux.c index 389fea28b9a..c1af83db520 100644 --- a/drivers/mtd/maps/uclinux.c +++ b/drivers/mtd/maps/uclinux.c @@ -4,8 +4,6 @@ * uclinux.c -- generic memory mapped MTD driver for uclinux * * (C) Copyright 2002, Greg Ungerer (gerg@snapgear.com) - * - * $Id: uclinux.c,v 1.12 2005/11/07 11:14:29 gleixner Exp $ */ /****************************************************************************/ @@ -15,24 +13,41 @@ #include <linux/init.h> #include <linux/kernel.h> #include <linux/fs.h> +#include <linux/mm.h> #include <linux/major.h> -#include <linux/root_dev.h> #include <linux/mtd/mtd.h> #include <linux/mtd/map.h> #include <linux/mtd/partitions.h> #include <asm/io.h> +#include <asm/sections.h> /****************************************************************************/ +#ifdef CONFIG_MTD_ROM +#define MAP_NAME "rom" +#else +#define MAP_NAME "ram" +#endif + +/* + * Blackfin uses uclinux_ram_map during startup, so it must not be static. + * Provide a dummy declaration to make sparse happy. + */ +extern struct map_info uclinux_ram_map; + struct map_info uclinux_ram_map = { - .name = "RAM", + .name = MAP_NAME, + .size = 0, }; -struct mtd_info *uclinux_ram_mtdinfo; +static unsigned long physaddr = -1; +module_param(physaddr, ulong, S_IRUGO); + +static struct mtd_info *uclinux_ram_mtdinfo; /****************************************************************************/ -struct mtd_partition uclinux_romfs[] = { +static struct mtd_partition uclinux_romfs[] = { { .name = "ROMfs" } }; @@ -40,75 +55,80 @@ struct mtd_partition uclinux_romfs[] = { /****************************************************************************/ -int uclinux_point(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char **mtdbuf) +static int uclinux_point(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, void **virt, resource_size_t *phys) { struct map_info *map = mtd->priv; - *mtdbuf = (u_char *) (map->virt + ((int) from)); + *virt = map->virt + from; + if (phys) + *phys = map->phys + from; *retlen = len; return(0); } /****************************************************************************/ -int __init uclinux_mtd_init(void) +static int __init uclinux_mtd_init(void) { struct mtd_info *mtd; struct map_info *mapp; - extern char _ebss; - unsigned long addr = (unsigned long) &_ebss; mapp = &uclinux_ram_map; - mapp->phys = addr; - mapp->size = PAGE_ALIGN(ntohl(*((unsigned long *)(addr + 8)))); + + if (physaddr == -1) + mapp->phys = (resource_size_t)__bss_stop; + else + mapp->phys = physaddr; + + if (!mapp->size) + mapp->size = PAGE_ALIGN(ntohl(*((unsigned long *)(mapp->phys + 8)))); mapp->bankwidth = 4; - printk("uclinux[mtd]: RAM probe address=0x%x size=0x%x\n", + printk("uclinux[mtd]: probe address=0x%x size=0x%x\n", (int) mapp->phys, (int) mapp->size); - mapp->virt = ioremap_nocache(mapp->phys, mapp->size); + /* + * The filesystem is guaranteed to be in direct mapped memory. It is + * directly following the kernels own bss region. Following the same + * mechanism used by architectures setting up traditional initrds we + * use phys_to_virt to get the virtual address of its start. + */ + mapp->virt = phys_to_virt(mapp->phys); if (mapp->virt == 0) { - printk("uclinux[mtd]: ioremap_nocache() failed\n"); + printk("uclinux[mtd]: no virtual mapping?\n"); return(-EIO); } simple_map_init(mapp); - mtd = do_map_probe("map_ram", mapp); + mtd = do_map_probe("map_" MAP_NAME, mapp); if (!mtd) { printk("uclinux[mtd]: failed to find a mapping?\n"); - iounmap(mapp->virt); return(-ENXIO); } mtd->owner = THIS_MODULE; - mtd->point = uclinux_point; + mtd->_point = uclinux_point; mtd->priv = mapp; uclinux_ram_mtdinfo = mtd; - add_mtd_partitions(mtd, uclinux_romfs, NUM_PARTITIONS); - - printk("uclinux[mtd]: set %s to be root filesystem\n", - uclinux_romfs[0].name); - ROOT_DEV = MKDEV(MTD_BLOCK_MAJOR, 0); + mtd_device_register(mtd, uclinux_romfs, NUM_PARTITIONS); return(0); } /****************************************************************************/ -void __exit uclinux_mtd_cleanup(void) +static void __exit uclinux_mtd_cleanup(void) { if (uclinux_ram_mtdinfo) { - del_mtd_partitions(uclinux_ram_mtdinfo); + mtd_device_unregister(uclinux_ram_mtdinfo); map_destroy(uclinux_ram_mtdinfo); uclinux_ram_mtdinfo = NULL; } - if (uclinux_ram_map.virt) { - iounmap((void *) uclinux_ram_map.virt); + if (uclinux_ram_map.virt) uclinux_ram_map.virt = 0; - } } /****************************************************************************/ @@ -118,6 +138,6 @@ module_exit(uclinux_mtd_cleanup); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Greg Ungerer <gerg@snapgear.com>"); -MODULE_DESCRIPTION("Generic RAM based MTD for uClinux"); +MODULE_DESCRIPTION("Generic MTD for uClinux"); /****************************************************************************/ diff --git a/drivers/mtd/maps/vmax301.c b/drivers/mtd/maps/vmax301.c deleted file mode 100644 index b3e48739543..00000000000 --- a/drivers/mtd/maps/vmax301.c +++ /dev/null @@ -1,198 +0,0 @@ -// $Id: vmax301.c,v 1.32 2005/11/07 11:14:29 gleixner Exp $ -/* ###################################################################### - - Tempustech VMAX SBC301 MTD Driver. - - The VMAx 301 is a SBC based on . It - comes with three builtin AMD 29F016B flash chips and a socket for SRAM or - more flash. Each unit has it's own 8k mapping into a settable region - (0xD8000). There are two 8k mappings for each MTD, the first is always set - to the lower 8k of the device the second is paged. Writing a 16 bit page - value to anywhere in the first 8k will cause the second 8k to page around. - - To boot the device a bios extension must be installed into the first 8k - of flash that is smart enough to copy itself down, page in the rest of - itself and begin executing. - - ##################################################################### */ - -#include <linux/module.h> -#include <linux/slab.h> -#include <linux/ioport.h> -#include <linux/init.h> -#include <linux/spinlock.h> -#include <asm/io.h> - -#include <linux/mtd/map.h> -#include <linux/mtd/mtd.h> - - -#define WINDOW_START 0xd8000 -#define WINDOW_LENGTH 0x2000 -#define WINDOW_SHIFT 25 -#define WINDOW_MASK 0x1FFF - -/* Actually we could use two spinlocks, but we'd have to have - more private space in the struct map_info. We lose a little - performance like this, but we'd probably lose more by having - the extra indirection from having one of the map->map_priv - fields pointing to yet another private struct. -*/ -static DEFINE_SPINLOCK(vmax301_spin); - -static void __vmax301_page(struct map_info *map, unsigned long page) -{ - writew(page, map->map_priv_2 - WINDOW_LENGTH); - map->map_priv_1 = page; -} - -static inline void vmax301_page(struct map_info *map, - unsigned long ofs) -{ - unsigned long page = (ofs >> WINDOW_SHIFT); - if (map->map_priv_1 != page) - __vmax301_page(map, page); -} - -static map_word vmax301_read8(struct map_info *map, unsigned long ofs) -{ - map_word ret; - spin_lock(&vmax301_spin); - vmax301_page(map, ofs); - ret.x[0] = readb(map->map_priv_2 + (ofs & WINDOW_MASK)); - spin_unlock(&vmax301_spin); - return ret; -} - -static void vmax301_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len) -{ - while(len) { - unsigned long thislen = len; - if (len > (WINDOW_LENGTH - (from & WINDOW_MASK))) - thislen = WINDOW_LENGTH-(from & WINDOW_MASK); - spin_lock(&vmax301_spin); - vmax301_page(map, from); - memcpy_fromio(to, map->map_priv_2 + from, thislen); - spin_unlock(&vmax301_spin); - to += thislen; - from += thislen; - len -= thislen; - } -} - -static void vmax301_write8(struct map_info *map, map_word d, unsigned long adr) -{ - spin_lock(&vmax301_spin); - vmax301_page(map, adr); - writeb(d.x[0], map->map_priv_2 + (adr & WINDOW_MASK)); - spin_unlock(&vmax301_spin); -} - -static void vmax301_copy_to(struct map_info *map, unsigned long to, const void *from, ssize_t len) -{ - while(len) { - unsigned long thislen = len; - if (len > (WINDOW_LENGTH - (to & WINDOW_MASK))) - thislen = WINDOW_LENGTH-(to & WINDOW_MASK); - - spin_lock(&vmax301_spin); - vmax301_page(map, to); - memcpy_toio(map->map_priv_2 + to, from, thislen); - spin_unlock(&vmax301_spin); - to += thislen; - from += thislen; - len -= thislen; - } -} - -static struct map_info vmax_map[2] = { - { - .name = "VMAX301 Internal Flash", - .phys = NO_XIP, - .size = 3*2*1024*1024, - .bankwidth = 1, - .read = vmax301_read8, - .copy_from = vmax301_copy_from, - .write = vmax301_write8, - .copy_to = vmax301_copy_to, - .map_priv_1 = WINDOW_START + WINDOW_LENGTH, - .map_priv_2 = 0xFFFFFFFF - }, - { - .name = "VMAX301 Socket", - .phys = NO_XIP, - .size = 0, - .bankwidth = 1, - .read = vmax301_read8, - .copy_from = vmax301_copy_from, - .write = vmax301_write8, - .copy_to = vmax301_copy_to, - .map_priv_1 = WINDOW_START + (3*WINDOW_LENGTH), - .map_priv_2 = 0xFFFFFFFF - } -}; - -static struct mtd_info *vmax_mtd[2] = {NULL, NULL}; - -static void __exit cleanup_vmax301(void) -{ - int i; - - for (i=0; i<2; i++) { - if (vmax_mtd[i]) { - del_mtd_device(vmax_mtd[i]); - map_destroy(vmax_mtd[i]); - } - } - iounmap((void *)vmax_map[0].map_priv_1 - WINDOW_START); -} - -int __init init_vmax301(void) -{ - int i; - unsigned long iomapadr; - // Print out our little header.. - printk("Tempustech VMAX 301 MEM:0x%x-0x%x\n",WINDOW_START, - WINDOW_START+4*WINDOW_LENGTH); - - iomapadr = (unsigned long)ioremap(WINDOW_START, WINDOW_LENGTH*4); - if (!iomapadr) { - printk("Failed to ioremap memory region\n"); - return -EIO; - } - /* Put the address in the map's private data area. - We store the actual MTD IO address rather than the - address of the first half, because it's used more - often. - */ - vmax_map[0].map_priv_2 = iomapadr + WINDOW_START; - vmax_map[1].map_priv_2 = iomapadr + (3*WINDOW_START); - - for (i=0; i<2; i++) { - vmax_mtd[i] = do_map_probe("cfi_probe", &vmax_map[i]); - if (!vmax_mtd[i]) - vmax_mtd[i] = do_map_probe("jedec", &vmax_map[i]); - if (!vmax_mtd[i]) - vmax_mtd[i] = do_map_probe("map_ram", &vmax_map[i]); - if (!vmax_mtd[i]) - vmax_mtd[i] = do_map_probe("map_rom", &vmax_map[i]); - if (vmax_mtd[i]) { - vmax_mtd[i]->owner = THIS_MODULE; - add_mtd_device(vmax_mtd[i]); - } - } - - if (!vmax_mtd[0] && !vmax_mtd[1]) { - iounmap((void *)iomapadr); - return -ENXIO; - } - - return 0; -} - -module_init(init_vmax301); -module_exit(cleanup_vmax301); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); -MODULE_DESCRIPTION("MTD map driver for Tempustech VMAX SBC301 board"); diff --git a/drivers/mtd/maps/vmu-flash.c b/drivers/mtd/maps/vmu-flash.c new file mode 100644 index 00000000000..6b223cfe92b --- /dev/null +++ b/drivers/mtd/maps/vmu-flash.c @@ -0,0 +1,824 @@ +/* vmu-flash.c + * Driver for SEGA Dreamcast Visual Memory Unit + * + * Copyright (c) Adrian McMenamin 2002 - 2009 + * Copyright (c) Paul Mundt 2001 + * + * Licensed under version 2 of the + * GNU General Public Licence + */ +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/maple.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> + +struct vmu_cache { + unsigned char *buffer; /* Cache */ + unsigned int block; /* Which block was cached */ + unsigned long jiffies_atc; /* When was it cached? */ + int valid; +}; + +struct mdev_part { + struct maple_device *mdev; + int partition; +}; + +struct vmupart { + u16 user_blocks; + u16 root_block; + u16 numblocks; + char *name; + struct vmu_cache *pcache; +}; + +struct memcard { + u16 tempA; + u16 tempB; + u32 partitions; + u32 blocklen; + u32 writecnt; + u32 readcnt; + u32 removeable; + int partition; + int read; + unsigned char *blockread; + struct vmupart *parts; + struct mtd_info *mtd; +}; + +struct vmu_block { + unsigned int num; /* block number */ + unsigned int ofs; /* block offset */ +}; + +static struct vmu_block *ofs_to_block(unsigned long src_ofs, + struct mtd_info *mtd, int partition) +{ + struct vmu_block *vblock; + struct maple_device *mdev; + struct memcard *card; + struct mdev_part *mpart; + int num; + + mpart = mtd->priv; + mdev = mpart->mdev; + card = maple_get_drvdata(mdev); + + if (src_ofs >= card->parts[partition].numblocks * card->blocklen) + goto failed; + + num = src_ofs / card->blocklen; + if (num > card->parts[partition].numblocks) + goto failed; + + vblock = kmalloc(sizeof(struct vmu_block), GFP_KERNEL); + if (!vblock) + goto failed; + + vblock->num = num; + vblock->ofs = src_ofs % card->blocklen; + return vblock; + +failed: + return NULL; +} + +/* Maple bus callback function for reads */ +static void vmu_blockread(struct mapleq *mq) +{ + struct maple_device *mdev; + struct memcard *card; + + mdev = mq->dev; + card = maple_get_drvdata(mdev); + /* copy the read in data */ + + if (unlikely(!card->blockread)) + return; + + memcpy(card->blockread, mq->recvbuf->buf + 12, + card->blocklen/card->readcnt); + +} + +/* Interface with maple bus to read blocks + * caching the results so that other parts + * of the driver can access block reads */ +static int maple_vmu_read_block(unsigned int num, unsigned char *buf, + struct mtd_info *mtd) +{ + struct memcard *card; + struct mdev_part *mpart; + struct maple_device *mdev; + int partition, error = 0, x, wait; + unsigned char *blockread = NULL; + struct vmu_cache *pcache; + __be32 sendbuf; + + mpart = mtd->priv; + mdev = mpart->mdev; + partition = mpart->partition; + card = maple_get_drvdata(mdev); + pcache = card->parts[partition].pcache; + pcache->valid = 0; + + /* prepare the cache for this block */ + if (!pcache->buffer) { + pcache->buffer = kmalloc(card->blocklen, GFP_KERNEL); + if (!pcache->buffer) { + dev_err(&mdev->dev, "VMU at (%d, %d) - read fails due" + " to lack of memory\n", mdev->port, + mdev->unit); + error = -ENOMEM; + goto outB; + } + } + + /* + * Reads may be phased - again the hardware spec + * supports this - though may not be any devices in + * the wild that implement it, but we will here + */ + for (x = 0; x < card->readcnt; x++) { + sendbuf = cpu_to_be32(partition << 24 | x << 16 | num); + + if (atomic_read(&mdev->busy) == 1) { + wait_event_interruptible_timeout(mdev->maple_wait, + atomic_read(&mdev->busy) == 0, HZ); + if (atomic_read(&mdev->busy) == 1) { + dev_notice(&mdev->dev, "VMU at (%d, %d)" + " is busy\n", mdev->port, mdev->unit); + error = -EAGAIN; + goto outB; + } + } + + atomic_set(&mdev->busy, 1); + blockread = kmalloc(card->blocklen/card->readcnt, GFP_KERNEL); + if (!blockread) { + error = -ENOMEM; + atomic_set(&mdev->busy, 0); + goto outB; + } + card->blockread = blockread; + + maple_getcond_callback(mdev, vmu_blockread, 0, + MAPLE_FUNC_MEMCARD); + error = maple_add_packet(mdev, MAPLE_FUNC_MEMCARD, + MAPLE_COMMAND_BREAD, 2, &sendbuf); + /* Very long timeouts seem to be needed when box is stressed */ + wait = wait_event_interruptible_timeout(mdev->maple_wait, + (atomic_read(&mdev->busy) == 0 || + atomic_read(&mdev->busy) == 2), HZ * 3); + /* + * MTD layer does not handle hotplugging well + * so have to return errors when VMU is unplugged + * in the middle of a read (busy == 2) + */ + if (error || atomic_read(&mdev->busy) == 2) { + if (atomic_read(&mdev->busy) == 2) + error = -ENXIO; + atomic_set(&mdev->busy, 0); + card->blockread = NULL; + goto outA; + } + if (wait == 0 || wait == -ERESTARTSYS) { + card->blockread = NULL; + atomic_set(&mdev->busy, 0); + error = -EIO; + list_del_init(&(mdev->mq->list)); + kfree(mdev->mq->sendbuf); + mdev->mq->sendbuf = NULL; + if (wait == -ERESTARTSYS) { + dev_warn(&mdev->dev, "VMU read on (%d, %d)" + " interrupted on block 0x%X\n", + mdev->port, mdev->unit, num); + } else + dev_notice(&mdev->dev, "VMU read on (%d, %d)" + " timed out on block 0x%X\n", + mdev->port, mdev->unit, num); + goto outA; + } + + memcpy(buf + (card->blocklen/card->readcnt) * x, blockread, + card->blocklen/card->readcnt); + + memcpy(pcache->buffer + (card->blocklen/card->readcnt) * x, + card->blockread, card->blocklen/card->readcnt); + card->blockread = NULL; + pcache->block = num; + pcache->jiffies_atc = jiffies; + pcache->valid = 1; + kfree(blockread); + } + + return error; + +outA: + kfree(blockread); +outB: + return error; +} + +/* communicate with maple bus for phased writing */ +static int maple_vmu_write_block(unsigned int num, const unsigned char *buf, + struct mtd_info *mtd) +{ + struct memcard *card; + struct mdev_part *mpart; + struct maple_device *mdev; + int partition, error, locking, x, phaselen, wait; + __be32 *sendbuf; + + mpart = mtd->priv; + mdev = mpart->mdev; + partition = mpart->partition; + card = maple_get_drvdata(mdev); + + phaselen = card->blocklen/card->writecnt; + + sendbuf = kmalloc(phaselen + 4, GFP_KERNEL); + if (!sendbuf) { + error = -ENOMEM; + goto fail_nosendbuf; + } + for (x = 0; x < card->writecnt; x++) { + sendbuf[0] = cpu_to_be32(partition << 24 | x << 16 | num); + memcpy(&sendbuf[1], buf + phaselen * x, phaselen); + /* wait until the device is not busy doing something else + * or 1 second - which ever is longer */ + if (atomic_read(&mdev->busy) == 1) { + wait_event_interruptible_timeout(mdev->maple_wait, + atomic_read(&mdev->busy) == 0, HZ); + if (atomic_read(&mdev->busy) == 1) { + error = -EBUSY; + dev_notice(&mdev->dev, "VMU write at (%d, %d)" + "failed - device is busy\n", + mdev->port, mdev->unit); + goto fail_nolock; + } + } + atomic_set(&mdev->busy, 1); + + locking = maple_add_packet(mdev, MAPLE_FUNC_MEMCARD, + MAPLE_COMMAND_BWRITE, phaselen / 4 + 2, sendbuf); + wait = wait_event_interruptible_timeout(mdev->maple_wait, + atomic_read(&mdev->busy) == 0, HZ/10); + if (locking) { + error = -EIO; + atomic_set(&mdev->busy, 0); + goto fail_nolock; + } + if (atomic_read(&mdev->busy) == 2) { + atomic_set(&mdev->busy, 0); + } else if (wait == 0 || wait == -ERESTARTSYS) { + error = -EIO; + dev_warn(&mdev->dev, "Write at (%d, %d) of block" + " 0x%X at phase %d failed: could not" + " communicate with VMU", mdev->port, + mdev->unit, num, x); + atomic_set(&mdev->busy, 0); + kfree(mdev->mq->sendbuf); + mdev->mq->sendbuf = NULL; + list_del_init(&(mdev->mq->list)); + goto fail_nolock; + } + } + kfree(sendbuf); + + return card->blocklen; + +fail_nolock: + kfree(sendbuf); +fail_nosendbuf: + dev_err(&mdev->dev, "VMU (%d, %d): write failed\n", mdev->port, + mdev->unit); + return error; +} + +/* mtd function to simulate reading byte by byte */ +static unsigned char vmu_flash_read_char(unsigned long ofs, int *retval, + struct mtd_info *mtd) +{ + struct vmu_block *vblock; + struct memcard *card; + struct mdev_part *mpart; + struct maple_device *mdev; + unsigned char *buf, ret; + int partition, error; + + mpart = mtd->priv; + mdev = mpart->mdev; + partition = mpart->partition; + card = maple_get_drvdata(mdev); + *retval = 0; + + buf = kmalloc(card->blocklen, GFP_KERNEL); + if (!buf) { + *retval = 1; + ret = -ENOMEM; + goto finish; + } + + vblock = ofs_to_block(ofs, mtd, partition); + if (!vblock) { + *retval = 3; + ret = -ENOMEM; + goto out_buf; + } + + error = maple_vmu_read_block(vblock->num, buf, mtd); + if (error) { + ret = error; + *retval = 2; + goto out_vblock; + } + + ret = buf[vblock->ofs]; + +out_vblock: + kfree(vblock); +out_buf: + kfree(buf); +finish: + return ret; +} + +/* mtd higher order function to read flash */ +static int vmu_flash_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct maple_device *mdev; + struct memcard *card; + struct mdev_part *mpart; + struct vmu_cache *pcache; + struct vmu_block *vblock; + int index = 0, retval, partition, leftover, numblocks; + unsigned char cx; + + mpart = mtd->priv; + mdev = mpart->mdev; + partition = mpart->partition; + card = maple_get_drvdata(mdev); + + numblocks = card->parts[partition].numblocks; + if (from + len > numblocks * card->blocklen) + len = numblocks * card->blocklen - from; + if (len == 0) + return -EIO; + /* Have we cached this bit already? */ + pcache = card->parts[partition].pcache; + do { + vblock = ofs_to_block(from + index, mtd, partition); + if (!vblock) + return -ENOMEM; + /* Have we cached this and is the cache valid and timely? */ + if (pcache->valid && + time_before(jiffies, pcache->jiffies_atc + HZ) && + (pcache->block == vblock->num)) { + /* we have cached it, so do necessary copying */ + leftover = card->blocklen - vblock->ofs; + if (vblock->ofs + len - index < card->blocklen) { + /* only a bit of this block to copy */ + memcpy(buf + index, + pcache->buffer + vblock->ofs, + len - index); + index = len; + } else { + /* otherwise copy remainder of whole block */ + memcpy(buf + index, pcache->buffer + + vblock->ofs, leftover); + index += leftover; + } + } else { + /* + * Not cached so read one byte - + * but cache the rest of the block + */ + cx = vmu_flash_read_char(from + index, &retval, mtd); + if (retval) { + *retlen = index; + kfree(vblock); + return cx; + } + memset(buf + index, cx, 1); + index++; + } + kfree(vblock); + } while (len > index); + *retlen = index; + + return 0; +} + +static int vmu_flash_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct maple_device *mdev; + struct memcard *card; + struct mdev_part *mpart; + int index = 0, partition, error = 0, numblocks; + struct vmu_cache *pcache; + struct vmu_block *vblock; + unsigned char *buffer; + + mpart = mtd->priv; + mdev = mpart->mdev; + partition = mpart->partition; + card = maple_get_drvdata(mdev); + + numblocks = card->parts[partition].numblocks; + if (to + len > numblocks * card->blocklen) + len = numblocks * card->blocklen - to; + if (len == 0) { + error = -EIO; + goto failed; + } + + vblock = ofs_to_block(to, mtd, partition); + if (!vblock) { + error = -ENOMEM; + goto failed; + } + + buffer = kmalloc(card->blocklen, GFP_KERNEL); + if (!buffer) { + error = -ENOMEM; + goto fail_buffer; + } + + do { + /* Read in the block we are to write to */ + error = maple_vmu_read_block(vblock->num, buffer, mtd); + if (error) + goto fail_io; + + do { + buffer[vblock->ofs] = buf[index]; + vblock->ofs++; + index++; + if (index >= len) + break; + } while (vblock->ofs < card->blocklen); + + /* write out new buffer */ + error = maple_vmu_write_block(vblock->num, buffer, mtd); + /* invalidate the cache */ + pcache = card->parts[partition].pcache; + pcache->valid = 0; + + if (error != card->blocklen) + goto fail_io; + + vblock->num++; + vblock->ofs = 0; + } while (len > index); + + kfree(buffer); + *retlen = index; + kfree(vblock); + return 0; + +fail_io: + kfree(buffer); +fail_buffer: + kfree(vblock); +failed: + dev_err(&mdev->dev, "VMU write failing with error %d\n", error); + return error; +} + +static void vmu_flash_sync(struct mtd_info *mtd) +{ + /* Do nothing here */ +} + +/* Maple bus callback function to recursively query hardware details */ +static void vmu_queryblocks(struct mapleq *mq) +{ + struct maple_device *mdev; + unsigned short *res; + struct memcard *card; + __be32 partnum; + struct vmu_cache *pcache; + struct mdev_part *mpart; + struct mtd_info *mtd_cur; + struct vmupart *part_cur; + int error; + + mdev = mq->dev; + card = maple_get_drvdata(mdev); + res = (unsigned short *) (mq->recvbuf->buf); + card->tempA = res[12]; + card->tempB = res[6]; + + dev_info(&mdev->dev, "VMU device at partition %d has %d user " + "blocks with a root block at %d\n", card->partition, + card->tempA, card->tempB); + + part_cur = &card->parts[card->partition]; + part_cur->user_blocks = card->tempA; + part_cur->root_block = card->tempB; + part_cur->numblocks = card->tempB + 1; + part_cur->name = kmalloc(12, GFP_KERNEL); + if (!part_cur->name) + goto fail_name; + + sprintf(part_cur->name, "vmu%d.%d.%d", + mdev->port, mdev->unit, card->partition); + mtd_cur = &card->mtd[card->partition]; + mtd_cur->name = part_cur->name; + mtd_cur->type = 8; + mtd_cur->flags = MTD_WRITEABLE|MTD_NO_ERASE; + mtd_cur->size = part_cur->numblocks * card->blocklen; + mtd_cur->erasesize = card->blocklen; + mtd_cur->_write = vmu_flash_write; + mtd_cur->_read = vmu_flash_read; + mtd_cur->_sync = vmu_flash_sync; + mtd_cur->writesize = card->blocklen; + + mpart = kmalloc(sizeof(struct mdev_part), GFP_KERNEL); + if (!mpart) + goto fail_mpart; + + mpart->mdev = mdev; + mpart->partition = card->partition; + mtd_cur->priv = mpart; + mtd_cur->owner = THIS_MODULE; + + pcache = kzalloc(sizeof(struct vmu_cache), GFP_KERNEL); + if (!pcache) + goto fail_cache_create; + part_cur->pcache = pcache; + + error = mtd_device_register(mtd_cur, NULL, 0); + if (error) + goto fail_mtd_register; + + maple_getcond_callback(mdev, NULL, 0, + MAPLE_FUNC_MEMCARD); + + /* + * Set up a recursive call to the (probably theoretical) + * second or more partition + */ + if (++card->partition < card->partitions) { + partnum = cpu_to_be32(card->partition << 24); + maple_getcond_callback(mdev, vmu_queryblocks, 0, + MAPLE_FUNC_MEMCARD); + maple_add_packet(mdev, MAPLE_FUNC_MEMCARD, + MAPLE_COMMAND_GETMINFO, 2, &partnum); + } + return; + +fail_mtd_register: + dev_err(&mdev->dev, "Could not register maple device at (%d, %d)" + "error is 0x%X\n", mdev->port, mdev->unit, error); + for (error = 0; error <= card->partition; error++) { + kfree(((card->parts)[error]).pcache); + ((card->parts)[error]).pcache = NULL; + } +fail_cache_create: +fail_mpart: + for (error = 0; error <= card->partition; error++) { + kfree(((card->mtd)[error]).priv); + ((card->mtd)[error]).priv = NULL; + } + maple_getcond_callback(mdev, NULL, 0, + MAPLE_FUNC_MEMCARD); + kfree(part_cur->name); +fail_name: + return; +} + +/* Handles very basic info about the flash, queries for details */ +static int vmu_connect(struct maple_device *mdev) +{ + unsigned long test_flash_data, basic_flash_data; + int c, error; + struct memcard *card; + u32 partnum = 0; + + test_flash_data = be32_to_cpu(mdev->devinfo.function); + /* Need to count how many bits are set - to find out which + * function_data element has details of the memory card + */ + c = hweight_long(test_flash_data); + + basic_flash_data = be32_to_cpu(mdev->devinfo.function_data[c - 1]); + + card = kmalloc(sizeof(struct memcard), GFP_KERNEL); + if (!card) { + error = -ENOMEM; + goto fail_nomem; + } + + card->partitions = (basic_flash_data >> 24 & 0xFF) + 1; + card->blocklen = ((basic_flash_data >> 16 & 0xFF) + 1) << 5; + card->writecnt = basic_flash_data >> 12 & 0xF; + card->readcnt = basic_flash_data >> 8 & 0xF; + card->removeable = basic_flash_data >> 7 & 1; + + card->partition = 0; + + /* + * Not sure there are actually any multi-partition devices in the + * real world, but the hardware supports them, so, so will we + */ + card->parts = kmalloc(sizeof(struct vmupart) * card->partitions, + GFP_KERNEL); + if (!card->parts) { + error = -ENOMEM; + goto fail_partitions; + } + + card->mtd = kmalloc(sizeof(struct mtd_info) * card->partitions, + GFP_KERNEL); + if (!card->mtd) { + error = -ENOMEM; + goto fail_mtd_info; + } + + maple_set_drvdata(mdev, card); + + /* + * We want to trap meminfo not get cond + * so set interval to zero, but rely on maple bus + * driver to pass back the results of the meminfo + */ + maple_getcond_callback(mdev, vmu_queryblocks, 0, + MAPLE_FUNC_MEMCARD); + + /* Make sure we are clear to go */ + if (atomic_read(&mdev->busy) == 1) { + wait_event_interruptible_timeout(mdev->maple_wait, + atomic_read(&mdev->busy) == 0, HZ); + if (atomic_read(&mdev->busy) == 1) { + dev_notice(&mdev->dev, "VMU at (%d, %d) is busy\n", + mdev->port, mdev->unit); + error = -EAGAIN; + goto fail_device_busy; + } + } + + atomic_set(&mdev->busy, 1); + + /* + * Set up the minfo call: vmu_queryblocks will handle + * the information passed back + */ + error = maple_add_packet(mdev, MAPLE_FUNC_MEMCARD, + MAPLE_COMMAND_GETMINFO, 2, &partnum); + if (error) { + dev_err(&mdev->dev, "Could not lock VMU at (%d, %d)" + " error is 0x%X\n", mdev->port, mdev->unit, error); + goto fail_mtd_info; + } + return 0; + +fail_device_busy: + kfree(card->mtd); +fail_mtd_info: + kfree(card->parts); +fail_partitions: + kfree(card); +fail_nomem: + return error; +} + +static void vmu_disconnect(struct maple_device *mdev) +{ + struct memcard *card; + struct mdev_part *mpart; + int x; + + mdev->callback = NULL; + card = maple_get_drvdata(mdev); + for (x = 0; x < card->partitions; x++) { + mpart = ((card->mtd)[x]).priv; + mpart->mdev = NULL; + mtd_device_unregister(&((card->mtd)[x])); + kfree(((card->parts)[x]).name); + } + kfree(card->parts); + kfree(card->mtd); + kfree(card); +} + +/* Callback to handle eccentricities of both mtd subsystem + * and general flakyness of Dreamcast VMUs + */ +static int vmu_can_unload(struct maple_device *mdev) +{ + struct memcard *card; + int x; + struct mtd_info *mtd; + + card = maple_get_drvdata(mdev); + for (x = 0; x < card->partitions; x++) { + mtd = &((card->mtd)[x]); + if (mtd->usecount > 0) + return 0; + } + return 1; +} + +#define ERRSTR "VMU at (%d, %d) file error -" + +static void vmu_file_error(struct maple_device *mdev, void *recvbuf) +{ + enum maple_file_errors error = ((int *)recvbuf)[1]; + + switch (error) { + + case MAPLE_FILEERR_INVALID_PARTITION: + dev_notice(&mdev->dev, ERRSTR " invalid partition number\n", + mdev->port, mdev->unit); + break; + + case MAPLE_FILEERR_PHASE_ERROR: + dev_notice(&mdev->dev, ERRSTR " phase error\n", + mdev->port, mdev->unit); + break; + + case MAPLE_FILEERR_INVALID_BLOCK: + dev_notice(&mdev->dev, ERRSTR " invalid block number\n", + mdev->port, mdev->unit); + break; + + case MAPLE_FILEERR_WRITE_ERROR: + dev_notice(&mdev->dev, ERRSTR " write error\n", + mdev->port, mdev->unit); + break; + + case MAPLE_FILEERR_INVALID_WRITE_LENGTH: + dev_notice(&mdev->dev, ERRSTR " invalid write length\n", + mdev->port, mdev->unit); + break; + + case MAPLE_FILEERR_BAD_CRC: + dev_notice(&mdev->dev, ERRSTR " bad CRC\n", + mdev->port, mdev->unit); + break; + + default: + dev_notice(&mdev->dev, ERRSTR " 0x%X\n", + mdev->port, mdev->unit, error); + } +} + + +static int probe_maple_vmu(struct device *dev) +{ + int error; + struct maple_device *mdev = to_maple_dev(dev); + struct maple_driver *mdrv = to_maple_driver(dev->driver); + + mdev->can_unload = vmu_can_unload; + mdev->fileerr_handler = vmu_file_error; + mdev->driver = mdrv; + + error = vmu_connect(mdev); + if (error) + return error; + + return 0; +} + +static int remove_maple_vmu(struct device *dev) +{ + struct maple_device *mdev = to_maple_dev(dev); + + vmu_disconnect(mdev); + return 0; +} + +static struct maple_driver vmu_flash_driver = { + .function = MAPLE_FUNC_MEMCARD, + .drv = { + .name = "Dreamcast_visual_memory", + .probe = probe_maple_vmu, + .remove = remove_maple_vmu, + }, +}; + +static int __init vmu_flash_map_init(void) +{ + return maple_driver_register(&vmu_flash_driver); +} + +static void __exit vmu_flash_map_exit(void) +{ + maple_driver_unregister(&vmu_flash_driver); +} + +module_init(vmu_flash_map_init); +module_exit(vmu_flash_map_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Adrian McMenamin"); +MODULE_DESCRIPTION("Flash mapping for Sega Dreamcast visual memory"); diff --git a/drivers/mtd/maps/walnut.c b/drivers/mtd/maps/walnut.c deleted file mode 100644 index ca932122fb6..00000000000 --- a/drivers/mtd/maps/walnut.c +++ /dev/null @@ -1,124 +0,0 @@ -/* - * $Id: walnut.c,v 1.3 2005/11/07 11:14:29 gleixner Exp $ - * - * Mapping for Walnut flash - * (used ebony.c as a "framework") - * - * Heikki Lindholm <holindho@infradead.org> - * - * - * 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/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> -#include <asm/io.h> -#include <asm/ibm4xx.h> -#include <platforms/4xx/walnut.h> - -/* these should be in platforms/4xx/walnut.h ? */ -#define WALNUT_FLASH_ONBD_N(x) (x & 0x02) -#define WALNUT_FLASH_SRAM_SEL(x) (x & 0x01) -#define WALNUT_FLASH_LOW 0xFFF00000 -#define WALNUT_FLASH_HIGH 0xFFF80000 -#define WALNUT_FLASH_SIZE 0x80000 - -static struct mtd_info *flash; - -static struct map_info walnut_map = { - .name = "Walnut flash", - .size = WALNUT_FLASH_SIZE, - .bankwidth = 1, -}; - -/* Actually, OpenBIOS is the last 128 KiB of the flash - better - * partitioning could be made */ -static struct mtd_partition walnut_partitions[] = { - { - .name = "OpenBIOS", - .offset = 0x0, - .size = WALNUT_FLASH_SIZE, - /*.mask_flags = MTD_WRITEABLE, */ /* force read-only */ - } -}; - -int __init init_walnut(void) -{ - u8 fpga_brds1; - void *fpga_brds1_adr; - void *fpga_status_adr; - unsigned long flash_base; - - /* this should already be mapped (platform/4xx/walnut.c) */ - fpga_status_adr = ioremap(WALNUT_FPGA_BASE, 8); - if (!fpga_status_adr) - return -ENOMEM; - - fpga_brds1_adr = fpga_status_adr+5; - fpga_brds1 = readb(fpga_brds1_adr); - /* iounmap(fpga_status_adr); */ - - if (WALNUT_FLASH_ONBD_N(fpga_brds1)) { - printk("The on-board flash is disabled (U79 sw 5)!"); - iounmap(fpga_status_adr); - return -EIO; - } - if (WALNUT_FLASH_SRAM_SEL(fpga_brds1)) - flash_base = WALNUT_FLASH_LOW; - else - flash_base = WALNUT_FLASH_HIGH; - - walnut_map.phys = flash_base; - walnut_map.virt = - (void __iomem *)ioremap(flash_base, walnut_map.size); - - if (!walnut_map.virt) { - printk("Failed to ioremap flash.\n"); - iounmap(fpga_status_adr); - return -EIO; - } - - simple_map_init(&walnut_map); - - flash = do_map_probe("jedec_probe", &walnut_map); - if (flash) { - flash->owner = THIS_MODULE; - add_mtd_partitions(flash, walnut_partitions, - ARRAY_SIZE(walnut_partitions)); - } else { - printk("map probe failed for flash\n"); - iounmap(fpga_status_adr); - return -ENXIO; - } - - iounmap(fpga_status_adr); - return 0; -} - -static void __exit cleanup_walnut(void) -{ - if (flash) { - del_mtd_partitions(flash); - map_destroy(flash); - } - - if (walnut_map.virt) { - iounmap((void *)walnut_map.virt); - walnut_map.virt = 0; - } -} - -module_init(init_walnut); -module_exit(cleanup_walnut); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Heikki Lindholm <holindho@infradead.org>"); -MODULE_DESCRIPTION("MTD map and partitions for IBM 405GP Walnut boards"); diff --git a/drivers/mtd/maps/wr_sbc82xx_flash.c b/drivers/mtd/maps/wr_sbc82xx_flash.c deleted file mode 100644 index ac5b8105b6e..00000000000 --- a/drivers/mtd/maps/wr_sbc82xx_flash.c +++ /dev/null @@ -1,180 +0,0 @@ -/* - * $Id: wr_sbc82xx_flash.c,v 1.8 2005/11/07 11:14:29 gleixner Exp $ - * - * Map for flash chips on Wind River PowerQUICC II SBC82xx board. - * - * Copyright (C) 2004 Red Hat, Inc. - * - * Author: David Woodhouse <dwmw2@infradead.org> - * - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <linux/slab.h> -#include <asm/io.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> - -#include <asm/immap_cpm2.h> - -static struct mtd_info *sbcmtd[3]; -static struct mtd_partition *sbcmtd_parts[3]; - -struct map_info sbc82xx_flash_map[3] = { - {.name = "Boot flash"}, - {.name = "Alternate boot flash"}, - {.name = "User flash"} -}; - -static struct mtd_partition smallflash_parts[] = { - { - .name = "space", - .size = 0x100000, - .offset = 0, - }, { - .name = "bootloader", - .size = MTDPART_SIZ_FULL, - .offset = MTDPART_OFS_APPEND, - } -}; - -static struct mtd_partition bigflash_parts[] = { - { - .name = "bootloader", - .size = 0x00100000, - .offset = 0, - }, { - .name = "file system", - .size = 0x01f00000, - .offset = MTDPART_OFS_APPEND, - }, { - .name = "boot config", - .size = 0x00100000, - .offset = MTDPART_OFS_APPEND, - }, { - .name = "space", - .size = 0x01f00000, - .offset = MTDPART_OFS_APPEND, - } -}; - -static const char *part_probes[] __initdata = {"cmdlinepart", "RedBoot", NULL}; - -#define init_sbc82xx_one_flash(map, br, or) \ -do { \ - (map).phys = (br & 1) ? (br & 0xffff8000) : 0; \ - (map).size = (br & 1) ? (~(or & 0xffff8000) + 1) : 0; \ - switch (br & 0x00001800) { \ - case 0x00000000: \ - case 0x00000800: (map).bankwidth = 1; break; \ - case 0x00001000: (map).bankwidth = 2; break; \ - case 0x00001800: (map).bankwidth = 4; break; \ - } \ -} while (0); - -int __init init_sbc82xx_flash(void) -{ - volatile memctl_cpm2_t *mc = &cpm2_immr->im_memctl; - int bigflash; - int i; - -#ifdef CONFIG_SBC8560 - mc = ioremap(0xff700000 + 0x5000, sizeof(memctl_cpm2_t)); -#else - mc = &cpm2_immr->im_memctl; -#endif - - bigflash = 1; - if ((mc->memc_br0 & 0x00001800) == 0x00001800) - bigflash = 0; - - init_sbc82xx_one_flash(sbc82xx_flash_map[0], mc->memc_br0, mc->memc_or0); - init_sbc82xx_one_flash(sbc82xx_flash_map[1], mc->memc_br6, mc->memc_or6); - init_sbc82xx_one_flash(sbc82xx_flash_map[2], mc->memc_br1, mc->memc_or1); - -#ifdef CONFIG_SBC8560 - iounmap((void *) mc); -#endif - - for (i=0; i<3; i++) { - int8_t flashcs[3] = { 0, 6, 1 }; - int nr_parts; - - printk(KERN_NOTICE "PowerQUICC II %s (%ld MiB on CS%d", - sbc82xx_flash_map[i].name, - (sbc82xx_flash_map[i].size >> 20), - flashcs[i]); - if (!sbc82xx_flash_map[i].phys) { - /* We know it can't be at zero. */ - printk("): disabled by bootloader.\n"); - continue; - } - printk(" at %08lx)\n", sbc82xx_flash_map[i].phys); - - sbc82xx_flash_map[i].virt = ioremap(sbc82xx_flash_map[i].phys, sbc82xx_flash_map[i].size); - - if (!sbc82xx_flash_map[i].virt) { - printk("Failed to ioremap\n"); - continue; - } - - simple_map_init(&sbc82xx_flash_map[i]); - - sbcmtd[i] = do_map_probe("cfi_probe", &sbc82xx_flash_map[i]); - - if (!sbcmtd[i]) - continue; - - sbcmtd[i]->owner = THIS_MODULE; - - nr_parts = parse_mtd_partitions(sbcmtd[i], part_probes, - &sbcmtd_parts[i], 0); - if (nr_parts > 0) { - add_mtd_partitions (sbcmtd[i], sbcmtd_parts[i], nr_parts); - continue; - } - - /* No partitioning detected. Use default */ - if (i == 2) { - add_mtd_device(sbcmtd[i]); - } else if (i == bigflash) { - add_mtd_partitions (sbcmtd[i], bigflash_parts, ARRAY_SIZE(bigflash_parts)); - } else { - add_mtd_partitions (sbcmtd[i], smallflash_parts, ARRAY_SIZE(smallflash_parts)); - } - } - return 0; -} - -static void __exit cleanup_sbc82xx_flash(void) -{ - int i; - - for (i=0; i<3; i++) { - if (!sbcmtd[i]) - continue; - - if (i<2 || sbcmtd_parts[i]) - del_mtd_partitions(sbcmtd[i]); - else - del_mtd_device(sbcmtd[i]); - - kfree(sbcmtd_parts[i]); - map_destroy(sbcmtd[i]); - - iounmap((void *)sbc82xx_flash_map[i].virt); - sbc82xx_flash_map[i].virt = 0; - } -} - -module_init(init_sbc82xx_flash); -module_exit(cleanup_sbc82xx_flash); - - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); -MODULE_DESCRIPTION("Flash map driver for WindRiver PowerQUICC II"); diff --git a/drivers/mtd/mtd_blkdevs.c b/drivers/mtd/mtd_blkdevs.c index 458d3c8ae1e..43e30992a36 100644 --- a/drivers/mtd/mtd_blkdevs.c +++ b/drivers/mtd/mtd_blkdevs.c @@ -1,9 +1,21 @@ /* - * $Id: mtd_blkdevs.c,v 1.27 2005/11/07 11:14:20 gleixner Exp $ + * Interface to Linux block layer for MTD 'translation layers'. * - * (C) 2003 David Woodhouse <dwmw2@infradead.org> + * Copyright © 2003-2010 David Woodhouse <dwmw2@infradead.org> * - * Interface to Linux 2.5 block layer for MTD 'translation layers'. + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ @@ -18,22 +30,48 @@ #include <linux/blkpg.h> #include <linux/spinlock.h> #include <linux/hdreg.h> -#include <linux/init.h> #include <linux/mutex.h> #include <asm/uaccess.h> +#include "mtdcore.h" + static LIST_HEAD(blktrans_majors); +static DEFINE_MUTEX(blktrans_ref_mutex); -extern struct mutex mtd_table_mutex; -extern struct mtd_info *mtd_table[]; +static void blktrans_dev_release(struct kref *kref) +{ + struct mtd_blktrans_dev *dev = + container_of(kref, struct mtd_blktrans_dev, ref); + + dev->disk->private_data = NULL; + blk_cleanup_queue(dev->rq); + put_disk(dev->disk); + list_del(&dev->list); + kfree(dev); +} + +static struct mtd_blktrans_dev *blktrans_dev_get(struct gendisk *disk) +{ + struct mtd_blktrans_dev *dev; + + mutex_lock(&blktrans_ref_mutex); + dev = disk->private_data; + + if (!dev) + goto unlock; + kref_get(&dev->ref); +unlock: + mutex_unlock(&blktrans_ref_mutex); + return dev; +} + +static void blktrans_dev_put(struct mtd_blktrans_dev *dev) +{ + mutex_lock(&blktrans_ref_mutex); + kref_put(&dev->ref, blktrans_dev_release); + mutex_unlock(&blktrans_ref_mutex); +} -struct mtd_blkcore_priv { - struct completion thread_dead; - int exiting; - wait_queue_head_t thread_wq; - struct request_queue *rq; - spinlock_t queue_lock; -}; static int do_blktrans_request(struct mtd_blktrans_ops *tr, struct mtd_blktrans_dev *dev, @@ -42,185 +80,237 @@ static int do_blktrans_request(struct mtd_blktrans_ops *tr, unsigned long block, nsect; char *buf; - block = req->sector; - nsect = req->current_nr_sectors; - buf = req->buffer; + block = blk_rq_pos(req) << 9 >> tr->blkshift; + nsect = blk_rq_cur_bytes(req) >> tr->blkshift; + buf = bio_data(req->bio); - if (!(req->flags & REQ_CMD)) - return 0; + if (req->cmd_type != REQ_TYPE_FS) + return -EIO; - if (block + nsect > get_capacity(req->rq_disk)) - return 0; + if (req->cmd_flags & REQ_FLUSH) + return tr->flush(dev); + + if (blk_rq_pos(req) + blk_rq_cur_sectors(req) > + get_capacity(req->rq_disk)) + return -EIO; + + if (req->cmd_flags & REQ_DISCARD) + return tr->discard(dev, block, nsect); switch(rq_data_dir(req)) { case READ: - for (; nsect > 0; nsect--, block++, buf += 512) + for (; nsect > 0; nsect--, block++, buf += tr->blksize) if (tr->readsect(dev, block, buf)) - return 0; - return 1; - + return -EIO; + rq_flush_dcache_pages(req); + return 0; case WRITE: if (!tr->writesect) - return 0; + return -EIO; - for (; nsect > 0; nsect--, block++, buf += 512) + rq_flush_dcache_pages(req); + for (; nsect > 0; nsect--, block++, buf += tr->blksize) if (tr->writesect(dev, block, buf)) - return 0; - return 1; - - default: - printk(KERN_NOTICE "Unknown request %ld\n", rq_data_dir(req)); + return -EIO; return 0; + default: + printk(KERN_NOTICE "Unknown request %u\n", rq_data_dir(req)); + return -EIO; } } -static int mtd_blktrans_thread(void *arg) +int mtd_blktrans_cease_background(struct mtd_blktrans_dev *dev) { - struct mtd_blktrans_ops *tr = arg; - struct request_queue *rq = tr->blkcore_priv->rq; - - /* we might get involved when memory gets low, so use PF_MEMALLOC */ - current->flags |= PF_MEMALLOC | PF_NOFREEZE; - - daemonize("%sd", tr->name); + return dev->bg_stop; +} +EXPORT_SYMBOL_GPL(mtd_blktrans_cease_background); - /* daemonize() doesn't do this for us since some kernel threads - actually want to deal with signals. We can't just call - exit_sighand() since that'll cause an oops when we finally - do exit. */ - spin_lock_irq(¤t->sighand->siglock); - sigfillset(¤t->blocked); - recalc_sigpending(); - spin_unlock_irq(¤t->sighand->siglock); +static void mtd_blktrans_work(struct work_struct *work) +{ + struct mtd_blktrans_dev *dev = + container_of(work, struct mtd_blktrans_dev, work); + struct mtd_blktrans_ops *tr = dev->tr; + struct request_queue *rq = dev->rq; + struct request *req = NULL; + int background_done = 0; spin_lock_irq(rq->queue_lock); - while (!tr->blkcore_priv->exiting) { - struct request *req; - struct mtd_blktrans_dev *dev; - int res = 0; - DECLARE_WAITQUEUE(wait, current); - - req = elv_next_request(rq); - - if (!req) { - add_wait_queue(&tr->blkcore_priv->thread_wq, &wait); - set_current_state(TASK_INTERRUPTIBLE); - - spin_unlock_irq(rq->queue_lock); - - schedule(); - remove_wait_queue(&tr->blkcore_priv->thread_wq, &wait); - - spin_lock_irq(rq->queue_lock); - - continue; + while (1) { + int res; + + dev->bg_stop = false; + if (!req && !(req = blk_fetch_request(rq))) { + if (tr->background && !background_done) { + spin_unlock_irq(rq->queue_lock); + mutex_lock(&dev->lock); + tr->background(dev); + mutex_unlock(&dev->lock); + spin_lock_irq(rq->queue_lock); + /* + * Do background processing just once per idle + * period. + */ + background_done = !dev->bg_stop; + continue; + } + break; } - dev = req->rq_disk->private_data; - tr = dev->tr; - spin_unlock_irq(rq->queue_lock); mutex_lock(&dev->lock); - res = do_blktrans_request(tr, dev, req); + res = do_blktrans_request(dev->tr, dev, req); mutex_unlock(&dev->lock); spin_lock_irq(rq->queue_lock); - end_request(req, res); + if (!__blk_end_request_cur(req, res)) + req = NULL; + + background_done = 0; } - spin_unlock_irq(rq->queue_lock); - complete_and_exit(&tr->blkcore_priv->thread_dead, 0); + if (req) + __blk_end_request_all(req, -EIO); + + spin_unlock_irq(rq->queue_lock); } static void mtd_blktrans_request(struct request_queue *rq) { - struct mtd_blktrans_ops *tr = rq->queuedata; - wake_up(&tr->blkcore_priv->thread_wq); -} + struct mtd_blktrans_dev *dev; + struct request *req = NULL; + + dev = rq->queuedata; + if (!dev) + while ((req = blk_fetch_request(rq)) != NULL) + __blk_end_request_all(req, -ENODEV); + else + queue_work(dev->wq, &dev->work); +} -static int blktrans_open(struct inode *i, struct file *f) +static int blktrans_open(struct block_device *bdev, fmode_t mode) { - struct mtd_blktrans_dev *dev; - struct mtd_blktrans_ops *tr; - int ret = -ENODEV; + struct mtd_blktrans_dev *dev = blktrans_dev_get(bdev->bd_disk); + int ret = 0; - dev = i->i_bdev->bd_disk->private_data; - tr = dev->tr; + if (!dev) + return -ERESTARTSYS; /* FIXME: busy loop! -arnd*/ - if (!try_module_get(dev->mtd->owner)) - goto out; + mutex_lock(&dev->lock); - if (!try_module_get(tr->owner)) - goto out_tr; + if (dev->open) + goto unlock; - /* FIXME: Locking. A hot pluggable device can go away - (del_mtd_device can be called for it) without its module - being unloaded. */ - dev->mtd->usecount++; + kref_get(&dev->ref); + __module_get(dev->tr->owner); - ret = 0; - if (tr->open && (ret = tr->open(dev))) { - dev->mtd->usecount--; - module_put(dev->mtd->owner); - out_tr: - module_put(tr->owner); + if (!dev->mtd) + goto unlock; + + if (dev->tr->open) { + ret = dev->tr->open(dev); + if (ret) + goto error_put; } - out: + + ret = __get_mtd_device(dev->mtd); + if (ret) + goto error_release; + dev->file_mode = mode; + +unlock: + dev->open++; + mutex_unlock(&dev->lock); + blktrans_dev_put(dev); + return ret; + +error_release: + if (dev->tr->release) + dev->tr->release(dev); +error_put: + module_put(dev->tr->owner); + kref_put(&dev->ref, blktrans_dev_release); + mutex_unlock(&dev->lock); + blktrans_dev_put(dev); return ret; } -static int blktrans_release(struct inode *i, struct file *f) +static void blktrans_release(struct gendisk *disk, fmode_t mode) { - struct mtd_blktrans_dev *dev; - struct mtd_blktrans_ops *tr; - int ret = 0; + struct mtd_blktrans_dev *dev = blktrans_dev_get(disk); - dev = i->i_bdev->bd_disk->private_data; - tr = dev->tr; + if (!dev) + return; - if (tr->release) - ret = tr->release(dev); + mutex_lock(&dev->lock); - if (!ret) { - dev->mtd->usecount--; - module_put(dev->mtd->owner); - module_put(tr->owner); - } + if (--dev->open) + goto unlock; - return ret; + kref_put(&dev->ref, blktrans_dev_release); + module_put(dev->tr->owner); + + if (dev->mtd) { + if (dev->tr->release) + dev->tr->release(dev); + __put_mtd_device(dev->mtd); + } +unlock: + mutex_unlock(&dev->lock); + blktrans_dev_put(dev); } static int blktrans_getgeo(struct block_device *bdev, struct hd_geometry *geo) { - struct mtd_blktrans_dev *dev = bdev->bd_disk->private_data; + struct mtd_blktrans_dev *dev = blktrans_dev_get(bdev->bd_disk); + int ret = -ENXIO; + + if (!dev) + return ret; - if (dev->tr->getgeo) - return dev->tr->getgeo(dev, geo); - return -ENOTTY; + mutex_lock(&dev->lock); + + if (!dev->mtd) + goto unlock; + + ret = dev->tr->getgeo ? dev->tr->getgeo(dev, geo) : 0; +unlock: + mutex_unlock(&dev->lock); + blktrans_dev_put(dev); + return ret; } -static int blktrans_ioctl(struct inode *inode, struct file *file, +static int blktrans_ioctl(struct block_device *bdev, fmode_t mode, unsigned int cmd, unsigned long arg) { - struct mtd_blktrans_dev *dev = inode->i_bdev->bd_disk->private_data; - struct mtd_blktrans_ops *tr = dev->tr; + struct mtd_blktrans_dev *dev = blktrans_dev_get(bdev->bd_disk); + int ret = -ENXIO; + + if (!dev) + return ret; + + mutex_lock(&dev->lock); + + if (!dev->mtd) + goto unlock; switch (cmd) { case BLKFLSBUF: - if (tr->flush) - return tr->flush(dev); - /* The core code did the work, we had nothing to do. */ - return 0; + ret = dev->tr->flush ? dev->tr->flush(dev) : 0; + break; default: - return -ENOTTY; + ret = -ENOTTY; } +unlock: + mutex_unlock(&dev->lock); + blktrans_dev_put(dev); + return ret; } -struct block_device_operations mtd_blktrans_ops = { +static const struct block_device_operations mtd_block_ops = { .owner = THIS_MODULE, .open = blktrans_open, .release = blktrans_release, @@ -231,17 +321,18 @@ struct block_device_operations mtd_blktrans_ops = { int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new) { struct mtd_blktrans_ops *tr = new->tr; - struct list_head *this; + struct mtd_blktrans_dev *d; int last_devnum = -1; struct gendisk *gd; + int ret; - if (!!mutex_trylock(&mtd_table_mutex)) { + if (mutex_trylock(&mtd_table_mutex)) { mutex_unlock(&mtd_table_mutex); BUG(); } - list_for_each(this, &tr->devs) { - struct mtd_blktrans_dev *d = list_entry(this, struct mtd_blktrans_dev, list); + mutex_lock(&blktrans_ref_mutex); + list_for_each_entry(d, &tr->devs, list) { if (new->devnum == -1) { /* Use first free number */ if (d->devnum != last_devnum+1) { @@ -252,6 +343,7 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new) } } else if (d->devnum == new->devnum) { /* Required number taken */ + mutex_unlock(&blktrans_ref_mutex); return -EBUSY; } else if (d->devnum > new->devnum) { /* Required number was free */ @@ -260,27 +352,41 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new) } last_devnum = d->devnum; } + + ret = -EBUSY; if (new->devnum == -1) new->devnum = last_devnum+1; - if ((new->devnum << tr->part_bits) > 256) { - return -EBUSY; + /* Check that the device and any partitions will get valid + * minor numbers and that the disk naming code below can cope + * with this number. */ + if (new->devnum > (MINORMASK >> tr->part_bits) || + (tr->part_bits && new->devnum >= 27 * 26)) { + mutex_unlock(&blktrans_ref_mutex); + goto error1; } - mutex_init(&new->lock); list_add_tail(&new->list, &tr->devs); added: + mutex_unlock(&blktrans_ref_mutex); + + mutex_init(&new->lock); + kref_init(&new->ref); if (!tr->writesect) new->readonly = 1; + /* Create gendisk */ + ret = -ENOMEM; gd = alloc_disk(1 << tr->part_bits); - if (!gd) { - list_del(&new->list); - return -ENOMEM; - } + + if (!gd) + goto error2; + + new->disk = gd; + gd->private_data = new; gd->major = tr->major; gd->first_minor = (new->devnum) << tr->part_bits; - gd->fops = &mtd_blktrans_ops; + gd->fops = &mtd_block_ops; if (tr->part_bits) if (new->devnum < 26) @@ -295,66 +401,121 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new) snprintf(gd->disk_name, sizeof(gd->disk_name), "%s%d", tr->name, new->devnum); - /* 2.5 has capacity in units of 512 bytes while still - having BLOCK_SIZE_BITS set to 10. Just to keep us amused. */ - set_capacity(gd, (new->size * new->blksize) >> 9); + set_capacity(gd, (new->size * tr->blksize) >> 9); - gd->private_data = new; - new->blkcore_priv = gd; - gd->queue = tr->blkcore_priv->rq; + /* Create the request queue */ + spin_lock_init(&new->queue_lock); + new->rq = blk_init_queue(mtd_blktrans_request, &new->queue_lock); + + if (!new->rq) + goto error3; + + if (tr->flush) + blk_queue_flush(new->rq, REQ_FLUSH); + + new->rq->queuedata = new; + blk_queue_logical_block_size(new->rq, tr->blksize); + + queue_flag_set_unlocked(QUEUE_FLAG_NONROT, new->rq); + + if (tr->discard) { + queue_flag_set_unlocked(QUEUE_FLAG_DISCARD, new->rq); + new->rq->limits.max_discard_sectors = UINT_MAX; + } + + gd->queue = new->rq; + + /* Create processing workqueue */ + new->wq = alloc_workqueue("%s%d", 0, 0, + tr->name, new->mtd->index); + if (!new->wq) + goto error4; + INIT_WORK(&new->work, mtd_blktrans_work); + + gd->driverfs_dev = &new->mtd->dev; if (new->readonly) set_disk_ro(gd, 1); add_disk(gd); + if (new->disk_attributes) { + ret = sysfs_create_group(&disk_to_dev(gd)->kobj, + new->disk_attributes); + WARN_ON(ret); + } return 0; +error4: + blk_cleanup_queue(new->rq); +error3: + put_disk(new->disk); +error2: + list_del(&new->list); +error1: + return ret; } int del_mtd_blktrans_dev(struct mtd_blktrans_dev *old) { - if (!!mutex_trylock(&mtd_table_mutex)) { + unsigned long flags; + + if (mutex_trylock(&mtd_table_mutex)) { mutex_unlock(&mtd_table_mutex); BUG(); } - list_del(&old->list); + if (old->disk_attributes) + sysfs_remove_group(&disk_to_dev(old->disk)->kobj, + old->disk_attributes); + + /* Stop new requests to arrive */ + del_gendisk(old->disk); + + /* Stop workqueue. This will perform any pending request. */ + destroy_workqueue(old->wq); + + /* Kill current requests */ + spin_lock_irqsave(&old->queue_lock, flags); + old->rq->queuedata = NULL; + blk_start_queue(old->rq); + spin_unlock_irqrestore(&old->queue_lock, flags); + + /* If the device is currently open, tell trans driver to close it, + then put mtd device, and don't touch it again */ + mutex_lock(&old->lock); + if (old->open) { + if (old->tr->release) + old->tr->release(old); + __put_mtd_device(old->mtd); + } - del_gendisk(old->blkcore_priv); - put_disk(old->blkcore_priv); + old->mtd = NULL; + mutex_unlock(&old->lock); + blktrans_dev_put(old); return 0; } static void blktrans_notify_remove(struct mtd_info *mtd) { - struct list_head *this, *this2, *next; - - list_for_each(this, &blktrans_majors) { - struct mtd_blktrans_ops *tr = list_entry(this, struct mtd_blktrans_ops, list); - - list_for_each_safe(this2, next, &tr->devs) { - struct mtd_blktrans_dev *dev = list_entry(this2, struct mtd_blktrans_dev, list); + struct mtd_blktrans_ops *tr; + struct mtd_blktrans_dev *dev, *next; + list_for_each_entry(tr, &blktrans_majors, list) + list_for_each_entry_safe(dev, next, &tr->devs, list) if (dev->mtd == mtd) tr->remove_dev(dev); - } - } } static void blktrans_notify_add(struct mtd_info *mtd) { - struct list_head *this; + struct mtd_blktrans_ops *tr; if (mtd->type == MTD_ABSENT) return; - list_for_each(this, &blktrans_majors) { - struct mtd_blktrans_ops *tr = list_entry(this, struct mtd_blktrans_ops, list); - + list_for_each_entry(tr, &blktrans_majors, list) tr->add_mtd(tr, mtd); - } - } static struct mtd_notifier blktrans_notifier = { @@ -364,7 +525,8 @@ static struct mtd_notifier blktrans_notifier = { int register_mtd_blktrans(struct mtd_blktrans_ops *tr) { - int ret, i; + struct mtd_info *mtd; + int ret; /* Register the notifier if/when the first device type is registered, to prevent the link/init ordering from fucking @@ -372,84 +534,48 @@ int register_mtd_blktrans(struct mtd_blktrans_ops *tr) if (!blktrans_notifier.list.next) register_mtd_user(&blktrans_notifier); - tr->blkcore_priv = kmalloc(sizeof(*tr->blkcore_priv), GFP_KERNEL); - if (!tr->blkcore_priv) - return -ENOMEM; - - memset(tr->blkcore_priv, 0, sizeof(*tr->blkcore_priv)); mutex_lock(&mtd_table_mutex); ret = register_blkdev(tr->major, tr->name); - if (ret) { + if (ret < 0) { printk(KERN_WARNING "Unable to register %s block device on major %d: %d\n", tr->name, tr->major, ret); - kfree(tr->blkcore_priv); mutex_unlock(&mtd_table_mutex); return ret; } - spin_lock_init(&tr->blkcore_priv->queue_lock); - init_completion(&tr->blkcore_priv->thread_dead); - init_waitqueue_head(&tr->blkcore_priv->thread_wq); - - tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock); - if (!tr->blkcore_priv->rq) { - unregister_blkdev(tr->major, tr->name); - kfree(tr->blkcore_priv); - mutex_unlock(&mtd_table_mutex); - return -ENOMEM; - } - tr->blkcore_priv->rq->queuedata = tr; + if (ret) + tr->major = ret; - ret = kernel_thread(mtd_blktrans_thread, tr, CLONE_KERNEL); - if (ret < 0) { - blk_cleanup_queue(tr->blkcore_priv->rq); - unregister_blkdev(tr->major, tr->name); - kfree(tr->blkcore_priv); - mutex_unlock(&mtd_table_mutex); - return ret; - } + tr->blkshift = ffs(tr->blksize) - 1; INIT_LIST_HEAD(&tr->devs); list_add(&tr->list, &blktrans_majors); - for (i=0; i<MAX_MTD_DEVICES; i++) { - if (mtd_table[i] && mtd_table[i]->type != MTD_ABSENT) - tr->add_mtd(tr, mtd_table[i]); - } + mtd_for_each_device(mtd) + if (mtd->type != MTD_ABSENT) + tr->add_mtd(tr, mtd); mutex_unlock(&mtd_table_mutex); - return 0; } int deregister_mtd_blktrans(struct mtd_blktrans_ops *tr) { - struct list_head *this, *next; + struct mtd_blktrans_dev *dev, *next; mutex_lock(&mtd_table_mutex); - /* Clean up the kernel thread */ - tr->blkcore_priv->exiting = 1; - wake_up(&tr->blkcore_priv->thread_wq); - wait_for_completion(&tr->blkcore_priv->thread_dead); - /* Remove it from the list of active majors */ list_del(&tr->list); - list_for_each_safe(this, next, &tr->devs) { - struct mtd_blktrans_dev *dev = list_entry(this, struct mtd_blktrans_dev, list); + list_for_each_entry_safe(dev, next, &tr->devs, list) tr->remove_dev(dev); - } - blk_cleanup_queue(tr->blkcore_priv->rq); unregister_blkdev(tr->major, tr->name); - mutex_unlock(&mtd_table_mutex); - kfree(tr->blkcore_priv); - BUG_ON(!list_empty(&tr->devs)); return 0; } diff --git a/drivers/mtd/mtdblock.c b/drivers/mtd/mtdblock.c index 04ed34694b1..485ea751c7f 100644 --- a/drivers/mtd/mtdblock.c +++ b/drivers/mtd/mtdblock.c @@ -1,10 +1,23 @@ /* * Direct MTD block device access * - * $Id: mtdblock.c,v 1.68 2005/11/07 11:14:20 gleixner Exp $ + * Copyright © 1999-2010 David Woodhouse <dwmw2@infradead.org> + * Copyright © 2000-2003 Nicolas Pitre <nico@fluxnic.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * - * (C) 2000-2003 Nicolas Pitre <nico@cam.org> - * (C) 1999-2003 David Woodhouse <dwmw2@infradead.org> */ #include <linux/fs.h> @@ -19,17 +32,20 @@ #include <linux/mtd/mtd.h> #include <linux/mtd/blktrans.h> #include <linux/mutex.h> +#include <linux/major.h> -static struct mtdblk_dev { - struct mtd_info *mtd; +struct mtdblk_dev { + struct mtd_blktrans_dev mbd; int count; struct mutex cache_mutex; unsigned char *cache_data; unsigned long cache_offset; unsigned int cache_size; enum { STATE_EMPTY, STATE_CLEAN, STATE_DIRTY } cache_state; -} *mtdblks[MAX_MTD_DEVICES]; +}; + +static DEFINE_MUTEX(mtdblks_lock); /* * Cache stuff... @@ -70,7 +86,7 @@ static int erase_write (struct mtd_info *mtd, unsigned long pos, set_current_state(TASK_INTERRUPTIBLE); add_wait_queue(&wait_q, &wait); - ret = mtd->erase(mtd, &erase); + ret = mtd_erase(mtd, &erase); if (ret) { set_current_state(TASK_RUNNING); remove_wait_queue(&wait_q, &wait); @@ -84,10 +100,10 @@ static int erase_write (struct mtd_info *mtd, unsigned long pos, remove_wait_queue(&wait_q, &wait); /* - * Next, writhe data to flash. + * Next, write the data to flash. */ - ret = mtd->write(mtd, pos, len, &retlen, buf); + ret = mtd_write(mtd, pos, len, &retlen, buf); if (ret) return ret; if (retlen != len) @@ -98,13 +114,13 @@ static int erase_write (struct mtd_info *mtd, unsigned long pos, static int write_cached_data (struct mtdblk_dev *mtdblk) { - struct mtd_info *mtd = mtdblk->mtd; + struct mtd_info *mtd = mtdblk->mbd.mtd; int ret; if (mtdblk->cache_state != STATE_DIRTY) return 0; - DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: writing cached data for \"%s\" " + pr_debug("mtdblock: writing cached data for \"%s\" " "at 0x%lx, size 0x%x\n", mtd->name, mtdblk->cache_offset, mtdblk->cache_size); @@ -114,7 +130,7 @@ static int write_cached_data (struct mtdblk_dev *mtdblk) return ret; /* - * Here we could argubly set the cache state to STATE_CLEAN. + * Here we could arguably set the cache state to STATE_CLEAN. * However this could lead to inconsistency since we will not * be notified if this content is altered on the flash by other * means. Let's declare it empty and leave buffering tasks to @@ -128,16 +144,16 @@ static int write_cached_data (struct mtdblk_dev *mtdblk) static int do_cached_write (struct mtdblk_dev *mtdblk, unsigned long pos, int len, const char *buf) { - struct mtd_info *mtd = mtdblk->mtd; + struct mtd_info *mtd = mtdblk->mbd.mtd; unsigned int sect_size = mtdblk->cache_size; size_t retlen; int ret; - DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: write on \"%s\" at 0x%lx, size 0x%x\n", + pr_debug("mtdblock: write on \"%s\" at 0x%lx, size 0x%x\n", mtd->name, pos, len); if (!sect_size) - return mtd->write(mtd, pos, len, &retlen, buf); + return mtd_write(mtd, pos, len, &retlen, buf); while (len > 0) { unsigned long sect_start = (pos/sect_size)*sect_size; @@ -169,8 +185,8 @@ static int do_cached_write (struct mtdblk_dev *mtdblk, unsigned long pos, mtdblk->cache_offset != sect_start) { /* fill the cache with the current sector */ mtdblk->cache_state = STATE_EMPTY; - ret = mtd->read(mtd, sect_start, sect_size, - &retlen, mtdblk->cache_data); + ret = mtd_read(mtd, sect_start, sect_size, + &retlen, mtdblk->cache_data); if (ret) return ret; if (retlen != sect_size) @@ -198,16 +214,16 @@ static int do_cached_write (struct mtdblk_dev *mtdblk, unsigned long pos, static int do_cached_read (struct mtdblk_dev *mtdblk, unsigned long pos, int len, char *buf) { - struct mtd_info *mtd = mtdblk->mtd; + struct mtd_info *mtd = mtdblk->mbd.mtd; unsigned int sect_size = mtdblk->cache_size; size_t retlen; int ret; - DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: read on \"%s\" at 0x%lx, size 0x%x\n", + pr_debug("mtdblock: read on \"%s\" at 0x%lx, size 0x%x\n", mtd->name, pos, len); if (!sect_size) - return mtd->read(mtd, pos, len, &retlen, buf); + return mtd_read(mtd, pos, len, &retlen, buf); while (len > 0) { unsigned long sect_start = (pos/sect_size)*sect_size; @@ -226,7 +242,7 @@ static int do_cached_read (struct mtdblk_dev *mtdblk, unsigned long pos, mtdblk->cache_offset == sect_start) { memcpy (buf, mtdblk->cache_data + offset, size); } else { - ret = mtd->read(mtd, pos, size, &retlen, buf); + ret = mtd_read(mtd, pos, size, &retlen, buf); if (ret) return ret; if (retlen != size) @@ -244,16 +260,16 @@ static int do_cached_read (struct mtdblk_dev *mtdblk, unsigned long pos, static int mtdblock_readsect(struct mtd_blktrans_dev *dev, unsigned long block, char *buf) { - struct mtdblk_dev *mtdblk = mtdblks[dev->devnum]; + struct mtdblk_dev *mtdblk = container_of(dev, struct mtdblk_dev, mbd); return do_cached_read(mtdblk, block<<9, 512, buf); } static int mtdblock_writesect(struct mtd_blktrans_dev *dev, unsigned long block, char *buf) { - struct mtdblk_dev *mtdblk = mtdblks[dev->devnum]; + struct mtdblk_dev *mtdblk = container_of(dev, struct mtdblk_dev, mbd); if (unlikely(!mtdblk->cache_data && mtdblk->cache_size)) { - mtdblk->cache_data = vmalloc(mtdblk->mtd->erasesize); + mtdblk->cache_data = vmalloc(mtdblk->mbd.mtd->erasesize); if (!mtdblk->cache_data) return -EINTR; /* -EINTR is not really correct, but it is the best match @@ -266,108 +282,101 @@ static int mtdblock_writesect(struct mtd_blktrans_dev *dev, static int mtdblock_open(struct mtd_blktrans_dev *mbd) { - struct mtdblk_dev *mtdblk; - struct mtd_info *mtd = mbd->mtd; - int dev = mbd->devnum; + struct mtdblk_dev *mtdblk = container_of(mbd, struct mtdblk_dev, mbd); - DEBUG(MTD_DEBUG_LEVEL1,"mtdblock_open\n"); + pr_debug("mtdblock_open\n"); - if (mtdblks[dev]) { - mtdblks[dev]->count++; + mutex_lock(&mtdblks_lock); + if (mtdblk->count) { + mtdblk->count++; + mutex_unlock(&mtdblks_lock); return 0; } /* OK, it's not open. Create cache info for it */ - mtdblk = kmalloc(sizeof(struct mtdblk_dev), GFP_KERNEL); - if (!mtdblk) - return -ENOMEM; - - memset(mtdblk, 0, sizeof(*mtdblk)); mtdblk->count = 1; - mtdblk->mtd = mtd; - mutex_init(&mtdblk->cache_mutex); mtdblk->cache_state = STATE_EMPTY; - if ( !(mtdblk->mtd->flags & MTD_NO_ERASE) && mtdblk->mtd->erasesize) { - mtdblk->cache_size = mtdblk->mtd->erasesize; + if (!(mbd->mtd->flags & MTD_NO_ERASE) && mbd->mtd->erasesize) { + mtdblk->cache_size = mbd->mtd->erasesize; mtdblk->cache_data = NULL; } - mtdblks[dev] = mtdblk; + mutex_unlock(&mtdblks_lock); - DEBUG(MTD_DEBUG_LEVEL1, "ok\n"); + pr_debug("ok\n"); return 0; } -static int mtdblock_release(struct mtd_blktrans_dev *mbd) +static void mtdblock_release(struct mtd_blktrans_dev *mbd) { - int dev = mbd->devnum; - struct mtdblk_dev *mtdblk = mtdblks[dev]; + struct mtdblk_dev *mtdblk = container_of(mbd, struct mtdblk_dev, mbd); - DEBUG(MTD_DEBUG_LEVEL1, "mtdblock_release\n"); + pr_debug("mtdblock_release\n"); + + mutex_lock(&mtdblks_lock); mutex_lock(&mtdblk->cache_mutex); write_cached_data(mtdblk); mutex_unlock(&mtdblk->cache_mutex); if (!--mtdblk->count) { - /* It was the last usage. Free the device */ - mtdblks[dev] = NULL; - if (mtdblk->mtd->sync) - mtdblk->mtd->sync(mtdblk->mtd); + /* + * It was the last usage. Free the cache, but only sync if + * opened for writing. + */ + if (mbd->file_mode & FMODE_WRITE) + mtd_sync(mbd->mtd); vfree(mtdblk->cache_data); - kfree(mtdblk); } - DEBUG(MTD_DEBUG_LEVEL1, "ok\n"); - return 0; + mutex_unlock(&mtdblks_lock); + + pr_debug("ok\n"); } static int mtdblock_flush(struct mtd_blktrans_dev *dev) { - struct mtdblk_dev *mtdblk = mtdblks[dev->devnum]; + struct mtdblk_dev *mtdblk = container_of(dev, struct mtdblk_dev, mbd); mutex_lock(&mtdblk->cache_mutex); write_cached_data(mtdblk); mutex_unlock(&mtdblk->cache_mutex); - - if (mtdblk->mtd->sync) - mtdblk->mtd->sync(mtdblk->mtd); + mtd_sync(dev->mtd); return 0; } static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) { - struct mtd_blktrans_dev *dev = kmalloc(sizeof(*dev), GFP_KERNEL); + struct mtdblk_dev *dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) return; - memset(dev, 0, sizeof(*dev)); + dev->mbd.mtd = mtd; + dev->mbd.devnum = mtd->index; - dev->mtd = mtd; - dev->devnum = mtd->index; - dev->blksize = 512; - dev->size = mtd->size >> 9; - dev->tr = tr; + dev->mbd.size = mtd->size >> 9; + dev->mbd.tr = tr; if (!(mtd->flags & MTD_WRITEABLE)) - dev->readonly = 1; + dev->mbd.readonly = 1; - add_mtd_blktrans_dev(dev); + if (add_mtd_blktrans_dev(&dev->mbd)) + kfree(dev); } static void mtdblock_remove_dev(struct mtd_blktrans_dev *dev) { del_mtd_blktrans_dev(dev); - kfree(dev); } static struct mtd_blktrans_ops mtdblock_tr = { .name = "mtdblock", - .major = 31, + .major = MTD_BLOCK_MAJOR, .part_bits = 0, + .blksize = 512, .open = mtdblock_open, .flush = mtdblock_flush, .release = mtdblock_release, @@ -393,5 +402,5 @@ module_exit(cleanup_mtdblock); MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Nicolas Pitre <nico@cam.org> et al."); +MODULE_AUTHOR("Nicolas Pitre <nico@fluxnic.net> et al."); MODULE_DESCRIPTION("Caching read/erase/writeback block device emulation access to MTD devices"); diff --git a/drivers/mtd/mtdblock_ro.c b/drivers/mtd/mtdblock_ro.c index 29563ed258a..fb5dc89369d 100644 --- a/drivers/mtd/mtdblock_ro.c +++ b/drivers/mtd/mtdblock_ro.c @@ -1,22 +1,37 @@ /* - * $Id: mtdblock_ro.c,v 1.19 2004/11/16 18:28:59 dwmw2 Exp $ + * Simple read-only (writable only for RAM) mtdblock driver * - * (C) 2003 David Woodhouse <dwmw2@infradead.org> + * Copyright © 2001-2010 David Woodhouse <dwmw2@infradead.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * - * Simple read-only (writable only for RAM) mtdblock driver */ #include <linux/init.h> #include <linux/slab.h> #include <linux/mtd/mtd.h> #include <linux/mtd/blktrans.h> +#include <linux/module.h> +#include <linux/major.h> static int mtdblock_readsect(struct mtd_blktrans_dev *dev, unsigned long block, char *buf) { size_t retlen; - if (dev->mtd->read(dev->mtd, (block * 512), 512, &retlen, buf)) + if (mtd_read(dev->mtd, (block * 512), 512, &retlen, buf)) return 1; return 0; } @@ -26,40 +41,39 @@ static int mtdblock_writesect(struct mtd_blktrans_dev *dev, { size_t retlen; - if (dev->mtd->write(dev->mtd, (block * 512), 512, &retlen, buf)) + if (mtd_write(dev->mtd, (block * 512), 512, &retlen, buf)) return 1; return 0; } static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) { - struct mtd_blktrans_dev *dev = kmalloc(sizeof(*dev), GFP_KERNEL); + struct mtd_blktrans_dev *dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) return; - memset(dev, 0, sizeof(*dev)); - dev->mtd = mtd; dev->devnum = mtd->index; - dev->blksize = 512; + dev->size = mtd->size >> 9; dev->tr = tr; dev->readonly = 1; - add_mtd_blktrans_dev(dev); + if (add_mtd_blktrans_dev(dev)) + kfree(dev); } static void mtdblock_remove_dev(struct mtd_blktrans_dev *dev) { del_mtd_blktrans_dev(dev); - kfree(dev); } static struct mtd_blktrans_ops mtdblock_tr = { .name = "mtdblock", - .major = 31, + .major = MTD_BLOCK_MAJOR, .part_bits = 0, + .blksize = 512, .readsect = mtdblock_readsect, .writesect = mtdblock_writesect, .add_mtd = mtdblock_add_mtd, diff --git a/drivers/mtd/mtdchar.c b/drivers/mtd/mtdchar.c index 5b6acfcb2b8..a0f54e80670 100644 --- a/drivers/mtd/mtdchar.c +++ b/drivers/mtd/mtdchar.c @@ -1,165 +1,194 @@ /* - * $Id: mtdchar.c,v 1.76 2005/11/07 11:14:20 gleixner Exp $ + * Copyright © 1999-2010 David Woodhouse <dwmw2@infradead.org> * - * Character-device access to raw MTD devices. + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #include <linux/device.h> #include <linux/fs.h> +#include <linux/mm.h> +#include <linux/err.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/sched.h> - +#include <linux/mutex.h> +#include <linux/backing-dev.h> +#include <linux/compat.h> +#include <linux/mount.h> +#include <linux/blkpg.h> +#include <linux/magic.h> +#include <linux/major.h> #include <linux/mtd/mtd.h> -#include <linux/mtd/compatmac.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/map.h> #include <asm/uaccess.h> -static struct class *mtd_class; - -static void mtd_notify_add(struct mtd_info* mtd) -{ - if (!mtd) - return; - - class_device_create(mtd_class, NULL, MKDEV(MTD_CHAR_MAJOR, mtd->index*2), - NULL, "mtd%d", mtd->index); - - class_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; - - class_device_destroy(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2)); - class_device_destroy(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1)); -} +#include "mtdcore.h" -static struct mtd_notifier notifier = { - .add = mtd_notify_add, - .remove = mtd_notify_remove, -}; +static DEFINE_MUTEX(mtd_mutex); /* * Data structure to hold the pointer to the mtd device as well - * as mode information ofr various use cases. + * as mode information of various use cases. */ struct mtd_file_info { struct mtd_info *mtd; + struct inode *ino; enum mtd_file_modes mode; }; -static loff_t mtd_lseek (struct file *file, loff_t offset, int orig) +static loff_t mtdchar_lseek(struct file *file, loff_t offset, int orig) { struct mtd_file_info *mfi = file->private_data; - struct mtd_info *mtd = mfi->mtd; - - switch (orig) { - case SEEK_SET: - break; - case SEEK_CUR: - offset += file->f_pos; - break; - case SEEK_END: - offset += mtd->size; - break; - default: - return -EINVAL; - } - - if (offset >= 0 && offset <= mtd->size) - return file->f_pos = offset; - - return -EINVAL; + return fixed_size_llseek(file, offset, orig, mfi->mtd->size); } +static int count; +static struct vfsmount *mnt; +static struct file_system_type mtd_inodefs_type; - -static int mtd_open(struct inode *inode, struct file *file) +static int mtdchar_open(struct inode *inode, struct file *file) { int minor = iminor(inode); int devnum = minor >> 1; + int ret = 0; struct mtd_info *mtd; struct mtd_file_info *mfi; + struct inode *mtd_ino; - DEBUG(MTD_DEBUG_LEVEL0, "MTD_open\n"); - - if (devnum >= MAX_MTD_DEVICES) - return -ENODEV; + pr_debug("MTD_open\n"); /* You can't open the RO devices RW */ - if ((file->f_mode & 2) && (minor & 1)) + if ((file->f_mode & FMODE_WRITE) && (minor & 1)) return -EACCES; + ret = simple_pin_fs(&mtd_inodefs_type, &mnt, &count); + if (ret) + return ret; + + mutex_lock(&mtd_mutex); mtd = get_mtd_device(NULL, devnum); - if (!mtd) - return -ENODEV; + if (IS_ERR(mtd)) { + ret = PTR_ERR(mtd); + goto out; + } - if (MTD_ABSENT == mtd->type) { - put_mtd_device(mtd); - return -ENODEV; + if (mtd->type == MTD_ABSENT) { + ret = -ENODEV; + goto out1; } + mtd_ino = iget_locked(mnt->mnt_sb, devnum); + if (!mtd_ino) { + ret = -ENOMEM; + goto out1; + } + if (mtd_ino->i_state & I_NEW) { + mtd_ino->i_private = mtd; + mtd_ino->i_mode = S_IFCHR; + mtd_ino->i_data.backing_dev_info = mtd->backing_dev_info; + unlock_new_inode(mtd_ino); + } + file->f_mapping = mtd_ino->i_mapping; + /* You can't open it RW if it's not a writeable device */ - if ((file->f_mode & 2) && !(mtd->flags & MTD_WRITEABLE)) { - put_mtd_device(mtd); - return -EACCES; + if ((file->f_mode & FMODE_WRITE) && !(mtd->flags & MTD_WRITEABLE)) { + ret = -EACCES; + goto out2; } mfi = kzalloc(sizeof(*mfi), GFP_KERNEL); if (!mfi) { - put_mtd_device(mtd); - return -ENOMEM; + ret = -ENOMEM; + goto out2; } + mfi->ino = mtd_ino; mfi->mtd = mtd; file->private_data = mfi; - + mutex_unlock(&mtd_mutex); return 0; -} /* mtd_open */ + +out2: + iput(mtd_ino); +out1: + put_mtd_device(mtd); +out: + mutex_unlock(&mtd_mutex); + simple_release_fs(&mnt, &count); + return ret; +} /* mtdchar_open */ /*====================================================================*/ -static int mtd_close(struct inode *inode, struct file *file) +static int mtdchar_close(struct inode *inode, struct file *file) { struct mtd_file_info *mfi = file->private_data; struct mtd_info *mtd = mfi->mtd; - DEBUG(MTD_DEBUG_LEVEL0, "MTD_close\n"); + pr_debug("MTD_close\n"); - if (mtd->sync) - mtd->sync(mtd); + /* Only sync if opened RW */ + if ((file->f_mode & FMODE_WRITE)) + mtd_sync(mtd); + + iput(mfi->ino); put_mtd_device(mtd); file->private_data = NULL; kfree(mfi); + simple_release_fs(&mnt, &count); return 0; -} /* mtd_close */ +} /* mtdchar_close */ -/* FIXME: This _really_ needs to die. In 2.5, we should lock the - userspace buffer down and use it directly with readv/writev. -*/ -#define MAX_KMALLOC_SIZE 0x20000 +/* Back in June 2001, dwmw2 wrote: + * + * FIXME: This _really_ needs to die. In 2.5, we should lock the + * userspace buffer down and use it directly with readv/writev. + * + * The implementation below, using mtd_kmalloc_up_to, mitigates + * allocation failures when the system is under low-memory situations + * or if memory is highly fragmented at the cost of reducing the + * performance of the requested transfer due to a smaller buffer size. + * + * A more complex but more memory-efficient implementation based on + * get_user_pages and iovecs to cover extents of those pages is a + * longer-term goal, as intimated by dwmw2 above. However, for the + * write case, this requires yet more complex head and tail transfer + * handling when those head and tail offsets and sizes are such that + * alignment requirements are not met in the NAND subdriver. + */ -static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t *ppos) +static ssize_t mtdchar_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) { struct mtd_file_info *mfi = file->private_data; struct mtd_info *mtd = mfi->mtd; - size_t retlen=0; + size_t retlen; size_t total_retlen=0; int ret=0; int len; + size_t size = count; char *kbuf; - DEBUG(MTD_DEBUG_LEVEL0,"MTD_read\n"); + pr_debug("MTD_read\n"); if (*ppos + count > mtd->size) count = mtd->size - *ppos; @@ -167,57 +196,48 @@ static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t if (!count) return 0; - /* FIXME: Use kiovec in 2.5 to lock down the user's buffers - and pass them directly to the MTD functions */ - - if (count > MAX_KMALLOC_SIZE) - kbuf=kmalloc(MAX_KMALLOC_SIZE, GFP_KERNEL); - else - kbuf=kmalloc(count, GFP_KERNEL); - + kbuf = mtd_kmalloc_up_to(mtd, &size); if (!kbuf) return -ENOMEM; while (count) { - - if (count > MAX_KMALLOC_SIZE) - len = MAX_KMALLOC_SIZE; - else - len = count; + len = min_t(size_t, count, size); switch (mfi->mode) { - case MTD_MODE_OTP_FACTORY: - ret = mtd->read_fact_prot_reg(mtd, *ppos, len, &retlen, kbuf); + case MTD_FILE_MODE_OTP_FACTORY: + ret = mtd_read_fact_prot_reg(mtd, *ppos, len, + &retlen, kbuf); break; - case MTD_MODE_OTP_USER: - ret = mtd->read_user_prot_reg(mtd, *ppos, len, &retlen, kbuf); + case MTD_FILE_MODE_OTP_USER: + ret = mtd_read_user_prot_reg(mtd, *ppos, len, + &retlen, kbuf); break; - case MTD_MODE_RAW: + case MTD_FILE_MODE_RAW: { struct mtd_oob_ops ops; - ops.mode = MTD_OOB_RAW; + ops.mode = MTD_OPS_RAW; ops.datbuf = kbuf; ops.oobbuf = NULL; ops.len = len; - ret = mtd->read_oob(mtd, *ppos, &ops); + ret = mtd_read_oob(mtd, *ppos, &ops); retlen = ops.retlen; break; } default: - ret = mtd->read(mtd, *ppos, len, &retlen, kbuf); + ret = mtd_read(mtd, *ppos, len, &retlen, kbuf); } - /* Nand returns -EBADMSG on ecc errors, but it returns + /* Nand returns -EBADMSG on ECC errors, but it returns * the data. For our userspace tools it is important - * to dump areas with ecc errors ! + * to dump areas with ECC errors! * For kernel internal usage it also might return -EUCLEAN - * to signal the caller that a bitflip has occured and has + * to signal the caller that a bitflip has occurred and has * been corrected by the ECC algorithm. * Userspace software which accesses NAND this way * must be aware of the fact that it deals with NAND */ - if (!ret || (ret == -EUCLEAN) || (ret == -EBADMSG)) { + if (!ret || mtd_is_bitflip_or_eccerr(ret)) { *ppos += retlen; if (copy_to_user(buf, kbuf, retlen)) { kfree(kbuf); @@ -240,19 +260,21 @@ static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t kfree(kbuf); return total_retlen; -} /* mtd_read */ +} /* mtdchar_read */ -static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count,loff_t *ppos) +static ssize_t mtdchar_write(struct file *file, const char __user *buf, size_t count, + loff_t *ppos) { struct mtd_file_info *mfi = file->private_data; struct mtd_info *mtd = mfi->mtd; + size_t size = count; char *kbuf; size_t retlen; size_t total_retlen=0; int ret=0; int len; - DEBUG(MTD_DEBUG_LEVEL0,"MTD_write\n"); + pr_debug("MTD_write\n"); if (*ppos == mtd->size) return -ENOSPC; @@ -263,20 +285,12 @@ static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count if (!count) return 0; - if (count > MAX_KMALLOC_SIZE) - kbuf=kmalloc(MAX_KMALLOC_SIZE, GFP_KERNEL); - else - kbuf=kmalloc(count, GFP_KERNEL); - + kbuf = mtd_kmalloc_up_to(mtd, &size); if (!kbuf) return -ENOMEM; while (count) { - - if (count > MAX_KMALLOC_SIZE) - len = MAX_KMALLOC_SIZE; - else - len = count; + len = min_t(size_t, count, size); if (copy_from_user(kbuf, buf, len)) { kfree(kbuf); @@ -284,34 +298,41 @@ static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count } switch (mfi->mode) { - case MTD_MODE_OTP_FACTORY: + case MTD_FILE_MODE_OTP_FACTORY: ret = -EROFS; break; - case MTD_MODE_OTP_USER: - if (!mtd->write_user_prot_reg) { - ret = -EOPNOTSUPP; - break; - } - ret = mtd->write_user_prot_reg(mtd, *ppos, len, &retlen, kbuf); + case MTD_FILE_MODE_OTP_USER: + ret = mtd_write_user_prot_reg(mtd, *ppos, len, + &retlen, kbuf); break; - case MTD_MODE_RAW: + case MTD_FILE_MODE_RAW: { struct mtd_oob_ops ops; - ops.mode = MTD_OOB_RAW; + ops.mode = MTD_OPS_RAW; ops.datbuf = kbuf; ops.oobbuf = NULL; + ops.ooboffs = 0; ops.len = len; - ret = mtd->write_oob(mtd, *ppos, &ops); + ret = mtd_write_oob(mtd, *ppos, &ops); retlen = ops.retlen; break; } default: - ret = (*(mtd->write))(mtd, *ppos, len, &retlen, kbuf); + ret = mtd_write(mtd, *ppos, len, &retlen, kbuf); } + + /* + * Return -ENOSPC only if no data could be written at all. + * Otherwise just return the number of bytes that actually + * have been written. + */ + if ((ret == -ENOSPC) && (total_retlen)) + break; + if (!ret) { *ppos += retlen; total_retlen += retlen; @@ -326,7 +347,7 @@ static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count kfree(kbuf); return total_retlen; -} /* mtd_write */ +} /* mtdchar_write */ /*====================================================================== @@ -338,38 +359,262 @@ static void mtdchar_erase_callback (struct erase_info *instr) wake_up((wait_queue_head_t *)instr->priv); } -#if defined(CONFIG_MTD_OTP) || defined(CONFIG_MTD_ONENAND_OTP) static int otp_select_filemode(struct mtd_file_info *mfi, int mode) { struct mtd_info *mtd = mfi->mtd; - int ret = 0; + size_t retlen; switch (mode) { case MTD_OTP_FACTORY: - if (!mtd->read_fact_prot_reg) - ret = -EOPNOTSUPP; - else - mfi->mode = MTD_MODE_OTP_FACTORY; + if (mtd_read_fact_prot_reg(mtd, -1, 0, &retlen, NULL) == + -EOPNOTSUPP) + return -EOPNOTSUPP; + + mfi->mode = MTD_FILE_MODE_OTP_FACTORY; break; case MTD_OTP_USER: - if (!mtd->read_fact_prot_reg) - ret = -EOPNOTSUPP; - else - mfi->mode = MTD_MODE_OTP_USER; + if (mtd_read_user_prot_reg(mtd, -1, 0, &retlen, NULL) == + -EOPNOTSUPP) + return -EOPNOTSUPP; + + mfi->mode = MTD_FILE_MODE_OTP_USER; break; - default: - ret = -EINVAL; case MTD_OTP_OFF: + mfi->mode = MTD_FILE_MODE_NORMAL; break; + default: + return -EINVAL; + } + + return 0; +} + +static int mtdchar_writeoob(struct file *file, struct mtd_info *mtd, + uint64_t start, uint32_t length, void __user *ptr, + uint32_t __user *retp) +{ + struct mtd_file_info *mfi = file->private_data; + struct mtd_oob_ops ops; + uint32_t retlen; + int ret = 0; + + if (!(file->f_mode & FMODE_WRITE)) + return -EPERM; + + if (length > 4096) + return -EINVAL; + + if (!mtd->_write_oob) + ret = -EOPNOTSUPP; + else + ret = access_ok(VERIFY_READ, ptr, length) ? 0 : -EFAULT; + + if (ret) + return ret; + + ops.ooblen = length; + ops.ooboffs = start & (mtd->writesize - 1); + ops.datbuf = NULL; + ops.mode = (mfi->mode == MTD_FILE_MODE_RAW) ? MTD_OPS_RAW : + MTD_OPS_PLACE_OOB; + + if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs)) + return -EINVAL; + + ops.oobbuf = memdup_user(ptr, length); + if (IS_ERR(ops.oobbuf)) + return PTR_ERR(ops.oobbuf); + + start &= ~((uint64_t)mtd->writesize - 1); + ret = mtd_write_oob(mtd, start, &ops); + + if (ops.oobretlen > 0xFFFFFFFFU) + ret = -EOVERFLOW; + retlen = ops.oobretlen; + if (copy_to_user(retp, &retlen, sizeof(length))) + ret = -EFAULT; + + kfree(ops.oobbuf); + return ret; +} + +static int mtdchar_readoob(struct file *file, struct mtd_info *mtd, + uint64_t start, uint32_t length, void __user *ptr, + uint32_t __user *retp) +{ + struct mtd_file_info *mfi = file->private_data; + struct mtd_oob_ops ops; + int ret = 0; + + if (length > 4096) + return -EINVAL; + + if (!access_ok(VERIFY_WRITE, ptr, length)) + return -EFAULT; + + ops.ooblen = length; + ops.ooboffs = start & (mtd->writesize - 1); + ops.datbuf = NULL; + ops.mode = (mfi->mode == MTD_FILE_MODE_RAW) ? MTD_OPS_RAW : + MTD_OPS_PLACE_OOB; + + if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs)) + return -EINVAL; + + ops.oobbuf = kmalloc(length, GFP_KERNEL); + if (!ops.oobbuf) + return -ENOMEM; + + start &= ~((uint64_t)mtd->writesize - 1); + ret = mtd_read_oob(mtd, start, &ops); + + if (put_user(ops.oobretlen, retp)) + ret = -EFAULT; + else if (ops.oobretlen && copy_to_user(ptr, ops.oobbuf, + ops.oobretlen)) + ret = -EFAULT; + + kfree(ops.oobbuf); + + /* + * NAND returns -EBADMSG on ECC errors, but it returns the OOB + * data. For our userspace tools it is important to dump areas + * with ECC errors! + * For kernel internal usage it also might return -EUCLEAN + * to signal the caller that a bitflip has occured and has + * been corrected by the ECC algorithm. + * + * Note: currently the standard NAND function, nand_read_oob_std, + * does not calculate ECC for the OOB area, so do not rely on + * this behavior unless you have replaced it with your own. + */ + if (mtd_is_bitflip_or_eccerr(ret)) + return 0; + + return ret; +} + +/* + * Copies (and truncates, if necessary) data from the larger struct, + * nand_ecclayout, to the smaller, deprecated layout struct, + * nand_ecclayout_user. This is necessary only to support the deprecated + * API ioctl ECCGETLAYOUT while allowing all new functionality to use + * nand_ecclayout flexibly (i.e. the struct may change size in new + * releases without requiring major rewrites). + */ +static int shrink_ecclayout(const struct nand_ecclayout *from, + struct nand_ecclayout_user *to) +{ + int i; + + if (!from || !to) + return -EINVAL; + + memset(to, 0, sizeof(*to)); + + to->eccbytes = min((int)from->eccbytes, MTD_MAX_ECCPOS_ENTRIES); + for (i = 0; i < to->eccbytes; i++) + to->eccpos[i] = from->eccpos[i]; + + for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES; i++) { + if (from->oobfree[i].length == 0 && + from->oobfree[i].offset == 0) + break; + to->oobavail += from->oobfree[i].length; + to->oobfree[i] = from->oobfree[i]; + } + + return 0; +} + +static int mtdchar_blkpg_ioctl(struct mtd_info *mtd, + struct blkpg_ioctl_arg __user *arg) +{ + struct blkpg_ioctl_arg a; + struct blkpg_partition p; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (copy_from_user(&a, arg, sizeof(struct blkpg_ioctl_arg))) + return -EFAULT; + + if (copy_from_user(&p, a.data, sizeof(struct blkpg_partition))) + return -EFAULT; + + switch (a.op) { + case BLKPG_ADD_PARTITION: + + /* Only master mtd device must be used to add partitions */ + if (mtd_is_partition(mtd)) + return -EINVAL; + + return mtd_add_partition(mtd, p.devname, p.start, p.length); + + case BLKPG_DEL_PARTITION: + + if (p.pno < 0) + return -EINVAL; + + return mtd_del_partition(mtd, p.pno); + + default: + return -EINVAL; + } +} + +static int mtdchar_write_ioctl(struct mtd_info *mtd, + struct mtd_write_req __user *argp) +{ + struct mtd_write_req req; + struct mtd_oob_ops ops; + const void __user *usr_data, *usr_oob; + int ret; + + if (copy_from_user(&req, argp, sizeof(req))) + return -EFAULT; + + usr_data = (const void __user *)(uintptr_t)req.usr_data; + usr_oob = (const void __user *)(uintptr_t)req.usr_oob; + if (!access_ok(VERIFY_READ, usr_data, req.len) || + !access_ok(VERIFY_READ, usr_oob, req.ooblen)) + return -EFAULT; + + if (!mtd->_write_oob) + return -EOPNOTSUPP; + + ops.mode = req.mode; + ops.len = (size_t)req.len; + ops.ooblen = (size_t)req.ooblen; + ops.ooboffs = 0; + + if (usr_data) { + ops.datbuf = memdup_user(usr_data, ops.len); + if (IS_ERR(ops.datbuf)) + return PTR_ERR(ops.datbuf); + } else { + ops.datbuf = NULL; } + + if (usr_oob) { + ops.oobbuf = memdup_user(usr_oob, ops.ooblen); + if (IS_ERR(ops.oobbuf)) { + kfree(ops.datbuf); + return PTR_ERR(ops.oobbuf); + } + } else { + ops.oobbuf = NULL; + } + + ret = mtd_write_oob(mtd, (loff_t)req.start, &ops); + + kfree(ops.datbuf); + kfree(ops.oobbuf); + return ret; } -#else -# define otp_select_filemode(f,m) -EOPNOTSUPP -#endif -static int mtd_ioctl(struct inode *inode, struct file *file, - u_int cmd, u_long arg) +static int mtdchar_ioctl(struct file *file, u_int cmd, u_long arg) { struct mtd_file_info *mfi = file->private_data; struct mtd_info *mtd = mfi->mtd; @@ -378,7 +623,7 @@ static int mtd_ioctl(struct inode *inode, struct file *file, u_long size; struct mtd_info_user info; - DEBUG(MTD_DEBUG_LEVEL0, "MTD_ioctl\n"); + pr_debug("MTD_ioctl\n"); size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT; if (cmd & IOC_IN) { @@ -398,40 +643,49 @@ static int mtd_ioctl(struct inode *inode, struct file *file, case MEMGETREGIONINFO: { - struct region_info_user ur; + uint32_t ur_idx; + struct mtd_erase_region_info *kr; + struct region_info_user __user *ur = argp; - if (copy_from_user(&ur, argp, sizeof(struct region_info_user))) + if (get_user(ur_idx, &(ur->regionindex))) return -EFAULT; - if (ur.regionindex >= mtd->numeraseregions) + if (ur_idx >= mtd->numeraseregions) return -EINVAL; - if (copy_to_user(argp, &(mtd->eraseregions[ur.regionindex]), - sizeof(struct mtd_erase_region_info))) + + kr = &(mtd->eraseregions[ur_idx]); + + if (put_user(kr->offset, &(ur->offset)) + || put_user(kr->erasesize, &(ur->erasesize)) + || put_user(kr->numblocks, &(ur->numblocks))) return -EFAULT; + break; } case MEMGETINFO: + memset(&info, 0, sizeof(info)); info.type = mtd->type; info.flags = mtd->flags; info.size = mtd->size; info.erasesize = mtd->erasesize; info.writesize = mtd->writesize; info.oobsize = mtd->oobsize; - info.ecctype = mtd->ecctype; - info.eccsize = mtd->eccsize; + /* The below field is obsolete */ + info.padding = 0; if (copy_to_user(argp, &info, sizeof(struct mtd_info_user))) return -EFAULT; break; case MEMERASE: + case MEMERASE64: { struct erase_info *erase; - if(!(file->f_mode & 2)) + if(!(file->f_mode & FMODE_WRITE)) return -EPERM; - erase=kmalloc(sizeof(struct erase_info),GFP_KERNEL); + erase=kzalloc(sizeof(struct erase_info),GFP_KERNEL); if (!erase) ret = -ENOMEM; else { @@ -440,11 +694,26 @@ static int mtd_ioctl(struct inode *inode, struct file *file, init_waitqueue_head(&waitq); - memset (erase,0,sizeof(struct erase_info)); - if (copy_from_user(&erase->addr, argp, - sizeof(struct erase_info_user))) { - kfree(erase); - return -EFAULT; + if (cmd == MEMERASE64) { + struct erase_info_user64 einfo64; + + if (copy_from_user(&einfo64, argp, + sizeof(struct erase_info_user64))) { + kfree(erase); + return -EFAULT; + } + erase->addr = einfo64.start; + erase->len = einfo64.length; + } else { + struct erase_info_user einfo32; + + if (copy_from_user(&einfo32, argp, + sizeof(struct erase_info_user))) { + kfree(erase); + return -EFAULT; + } + erase->addr = einfo32.start; + erase->len = einfo32.length; } erase->mtd = mtd; erase->callback = mtdchar_erase_callback; @@ -459,7 +728,7 @@ static int mtd_ioctl(struct inode *inode, struct file *file, wq_head is no longer there when the callback routine tries to wake us up. */ - ret = mtd->erase(mtd, erase); + ret = mtd_erase(mtd, erase); if (!ret) { set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&waitq, &wait); @@ -479,126 +748,96 @@ static int mtd_ioctl(struct inode *inode, struct file *file, case MEMWRITEOOB: { struct mtd_oob_buf buf; - struct mtd_oob_ops ops; - - if(!(file->f_mode & 2)) - return -EPERM; - - if (copy_from_user(&buf, argp, sizeof(struct mtd_oob_buf))) - return -EFAULT; - - if (buf.length > 4096) - return -EINVAL; - - if (!mtd->write_oob) - ret = -EOPNOTSUPP; - else - ret = access_ok(VERIFY_READ, buf.ptr, - buf.length) ? 0 : EFAULT; + struct mtd_oob_buf __user *buf_user = argp; - if (ret) - return ret; - - ops.len = buf.length; - ops.ooblen = buf.length; - ops.ooboffs = buf.start & (mtd->oobsize - 1); - ops.datbuf = NULL; - ops.mode = MTD_OOB_PLACE; - - if (ops.ooboffs && ops.len > (mtd->oobsize - ops.ooboffs)) - return -EINVAL; - - ops.oobbuf = kmalloc(buf.length, GFP_KERNEL); - if (!ops.oobbuf) - return -ENOMEM; - - if (copy_from_user(ops.oobbuf, buf.ptr, buf.length)) { - kfree(ops.oobbuf); - return -EFAULT; - } - - buf.start &= ~(mtd->oobsize - 1); - ret = mtd->write_oob(mtd, buf.start, &ops); - - if (copy_to_user(argp + sizeof(uint32_t), &ops.retlen, - sizeof(uint32_t))) + /* NOTE: writes return length to buf_user->length */ + if (copy_from_user(&buf, argp, sizeof(buf))) ret = -EFAULT; - - kfree(ops.oobbuf); + else + ret = mtdchar_writeoob(file, mtd, buf.start, buf.length, + buf.ptr, &buf_user->length); break; - } case MEMREADOOB: { struct mtd_oob_buf buf; - struct mtd_oob_ops ops; + struct mtd_oob_buf __user *buf_user = argp; - if (copy_from_user(&buf, argp, sizeof(struct mtd_oob_buf))) - return -EFAULT; - - if (buf.length > 4096) - return -EINVAL; - - if (!mtd->read_oob) - ret = -EOPNOTSUPP; + /* NOTE: writes return length to buf_user->start */ + if (copy_from_user(&buf, argp, sizeof(buf))) + ret = -EFAULT; else - ret = access_ok(VERIFY_WRITE, buf.ptr, - buf.length) ? 0 : -EFAULT; - if (ret) - return ret; - - ops.len = buf.length; - ops.ooblen = buf.length; - ops.ooboffs = buf.start & (mtd->oobsize - 1); - ops.datbuf = NULL; - ops.mode = MTD_OOB_PLACE; + ret = mtdchar_readoob(file, mtd, buf.start, buf.length, + buf.ptr, &buf_user->start); + break; + } - if (ops.ooboffs && ops.len > (mtd->oobsize - ops.ooboffs)) - return -EINVAL; + case MEMWRITEOOB64: + { + struct mtd_oob_buf64 buf; + struct mtd_oob_buf64 __user *buf_user = argp; - ops.oobbuf = kmalloc(buf.length, GFP_KERNEL); - if (!ops.oobbuf) - return -ENOMEM; + if (copy_from_user(&buf, argp, sizeof(buf))) + ret = -EFAULT; + else + ret = mtdchar_writeoob(file, mtd, buf.start, buf.length, + (void __user *)(uintptr_t)buf.usr_ptr, + &buf_user->length); + break; + } - buf.start &= ~(mtd->oobsize - 1); - ret = mtd->read_oob(mtd, buf.start, &ops); + case MEMREADOOB64: + { + struct mtd_oob_buf64 buf; + struct mtd_oob_buf64 __user *buf_user = argp; - if (put_user(ops.retlen, (uint32_t __user *)argp)) - ret = -EFAULT; - else if (ops.retlen && copy_to_user(buf.ptr, ops.oobbuf, - ops.retlen)) + if (copy_from_user(&buf, argp, sizeof(buf))) ret = -EFAULT; + else + ret = mtdchar_readoob(file, mtd, buf.start, buf.length, + (void __user *)(uintptr_t)buf.usr_ptr, + &buf_user->length); + break; + } - kfree(ops.oobbuf); + case MEMWRITE: + { + ret = mtdchar_write_ioctl(mtd, + (struct mtd_write_req __user *)arg); break; } case MEMLOCK: { - struct erase_info_user info; + struct erase_info_user einfo; - if (copy_from_user(&info, argp, sizeof(info))) + if (copy_from_user(&einfo, argp, sizeof(einfo))) return -EFAULT; - if (!mtd->lock) - ret = -EOPNOTSUPP; - else - ret = mtd->lock(mtd, info.start, info.length); + ret = mtd_lock(mtd, einfo.start, einfo.length); break; } case MEMUNLOCK: { - struct erase_info_user info; + struct erase_info_user einfo; - if (copy_from_user(&info, argp, sizeof(info))) + if (copy_from_user(&einfo, argp, sizeof(einfo))) return -EFAULT; - if (!mtd->unlock) - ret = -EOPNOTSUPP; - else - ret = mtd->unlock(mtd, info.start, info.length); + ret = mtd_unlock(mtd, einfo.start, einfo.length); + break; + } + + case MEMISLOCKED: + { + struct erase_info_user einfo; + + if (copy_from_user(&einfo, argp, sizeof(einfo))) + return -EFAULT; + + ret = mtd_is_locked(mtd, einfo.start, einfo.length); break; } @@ -616,6 +855,7 @@ static int mtd_ioctl(struct inode *inode, struct file *file, memcpy(&oi.eccpos, mtd->ecclayout->eccpos, sizeof(oi.eccpos)); memcpy(&oi.oobfree, mtd->ecclayout->oobfree, sizeof(oi.oobfree)); + oi.eccbytes = mtd->ecclayout->eccbytes; if (copy_to_user(argp, &oi, sizeof(struct nand_oobinfo))) return -EFAULT; @@ -628,10 +868,7 @@ static int mtd_ioctl(struct inode *inode, struct file *file, if (copy_from_user(&offs, argp, sizeof(loff_t))) return -EFAULT; - if (!mtd->block_isbad) - ret = -EOPNOTSUPP; - else - return mtd->block_isbad(mtd, offs); + return mtd_block_isbad(mtd, offs); break; } @@ -641,21 +878,17 @@ static int mtd_ioctl(struct inode *inode, struct file *file, if (copy_from_user(&offs, argp, sizeof(loff_t))) return -EFAULT; - if (!mtd->block_markbad) - ret = -EOPNOTSUPP; - else - return mtd->block_markbad(mtd, offs); + return mtd_block_markbad(mtd, offs); break; } -#if defined(CONFIG_MTD_OTP) || defined(CONFIG_MTD_ONENAND_OTP) case OTPSELECT: { int mode; if (copy_from_user(&mode, argp, sizeof(int))) return -EFAULT; - mfi->mode = MTD_MODE_NORMAL; + mfi->mode = MTD_FILE_MODE_NORMAL; ret = otp_select_filemode(mfi, mode); @@ -667,27 +900,26 @@ static int mtd_ioctl(struct inode *inode, struct file *file, case OTPGETREGIONINFO: { struct otp_info *buf = kmalloc(4096, GFP_KERNEL); + size_t retlen; if (!buf) return -ENOMEM; - ret = -EOPNOTSUPP; switch (mfi->mode) { - case MTD_MODE_OTP_FACTORY: - if (mtd->get_fact_prot_info) - ret = mtd->get_fact_prot_info(mtd, buf, 4096); + case MTD_FILE_MODE_OTP_FACTORY: + ret = mtd_get_fact_prot_info(mtd, 4096, &retlen, buf); break; - case MTD_MODE_OTP_USER: - if (mtd->get_user_prot_info) - ret = mtd->get_user_prot_info(mtd, buf, 4096); + case MTD_FILE_MODE_OTP_USER: + ret = mtd_get_user_prot_info(mtd, 4096, &retlen, buf); break; default: + ret = -EINVAL; break; } - if (ret >= 0) { + if (!ret) { if (cmd == OTPGETREGIONCOUNT) { - int nbr = ret / sizeof(struct otp_info); + int nbr = retlen / sizeof(struct otp_info); ret = copy_to_user(argp, &nbr, sizeof(int)); } else - ret = copy_to_user(argp, buf, ret); + ret = copy_to_user(argp, buf, retlen); if (ret) ret = -EFAULT; } @@ -697,27 +929,33 @@ static int mtd_ioctl(struct inode *inode, struct file *file, case OTPLOCK: { - struct otp_info info; + struct otp_info oinfo; - if (mfi->mode != MTD_MODE_OTP_USER) + if (mfi->mode != MTD_FILE_MODE_OTP_USER) return -EINVAL; - if (copy_from_user(&info, argp, sizeof(info))) + if (copy_from_user(&oinfo, argp, sizeof(oinfo))) return -EFAULT; - if (!mtd->lock_user_prot_reg) - return -EOPNOTSUPP; - ret = mtd->lock_user_prot_reg(mtd, info.start, info.length); + ret = mtd_lock_user_prot_reg(mtd, oinfo.start, oinfo.length); break; } -#endif + /* This ioctl is being deprecated - it truncates the ECC layout */ case ECCGETLAYOUT: { + struct nand_ecclayout_user *usrlay; + if (!mtd->ecclayout) return -EOPNOTSUPP; - if (copy_to_user(argp, &mtd->ecclayout, - sizeof(struct nand_ecclayout))) - return -EFAULT; + usrlay = kmalloc(sizeof(*usrlay), GFP_KERNEL); + if (!usrlay) + return -ENOMEM; + + shrink_ecclayout(mtd->ecclayout, usrlay); + + if (copy_to_user(argp, usrlay, sizeof(*usrlay))) + ret = -EFAULT; + kfree(usrlay); break; } @@ -734,17 +972,17 @@ static int mtd_ioctl(struct inode *inode, struct file *file, mfi->mode = 0; switch(arg) { - case MTD_MODE_OTP_FACTORY: - case MTD_MODE_OTP_USER: + case MTD_FILE_MODE_OTP_FACTORY: + case MTD_FILE_MODE_OTP_USER: ret = otp_select_filemode(mfi, arg); break; - case MTD_MODE_RAW: - if (!mtd->read_oob || !mtd->write_oob) + case MTD_FILE_MODE_RAW: + if (!mtd_has_oob(mtd)) return -EOPNOTSUPP; mfi->mode = arg; - case MTD_MODE_NORMAL: + case MTD_FILE_MODE_NORMAL: break; default: ret = -EINVAL; @@ -753,6 +991,20 @@ static int mtd_ioctl(struct inode *inode, struct file *file, break; } + case BLKPG: + { + ret = mtdchar_blkpg_ioctl(mtd, + (struct blkpg_ioctl_arg __user *)arg); + break; + } + + case BLKRRPART: + { + /* No reread partition feature. Just return ok */ + ret = 0; + break; + } + default: ret = -ENOTTY; } @@ -760,47 +1012,202 @@ static int mtd_ioctl(struct inode *inode, struct file *file, return ret; } /* memory_ioctl */ -static struct file_operations mtd_fops = { - .owner = THIS_MODULE, - .llseek = mtd_lseek, - .read = mtd_read, - .write = mtd_write, - .ioctl = mtd_ioctl, - .open = mtd_open, - .release = mtd_close, +static long mtdchar_unlocked_ioctl(struct file *file, u_int cmd, u_long arg) +{ + int ret; + + mutex_lock(&mtd_mutex); + ret = mtdchar_ioctl(file, cmd, arg); + mutex_unlock(&mtd_mutex); + + return ret; +} + +#ifdef CONFIG_COMPAT + +struct mtd_oob_buf32 { + u_int32_t start; + u_int32_t length; + compat_caddr_t ptr; /* unsigned char* */ }; -static int __init init_mtdchar(void) +#define MEMWRITEOOB32 _IOWR('M', 3, struct mtd_oob_buf32) +#define MEMREADOOB32 _IOWR('M', 4, struct mtd_oob_buf32) + +static long mtdchar_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { - if (register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops)) { - printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n", - MTD_CHAR_MAJOR); - return -EAGAIN; + struct mtd_file_info *mfi = file->private_data; + struct mtd_info *mtd = mfi->mtd; + void __user *argp = compat_ptr(arg); + int ret = 0; + + mutex_lock(&mtd_mutex); + + switch (cmd) { + case MEMWRITEOOB32: + { + struct mtd_oob_buf32 buf; + struct mtd_oob_buf32 __user *buf_user = argp; + + if (copy_from_user(&buf, argp, sizeof(buf))) + ret = -EFAULT; + else + ret = mtdchar_writeoob(file, mtd, buf.start, + buf.length, compat_ptr(buf.ptr), + &buf_user->length); + break; } - mtd_class = class_create(THIS_MODULE, "mtd"); + case MEMREADOOB32: + { + struct mtd_oob_buf32 buf; + struct mtd_oob_buf32 __user *buf_user = argp; - if (IS_ERR(mtd_class)) { - printk(KERN_ERR "Error creating mtd class.\n"); - unregister_chrdev(MTD_CHAR_MAJOR, "mtd"); - return PTR_ERR(mtd_class); + /* NOTE: writes return length to buf->start */ + if (copy_from_user(&buf, argp, sizeof(buf))) + ret = -EFAULT; + else + ret = mtdchar_readoob(file, mtd, buf.start, + buf.length, compat_ptr(buf.ptr), + &buf_user->start); + break; + } + default: + ret = mtdchar_ioctl(file, cmd, (unsigned long)argp); } - register_mtd_user(¬ifier); - return 0; + mutex_unlock(&mtd_mutex); + + return ret; } -static void __exit cleanup_mtdchar(void) +#endif /* CONFIG_COMPAT */ + +/* + * 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 mtdchar_get_unmapped_area(struct file *file, + unsigned long addr, + unsigned long len, + unsigned long pgoff, + unsigned long flags) { - unregister_mtd_user(¬ifier); - class_destroy(mtd_class); - unregister_chrdev(MTD_CHAR_MAJOR, "mtd"); + struct mtd_file_info *mfi = file->private_data; + struct mtd_info *mtd = mfi->mtd; + unsigned long offset; + int ret; + + 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; + + ret = mtd_get_unmapped_area(mtd, len, offset, flags); + return ret == -EOPNOTSUPP ? -ENODEV : ret; +} +#endif + +/* + * set up a mapping for shared memory segments + */ +static int mtdchar_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; + struct map_info *map = mtd->priv; + + /* This is broken because it assumes the MTD device is map-based + and that mtd->priv is a valid struct map_info. It should be + replaced with something that uses the mtd_get_unmapped_area() + operation properly. */ + if (0 /*mtd->type == MTD_RAM || mtd->type == MTD_ROM*/) { +#ifdef pgprot_noncached + if (file->f_flags & O_DSYNC || map->phys >= __pa(high_memory)) + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); +#endif + return vm_iomap_memory(vma, map->phys, map->size); + } + return -ENODEV; +#else + return vma->vm_flags & VM_SHARED ? 0 : -EACCES; +#endif +} + +static const struct file_operations mtd_fops = { + .owner = THIS_MODULE, + .llseek = mtdchar_lseek, + .read = mtdchar_read, + .write = mtdchar_write, + .unlocked_ioctl = mtdchar_unlocked_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = mtdchar_compat_ioctl, +#endif + .open = mtdchar_open, + .release = mtdchar_close, + .mmap = mtdchar_mmap, +#ifndef CONFIG_MMU + .get_unmapped_area = mtdchar_get_unmapped_area, +#endif +}; + +static const struct super_operations mtd_ops = { + .drop_inode = generic_delete_inode, + .statfs = simple_statfs, +}; + +static struct dentry *mtd_inodefs_mount(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) +{ + return mount_pseudo(fs_type, "mtd_inode:", &mtd_ops, NULL, MTD_INODE_FS_MAGIC); } -module_init(init_mtdchar); -module_exit(cleanup_mtdchar); +static struct file_system_type mtd_inodefs_type = { + .name = "mtd_inodefs", + .mount = mtd_inodefs_mount, + .kill_sb = kill_anon_super, +}; +MODULE_ALIAS_FS("mtd_inodefs"); + +int __init init_mtdchar(void) +{ + int ret; + + ret = __register_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS, + "mtd", &mtd_fops); + if (ret < 0) { + pr_err("Can't allocate major number %d for MTD\n", + MTD_CHAR_MAJOR); + return ret; + } + ret = register_filesystem(&mtd_inodefs_type); + if (ret) { + pr_err("Can't register mtd_inodefs filesystem, error %d\n", + ret); + goto err_unregister_chdev; + } + + return ret; + +err_unregister_chdev: + __unregister_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS, "mtd"); + return ret; +} + +void __exit cleanup_mtdchar(void) +{ + unregister_filesystem(&mtd_inodefs_type); + __unregister_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS, "mtd"); +} -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 1fea631b585..b9000563b9f 100644 --- a/drivers/mtd/mtdconcat.c +++ b/drivers/mtd/mtdconcat.c @@ -1,13 +1,25 @@ /* * MTD device concatenation layer * - * (C) 2002 Robert Kaiser <rkaiser@sysgo.de> + * Copyright © 2002 Robert Kaiser <rkaiser@sysgo.de> + * Copyright © 2002-2010 David Woodhouse <dwmw2@infradead.org> * * NAND support by Christian Gan <cgan@iders.ca> * - * This code is GPL + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * - * $Id: mtdconcat.c,v 1.11 2005/11/07 11:14:20 gleixner Exp $ */ #include <linux/kernel.h> @@ -15,6 +27,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> @@ -59,8 +72,6 @@ concat_read(struct mtd_info *mtd, loff_t from, size_t len, int ret = 0, err; int i; - *retlen = 0; - for (i = 0; i < concat->num_subdev; i++) { struct mtd_info *subdev = concat->subdev[i]; size_t size, retsize; @@ -78,14 +89,14 @@ concat_read(struct mtd_info *mtd, loff_t from, size_t len, /* Entire transaction goes into this subdev */ size = len; - err = subdev->read(subdev, from, size, &retsize, buf); + err = mtd_read(subdev, from, size, &retsize, buf); /* Save information about bitflips! */ if (unlikely(err)) { - if (err == -EBADMSG) { + if (mtd_is_eccerr(err)) { mtd->ecc_stats.failed++; ret = err; - } else if (err == -EUCLEAN) { + } else if (mtd_is_bitflip(err)) { mtd->ecc_stats.corrected++; /* Do not overwrite -EBADMSG !! */ if (!ret) @@ -113,11 +124,6 @@ concat_write(struct mtd_info *mtd, loff_t to, size_t len, int err = -EINVAL; int i; - if (!(mtd->flags & MTD_WRITEABLE)) - return -EROFS; - - *retlen = 0; - for (i = 0; i < concat->num_subdev; i++) { struct mtd_info *subdev = concat->subdev[i]; size_t size, retsize; @@ -132,11 +138,7 @@ concat_write(struct mtd_info *mtd, loff_t to, size_t len, else size = len; - if (!(subdev->flags & MTD_WRITEABLE)) - err = -EROFS; - else - err = subdev->write(subdev, to, size, &retsize, buf); - + err = mtd_write(subdev, to, size, &retsize, buf); if (err) break; @@ -163,31 +165,21 @@ concat_writev(struct mtd_info *mtd, const struct kvec *vecs, int i; int err = -EINVAL; - if (!(mtd->flags & MTD_WRITEABLE)) - return -EROFS; - - *retlen = 0; - /* Calculate total length of data */ for (i = 0; i < count; i++) total_len += vecs[i].iov_len; - /* Do not allow write past end of device */ - if ((to + total_len) > mtd->size) - return -EINVAL; - /* Check alignment */ if (mtd->writesize > 1) { - loff_t __to = to; + uint64_t __to = to; if (do_div(__to, mtd->writesize) || (total_len % mtd->writesize)) return -EINVAL; } /* make a copy of vecs */ - vecs_copy = kmalloc(sizeof(struct kvec) * count, GFP_KERNEL); + vecs_copy = kmemdup(vecs, sizeof(struct kvec) * count, GFP_KERNEL); if (!vecs_copy) return -ENOMEM; - memcpy(vecs_copy, vecs, sizeof(struct kvec) * count); entry_low = 0; for (i = 0; i < concat->num_subdev; i++) { @@ -199,7 +191,7 @@ concat_writev(struct mtd_info *mtd, const struct kvec *vecs, continue; } - size = min(total_len, (size_t)(subdev->size - to)); + size = min_t(uint64_t, total_len, subdev->size - to); wsize = size; /* store for future use */ entry_high = entry_low; @@ -212,11 +204,8 @@ concat_writev(struct mtd_info *mtd, const struct kvec *vecs, old_iov_len = vecs_copy[entry_high].iov_len; vecs_copy[entry_high].iov_len = size; - if (!(subdev->flags & MTD_WRITEABLE)) - err = -EROFS; - else - err = subdev->writev(subdev, &vecs_copy[entry_low], - entry_high - entry_low + 1, to, &retsize); + err = mtd_writev(subdev, &vecs_copy[entry_low], + entry_high - entry_low + 1, to, &retsize); vecs_copy[entry_high].iov_len = old_iov_len - size; vecs_copy[entry_high].iov_base += size; @@ -247,7 +236,7 @@ concat_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) struct mtd_oob_ops devops = *ops; int i, err, ret = 0; - ops->retlen = 0; + ops->retlen = ops->oobretlen = 0; for (i = 0; i < concat->num_subdev; i++) { struct mtd_info *subdev = concat->subdev[i]; @@ -261,15 +250,16 @@ concat_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) if (from + devops.len > subdev->size) devops.len = subdev->size - from; - err = subdev->read_oob(subdev, from, &devops); + err = mtd_read_oob(subdev, from, &devops); ops->retlen += devops.retlen; + ops->oobretlen += devops.oobretlen; /* Save information about bitflips! */ if (unlikely(err)) { - if (err == -EBADMSG) { + if (mtd_is_eccerr(err)) { mtd->ecc_stats.failed++; ret = err; - } else if (err == -EUCLEAN) { + } else if (mtd_is_bitflip(err)) { mtd->ecc_stats.corrected++; /* Do not overwrite -EBADMSG !! */ if (!ret) @@ -278,14 +268,18 @@ concat_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) return err; } - devops.len = ops->len - ops->retlen; - if (!devops.len) - return ret; - - if (devops.datbuf) + if (devops.datbuf) { + devops.len = ops->len - ops->retlen; + if (!devops.len) + return ret; devops.datbuf += devops.retlen; - if (devops.oobbuf) - devops.oobbuf += devops.ooblen; + } + if (devops.oobbuf) { + devops.ooblen = ops->ooblen - ops->oobretlen; + if (!devops.ooblen) + return ret; + devops.oobbuf += ops->oobretlen; + } from = 0; } @@ -302,7 +296,7 @@ concat_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops) if (!(mtd->flags & MTD_WRITEABLE)) return -EROFS; - ops->retlen = 0; + ops->retlen = ops->oobretlen = 0; for (i = 0; i < concat->num_subdev; i++) { struct mtd_info *subdev = concat->subdev[i]; @@ -316,19 +310,23 @@ concat_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops) if (to + devops.len > subdev->size) devops.len = subdev->size - to; - err = subdev->write_oob(subdev, to, &devops); - ops->retlen += devops.retlen; + err = mtd_write_oob(subdev, to, &devops); + ops->retlen += devops.oobretlen; if (err) return err; - devops.len = ops->len - ops->retlen; - if (!devops.len) - return 0; - - if (devops.datbuf) + if (devops.datbuf) { + devops.len = ops->len - ops->retlen; + if (!devops.len) + return 0; devops.datbuf += devops.retlen; - if (devops.oobbuf) - devops.oobbuf += devops.ooblen; + } + if (devops.oobbuf) { + devops.ooblen = ops->ooblen - ops->oobretlen; + if (!devops.ooblen) + return 0; + devops.oobbuf += devops.oobretlen; + } to = 0; } return -EINVAL; @@ -358,7 +356,7 @@ static int concat_dev_erase(struct mtd_info *mtd, struct erase_info *erase) * FIXME: Allow INTERRUPTIBLE. Which means * not having the wait_queue head on the stack. */ - err = mtd->erase(mtd, erase); + err = mtd_erase(mtd, erase); if (!err) { set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&waitq, &wait); @@ -378,18 +376,9 @@ static int concat_erase(struct mtd_info *mtd, struct erase_info *instr) struct mtd_concat *concat = CONCAT(mtd); struct mtd_info *subdev; int i, err; - u_int32_t length, offset = 0; + uint64_t length, offset = 0; struct erase_info *erase; - if (!(mtd->flags & MTD_WRITEABLE)) - return -EROFS; - - if (instr->addr > concat->mtd.size) - return -EINVAL; - - if (instr->len + instr->addr > concat->mtd.size) - return -EINVAL; - /* * Check for proper erase block alignment of the to-be-erased area. * It is easier to do this based on the super device's erase @@ -419,7 +408,7 @@ static int concat_erase(struct mtd_info *mtd, struct erase_info *instr) * to-be-erased area begins. Verify that the starting * offset is aligned to this region's erase size: */ - if (instr->addr & (erase_regions[i].erasesize - 1)) + if (i < 0 || instr->addr & (erase_regions[i].erasesize - 1)) return -EINVAL; /* @@ -432,13 +421,11 @@ static int concat_erase(struct mtd_info *mtd, struct erase_info *instr) /* * check if the ending offset is aligned to this region's erase size */ - if ((instr->addr + instr->len) & (erase_regions[i].erasesize - - 1)) + if (i < 0 || ((instr->addr + instr->len) & + (erase_regions[i].erasesize - 1))) return -EINVAL; } - instr->fail_addr = 0xffffffff; - /* make a local copy of instr to avoid modifying the caller's struct */ erase = kmalloc(sizeof (struct erase_info), GFP_KERNEL); @@ -477,16 +464,12 @@ static int concat_erase(struct mtd_info *mtd, struct erase_info *instr) else erase->len = length; - if (!(subdev->flags & MTD_WRITEABLE)) { - err = -EROFS; - break; - } length -= erase->len; if ((err = concat_dev_erase(subdev, erase))) { /* sanity check: should never happen since * block alignment has been checked above */ BUG_ON(err == -EINVAL); - if (erase->fail_addr != 0xffffffff) + if (erase->fail_addr != MTD_FAIL_ADDR_UNKNOWN) instr->fail_addr = erase->fail_addr + offset; break; } @@ -511,17 +494,14 @@ static int concat_erase(struct mtd_info *mtd, struct erase_info *instr) return 0; } -static int concat_lock(struct mtd_info *mtd, loff_t ofs, size_t len) +static int concat_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) { struct mtd_concat *concat = CONCAT(mtd); int i, err = -EINVAL; - if ((len + ofs) > mtd->size) - return -EINVAL; - for (i = 0; i < concat->num_subdev; i++) { struct mtd_info *subdev = concat->subdev[i]; - size_t size; + uint64_t size; if (ofs >= subdev->size) { size = 0; @@ -533,8 +513,7 @@ static int concat_lock(struct mtd_info *mtd, loff_t ofs, size_t len) else size = len; - err = subdev->lock(subdev, ofs, size); - + err = mtd_lock(subdev, ofs, size); if (err) break; @@ -549,17 +528,14 @@ static int concat_lock(struct mtd_info *mtd, loff_t ofs, size_t len) return err; } -static int concat_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) +static int concat_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) { struct mtd_concat *concat = CONCAT(mtd); int i, err = 0; - if ((len + ofs) > mtd->size) - return -EINVAL; - for (i = 0; i < concat->num_subdev; i++) { struct mtd_info *subdev = concat->subdev[i]; - size_t size; + uint64_t size; if (ofs >= subdev->size) { size = 0; @@ -571,8 +547,7 @@ static int concat_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) else size = len; - err = subdev->unlock(subdev, ofs, size); - + err = mtd_unlock(subdev, ofs, size); if (err) break; @@ -594,7 +569,7 @@ static void concat_sync(struct mtd_info *mtd) for (i = 0; i < concat->num_subdev; i++) { struct mtd_info *subdev = concat->subdev[i]; - subdev->sync(subdev); + mtd_sync(subdev); } } @@ -605,7 +580,7 @@ static int concat_suspend(struct mtd_info *mtd) for (i = 0; i < concat->num_subdev; i++) { struct mtd_info *subdev = concat->subdev[i]; - if ((rc = subdev->suspend(subdev)) < 0) + if ((rc = mtd_suspend(subdev)) < 0) return rc; } return rc; @@ -618,7 +593,7 @@ static void concat_resume(struct mtd_info *mtd) for (i = 0; i < concat->num_subdev; i++) { struct mtd_info *subdev = concat->subdev[i]; - subdev->resume(subdev); + mtd_resume(subdev); } } @@ -627,12 +602,9 @@ static int concat_block_isbad(struct mtd_info *mtd, loff_t ofs) struct mtd_concat *concat = CONCAT(mtd); int i, res = 0; - if (!concat->subdev[0]->block_isbad) + if (!mtd_can_have_bb(concat->subdev[0])) return res; - if (ofs > mtd->size) - return -EINVAL; - for (i = 0; i < concat->num_subdev; i++) { struct mtd_info *subdev = concat->subdev[i]; @@ -641,7 +613,7 @@ static int concat_block_isbad(struct mtd_info *mtd, loff_t ofs) continue; } - res = subdev->block_isbad(subdev, ofs); + res = mtd_block_isbad(subdev, ofs); break; } @@ -653,12 +625,6 @@ static int concat_block_markbad(struct mtd_info *mtd, loff_t ofs) struct mtd_concat *concat = CONCAT(mtd); int i, err = -EINVAL; - if (!concat->subdev[0]->block_markbad) - return 0; - - if (ofs > mtd->size) - return -EINVAL; - for (i = 0; i < concat->num_subdev; i++) { struct mtd_info *subdev = concat->subdev[i]; @@ -667,7 +633,7 @@ static int concat_block_markbad(struct mtd_info *mtd, loff_t ofs) continue; } - err = subdev->block_markbad(subdev, ofs); + err = mtd_block_markbad(subdev, ofs); if (!err) mtd->ecc_stats.badblocks++; break; @@ -677,6 +643,32 @@ 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; + } + + return mtd_get_unmapped_area(subdev, len, offset, flags); + } + + 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_ @@ -684,13 +676,14 @@ static int concat_block_markbad(struct mtd_info *mtd, loff_t ofs) */ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to concatenate */ int num_devs, /* number of subdevices */ - char *name) + const char *name) { /* name for the new device */ int i; size_t size; struct mtd_concat *concat; - u_int32_t max_erasesize, curr_erasesize; + uint32_t max_erasesize, curr_erasesize; int num_erase_region; + int max_writebufsize = 0; printk(KERN_NOTICE "Concatenating MTD devices:\n"); for (i = 0; i < num_devs; i++) @@ -699,41 +692,48 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c /* allocate the device structure */ size = SIZEOF_STRUCT_MTD_CONCAT(num_devs); - concat = kmalloc(size, GFP_KERNEL); + concat = kzalloc(size, GFP_KERNEL); if (!concat) { printk ("memory allocation error while creating concatenated device \"%s\"\n", name); return NULL; } - memset(concat, 0, size); concat->subdev = (struct mtd_info **) (concat + 1); /* * Set up the new "super" device's MTD object structure, check for - * incompatibilites between the subdevices. + * incompatibilities between the subdevices. */ concat->mtd.type = subdev[0]->type; concat->mtd.flags = subdev[0]->flags; concat->mtd.size = subdev[0]->size; concat->mtd.erasesize = subdev[0]->erasesize; concat->mtd.writesize = subdev[0]->writesize; + + for (i = 0; i < num_devs; i++) + if (max_writebufsize < subdev[i]->writebufsize) + max_writebufsize = subdev[i]->writebufsize; + concat->mtd.writebufsize = max_writebufsize; + + concat->mtd.subpage_sft = subdev[0]->subpage_sft; concat->mtd.oobsize = subdev[0]->oobsize; - concat->mtd.ecctype = subdev[0]->ecctype; - concat->mtd.eccsize = subdev[0]->eccsize; - if (subdev[0]->writev) - concat->mtd.writev = concat_writev; - if (subdev[0]->read_oob) - concat->mtd.read_oob = concat_read_oob; - if (subdev[0]->write_oob) - concat->mtd.write_oob = concat_write_oob; - if (subdev[0]->block_isbad) - concat->mtd.block_isbad = concat_block_isbad; - if (subdev[0]->block_markbad) - concat->mtd.block_markbad = concat_block_markbad; + concat->mtd.oobavail = subdev[0]->oobavail; + if (subdev[0]->_writev) + concat->mtd._writev = concat_writev; + if (subdev[0]->_read_oob) + concat->mtd._read_oob = concat_read_oob; + if (subdev[0]->_write_oob) + concat->mtd._write_oob = concat_write_oob; + if (subdev[0]->_block_isbad) + concat->mtd._block_isbad = concat_block_isbad; + if (subdev[0]->_block_markbad) + concat->mtd._block_markbad = concat_block_markbad; 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++) { @@ -760,15 +760,23 @@ 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; if (concat->mtd.writesize != subdev[i]->writesize || + concat->mtd.subpage_sft != subdev[i]->subpage_sft || concat->mtd.oobsize != subdev[i]->oobsize || - concat->mtd.ecctype != subdev[i]->ecctype || - concat->mtd.eccsize != subdev[i]->eccsize || - !concat->mtd.read_oob != !subdev[i]->read_oob || - !concat->mtd.write_oob != !subdev[i]->write_oob) { + !concat->mtd._read_oob != !subdev[i]->_read_oob || + !concat->mtd._write_oob != !subdev[i]->_write_oob) { kfree(concat); printk("Incompatible OOB or ECC data on \"%s\"\n", subdev[i]->name); @@ -783,14 +791,15 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c concat->num_subdev = num_devs; concat->mtd.name = name; - concat->mtd.erase = concat_erase; - concat->mtd.read = concat_read; - concat->mtd.write = concat_write; - concat->mtd.sync = concat_sync; - concat->mtd.lock = concat_lock; - concat->mtd.unlock = concat_unlock; - concat->mtd.suspend = concat_suspend; - concat->mtd.resume = concat_resume; + concat->mtd._erase = concat_erase; + concat->mtd._read = concat_read; + concat->mtd._write = concat_write; + concat->mtd._sync = concat_sync; + concat->mtd._lock = concat_lock; + 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: @@ -837,12 +846,14 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c concat->mtd.erasesize = curr_erasesize; concat->mtd.numeraseregions = 0; } else { + uint64_t tmp64; + /* * erase block size varies across the subdevices: allocate * space to store the data describing the variable erase regions */ struct mtd_erase_region_info *erase_region_p; - u_int32_t begin, position; + uint64_t begin, position; concat->mtd.erasesize = max_erasesize; concat->mtd.numeraseregions = num_erase_region; @@ -874,8 +885,9 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c erase_region_p->offset = begin; erase_region_p->erasesize = curr_erasesize; - erase_region_p->numblocks = - (position - begin) / curr_erasesize; + tmp64 = position - begin; + do_div(tmp64, curr_erasesize); + erase_region_p->numblocks = tmp64; begin = position; curr_erasesize = subdev[i]->erasesize; @@ -892,9 +904,9 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c erase_region_p->offset = begin; erase_region_p->erasesize = curr_erasesize; - erase_region_p->numblocks = - (position - - begin) / curr_erasesize; + tmp64 = position - begin; + do_div(tmp64, curr_erasesize); + erase_region_p->numblocks = tmp64; begin = position; curr_erasesize = @@ -904,14 +916,16 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c } position += subdev[i]->eraseregions[j]. - numblocks * curr_erasesize; + numblocks * (uint64_t)curr_erasesize; } } } /* Now write the final entry */ erase_region_p->offset = begin; erase_region_p->erasesize = curr_erasesize; - erase_region_p->numblocks = (position - begin) / curr_erasesize; + tmp64 = position - begin; + do_div(tmp64, curr_erasesize); + erase_region_p->numblocks = tmp64; } return &concat->mtd; diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index c4d26de7434..d201feeb3ca 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -1,37 +1,326 @@ /* - * $Id: mtdcore.c,v 1.47 2005/11/07 11:14:20 gleixner Exp $ - * * Core registration and callback routines for MTD * drivers and users. * + * Copyright © 1999-2010 David Woodhouse <dwmw2@infradead.org> + * Copyright © 2006 Red Hat UK Limited + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * */ #include <linux/module.h> #include <linux/kernel.h> -#include <linux/sched.h> #include <linux/ptrace.h> -#include <linux/slab.h> +#include <linux/seq_file.h> #include <linux/string.h> #include <linux/timer.h> #include <linux/major.h> #include <linux/fs.h> +#include <linux/err.h> #include <linux/ioctl.h> #include <linux/init.h> -#include <linux/mtd/compatmac.h> #include <linux/proc_fs.h> +#include <linux/idr.h> +#include <linux/backing-dev.h> +#include <linux/gfp.h> +#include <linux/slab.h> #include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> + +#include "mtdcore.h" + +/* + * backing device capabilities for non-mappable devices (such as NAND flash) + * - permits private mappings, copies are taken of the data + */ +static 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 + */ +static 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 + */ +static 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), +}; + +static int mtd_cls_suspend(struct device *dev, pm_message_t state); +static int mtd_cls_resume(struct device *dev); + +static struct class mtd_class = { + .name = "mtd", + .owner = THIS_MODULE, + .suspend = mtd_cls_suspend, + .resume = mtd_cls_resume, +}; + +static DEFINE_IDR(mtd_idr); /* These are exported solely for the purpose of mtd_blkdevs.c. You should not use them for _anything_ else */ DEFINE_MUTEX(mtd_table_mutex); -struct mtd_info *mtd_table[MAX_MTD_DEVICES]; - EXPORT_SYMBOL_GPL(mtd_table_mutex); -EXPORT_SYMBOL_GPL(mtd_table); + +struct mtd_info *__mtd_next_device(int i) +{ + return idr_get_next(&mtd_idr, &i); +} +EXPORT_SYMBOL_GPL(__mtd_next_device); static LIST_HEAD(mtd_notifiers); + +#define MTD_DEVT(index) MKDEV(MTD_CHAR_MAJOR, (index)*2) + +/* 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 __maybe_unused *mtd = dev_get_drvdata(dev); + dev_t index = MTD_DEVT(mtd->index); + + /* remove /dev/mtdXro node if needed */ + if (index) + device_destroy(&mtd_class, index + 1); +} + +static int mtd_cls_suspend(struct device *dev, pm_message_t state) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + + return mtd ? mtd_suspend(mtd) : 0; +} + +static int mtd_cls_resume(struct device *dev) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + + if (mtd) + mtd_resume(mtd); + return 0; +} + +static ssize_t mtd_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mtd_info *mtd = dev_get_drvdata(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; + case MTD_MLCNANDFLASH: + type = "mlc-nand"; + 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_get_drvdata(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_get_drvdata(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_get_drvdata(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_get_drvdata(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_subpagesize_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + unsigned int subpagesize = mtd->writesize >> mtd->subpage_sft; + + return snprintf(buf, PAGE_SIZE, "%u\n", subpagesize); + +} +static DEVICE_ATTR(subpagesize, S_IRUGO, mtd_subpagesize_show, NULL); + +static ssize_t mtd_oobsize_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mtd_info *mtd = dev_get_drvdata(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_get_drvdata(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_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", mtd->name); + +} +static DEVICE_ATTR(name, S_IRUGO, mtd_name_show, NULL); + +static ssize_t mtd_ecc_strength_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", mtd->ecc_strength); +} +static DEVICE_ATTR(ecc_strength, S_IRUGO, mtd_ecc_strength_show, NULL); + +static ssize_t mtd_bitflip_threshold_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", mtd->bitflip_threshold); +} + +static ssize_t mtd_bitflip_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + unsigned int bitflip_threshold; + int retval; + + retval = kstrtouint(buf, 0, &bitflip_threshold); + if (retval) + return retval; + + mtd->bitflip_threshold = bitflip_threshold; + return count; +} +static DEVICE_ATTR(bitflip_threshold, S_IRUGO | S_IWUSR, + mtd_bitflip_threshold_show, + mtd_bitflip_threshold_store); + +static ssize_t mtd_ecc_step_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", mtd->ecc_step_size); + +} +static DEVICE_ATTR(ecc_step_size, S_IRUGO, mtd_ecc_step_size_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_subpagesize.attr, + &dev_attr_oobsize.attr, + &dev_attr_numeraseregions.attr, + &dev_attr_name.attr, + &dev_attr_ecc_strength.attr, + &dev_attr_ecc_step_size.attr, + &dev_attr_bitflip_threshold.attr, + NULL, +}; +ATTRIBUTE_GROUPS(mtd); + +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 @@ -39,51 +328,97 @@ static LIST_HEAD(mtd_notifiers); * Add a device to the list of MTD devices present in the system, and * 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) + * if there is insufficient memory or a sysfs error. */ int add_mtd_device(struct mtd_info *mtd) { - int i; + struct mtd_notifier *not; + int i, error; + + 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); - for (i=0; i < MAX_MTD_DEVICES; i++) - if (!mtd_table[i]) { - struct list_head *this; - - mtd_table[i] = mtd; - mtd->index = i; - mtd->usecount = 0; - - /* Some chips always power up locked. Unlock them now */ - if ((mtd->flags & MTD_WRITEABLE) - && (mtd->flags & MTD_STUPID_LOCK) && mtd->unlock) { - if (mtd->unlock(mtd, 0, mtd->size)) - printk(KERN_WARNING - "%s: unlock failed, " - "writes may not work\n", - mtd->name); - } + i = idr_alloc(&mtd_idr, mtd, 0, 0, GFP_KERNEL); + if (i < 0) + goto fail_locked; + + mtd->index = i; + mtd->usecount = 0; + + /* default value if not set by driver */ + if (mtd->bitflip_threshold == 0) + mtd->bitflip_threshold = mtd->ecc_strength; + + if (is_power_of_2(mtd->erasesize)) + mtd->erasesize_shift = ffs(mtd->erasesize) - 1; + else + mtd->erasesize_shift = 0; + + if (is_power_of_2(mtd->writesize)) + mtd->writesize_shift = ffs(mtd->writesize) - 1; + else + mtd->writesize_shift = 0; + + mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1; + mtd->writesize_mask = (1 << mtd->writesize_shift) - 1; + + /* Some chips always power up locked. Unlock them now */ + if ((mtd->flags & MTD_WRITEABLE) && (mtd->flags & MTD_POWERUP_LOCK)) { + error = mtd_unlock(mtd, 0, mtd->size); + if (error && error != -EOPNOTSUPP) + printk(KERN_WARNING + "%s: unlock failed, writes may not work\n", + mtd->name); + } - 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 */ - list_for_each(this, &mtd_notifiers) { - struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list); - not->add(mtd); - } + /* 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); + dev_set_drvdata(&mtd->dev, mtd); + if (device_register(&mtd->dev) != 0) + goto fail_added; + + if (MTD_DEVT(i)) + device_create(&mtd_class, mtd->dev.parent, + MTD_DEVT(i) + 1, + NULL, "mtd%dro", i); + + pr_debug("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 */ + list_for_each_entry(not, &mtd_notifiers, list) + not->add(mtd); - mutex_unlock(&mtd_table_mutex); - /* We _know_ we aren't being removed, because - our caller is still holding us here. So none - of this try_ nonsense, and no bitching about it - either. :) */ - __module_get(THIS_MODULE); - return 0; - } + mutex_unlock(&mtd_table_mutex); + /* We _know_ we aren't being removed, because + our caller is still holding us here. So none + of this try_ nonsense, and no bitching about it + either. :) */ + __module_get(THIS_MODULE); + return 0; +fail_added: + idr_remove(&mtd_idr, i); +fail_locked: mutex_unlock(&mtd_table_mutex); return 1; } @@ -98,39 +433,122 @@ int add_mtd_device(struct mtd_info *mtd) * if the requested device does not appear to be present in the list. */ -int del_mtd_device (struct mtd_info *mtd) +int del_mtd_device(struct mtd_info *mtd) { int ret; + struct mtd_notifier *not; mutex_lock(&mtd_table_mutex); - if (mtd_table[mtd->index] != mtd) { + if (idr_find(&mtd_idr, mtd->index) != mtd) { ret = -ENODEV; - } else if (mtd->usecount) { + goto out_error; + } + + /* 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) + not->remove(mtd); + + if (mtd->usecount) { printk(KERN_NOTICE "Removing MTD device #%d (%s) with use count %d\n", mtd->index, mtd->name, mtd->usecount); ret = -EBUSY; } else { - struct list_head *this; - - /* No need to get a refcount on the module containing - the notifier, since we hold the mtd_table_mutex */ - list_for_each(this, &mtd_notifiers) { - struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list); - not->remove(mtd); - } + device_unregister(&mtd->dev); - mtd_table[mtd->index] = NULL; + idr_remove(&mtd_idr, mtd->index); module_put(THIS_MODULE); ret = 0; } +out_error: mutex_unlock(&mtd_table_mutex); return ret; } /** + * mtd_device_parse_register - parse partitions and register an MTD device. + * + * @mtd: the MTD device to register + * @types: the list of MTD partition probes to try, see + * 'parse_mtd_partitions()' for more information + * @parser_data: MTD partition parser-specific data + * @parts: fallback partition information to register, if parsing fails; + * only valid if %nr_parts > %0 + * @nr_parts: the number of partitions in parts, if zero then the full + * MTD device is registered if no partition info is found + * + * This function aggregates MTD partitions parsing (done by + * 'parse_mtd_partitions()') and MTD device and partitions registering. It + * basically follows the most common pattern found in many MTD drivers: + * + * * It first tries to probe partitions on MTD device @mtd using parsers + * specified in @types (if @types is %NULL, then the default list of parsers + * is used, see 'parse_mtd_partitions()' for more information). If none are + * found this functions tries to fallback to information specified in + * @parts/@nr_parts. + * * If any partitioning info was found, this function registers the found + * partitions. + * * If no partitions were found this function just registers the MTD device + * @mtd and exits. + * + * Returns zero in case of success and a negative error code in case of failure. + */ +int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types, + struct mtd_part_parser_data *parser_data, + const struct mtd_partition *parts, + int nr_parts) +{ + int err; + struct mtd_partition *real_parts; + + err = parse_mtd_partitions(mtd, types, &real_parts, parser_data); + if (err <= 0 && nr_parts && parts) { + real_parts = kmemdup(parts, sizeof(*parts) * nr_parts, + GFP_KERNEL); + if (!real_parts) + err = -ENOMEM; + else + err = nr_parts; + } + + if (err > 0) { + err = add_mtd_partitions(mtd, real_parts, err); + kfree(real_parts); + } else if (err == 0) { + err = add_mtd_device(mtd); + if (err == 1) + err = -ENODEV; + } + + return err; +} +EXPORT_SYMBOL_GPL(mtd_device_parse_register); + +/** + * mtd_device_unregister - unregister an existing MTD device. + * + * @master: the MTD device to unregister. This will unregister both the master + * and any partitions if registered. + */ +int mtd_device_unregister(struct mtd_info *master) +{ + int err; + + err = del_mtd_partitions(master); + if (err) + return err; + + if (!device_is_registered(&master->dev)) + return 0; + + return del_mtd_device(master); +} +EXPORT_SYMBOL_GPL(mtd_device_unregister); + +/** * register_mtd_user - register a 'user' of MTD devices. * @new: pointer to notifier info structure * @@ -138,23 +556,22 @@ int del_mtd_device (struct mtd_info *mtd) * or removal of MTD devices. Causes the 'add' callback to be immediately * invoked for each MTD device currently present in the system. */ - void register_mtd_user (struct mtd_notifier *new) { - int i; + struct mtd_info *mtd; mutex_lock(&mtd_table_mutex); list_add(&new->list, &mtd_notifiers); - __module_get(THIS_MODULE); + __module_get(THIS_MODULE); - for (i=0; i< MAX_MTD_DEVICES; i++) - if (mtd_table[i]) - new->add(mtd_table[i]); + mtd_for_each_device(mtd) + new->add(mtd); mutex_unlock(&mtd_table_mutex); } +EXPORT_SYMBOL_GPL(register_mtd_user); /** * unregister_mtd_user - unregister a 'user' of MTD devices. @@ -165,24 +582,22 @@ void register_mtd_user (struct mtd_notifier *new) * 'remove' callback to be immediately invoked for each MTD device * currently present in the system. */ - int unregister_mtd_user (struct mtd_notifier *old) { - int i; + struct mtd_info *mtd; mutex_lock(&mtd_table_mutex); module_put(THIS_MODULE); - for (i=0; i< MAX_MTD_DEVICES; i++) - if (mtd_table[i]) - old->remove(mtd_table[i]); + mtd_for_each_device(mtd) + old->remove(mtd); list_del(&old->list); mutex_unlock(&mtd_table_mutex); return 0; } - +EXPORT_SYMBOL_GPL(unregister_mtd_user); /** * get_mtd_device - obtain a validated handle for an MTD device @@ -192,157 +607,632 @@ int unregister_mtd_user (struct mtd_notifier *old) * Given a number and NULL address, return the num'th entry in the device * table, if any. Given an address and num == -1, search the device table * for a device with that address and return if it's still present. Given - * both, return the num'th driver only if its address matches. Return NULL - * if not. + * both, return the num'th driver only if its address matches. Return + * error code if not. */ - struct mtd_info *get_mtd_device(struct mtd_info *mtd, int num) { - struct mtd_info *ret = NULL; - int i; + struct mtd_info *ret = NULL, *other; + int err = -ENODEV; mutex_lock(&mtd_table_mutex); if (num == -1) { - for (i=0; i< MAX_MTD_DEVICES; i++) - if (mtd_table[i] == mtd) - ret = mtd_table[i]; - } else if (num < MAX_MTD_DEVICES) { - ret = mtd_table[num]; + mtd_for_each_device(other) { + if (other == mtd) { + ret = mtd; + break; + } + } + } else if (num >= 0) { + ret = idr_find(&mtd_idr, num); if (mtd && mtd != ret) ret = NULL; } - if (ret && !try_module_get(ret->owner)) - ret = NULL; - - if (ret) - ret->usecount++; + if (!ret) { + ret = ERR_PTR(err); + goto out; + } + err = __get_mtd_device(ret); + if (err) + ret = ERR_PTR(err); +out: mutex_unlock(&mtd_table_mutex); return ret; } +EXPORT_SYMBOL_GPL(get_mtd_device); -void put_mtd_device(struct mtd_info *mtd) + +int __get_mtd_device(struct mtd_info *mtd) +{ + int err; + + if (!try_module_get(mtd->owner)) + return -ENODEV; + + if (mtd->_get_device) { + err = mtd->_get_device(mtd); + + if (err) { + module_put(mtd->owner); + return err; + } + } + mtd->usecount++; + return 0; +} +EXPORT_SYMBOL_GPL(__get_mtd_device); + +/** + * get_mtd_device_nm - obtain a validated handle for an MTD device by + * device name + * @name: MTD device name to open + * + * This function returns MTD device description structure in case of + * success and an error code in case of failure. + */ +struct mtd_info *get_mtd_device_nm(const char *name) { - int c; + int err = -ENODEV; + struct mtd_info *mtd = NULL, *other; + + mutex_lock(&mtd_table_mutex); + + mtd_for_each_device(other) { + if (!strcmp(name, other->name)) { + mtd = other; + break; + } + } + + if (!mtd) + goto out_unlock; + + err = __get_mtd_device(mtd); + if (err) + goto out_unlock; + mutex_unlock(&mtd_table_mutex); + return mtd; + +out_unlock: + mutex_unlock(&mtd_table_mutex); + return ERR_PTR(err); +} +EXPORT_SYMBOL_GPL(get_mtd_device_nm); + +void put_mtd_device(struct mtd_info *mtd) +{ mutex_lock(&mtd_table_mutex); - c = --mtd->usecount; + __put_mtd_device(mtd); mutex_unlock(&mtd_table_mutex); - BUG_ON(c < 0); + +} +EXPORT_SYMBOL_GPL(put_mtd_device); + +void __put_mtd_device(struct mtd_info *mtd) +{ + --mtd->usecount; + BUG_ON(mtd->usecount < 0); + + if (mtd->_put_device) + mtd->_put_device(mtd); module_put(mtd->owner); } +EXPORT_SYMBOL_GPL(__put_mtd_device); + +/* + * Erase is an asynchronous operation. Device drivers are supposed + * to call instr->callback() whenever the operation completes, even + * if it completes with a failure. + * Callers are supposed to pass a callback function and wait for it + * to be called before writing to the block. + */ +int mtd_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + if (instr->addr > mtd->size || instr->len > mtd->size - instr->addr) + return -EINVAL; + if (!(mtd->flags & MTD_WRITEABLE)) + return -EROFS; + instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN; + if (!instr->len) { + instr->state = MTD_ERASE_DONE; + mtd_erase_callback(instr); + return 0; + } + return mtd->_erase(mtd, instr); +} +EXPORT_SYMBOL_GPL(mtd_erase); + +/* + * This stuff for eXecute-In-Place. phys is optional and may be set to NULL. + */ +int mtd_point(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, + void **virt, resource_size_t *phys) +{ + *retlen = 0; + *virt = NULL; + if (phys) + *phys = 0; + if (!mtd->_point) + return -EOPNOTSUPP; + if (from < 0 || from > mtd->size || len > mtd->size - from) + return -EINVAL; + if (!len) + return 0; + return mtd->_point(mtd, from, len, retlen, virt, phys); +} +EXPORT_SYMBOL_GPL(mtd_point); + +/* We probably shouldn't allow XIP if the unpoint isn't a NULL */ +int mtd_unpoint(struct mtd_info *mtd, loff_t from, size_t len) +{ + if (!mtd->_point) + return -EOPNOTSUPP; + if (from < 0 || from > mtd->size || len > mtd->size - from) + return -EINVAL; + if (!len) + return 0; + return mtd->_unpoint(mtd, from, len); +} +EXPORT_SYMBOL_GPL(mtd_unpoint); + +/* + * 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 + */ +unsigned long mtd_get_unmapped_area(struct mtd_info *mtd, unsigned long len, + unsigned long offset, unsigned long flags) +{ + if (!mtd->_get_unmapped_area) + return -EOPNOTSUPP; + if (offset > mtd->size || len > mtd->size - offset) + return -EINVAL; + return mtd->_get_unmapped_area(mtd, len, offset, flags); +} +EXPORT_SYMBOL_GPL(mtd_get_unmapped_area); + +int mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, + u_char *buf) +{ + int ret_code; + *retlen = 0; + if (from < 0 || from > mtd->size || len > mtd->size - from) + return -EINVAL; + if (!len) + return 0; + + /* + * In the absence of an error, drivers return a non-negative integer + * representing the maximum number of bitflips that were corrected on + * any one ecc region (if applicable; zero otherwise). + */ + ret_code = mtd->_read(mtd, from, len, retlen, buf); + if (unlikely(ret_code < 0)) + return ret_code; + if (mtd->ecc_strength == 0) + return 0; /* device lacks ecc */ + return ret_code >= mtd->bitflip_threshold ? -EUCLEAN : 0; +} +EXPORT_SYMBOL_GPL(mtd_read); -/* default_mtd_writev - default mtd writev method for MTD devices that - * dont implement their own +int mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, + const u_char *buf) +{ + *retlen = 0; + if (to < 0 || to > mtd->size || len > mtd->size - to) + return -EINVAL; + if (!mtd->_write || !(mtd->flags & MTD_WRITEABLE)) + return -EROFS; + if (!len) + return 0; + return mtd->_write(mtd, to, len, retlen, buf); +} +EXPORT_SYMBOL_GPL(mtd_write); + +/* + * In blackbox flight recorder like scenarios we want to make successful writes + * in interrupt context. panic_write() is only intended to be called when its + * known the kernel is about to panic and we need the write to succeed. Since + * the kernel is not going to be running for much longer, this function can + * break locks and delay to ensure the write succeeds (but not sleep). */ +int mtd_panic_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, + const u_char *buf) +{ + *retlen = 0; + if (!mtd->_panic_write) + return -EOPNOTSUPP; + if (to < 0 || to > mtd->size || len > mtd->size - to) + return -EINVAL; + if (!(mtd->flags & MTD_WRITEABLE)) + return -EROFS; + if (!len) + return 0; + return mtd->_panic_write(mtd, to, len, retlen, buf); +} +EXPORT_SYMBOL_GPL(mtd_panic_write); + +int mtd_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) +{ + int ret_code; + ops->retlen = ops->oobretlen = 0; + if (!mtd->_read_oob) + return -EOPNOTSUPP; + /* + * In cases where ops->datbuf != NULL, mtd->_read_oob() has semantics + * similar to mtd->_read(), returning a non-negative integer + * representing max bitflips. In other cases, mtd->_read_oob() may + * return -EUCLEAN. In all cases, perform similar logic to mtd_read(). + */ + ret_code = mtd->_read_oob(mtd, from, ops); + if (unlikely(ret_code < 0)) + return ret_code; + if (mtd->ecc_strength == 0) + return 0; /* device lacks ecc */ + return ret_code >= mtd->bitflip_threshold ? -EUCLEAN : 0; +} +EXPORT_SYMBOL_GPL(mtd_read_oob); + +/* + * Method to access the protection register area, present in some flash + * devices. The user data is one time programmable but the factory data is read + * only. + */ +int mtd_get_fact_prot_info(struct mtd_info *mtd, size_t len, size_t *retlen, + struct otp_info *buf) +{ + if (!mtd->_get_fact_prot_info) + return -EOPNOTSUPP; + if (!len) + return 0; + return mtd->_get_fact_prot_info(mtd, len, retlen, buf); +} +EXPORT_SYMBOL_GPL(mtd_get_fact_prot_info); + +int mtd_read_fact_prot_reg(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + *retlen = 0; + if (!mtd->_read_fact_prot_reg) + return -EOPNOTSUPP; + if (!len) + return 0; + return mtd->_read_fact_prot_reg(mtd, from, len, retlen, buf); +} +EXPORT_SYMBOL_GPL(mtd_read_fact_prot_reg); -int default_mtd_writev(struct mtd_info *mtd, const struct kvec *vecs, - unsigned long count, loff_t to, size_t *retlen) +int mtd_get_user_prot_info(struct mtd_info *mtd, size_t len, size_t *retlen, + struct otp_info *buf) +{ + if (!mtd->_get_user_prot_info) + return -EOPNOTSUPP; + if (!len) + return 0; + return mtd->_get_user_prot_info(mtd, len, retlen, buf); +} +EXPORT_SYMBOL_GPL(mtd_get_user_prot_info); + +int mtd_read_user_prot_reg(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + *retlen = 0; + if (!mtd->_read_user_prot_reg) + return -EOPNOTSUPP; + if (!len) + return 0; + return mtd->_read_user_prot_reg(mtd, from, len, retlen, buf); +} +EXPORT_SYMBOL_GPL(mtd_read_user_prot_reg); + +int mtd_write_user_prot_reg(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, u_char *buf) +{ + int ret; + + *retlen = 0; + if (!mtd->_write_user_prot_reg) + return -EOPNOTSUPP; + if (!len) + return 0; + ret = mtd->_write_user_prot_reg(mtd, to, len, retlen, buf); + if (ret) + return ret; + + /* + * If no data could be written at all, we are out of memory and + * must return -ENOSPC. + */ + return (*retlen) ? 0 : -ENOSPC; +} +EXPORT_SYMBOL_GPL(mtd_write_user_prot_reg); + +int mtd_lock_user_prot_reg(struct mtd_info *mtd, loff_t from, size_t len) +{ + if (!mtd->_lock_user_prot_reg) + return -EOPNOTSUPP; + if (!len) + return 0; + return mtd->_lock_user_prot_reg(mtd, from, len); +} +EXPORT_SYMBOL_GPL(mtd_lock_user_prot_reg); + +/* Chip-supported device locking */ +int mtd_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + if (!mtd->_lock) + return -EOPNOTSUPP; + if (ofs < 0 || ofs > mtd->size || len > mtd->size - ofs) + return -EINVAL; + if (!len) + return 0; + return mtd->_lock(mtd, ofs, len); +} +EXPORT_SYMBOL_GPL(mtd_lock); + +int mtd_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + if (!mtd->_unlock) + return -EOPNOTSUPP; + if (ofs < 0 || ofs > mtd->size || len > mtd->size - ofs) + return -EINVAL; + if (!len) + return 0; + return mtd->_unlock(mtd, ofs, len); +} +EXPORT_SYMBOL_GPL(mtd_unlock); + +int mtd_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + if (!mtd->_is_locked) + return -EOPNOTSUPP; + if (ofs < 0 || ofs > mtd->size || len > mtd->size - ofs) + return -EINVAL; + if (!len) + return 0; + return mtd->_is_locked(mtd, ofs, len); +} +EXPORT_SYMBOL_GPL(mtd_is_locked); + +int mtd_block_isbad(struct mtd_info *mtd, loff_t ofs) +{ + if (!mtd->_block_isbad) + return 0; + if (ofs < 0 || ofs > mtd->size) + return -EINVAL; + return mtd->_block_isbad(mtd, ofs); +} +EXPORT_SYMBOL_GPL(mtd_block_isbad); + +int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ + if (!mtd->_block_markbad) + return -EOPNOTSUPP; + if (ofs < 0 || ofs > mtd->size) + return -EINVAL; + if (!(mtd->flags & MTD_WRITEABLE)) + return -EROFS; + return mtd->_block_markbad(mtd, ofs); +} +EXPORT_SYMBOL_GPL(mtd_block_markbad); + +/* + * default_mtd_writev - the default writev method + * @mtd: mtd device description object pointer + * @vecs: the vectors to write + * @count: count of vectors in @vecs + * @to: the MTD device offset to write to + * @retlen: on exit contains the count of bytes written to the MTD device. + * + * This function returns zero in case of success and a negative error code in + * case of failure. + */ +static int default_mtd_writev(struct mtd_info *mtd, const struct kvec *vecs, + unsigned long count, loff_t to, size_t *retlen) { unsigned long i; size_t totlen = 0, thislen; int ret = 0; - if(!mtd->write) { - ret = -EROFS; - } else { - for (i=0; i<count; i++) { - if (!vecs[i].iov_len) - continue; - ret = mtd->write(mtd, to, vecs[i].iov_len, &thislen, vecs[i].iov_base); - totlen += thislen; - if (ret || thislen != vecs[i].iov_len) - break; - to += vecs[i].iov_len; - } + for (i = 0; i < count; i++) { + if (!vecs[i].iov_len) + continue; + ret = mtd_write(mtd, to, vecs[i].iov_len, &thislen, + vecs[i].iov_base); + totlen += thislen; + if (ret || thislen != vecs[i].iov_len) + break; + to += vecs[i].iov_len; } - if (retlen) - *retlen = totlen; + *retlen = totlen; return ret; } -EXPORT_SYMBOL(add_mtd_device); -EXPORT_SYMBOL(del_mtd_device); -EXPORT_SYMBOL(get_mtd_device); -EXPORT_SYMBOL(put_mtd_device); -EXPORT_SYMBOL(register_mtd_user); -EXPORT_SYMBOL(unregister_mtd_user); -EXPORT_SYMBOL(default_mtd_writev); +/* + * mtd_writev - the vector-based MTD write method + * @mtd: mtd device description object pointer + * @vecs: the vectors to write + * @count: count of vectors in @vecs + * @to: the MTD device offset to write to + * @retlen: on exit contains the count of bytes written to the MTD device. + * + * This function returns zero in case of success and a negative error code in + * case of failure. + */ +int mtd_writev(struct mtd_info *mtd, const struct kvec *vecs, + unsigned long count, loff_t to, size_t *retlen) +{ + *retlen = 0; + if (!(mtd->flags & MTD_WRITEABLE)) + return -EROFS; + if (!mtd->_writev) + return default_mtd_writev(mtd, vecs, count, to, retlen); + return mtd->_writev(mtd, vecs, count, to, retlen); +} +EXPORT_SYMBOL_GPL(mtd_writev); + +/** + * mtd_kmalloc_up_to - allocate a contiguous buffer up to the specified size + * @mtd: mtd device description object pointer + * @size: a pointer to the ideal or maximum size of the allocation, points + * to the actual allocation size on success. + * + * This routine attempts to allocate a contiguous kernel buffer up to + * the specified size, backing off the size of the request exponentially + * until the request succeeds or until the allocation size falls below + * the system page size. This attempts to make sure it does not adversely + * impact system performance, so when allocating more than one page, we + * ask the memory allocator to avoid re-trying, swapping, writing back + * or performing I/O. + * + * Note, this function also makes sure that the allocated buffer is aligned to + * the MTD device's min. I/O unit, i.e. the "mtd->writesize" value. + * + * This is called, for example by mtd_{read,write} and jffs2_scan_medium, + * to handle smaller (i.e. degraded) buffer allocations under low- or + * fragmented-memory situations where such reduced allocations, from a + * requested ideal, are allowed. + * + * Returns a pointer to the allocated buffer on success; otherwise, NULL. + */ +void *mtd_kmalloc_up_to(const struct mtd_info *mtd, size_t *size) +{ + gfp_t flags = __GFP_NOWARN | __GFP_WAIT | + __GFP_NORETRY | __GFP_NO_KSWAPD; + size_t min_alloc = max_t(size_t, mtd->writesize, PAGE_SIZE); + void *kbuf; + + *size = min_t(size_t, *size, KMALLOC_MAX_SIZE); + + while (*size > min_alloc) { + kbuf = kmalloc(*size, flags); + if (kbuf) + return kbuf; + + *size >>= 1; + *size = ALIGN(*size, mtd->writesize); + } + + /* + * For the last resort allocation allow 'kmalloc()' to do all sorts of + * things (write-back, dropping caches, etc) by using GFP_KERNEL. + */ + return kmalloc(*size, GFP_KERNEL); +} +EXPORT_SYMBOL_GPL(mtd_kmalloc_up_to); #ifdef CONFIG_PROC_FS /*====================================================================*/ /* Support for /proc/mtd */ -static struct proc_dir_entry *proc_mtd; - -static inline int mtd_proc_info (char *buf, int i) +static int mtd_proc_show(struct seq_file *m, void *v) { - struct mtd_info *this = mtd_table[i]; + struct mtd_info *mtd; - if (!this) - return 0; - - return sprintf(buf, "mtd%d: %8.8x %8.8x \"%s\"\n", i, this->size, - this->erasesize, this->name); + seq_puts(m, "dev: size erasesize name\n"); + mutex_lock(&mtd_table_mutex); + mtd_for_each_device(mtd) { + seq_printf(m, "mtd%d: %8.8llx %8.8x \"%s\"\n", + mtd->index, (unsigned long long)mtd->size, + mtd->erasesize, mtd->name); + } + mutex_unlock(&mtd_table_mutex); + return 0; } -static int mtd_read_proc (char *page, char **start, off_t off, int count, - int *eof, void *data_unused) +static int mtd_proc_open(struct inode *inode, struct file *file) { - int len, l, i; - off_t begin = 0; + return single_open(file, mtd_proc_show, NULL); +} - mutex_lock(&mtd_table_mutex); +static const struct file_operations mtd_proc_ops = { + .open = mtd_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +#endif /* CONFIG_PROC_FS */ - len = sprintf(page, "dev: size erasesize name\n"); - for (i=0; i< MAX_MTD_DEVICES; i++) { +/*====================================================================*/ +/* Init code */ + +static int __init mtd_bdi_init(struct backing_dev_info *bdi, const char *name) +{ + int ret; - l = mtd_proc_info(page + len, i); - len += l; - if (len+begin > off+count) - goto done; - if (len+begin < off) { - begin += len; - len = 0; - } - } + ret = bdi_init(bdi); + if (!ret) + ret = bdi_register(bdi, NULL, "%s", name); - *eof = 1; + if (ret) + bdi_destroy(bdi); -done: - mutex_unlock(&mtd_table_mutex); - if (off >= len+begin) - return 0; - *start = page + (off-begin); - return ((count < begin+len-off) ? count : begin+len-off); + return ret; } -/*====================================================================*/ -/* Init code */ +static struct proc_dir_entry *proc_mtd; static int __init init_mtd(void) { - if ((proc_mtd = create_proc_entry( "mtd", 0, NULL ))) - proc_mtd->read_proc = mtd_read_proc; + int ret; + + ret = class_register(&mtd_class); + if (ret) + goto err_reg; + + ret = mtd_bdi_init(&mtd_bdi_unmappable, "mtd-unmap"); + if (ret) + goto err_bdi1; + + ret = mtd_bdi_init(&mtd_bdi_ro_mappable, "mtd-romap"); + if (ret) + goto err_bdi2; + + ret = mtd_bdi_init(&mtd_bdi_rw_mappable, "mtd-rwmap"); + if (ret) + goto err_bdi3; + + proc_mtd = proc_create("mtd", 0, NULL, &mtd_proc_ops); + + ret = init_mtdchar(); + if (ret) + goto out_procfs; + return 0; + +out_procfs: + if (proc_mtd) + remove_proc_entry("mtd", NULL); +err_bdi3: + bdi_destroy(&mtd_bdi_ro_mappable); +err_bdi2: + bdi_destroy(&mtd_bdi_unmappable); +err_bdi1: + class_unregister(&mtd_class); +err_reg: + pr_err("Error registering mtd class or bdi: %d\n", ret); + return ret; } static void __exit cleanup_mtd(void) { - if (proc_mtd) - remove_proc_entry( "mtd", NULL); + cleanup_mtdchar(); + if (proc_mtd) + remove_proc_entry("mtd", NULL); + class_unregister(&mtd_class); + bdi_destroy(&mtd_bdi_unmappable); + bdi_destroy(&mtd_bdi_ro_mappable); + bdi_destroy(&mtd_bdi_rw_mappable); } 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/mtdcore.h b/drivers/mtd/mtdcore.h new file mode 100644 index 00000000000..7b0353399a1 --- /dev/null +++ b/drivers/mtd/mtdcore.h @@ -0,0 +1,23 @@ +/* + * These are exported solely for the purpose of mtd_blkdevs.c and mtdchar.c. + * You should not use them for _anything_ else. + */ + +extern struct mutex mtd_table_mutex; + +struct mtd_info *__mtd_next_device(int i); +int add_mtd_device(struct mtd_info *mtd); +int del_mtd_device(struct mtd_info *mtd); +int add_mtd_partitions(struct mtd_info *, const struct mtd_partition *, int); +int del_mtd_partitions(struct mtd_info *); +int parse_mtd_partitions(struct mtd_info *master, const char * const *types, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data); + +int __init init_mtdchar(void); +void __exit cleanup_mtdchar(void); + +#define mtd_for_each_device(mtd) \ + for ((mtd) = __mtd_next_device(0); \ + (mtd) != NULL; \ + (mtd) = __mtd_next_device(mtd->index + 1)) diff --git a/drivers/mtd/mtdoops.c b/drivers/mtd/mtdoops.c new file mode 100644 index 00000000000..97bb8f6304d --- /dev/null +++ b/drivers/mtd/mtdoops.c @@ -0,0 +1,451 @@ +/* + * MTD Oops/Panic logger + * + * Copyright © 2007 Nokia Corporation. All rights reserved. + * + * Author: Richard Purdie <rpurdie@openedhand.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/console.h> +#include <linux/vmalloc.h> +#include <linux/workqueue.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/mtd/mtd.h> +#include <linux/kmsg_dump.h> + +/* Maximum MTD partition size */ +#define MTDOOPS_MAX_MTD_SIZE (8 * 1024 * 1024) + +#define MTDOOPS_KERNMSG_MAGIC 0x5d005d00 +#define MTDOOPS_HEADER_SIZE 8 + +static unsigned long record_size = 4096; +module_param(record_size, ulong, 0400); +MODULE_PARM_DESC(record_size, + "record size for MTD OOPS pages in bytes (default 4096)"); + +static char mtddev[80]; +module_param_string(mtddev, mtddev, 80, 0400); +MODULE_PARM_DESC(mtddev, + "name or index number of the MTD device to use"); + +static int dump_oops = 1; +module_param(dump_oops, int, 0600); +MODULE_PARM_DESC(dump_oops, + "set to 1 to dump oopses, 0 to only dump panics (default 1)"); + +static struct mtdoops_context { + struct kmsg_dumper dump; + + int mtd_index; + struct work_struct work_erase; + struct work_struct work_write; + struct mtd_info *mtd; + int oops_pages; + int nextpage; + int nextcount; + unsigned long *oops_page_used; + + void *oops_buf; +} oops_cxt; + +static void mark_page_used(struct mtdoops_context *cxt, int page) +{ + set_bit(page, cxt->oops_page_used); +} + +static void mark_page_unused(struct mtdoops_context *cxt, int page) +{ + clear_bit(page, cxt->oops_page_used); +} + +static int page_is_used(struct mtdoops_context *cxt, int page) +{ + return test_bit(page, cxt->oops_page_used); +} + +static void mtdoops_erase_callback(struct erase_info *done) +{ + wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv; + wake_up(wait_q); +} + +static int mtdoops_erase_block(struct mtdoops_context *cxt, int offset) +{ + struct mtd_info *mtd = cxt->mtd; + u32 start_page_offset = mtd_div_by_eb(offset, mtd) * mtd->erasesize; + u32 start_page = start_page_offset / record_size; + u32 erase_pages = mtd->erasesize / record_size; + struct erase_info erase; + DECLARE_WAITQUEUE(wait, current); + wait_queue_head_t wait_q; + int ret; + int page; + + init_waitqueue_head(&wait_q); + erase.mtd = mtd; + erase.callback = mtdoops_erase_callback; + erase.addr = offset; + erase.len = mtd->erasesize; + erase.priv = (u_long)&wait_q; + + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&wait_q, &wait); + + ret = mtd_erase(mtd, &erase); + if (ret) { + set_current_state(TASK_RUNNING); + remove_wait_queue(&wait_q, &wait); + printk(KERN_WARNING "mtdoops: erase of region [0x%llx, 0x%llx] on \"%s\" failed\n", + (unsigned long long)erase.addr, + (unsigned long long)erase.len, mtddev); + return ret; + } + + schedule(); /* Wait for erase to finish. */ + remove_wait_queue(&wait_q, &wait); + + /* Mark pages as unused */ + for (page = start_page; page < start_page + erase_pages; page++) + mark_page_unused(cxt, page); + + return 0; +} + +static void mtdoops_inc_counter(struct mtdoops_context *cxt) +{ + cxt->nextpage++; + if (cxt->nextpage >= cxt->oops_pages) + cxt->nextpage = 0; + cxt->nextcount++; + if (cxt->nextcount == 0xffffffff) + cxt->nextcount = 0; + + if (page_is_used(cxt, cxt->nextpage)) { + schedule_work(&cxt->work_erase); + return; + } + + printk(KERN_DEBUG "mtdoops: ready %d, %d (no erase)\n", + cxt->nextpage, cxt->nextcount); +} + +/* Scheduled work - when we can't proceed without erasing a block */ +static void mtdoops_workfunc_erase(struct work_struct *work) +{ + struct mtdoops_context *cxt = + container_of(work, struct mtdoops_context, work_erase); + struct mtd_info *mtd = cxt->mtd; + int i = 0, j, ret, mod; + + /* We were unregistered */ + if (!mtd) + return; + + mod = (cxt->nextpage * record_size) % mtd->erasesize; + if (mod != 0) { + cxt->nextpage = cxt->nextpage + ((mtd->erasesize - mod) / record_size); + if (cxt->nextpage >= cxt->oops_pages) + cxt->nextpage = 0; + } + + while ((ret = mtd_block_isbad(mtd, cxt->nextpage * record_size)) > 0) { +badblock: + printk(KERN_WARNING "mtdoops: bad block at %08lx\n", + cxt->nextpage * record_size); + i++; + cxt->nextpage = cxt->nextpage + (mtd->erasesize / record_size); + if (cxt->nextpage >= cxt->oops_pages) + cxt->nextpage = 0; + if (i == cxt->oops_pages / (mtd->erasesize / record_size)) { + printk(KERN_ERR "mtdoops: all blocks bad!\n"); + return; + } + } + + if (ret < 0) { + printk(KERN_ERR "mtdoops: mtd_block_isbad failed, aborting\n"); + return; + } + + for (j = 0, ret = -1; (j < 3) && (ret < 0); j++) + ret = mtdoops_erase_block(cxt, cxt->nextpage * record_size); + + if (ret >= 0) { + printk(KERN_DEBUG "mtdoops: ready %d, %d\n", + cxt->nextpage, cxt->nextcount); + return; + } + + if (ret == -EIO) { + ret = mtd_block_markbad(mtd, cxt->nextpage * record_size); + if (ret < 0 && ret != -EOPNOTSUPP) { + printk(KERN_ERR "mtdoops: block_markbad failed, aborting\n"); + return; + } + } + goto badblock; +} + +static void mtdoops_write(struct mtdoops_context *cxt, int panic) +{ + struct mtd_info *mtd = cxt->mtd; + size_t retlen; + u32 *hdr; + int ret; + + /* Add mtdoops header to the buffer */ + hdr = cxt->oops_buf; + hdr[0] = cxt->nextcount; + hdr[1] = MTDOOPS_KERNMSG_MAGIC; + + if (panic) { + ret = mtd_panic_write(mtd, cxt->nextpage * record_size, + record_size, &retlen, cxt->oops_buf); + if (ret == -EOPNOTSUPP) { + printk(KERN_ERR "mtdoops: Cannot write from panic without panic_write\n"); + return; + } + } else + ret = mtd_write(mtd, cxt->nextpage * record_size, + record_size, &retlen, cxt->oops_buf); + + if (retlen != record_size || ret < 0) + printk(KERN_ERR "mtdoops: write failure at %ld (%td of %ld written), error %d\n", + cxt->nextpage * record_size, retlen, record_size, ret); + mark_page_used(cxt, cxt->nextpage); + memset(cxt->oops_buf, 0xff, record_size); + + mtdoops_inc_counter(cxt); +} + +static void mtdoops_workfunc_write(struct work_struct *work) +{ + struct mtdoops_context *cxt = + container_of(work, struct mtdoops_context, work_write); + + mtdoops_write(cxt, 0); +} + +static void find_next_position(struct mtdoops_context *cxt) +{ + struct mtd_info *mtd = cxt->mtd; + int ret, page, maxpos = 0; + u32 count[2], maxcount = 0xffffffff; + size_t retlen; + + for (page = 0; page < cxt->oops_pages; page++) { + if (mtd_block_isbad(mtd, page * record_size)) + continue; + /* Assume the page is used */ + mark_page_used(cxt, page); + ret = mtd_read(mtd, page * record_size, MTDOOPS_HEADER_SIZE, + &retlen, (u_char *)&count[0]); + if (retlen != MTDOOPS_HEADER_SIZE || + (ret < 0 && !mtd_is_bitflip(ret))) { + printk(KERN_ERR "mtdoops: read failure at %ld (%td of %d read), err %d\n", + page * record_size, retlen, + MTDOOPS_HEADER_SIZE, ret); + continue; + } + + if (count[0] == 0xffffffff && count[1] == 0xffffffff) + mark_page_unused(cxt, page); + if (count[0] == 0xffffffff || count[1] != MTDOOPS_KERNMSG_MAGIC) + continue; + if (maxcount == 0xffffffff) { + maxcount = count[0]; + maxpos = page; + } else if (count[0] < 0x40000000 && maxcount > 0xc0000000) { + maxcount = count[0]; + maxpos = page; + } else if (count[0] > maxcount && count[0] < 0xc0000000) { + maxcount = count[0]; + maxpos = page; + } else if (count[0] > maxcount && count[0] > 0xc0000000 + && maxcount > 0x80000000) { + maxcount = count[0]; + maxpos = page; + } + } + if (maxcount == 0xffffffff) { + cxt->nextpage = cxt->oops_pages - 1; + cxt->nextcount = 0; + } + else { + cxt->nextpage = maxpos; + cxt->nextcount = maxcount; + } + + mtdoops_inc_counter(cxt); +} + +static void mtdoops_do_dump(struct kmsg_dumper *dumper, + enum kmsg_dump_reason reason) +{ + struct mtdoops_context *cxt = container_of(dumper, + struct mtdoops_context, dump); + + /* Only dump oopses if dump_oops is set */ + if (reason == KMSG_DUMP_OOPS && !dump_oops) + return; + + kmsg_dump_get_buffer(dumper, true, cxt->oops_buf + MTDOOPS_HEADER_SIZE, + record_size - MTDOOPS_HEADER_SIZE, NULL); + + /* Panics must be written immediately */ + if (reason != KMSG_DUMP_OOPS) + mtdoops_write(cxt, 1); + + /* For other cases, schedule work to write it "nicely" */ + schedule_work(&cxt->work_write); +} + +static void mtdoops_notify_add(struct mtd_info *mtd) +{ + struct mtdoops_context *cxt = &oops_cxt; + u64 mtdoops_pages = div_u64(mtd->size, record_size); + int err; + + if (!strcmp(mtd->name, mtddev)) + cxt->mtd_index = mtd->index; + + if (mtd->index != cxt->mtd_index || cxt->mtd_index < 0) + return; + + if (mtd->size < mtd->erasesize * 2) { + printk(KERN_ERR "mtdoops: MTD partition %d not big enough for mtdoops\n", + mtd->index); + return; + } + if (mtd->erasesize < record_size) { + printk(KERN_ERR "mtdoops: eraseblock size of MTD partition %d too small\n", + mtd->index); + return; + } + if (mtd->size > MTDOOPS_MAX_MTD_SIZE) { + printk(KERN_ERR "mtdoops: mtd%d is too large (limit is %d MiB)\n", + mtd->index, MTDOOPS_MAX_MTD_SIZE / 1024 / 1024); + return; + } + + /* oops_page_used is a bit field */ + cxt->oops_page_used = vmalloc(DIV_ROUND_UP(mtdoops_pages, + BITS_PER_LONG) * sizeof(unsigned long)); + if (!cxt->oops_page_used) { + printk(KERN_ERR "mtdoops: could not allocate page array\n"); + return; + } + + cxt->dump.max_reason = KMSG_DUMP_OOPS; + cxt->dump.dump = mtdoops_do_dump; + err = kmsg_dump_register(&cxt->dump); + if (err) { + printk(KERN_ERR "mtdoops: registering kmsg dumper failed, error %d\n", err); + vfree(cxt->oops_page_used); + cxt->oops_page_used = NULL; + return; + } + + cxt->mtd = mtd; + cxt->oops_pages = (int)mtd->size / record_size; + find_next_position(cxt); + printk(KERN_INFO "mtdoops: Attached to MTD device %d\n", mtd->index); +} + +static void mtdoops_notify_remove(struct mtd_info *mtd) +{ + struct mtdoops_context *cxt = &oops_cxt; + + if (mtd->index != cxt->mtd_index || cxt->mtd_index < 0) + return; + + if (kmsg_dump_unregister(&cxt->dump) < 0) + printk(KERN_WARNING "mtdoops: could not unregister kmsg_dumper\n"); + + cxt->mtd = NULL; + flush_work(&cxt->work_erase); + flush_work(&cxt->work_write); +} + + +static struct mtd_notifier mtdoops_notifier = { + .add = mtdoops_notify_add, + .remove = mtdoops_notify_remove, +}; + +static int __init mtdoops_init(void) +{ + struct mtdoops_context *cxt = &oops_cxt; + int mtd_index; + char *endp; + + if (strlen(mtddev) == 0) { + printk(KERN_ERR "mtdoops: mtd device (mtddev=name/number) must be supplied\n"); + return -EINVAL; + } + if ((record_size & 4095) != 0) { + printk(KERN_ERR "mtdoops: record_size must be a multiple of 4096\n"); + return -EINVAL; + } + if (record_size < 4096) { + printk(KERN_ERR "mtdoops: record_size must be over 4096 bytes\n"); + return -EINVAL; + } + + /* Setup the MTD device to use */ + cxt->mtd_index = -1; + mtd_index = simple_strtoul(mtddev, &endp, 0); + if (*endp == '\0') + cxt->mtd_index = mtd_index; + + cxt->oops_buf = vmalloc(record_size); + if (!cxt->oops_buf) { + printk(KERN_ERR "mtdoops: failed to allocate buffer workspace\n"); + return -ENOMEM; + } + memset(cxt->oops_buf, 0xff, record_size); + + INIT_WORK(&cxt->work_erase, mtdoops_workfunc_erase); + INIT_WORK(&cxt->work_write, mtdoops_workfunc_write); + + register_mtd_user(&mtdoops_notifier); + return 0; +} + +static void __exit mtdoops_exit(void) +{ + struct mtdoops_context *cxt = &oops_cxt; + + unregister_mtd_user(&mtdoops_notifier); + vfree(cxt->oops_buf); + vfree(cxt->oops_page_used); +} + + +module_init(mtdoops_init); +module_exit(mtdoops_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Richard Purdie <rpurdie@openedhand.com>"); +MODULE_DESCRIPTION("MTD Oops/Panic console logger/driver"); diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index 06a930372b7..1ca9aec141f 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -1,14 +1,24 @@ /* * Simple MTD partitioning layer * - * (C) 2000 Nicolas Pitre <nico@cam.org> + * Copyright © 2000 Nicolas Pitre <nico@fluxnic.net> + * Copyright © 2002 Thomas Gleixner <gleixner@linutronix.de> + * Copyright © 2000-2010 David Woodhouse <dwmw2@infradead.org> * - * This code is GPL + * 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. * - * $Id: mtdpart.c,v 1.55 2005/11/07 11:14:20 gleixner Exp $ + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * - * 02-21-2002 Thomas Gleixner <gleixner@autronix.de> - * added support for read_oob, write_oob */ #include <linux/module.h> @@ -19,19 +29,20 @@ #include <linux/kmod.h> #include <linux/mtd/mtd.h> #include <linux/mtd/partitions.h> -#include <linux/mtd/compatmac.h> +#include <linux/err.h> + +#include "mtdcore.h" /* Our partition linked list */ static LIST_HEAD(mtd_partitions); +static DEFINE_MUTEX(mtd_partitions_mutex); /* Our partition node structure */ struct mtd_part { struct mtd_info mtd; struct mtd_info *master; - u_int32_t offset; - int index; + uint64_t offset; struct list_head list; - int registered; }; /* @@ -46,169 +57,195 @@ struct mtd_part { * to the _real_ device. */ -static int part_read (struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf) +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; - if (from >= mtd->size) - len = 0; - else if (from + len > mtd->size) - len = mtd->size - from; - res = part->master->read (part->master, from + part->offset, - len, retlen, buf); - if (unlikely(res)) { - if (res == -EUCLEAN) - mtd->ecc_stats.corrected++; - if (res == -EBADMSG) - mtd->ecc_stats.failed++; - } + stats = part->master->ecc_stats; + res = part->master->_read(part->master, from + part->offset, len, + retlen, buf); + if (unlikely(mtd_is_eccerr(res))) + mtd->ecc_stats.failed += + part->master->ecc_stats.failed - stats.failed; + else + mtd->ecc_stats.corrected += + part->master->ecc_stats.corrected - stats.corrected; return res; } -static int part_point (struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char **buf) +static int part_point(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, void **virt, resource_size_t *phys) { struct mtd_part *part = PART(mtd); - if (from >= mtd->size) - len = 0; - else if (from + len > mtd->size) - len = mtd->size - from; - return part->master->point (part->master, from + part->offset, - len, retlen, buf); + + return part->master->_point(part->master, from + part->offset, len, + retlen, virt, phys); } -static void part_unpoint (struct mtd_info *mtd, u_char *addr, loff_t from, size_t len) +static int part_unpoint(struct mtd_info *mtd, loff_t from, size_t len) { struct mtd_part *part = PART(mtd); - part->master->unpoint (part->master, addr, from + part->offset, len); + return 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) + struct mtd_oob_ops *ops) { struct mtd_part *part = PART(mtd); int res; if (from >= mtd->size) return -EINVAL; - if (from + ops->len > mtd->size) + if (ops->datbuf && from + ops->len > mtd->size) return -EINVAL; - res = part->master->read_oob(part->master, from + part->offset, ops); + /* + * If OOB is also requested, make sure that we do not read past the end + * of this partition. + */ + if (ops->oobbuf) { + size_t len, pages; + + if (ops->mode == MTD_OPS_AUTO_OOB) + len = mtd->oobavail; + else + len = mtd->oobsize; + pages = mtd_div_by_ws(mtd->size, mtd); + pages -= mtd_div_by_ws(from, mtd); + if (ops->ooboffs + ops->ooblen > pages * len) + return -EINVAL; + } + + res = part->master->_read_oob(part->master, from + part->offset, ops); if (unlikely(res)) { - if (res == -EUCLEAN) + if (mtd_is_bitflip(res)) mtd->ecc_stats.corrected++; - if (res == -EBADMSG) + if (mtd_is_eccerr(res)) mtd->ecc_stats.failed++; } return res; } -static int part_read_user_prot_reg (struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf) +static int part_read_user_prot_reg(struct mtd_info *mtd, loff_t from, + size_t len, size_t *retlen, u_char *buf) { struct mtd_part *part = PART(mtd); - return part->master->read_user_prot_reg (part->master, from, - len, retlen, buf); + return part->master->_read_user_prot_reg(part->master, from, len, + retlen, buf); } -static int part_get_user_prot_info (struct mtd_info *mtd, - struct otp_info *buf, size_t len) +static int part_get_user_prot_info(struct mtd_info *mtd, size_t len, + size_t *retlen, struct otp_info *buf) { struct mtd_part *part = PART(mtd); - return part->master->get_user_prot_info (part->master, buf, len); + return part->master->_get_user_prot_info(part->master, len, retlen, + buf); } -static int part_read_fact_prot_reg (struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf) +static int part_read_fact_prot_reg(struct mtd_info *mtd, loff_t from, + size_t len, size_t *retlen, u_char *buf) { struct mtd_part *part = PART(mtd); - return part->master->read_fact_prot_reg (part->master, from, - len, retlen, buf); + return part->master->_read_fact_prot_reg(part->master, from, len, + retlen, buf); } -static int part_get_fact_prot_info (struct mtd_info *mtd, - struct otp_info *buf, size_t len) +static int part_get_fact_prot_info(struct mtd_info *mtd, size_t len, + size_t *retlen, struct otp_info *buf) { struct mtd_part *part = PART(mtd); - return part->master->get_fact_prot_info (part->master, buf, len); + return part->master->_get_fact_prot_info(part->master, len, retlen, + buf); } -static int part_write (struct mtd_info *mtd, loff_t to, size_t len, - size_t *retlen, const u_char *buf) +static int part_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) { struct mtd_part *part = PART(mtd); - if (!(mtd->flags & MTD_WRITEABLE)) - return -EROFS; - if (to >= mtd->size) - len = 0; - else if (to + len > mtd->size) - len = mtd->size - to; - return part->master->write (part->master, to + part->offset, - len, retlen, buf); + return part->master->_write(part->master, to + part->offset, len, + retlen, buf); } -static int part_write_oob(struct mtd_info *mtd, loff_t to, - struct mtd_oob_ops *ops) +static int part_panic_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) { struct mtd_part *part = PART(mtd); + return part->master->_panic_write(part->master, to + part->offset, len, + retlen, buf); +} - if (!(mtd->flags & MTD_WRITEABLE)) - return -EROFS; +static int part_write_oob(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + struct mtd_part *part = PART(mtd); if (to >= mtd->size) return -EINVAL; - if (to + ops->len > mtd->size) + if (ops->datbuf && to + ops->len > mtd->size) return -EINVAL; - return part->master->write_oob(part->master, to + part->offset, ops); + return part->master->_write_oob(part->master, to + part->offset, ops); } -static int part_write_user_prot_reg (struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf) +static int part_write_user_prot_reg(struct mtd_info *mtd, loff_t from, + size_t len, size_t *retlen, u_char *buf) { struct mtd_part *part = PART(mtd); - return part->master->write_user_prot_reg (part->master, from, - len, retlen, buf); + return part->master->_write_user_prot_reg(part->master, from, len, + retlen, buf); } -static int part_lock_user_prot_reg (struct mtd_info *mtd, loff_t from, size_t len) +static int part_lock_user_prot_reg(struct mtd_info *mtd, loff_t from, + size_t len) { struct mtd_part *part = PART(mtd); - return part->master->lock_user_prot_reg (part->master, from, len); + return part->master->_lock_user_prot_reg(part->master, from, len); } -static int part_writev (struct mtd_info *mtd, const struct kvec *vecs, - unsigned long count, loff_t to, size_t *retlen) +static int part_writev(struct mtd_info *mtd, const struct kvec *vecs, + unsigned long count, loff_t to, size_t *retlen) { struct mtd_part *part = PART(mtd); - if (!(mtd->flags & MTD_WRITEABLE)) - return -EROFS; - return part->master->writev (part->master, vecs, count, - to + part->offset, retlen); + return part->master->_writev(part->master, vecs, count, + to + part->offset, retlen); } -static int part_erase (struct mtd_info *mtd, struct erase_info *instr) +static int part_erase(struct mtd_info *mtd, struct erase_info *instr) { struct mtd_part *part = PART(mtd); int ret; - if (!(mtd->flags & MTD_WRITEABLE)) - return -EROFS; - if (instr->addr >= mtd->size) - return -EINVAL; + instr->addr += part->offset; - ret = part->master->erase(part->master, instr); + ret = part->master->_erase(part->master, instr); + if (ret) { + if (instr->fail_addr != MTD_FAIL_ADDR_UNKNOWN) + instr->fail_addr -= part->offset; + instr->addr -= part->offset; + } return ret; } void mtd_erase_callback(struct erase_info *instr) { - if (instr->mtd->erase == part_erase) { + if (instr->mtd->_erase == part_erase) { struct mtd_part *part = PART(instr->mtd); - if (instr->fail_addr != 0xffffffff) + if (instr->fail_addr != MTD_FAIL_ADDR_UNKNOWN) instr->fail_addr -= part->offset; instr->addr -= part->offset; } @@ -217,65 +254,67 @@ void mtd_erase_callback(struct erase_info *instr) } EXPORT_SYMBOL_GPL(mtd_erase_callback); -static int part_lock (struct mtd_info *mtd, loff_t ofs, size_t len) +static int part_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) { struct mtd_part *part = PART(mtd); - if ((len + ofs) > mtd->size) - return -EINVAL; - return part->master->lock(part->master, ofs + part->offset, len); + return part->master->_lock(part->master, ofs + part->offset, len); } -static int part_unlock (struct mtd_info *mtd, loff_t ofs, size_t len) +static int part_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) { struct mtd_part *part = PART(mtd); - if ((len + ofs) > mtd->size) - return -EINVAL; - return part->master->unlock(part->master, ofs + part->offset, len); + return part->master->_unlock(part->master, ofs + part->offset, len); +} + +static int part_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + struct mtd_part *part = PART(mtd); + return part->master->_is_locked(part->master, ofs + part->offset, len); } static void part_sync(struct mtd_info *mtd) { struct mtd_part *part = PART(mtd); - part->master->sync(part->master); + part->master->_sync(part->master); } static int part_suspend(struct mtd_info *mtd) { struct mtd_part *part = PART(mtd); - return part->master->suspend(part->master); + return part->master->_suspend(part->master); } static void part_resume(struct mtd_info *mtd) { struct mtd_part *part = PART(mtd); - part->master->resume(part->master); + part->master->_resume(part->master); } -static int part_block_isbad (struct mtd_info *mtd, loff_t ofs) +static int part_block_isbad(struct mtd_info *mtd, loff_t ofs) { struct mtd_part *part = PART(mtd); - if (ofs >= mtd->size) - return -EINVAL; ofs += part->offset; - return part->master->block_isbad(part->master, ofs); + return part->master->_block_isbad(part->master, ofs); } -static int part_block_markbad (struct mtd_info *mtd, loff_t ofs) +static int part_block_markbad(struct mtd_info *mtd, loff_t ofs) { struct mtd_part *part = PART(mtd); int res; - if (!(mtd->flags & MTD_WRITEABLE)) - return -EROFS; - if (ofs >= mtd->size) - return -EINVAL; ofs += part->offset; - res = part->master->block_markbad(part->master, ofs); + res = part->master->_block_markbad(part->master, ofs); if (!res) mtd->ecc_stats.badblocks++; return res; } +static inline void free_partition(struct mtd_part *p) +{ + kfree(p->mtd.name); + kfree(p); +} + /* * This function unregisters and destroy all slave MTD objects which are * attached to the given master MTD object. @@ -283,31 +322,307 @@ static int part_block_markbad (struct mtd_info *mtd, loff_t ofs) int del_mtd_partitions(struct mtd_info *master) { - struct list_head *node; - struct mtd_part *slave; + struct mtd_part *slave, *next; + int ret, err = 0; - for (node = mtd_partitions.next; - node != &mtd_partitions; - node = node->next) { - slave = list_entry(node, struct mtd_part, list); + mutex_lock(&mtd_partitions_mutex); + list_for_each_entry_safe(slave, next, &mtd_partitions, list) if (slave->master == master) { - struct list_head *prev = node->prev; - __list_del(prev, node->next); - if(slave->registered) - del_mtd_device(&slave->mtd); - kfree(slave); - node = prev; + ret = del_mtd_device(&slave->mtd); + if (ret < 0) { + err = ret; + continue; + } + list_del(&slave->list); + free_partition(slave); } + mutex_unlock(&mtd_partitions_mutex); + + return err; +} + +static struct mtd_part *allocate_partition(struct mtd_info *master, + const struct mtd_partition *part, int partno, + uint64_t cur_offset) +{ + struct mtd_part *slave; + char *name; + + /* allocate the partition structure */ + slave = kzalloc(sizeof(*slave), GFP_KERNEL); + name = kstrdup(part->name, GFP_KERNEL); + if (!name || !slave) { + printk(KERN_ERR"memory allocation error while creating partitions for \"%s\"\n", + master->name); + kfree(name); + kfree(slave); + return ERR_PTR(-ENOMEM); } - return 0; + /* set up the MTD object for this partition */ + slave->mtd.type = master->type; + slave->mtd.flags = master->flags & ~part->mask_flags; + slave->mtd.size = part->size; + slave->mtd.writesize = master->writesize; + slave->mtd.writebufsize = master->writebufsize; + slave->mtd.oobsize = master->oobsize; + slave->mtd.oobavail = master->oobavail; + slave->mtd.subpage_sft = master->subpage_sft; + + slave->mtd.name = 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; + + if (master->_panic_write) + slave->mtd._panic_write = part_panic_write; + + if (master->_point && master->_unpoint) { + slave->mtd._point = part_point; + 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) + slave->mtd._write_oob = part_write_oob; + if (master->_read_user_prot_reg) + slave->mtd._read_user_prot_reg = part_read_user_prot_reg; + if (master->_read_fact_prot_reg) + slave->mtd._read_fact_prot_reg = part_read_fact_prot_reg; + if (master->_write_user_prot_reg) + slave->mtd._write_user_prot_reg = part_write_user_prot_reg; + if (master->_lock_user_prot_reg) + slave->mtd._lock_user_prot_reg = part_lock_user_prot_reg; + if (master->_get_user_prot_info) + slave->mtd._get_user_prot_info = part_get_user_prot_info; + if (master->_get_fact_prot_info) + slave->mtd._get_fact_prot_info = part_get_fact_prot_info; + if (master->_sync) + slave->mtd._sync = part_sync; + if (!partno && !master->dev.class && master->_suspend && + master->_resume) { + slave->mtd._suspend = part_suspend; + slave->mtd._resume = part_resume; + } + if (master->_writev) + slave->mtd._writev = part_writev; + if (master->_lock) + slave->mtd._lock = part_lock; + if (master->_unlock) + slave->mtd._unlock = part_unlock; + if (master->_is_locked) + slave->mtd._is_locked = part_is_locked; + if (master->_block_isbad) + slave->mtd._block_isbad = part_block_isbad; + if (master->_block_markbad) + slave->mtd._block_markbad = part_block_markbad; + slave->mtd._erase = part_erase; + slave->master = master; + slave->offset = part->offset; + + if (slave->offset == MTDPART_OFS_APPEND) + slave->offset = cur_offset; + if (slave->offset == MTDPART_OFS_NXTBLK) { + slave->offset = cur_offset; + if (mtd_mod_by_eb(cur_offset, master) != 0) { + /* Round up to next erasesize */ + slave->offset = (mtd_div_by_eb(cur_offset, master) + 1) * master->erasesize; + printk(KERN_NOTICE "Moving partition %d: " + "0x%012llx -> 0x%012llx\n", partno, + (unsigned long long)cur_offset, (unsigned long long)slave->offset); + } + } + if (slave->offset == MTDPART_OFS_RETAIN) { + slave->offset = cur_offset; + if (master->size - slave->offset >= slave->mtd.size) { + slave->mtd.size = master->size - slave->offset + - slave->mtd.size; + } else { + printk(KERN_ERR "mtd partition \"%s\" doesn't have enough space: %#llx < %#llx, disabled\n", + part->name, master->size - slave->offset, + slave->mtd.size); + /* register to preserve ordering */ + goto out_register; + } + } + if (slave->mtd.size == MTDPART_SIZ_FULL) + slave->mtd.size = master->size - slave->offset; + + printk(KERN_NOTICE "0x%012llx-0x%012llx : \"%s\"\n", (unsigned long long)slave->offset, + (unsigned long long)(slave->offset + slave->mtd.size), slave->mtd.name); + + /* let's do some sanity checks */ + if (slave->offset >= master->size) { + /* let's register it anyway to preserve ordering */ + slave->offset = 0; + slave->mtd.size = 0; + printk(KERN_ERR"mtd: partition \"%s\" is out of reach -- disabled\n", + part->name); + goto out_register; + } + if (slave->offset + slave->mtd.size > master->size) { + slave->mtd.size = master->size - slave->offset; + printk(KERN_WARNING"mtd: partition \"%s\" extends beyond the end of device \"%s\" -- size truncated to %#llx\n", + part->name, master->name, (unsigned long long)slave->mtd.size); + } + if (master->numeraseregions > 1) { + /* Deal with variable erase size stuff */ + int i, max = master->numeraseregions; + u64 end = slave->offset + slave->mtd.size; + struct mtd_erase_region_info *regions = master->eraseregions; + + /* Find the first erase regions which is part of this + * partition. */ + for (i = 0; i < max && regions[i].offset <= slave->offset; i++) + ; + /* The loop searched for the region _behind_ the first one */ + if (i > 0) + i--; + + /* Pick biggest erasesize */ + for (; i < max && regions[i].offset < end; i++) { + if (slave->mtd.erasesize < regions[i].erasesize) { + slave->mtd.erasesize = regions[i].erasesize; + } + } + BUG_ON(slave->mtd.erasesize == 0); + } else { + /* Single erase size */ + slave->mtd.erasesize = master->erasesize; + } + + if ((slave->mtd.flags & MTD_WRITEABLE) && + mtd_mod_by_eb(slave->offset, &slave->mtd)) { + /* Doesn't start on a boundary of major erase size */ + /* FIXME: Let it be writable if it is on a boundary of + * _minor_ erase size though */ + slave->mtd.flags &= ~MTD_WRITEABLE; + printk(KERN_WARNING"mtd: partition \"%s\" doesn't start on an erase block boundary -- force read-only\n", + part->name); + } + if ((slave->mtd.flags & MTD_WRITEABLE) && + mtd_mod_by_eb(slave->mtd.size, &slave->mtd)) { + slave->mtd.flags &= ~MTD_WRITEABLE; + printk(KERN_WARNING"mtd: partition \"%s\" doesn't end on an erase block -- force read-only\n", + part->name); + } + + slave->mtd.ecclayout = master->ecclayout; + slave->mtd.ecc_step_size = master->ecc_step_size; + slave->mtd.ecc_strength = master->ecc_strength; + slave->mtd.bitflip_threshold = master->bitflip_threshold; + + if (master->_block_isbad) { + uint64_t offs = 0; + + while (offs < slave->mtd.size) { + if (mtd_block_isbad(master, offs + slave->offset)) + slave->mtd.ecc_stats.badblocks++; + offs += slave->mtd.erasesize; + } + } + +out_register: + return slave; } +int mtd_add_partition(struct mtd_info *master, const char *name, + long long offset, long long length) +{ + struct mtd_partition part; + struct mtd_part *p, *new; + uint64_t start, end; + int ret = 0; + + /* the direct offset is expected */ + if (offset == MTDPART_OFS_APPEND || + offset == MTDPART_OFS_NXTBLK) + return -EINVAL; + + if (length == MTDPART_SIZ_FULL) + length = master->size - offset; + + if (length <= 0) + return -EINVAL; + + part.name = name; + part.size = length; + part.offset = offset; + part.mask_flags = 0; + part.ecclayout = NULL; + + new = allocate_partition(master, &part, -1, offset); + if (IS_ERR(new)) + return PTR_ERR(new); + + start = offset; + end = offset + length; + + mutex_lock(&mtd_partitions_mutex); + list_for_each_entry(p, &mtd_partitions, list) + if (p->master == master) { + if ((start >= p->offset) && + (start < (p->offset + p->mtd.size))) + goto err_inv; + + if ((end >= p->offset) && + (end < (p->offset + p->mtd.size))) + goto err_inv; + } + + list_add(&new->list, &mtd_partitions); + mutex_unlock(&mtd_partitions_mutex); + + add_mtd_device(&new->mtd); + + return ret; +err_inv: + mutex_unlock(&mtd_partitions_mutex); + free_partition(new); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(mtd_add_partition); + +int mtd_del_partition(struct mtd_info *master, int partno) +{ + struct mtd_part *slave, *next; + int ret = -EINVAL; + + mutex_lock(&mtd_partitions_mutex); + list_for_each_entry_safe(slave, next, &mtd_partitions, list) + if ((slave->master == master) && + (slave->mtd.index == partno)) { + ret = del_mtd_device(&slave->mtd); + if (ret < 0) + break; + + list_del(&slave->list); + free_partition(slave); + break; + } + mutex_unlock(&mtd_partitions_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(mtd_del_partition); + /* * 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, @@ -315,250 +630,146 @@ int add_mtd_partitions(struct mtd_info *master, int nbparts) { struct mtd_part *slave; - u_int32_t cur_offset = 0; + uint64_t cur_offset = 0; int i; - printk (KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nbparts, master->name); + printk(KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nbparts, master->name); for (i = 0; i < nbparts; i++) { + slave = allocate_partition(master, parts + i, i, cur_offset); + if (IS_ERR(slave)) + return PTR_ERR(slave); - /* allocate the partition structure */ - slave = kmalloc (sizeof(*slave), GFP_KERNEL); - if (!slave) { - printk ("memory allocation error while creating partitions for \"%s\"\n", - master->name); - del_mtd_partitions(master); - return -ENOMEM; - } - memset(slave, 0, sizeof(*slave)); + mutex_lock(&mtd_partitions_mutex); list_add(&slave->list, &mtd_partitions); + mutex_unlock(&mtd_partitions_mutex); - /* set up the MTD object for this partition */ - slave->mtd.type = master->type; - slave->mtd.flags = master->flags & ~parts[i].mask_flags; - slave->mtd.size = parts[i].size; - slave->mtd.writesize = master->writesize; - slave->mtd.oobsize = master->oobsize; - slave->mtd.ecctype = master->ecctype; - slave->mtd.eccsize = master->eccsize; - - slave->mtd.name = parts[i].name; - slave->mtd.bank_size = master->bank_size; - slave->mtd.owner = master->owner; - - slave->mtd.read = part_read; - slave->mtd.write = part_write; - - if(master->point && master->unpoint){ - slave->mtd.point = part_point; - slave->mtd.unpoint = part_unpoint; - } + add_mtd_device(&slave->mtd); - if (master->read_oob) - slave->mtd.read_oob = part_read_oob; - if (master->write_oob) - slave->mtd.write_oob = part_write_oob; - if(master->read_user_prot_reg) - slave->mtd.read_user_prot_reg = part_read_user_prot_reg; - if(master->read_fact_prot_reg) - slave->mtd.read_fact_prot_reg = part_read_fact_prot_reg; - if(master->write_user_prot_reg) - slave->mtd.write_user_prot_reg = part_write_user_prot_reg; - if(master->lock_user_prot_reg) - slave->mtd.lock_user_prot_reg = part_lock_user_prot_reg; - if(master->get_user_prot_info) - slave->mtd.get_user_prot_info = part_get_user_prot_info; - if(master->get_fact_prot_info) - slave->mtd.get_fact_prot_info = part_get_fact_prot_info; - if (master->sync) - slave->mtd.sync = part_sync; - if (!i && master->suspend && master->resume) { - slave->mtd.suspend = part_suspend; - slave->mtd.resume = part_resume; - } - if (master->writev) - slave->mtd.writev = part_writev; - if (master->lock) - slave->mtd.lock = part_lock; - if (master->unlock) - slave->mtd.unlock = part_unlock; - if (master->block_isbad) - slave->mtd.block_isbad = part_block_isbad; - if (master->block_markbad) - slave->mtd.block_markbad = part_block_markbad; - slave->mtd.erase = part_erase; - slave->master = master; - slave->offset = parts[i].offset; - slave->index = i; - - if (slave->offset == MTDPART_OFS_APPEND) - slave->offset = cur_offset; - if (slave->offset == MTDPART_OFS_NXTBLK) { - slave->offset = cur_offset; - if ((cur_offset % master->erasesize) != 0) { - /* Round up to next erasesize */ - slave->offset = ((cur_offset / master->erasesize) + 1) * master->erasesize; - printk(KERN_NOTICE "Moving partition %d: " - "0x%08x -> 0x%08x\n", i, - cur_offset, slave->offset); - } - } - if (slave->mtd.size == MTDPART_SIZ_FULL) - slave->mtd.size = master->size - slave->offset; cur_offset = slave->offset + slave->mtd.size; - - printk (KERN_NOTICE "0x%08x-0x%08x : \"%s\"\n", slave->offset, - slave->offset + slave->mtd.size, slave->mtd.name); - - /* let's do some sanity checks */ - if (slave->offset >= master->size) { - /* let's register it anyway to preserve ordering */ - slave->offset = 0; - slave->mtd.size = 0; - printk ("mtd: partition \"%s\" is out of reach -- disabled\n", - parts[i].name); - } - if (slave->offset + slave->mtd.size > master->size) { - slave->mtd.size = master->size - slave->offset; - printk ("mtd: partition \"%s\" extends beyond the end of device \"%s\" -- size truncated to %#x\n", - parts[i].name, master->name, slave->mtd.size); - } - if (master->numeraseregions>1) { - /* Deal with variable erase size stuff */ - int i; - struct mtd_erase_region_info *regions = master->eraseregions; - - /* Find the first erase regions which is part of this partition. */ - for (i=0; i < master->numeraseregions && slave->offset >= regions[i].offset; i++) - ; - - for (i--; i < master->numeraseregions && slave->offset + slave->mtd.size > regions[i].offset; i++) { - if (slave->mtd.erasesize < regions[i].erasesize) { - slave->mtd.erasesize = regions[i].erasesize; - } - } - } else { - /* Single erase size */ - slave->mtd.erasesize = master->erasesize; - } - - if ((slave->mtd.flags & MTD_WRITEABLE) && - (slave->offset % slave->mtd.erasesize)) { - /* Doesn't start on a boundary of major erase size */ - /* FIXME: Let it be writable if it is on a boundary of _minor_ erase size though */ - slave->mtd.flags &= ~MTD_WRITEABLE; - printk ("mtd: partition \"%s\" doesn't start on an erase block boundary -- force read-only\n", - parts[i].name); - } - if ((slave->mtd.flags & MTD_WRITEABLE) && - (slave->mtd.size % slave->mtd.erasesize)) { - slave->mtd.flags &= ~MTD_WRITEABLE; - printk ("mtd: partition \"%s\" doesn't end on an erase block -- force read-only\n", - parts[i].name); - } - - slave->mtd.ecclayout = master->ecclayout; - if (master->block_isbad) { - uint32_t offs = 0; - - while(offs < slave->mtd.size) { - if (master->block_isbad(master, - offs + slave->offset)) - slave->mtd.ecc_stats.badblocks++; - offs += slave->mtd.erasesize; - } - } - - if(parts[i].mtdp) - { /* store the object pointer (caller may or may not register it */ - *parts[i].mtdp = &slave->mtd; - slave->registered = 0; - } - else - { - /* register our partition */ - add_mtd_device(&slave->mtd); - slave->registered = 1; - } } return 0; } -EXPORT_SYMBOL(add_mtd_partitions); -EXPORT_SYMBOL(del_mtd_partitions); - static DEFINE_SPINLOCK(part_parser_lock); static LIST_HEAD(part_parsers); static struct mtd_part_parser *get_partition_parser(const char *name) { - struct list_head *this; - void *ret = NULL; - spin_lock(&part_parser_lock); + struct mtd_part_parser *p, *ret = NULL; - list_for_each(this, &part_parsers) { - struct mtd_part_parser *p = list_entry(this, struct mtd_part_parser, list); + spin_lock(&part_parser_lock); + list_for_each_entry(p, &part_parsers, list) if (!strcmp(p->name, name) && try_module_get(p->owner)) { ret = p; break; } - } + spin_unlock(&part_parser_lock); return ret; } -int register_mtd_parser(struct mtd_part_parser *p) +#define put_partition_parser(p) do { module_put((p)->owner); } while (0) + +void register_mtd_parser(struct mtd_part_parser *p) { spin_lock(&part_parser_lock); list_add(&p->list, &part_parsers); spin_unlock(&part_parser_lock); - - return 0; } +EXPORT_SYMBOL_GPL(register_mtd_parser); -int deregister_mtd_parser(struct mtd_part_parser *p) +void deregister_mtd_parser(struct mtd_part_parser *p) { spin_lock(&part_parser_lock); list_del(&p->list); spin_unlock(&part_parser_lock); - return 0; } +EXPORT_SYMBOL_GPL(deregister_mtd_parser); + +/* + * Do not forget to update 'parse_mtd_partitions()' kerneldoc comment if you + * are changing this array! + */ +static const char * const default_mtd_part_types[] = { + "cmdlinepart", + "ofpart", + NULL +}; -int parse_mtd_partitions(struct mtd_info *master, const char **types, - struct mtd_partition **pparts, unsigned long origin) +/** + * parse_mtd_partitions - parse MTD partitions + * @master: the master partition (describes whole MTD device) + * @types: names of partition parsers to try or %NULL + * @pparts: array of partitions found is returned here + * @data: MTD partition parser-specific data + * + * This function tries to find partition on MTD device @master. It uses MTD + * partition parsers, specified in @types. However, if @types is %NULL, then + * the default list of parsers is used. The default list contains only the + * "cmdlinepart" and "ofpart" parsers ATM. + * Note: If there are more then one parser in @types, the kernel only takes the + * partitions parsed out by the first parser. + * + * This function may return: + * o a negative error code in case of failure + * o zero if no partitions were found + * o a positive number of found partitions, in which case on exit @pparts will + * point to an array containing this number of &struct mtd_info objects. + */ +int parse_mtd_partitions(struct mtd_info *master, const char *const *types, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) { struct mtd_part_parser *parser; int ret = 0; + if (!types) + types = default_mtd_part_types; + for ( ; ret <= 0 && *types; types++) { parser = get_partition_parser(*types); -#ifdef CONFIG_KMOD if (!parser && !request_module("%s", *types)) - parser = get_partition_parser(*types); -#endif - if (!parser) { - printk(KERN_NOTICE "%s partition parsing not available\n", - *types); + parser = get_partition_parser(*types); + if (!parser) continue; - } - ret = (*parser->parse_fn)(master, pparts, origin); + ret = (*parser->parse_fn)(master, pparts, data); + put_partition_parser(parser); if (ret > 0) { printk(KERN_NOTICE "%d %s partitions found on MTD device %s\n", ret, parser->name, master->name); + break; } - put_partition_parser(parser); } return ret; } -EXPORT_SYMBOL_GPL(parse_mtd_partitions); -EXPORT_SYMBOL_GPL(register_mtd_parser); -EXPORT_SYMBOL_GPL(deregister_mtd_parser); +int mtd_is_partition(const struct mtd_info *mtd) +{ + struct mtd_part *part; + int ispart = 0; -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Nicolas Pitre <nico@cam.org>"); -MODULE_DESCRIPTION("Generic support for partitioning of MTD devices"); + mutex_lock(&mtd_partitions_mutex); + list_for_each_entry(part, &mtd_partitions, list) + if (&part->mtd == mtd) { + ispart = 1; + break; + } + mutex_unlock(&mtd_partitions_mutex); + return ispart; +} +EXPORT_SYMBOL_GPL(mtd_is_partition); + +/* Returns the size of the entire flash chip */ +uint64_t mtd_get_device_size(const struct mtd_info *mtd) +{ + if (!mtd_is_partition(mtd)) + return mtd->size; + + return PART(mtd)->master->size; +} +EXPORT_SYMBOL_GPL(mtd_get_device_size); diff --git a/drivers/mtd/mtdsuper.c b/drivers/mtd/mtdsuper.c new file mode 100644 index 00000000000..20c02a3b741 --- /dev/null +++ b/drivers/mtd/mtdsuper.c @@ -0,0 +1,220 @@ +/* MTD-based superblock management + * + * Copyright © 2001-2007 Red Hat, Inc. All Rights Reserved. + * Copyright © 2001-2010 David Woodhouse <dwmw2@infradead.org> + * + * Written by: David Howells <dhowells@redhat.com> + * David Woodhouse <dwmw2@infradead.org> + * + * 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/mtd/super.h> +#include <linux/namei.h> +#include <linux/export.h> +#include <linux/ctype.h> +#include <linux/slab.h> +#include <linux/major.h> + +/* + * compare superblocks to see if they're equivalent + * - they are if the underlying MTD device is the same + */ +static int get_sb_mtd_compare(struct super_block *sb, void *_mtd) +{ + struct mtd_info *mtd = _mtd; + + if (sb->s_mtd == mtd) { + pr_debug("MTDSB: Match on device %d (\"%s\")\n", + mtd->index, mtd->name); + return 1; + } + + pr_debug("MTDSB: No match, device %d (\"%s\"), device %d (\"%s\")\n", + sb->s_mtd->index, sb->s_mtd->name, mtd->index, mtd->name); + return 0; +} + +/* + * mark the superblock by the MTD device it is using + * - set the device number to be the correct MTD block device for pesuperstence + * of NFS exports + */ +static int get_sb_mtd_set(struct super_block *sb, void *_mtd) +{ + struct mtd_info *mtd = _mtd; + + sb->s_mtd = mtd; + sb->s_dev = MKDEV(MTD_BLOCK_MAJOR, mtd->index); + sb->s_bdi = mtd->backing_dev_info; + return 0; +} + +/* + * get a superblock on an MTD-backed filesystem + */ +static struct dentry *mount_mtd_aux(struct file_system_type *fs_type, int flags, + const char *dev_name, void *data, + struct mtd_info *mtd, + int (*fill_super)(struct super_block *, void *, int)) +{ + struct super_block *sb; + int ret; + + sb = sget(fs_type, get_sb_mtd_compare, get_sb_mtd_set, flags, mtd); + if (IS_ERR(sb)) + goto out_error; + + if (sb->s_root) + goto already_mounted; + + /* fresh new superblock */ + pr_debug("MTDSB: New superblock for device %d (\"%s\")\n", + mtd->index, mtd->name); + + ret = fill_super(sb, data, flags & MS_SILENT ? 1 : 0); + if (ret < 0) { + deactivate_locked_super(sb); + return ERR_PTR(ret); + } + + /* go */ + sb->s_flags |= MS_ACTIVE; + return dget(sb->s_root); + + /* new mountpoint for an already mounted superblock */ +already_mounted: + pr_debug("MTDSB: Device %d (\"%s\") is already mounted\n", + mtd->index, mtd->name); + put_mtd_device(mtd); + return dget(sb->s_root); + +out_error: + put_mtd_device(mtd); + return ERR_CAST(sb); +} + +/* + * get a superblock on an MTD-backed filesystem by MTD device number + */ +static struct dentry *mount_mtd_nr(struct file_system_type *fs_type, int flags, + const char *dev_name, void *data, int mtdnr, + int (*fill_super)(struct super_block *, void *, int)) +{ + struct mtd_info *mtd; + + mtd = get_mtd_device(NULL, mtdnr); + if (IS_ERR(mtd)) { + pr_debug("MTDSB: Device #%u doesn't appear to exist\n", mtdnr); + return ERR_CAST(mtd); + } + + return mount_mtd_aux(fs_type, flags, dev_name, data, mtd, fill_super); +} + +/* + * set up an MTD-based superblock + */ +struct dentry *mount_mtd(struct file_system_type *fs_type, int flags, + const char *dev_name, void *data, + int (*fill_super)(struct super_block *, void *, int)) +{ +#ifdef CONFIG_BLOCK + struct block_device *bdev; + int ret, major; +#endif + int mtdnr; + + if (!dev_name) + return ERR_PTR(-EINVAL); + + pr_debug("MTDSB: dev_name \"%s\"\n", dev_name); + + /* the preferred way of mounting in future; especially when + * CONFIG_BLOCK=n - we specify the underlying MTD device by number or + * by name, so that we don't require block device support to be present + * in the kernel. */ + if (dev_name[0] == 'm' && dev_name[1] == 't' && dev_name[2] == 'd') { + if (dev_name[3] == ':') { + struct mtd_info *mtd; + + /* mount by MTD device name */ + pr_debug("MTDSB: mtd:%%s, name \"%s\"\n", + dev_name + 4); + + mtd = get_mtd_device_nm(dev_name + 4); + if (!IS_ERR(mtd)) + return mount_mtd_aux( + fs_type, flags, + dev_name, data, mtd, + fill_super); + + printk(KERN_NOTICE "MTD:" + " MTD device with name \"%s\" not found.\n", + dev_name + 4); + + } else if (isdigit(dev_name[3])) { + /* mount by MTD device number name */ + char *endptr; + + mtdnr = simple_strtoul(dev_name + 3, &endptr, 0); + if (!*endptr) { + /* It was a valid number */ + pr_debug("MTDSB: mtd%%d, mtdnr %d\n", + mtdnr); + return mount_mtd_nr(fs_type, flags, + dev_name, data, + mtdnr, fill_super); + } + } + } + +#ifdef CONFIG_BLOCK + /* try the old way - the hack where we allowed users to mount + * /dev/mtdblock$(n) but didn't actually _use_ the blockdev + */ + bdev = lookup_bdev(dev_name); + if (IS_ERR(bdev)) { + ret = PTR_ERR(bdev); + pr_debug("MTDSB: lookup_bdev() returned %d\n", ret); + return ERR_PTR(ret); + } + pr_debug("MTDSB: lookup_bdev() returned 0\n"); + + ret = -EINVAL; + + major = MAJOR(bdev->bd_dev); + mtdnr = MINOR(bdev->bd_dev); + bdput(bdev); + + if (major != MTD_BLOCK_MAJOR) + goto not_an_MTD_device; + + return mount_mtd_nr(fs_type, flags, dev_name, data, mtdnr, fill_super); + +not_an_MTD_device: +#endif /* CONFIG_BLOCK */ + + if (!(flags & MS_SILENT)) + printk(KERN_NOTICE + "MTD: Attempt to mount non-MTD device \"%s\"\n", + dev_name); + return ERR_PTR(-EINVAL); +} + +EXPORT_SYMBOL_GPL(mount_mtd); + +/* + * destroy an MTD-based superblock + */ +void kill_mtd_super(struct super_block *sb) +{ + generic_shutdown_super(sb); + put_mtd_device(sb->s_mtd); + sb->s_mtd = NULL; +} + +EXPORT_SYMBOL_GPL(kill_mtd_super); diff --git a/drivers/mtd/mtdswap.c b/drivers/mtd/mtdswap.c new file mode 100644 index 00000000000..8b33b26eb12 --- /dev/null +++ b/drivers/mtd/mtdswap.c @@ -0,0 +1,1591 @@ +/* + * Swap block device support for MTDs + * Turns an MTD device into a swap device with block wear leveling + * + * Copyright © 2007,2011 Nokia Corporation. All rights reserved. + * + * Authors: Jarkko Lavinen <jarkko.lavinen@nokia.com> + * + * Based on Richard Purdie's earlier implementation in 2007. Background + * support and lock-less operation written by Adrian Hunter. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/blktrans.h> +#include <linux/rbtree.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/genhd.h> +#include <linux/swap.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/device.h> +#include <linux/math64.h> + +#define MTDSWAP_PREFIX "mtdswap" + +/* + * The number of free eraseblocks when GC should stop + */ +#define CLEAN_BLOCK_THRESHOLD 20 + +/* + * Number of free eraseblocks below which GC can also collect low frag + * blocks. + */ +#define LOW_FRAG_GC_TRESHOLD 5 + +/* + * Wear level cost amortization. We want to do wear leveling on the background + * without disturbing gc too much. This is made by defining max GC frequency. + * Frequency value 6 means 1/6 of the GC passes will pick an erase block based + * on the biggest wear difference rather than the biggest dirtiness. + * + * The lower freq2 should be chosen so that it makes sure the maximum erase + * difference will decrease even if a malicious application is deliberately + * trying to make erase differences large. + */ +#define MAX_ERASE_DIFF 4000 +#define COLLECT_NONDIRTY_BASE MAX_ERASE_DIFF +#define COLLECT_NONDIRTY_FREQ1 6 +#define COLLECT_NONDIRTY_FREQ2 4 + +#define PAGE_UNDEF UINT_MAX +#define BLOCK_UNDEF UINT_MAX +#define BLOCK_ERROR (UINT_MAX - 1) +#define BLOCK_MAX (UINT_MAX - 2) + +#define EBLOCK_BAD (1 << 0) +#define EBLOCK_NOMAGIC (1 << 1) +#define EBLOCK_BITFLIP (1 << 2) +#define EBLOCK_FAILED (1 << 3) +#define EBLOCK_READERR (1 << 4) +#define EBLOCK_IDX_SHIFT 5 + +struct swap_eb { + struct rb_node rb; + struct rb_root *root; + + unsigned int flags; + unsigned int active_count; + unsigned int erase_count; + unsigned int pad; /* speeds up pointer decrement */ +}; + +#define MTDSWAP_ECNT_MIN(rbroot) (rb_entry(rb_first(rbroot), struct swap_eb, \ + rb)->erase_count) +#define MTDSWAP_ECNT_MAX(rbroot) (rb_entry(rb_last(rbroot), struct swap_eb, \ + rb)->erase_count) + +struct mtdswap_tree { + struct rb_root root; + unsigned int count; +}; + +enum { + MTDSWAP_CLEAN, + MTDSWAP_USED, + MTDSWAP_LOWFRAG, + MTDSWAP_HIFRAG, + MTDSWAP_DIRTY, + MTDSWAP_BITFLIP, + MTDSWAP_FAILING, + MTDSWAP_TREE_CNT, +}; + +struct mtdswap_dev { + struct mtd_blktrans_dev *mbd_dev; + struct mtd_info *mtd; + struct device *dev; + + unsigned int *page_data; + unsigned int *revmap; + + unsigned int eblks; + unsigned int spare_eblks; + unsigned int pages_per_eblk; + unsigned int max_erase_count; + struct swap_eb *eb_data; + + struct mtdswap_tree trees[MTDSWAP_TREE_CNT]; + + unsigned long long sect_read_count; + unsigned long long sect_write_count; + unsigned long long mtd_write_count; + unsigned long long mtd_read_count; + unsigned long long discard_count; + unsigned long long discard_page_count; + + unsigned int curr_write_pos; + struct swap_eb *curr_write; + + char *page_buf; + char *oob_buf; + + struct dentry *debugfs_root; +}; + +struct mtdswap_oobdata { + __le16 magic; + __le32 count; +} __attribute__((packed)); + +#define MTDSWAP_MAGIC_CLEAN 0x2095 +#define MTDSWAP_MAGIC_DIRTY (MTDSWAP_MAGIC_CLEAN + 1) +#define MTDSWAP_TYPE_CLEAN 0 +#define MTDSWAP_TYPE_DIRTY 1 +#define MTDSWAP_OOBSIZE sizeof(struct mtdswap_oobdata) + +#define MTDSWAP_ERASE_RETRIES 3 /* Before marking erase block bad */ +#define MTDSWAP_IO_RETRIES 3 + +enum { + MTDSWAP_SCANNED_CLEAN, + MTDSWAP_SCANNED_DIRTY, + MTDSWAP_SCANNED_BITFLIP, + MTDSWAP_SCANNED_BAD, +}; + +/* + * In the worst case mtdswap_writesect() has allocated the last clean + * page from the current block and is then pre-empted by the GC + * thread. The thread can consume a full erase block when moving a + * block. + */ +#define MIN_SPARE_EBLOCKS 2 +#define MIN_ERASE_BLOCKS (MIN_SPARE_EBLOCKS + 1) + +#define TREE_ROOT(d, name) (&d->trees[MTDSWAP_ ## name].root) +#define TREE_EMPTY(d, name) (TREE_ROOT(d, name)->rb_node == NULL) +#define TREE_NONEMPTY(d, name) (!TREE_EMPTY(d, name)) +#define TREE_COUNT(d, name) (d->trees[MTDSWAP_ ## name].count) + +#define MTDSWAP_MBD_TO_MTDSWAP(dev) ((struct mtdswap_dev *)dev->priv) + +static char partitions[128] = ""; +module_param_string(partitions, partitions, sizeof(partitions), 0444); +MODULE_PARM_DESC(partitions, "MTD partition numbers to use as swap " + "partitions=\"1,3,5\""); + +static unsigned int spare_eblocks = 10; +module_param(spare_eblocks, uint, 0444); +MODULE_PARM_DESC(spare_eblocks, "Percentage of spare erase blocks for " + "garbage collection (default 10%)"); + +static bool header; /* false */ +module_param(header, bool, 0444); +MODULE_PARM_DESC(header, + "Include builtin swap header (default 0, without header)"); + +static int mtdswap_gc(struct mtdswap_dev *d, unsigned int background); + +static loff_t mtdswap_eb_offset(struct mtdswap_dev *d, struct swap_eb *eb) +{ + return (loff_t)(eb - d->eb_data) * d->mtd->erasesize; +} + +static void mtdswap_eb_detach(struct mtdswap_dev *d, struct swap_eb *eb) +{ + unsigned int oldidx; + struct mtdswap_tree *tp; + + if (eb->root) { + tp = container_of(eb->root, struct mtdswap_tree, root); + oldidx = tp - &d->trees[0]; + + d->trees[oldidx].count--; + rb_erase(&eb->rb, eb->root); + } +} + +static void __mtdswap_rb_add(struct rb_root *root, struct swap_eb *eb) +{ + struct rb_node **p, *parent = NULL; + struct swap_eb *cur; + + p = &root->rb_node; + while (*p) { + parent = *p; + cur = rb_entry(parent, struct swap_eb, rb); + if (eb->erase_count > cur->erase_count) + p = &(*p)->rb_right; + else + p = &(*p)->rb_left; + } + + rb_link_node(&eb->rb, parent, p); + rb_insert_color(&eb->rb, root); +} + +static void mtdswap_rb_add(struct mtdswap_dev *d, struct swap_eb *eb, int idx) +{ + struct rb_root *root; + + if (eb->root == &d->trees[idx].root) + return; + + mtdswap_eb_detach(d, eb); + root = &d->trees[idx].root; + __mtdswap_rb_add(root, eb); + eb->root = root; + d->trees[idx].count++; +} + +static struct rb_node *mtdswap_rb_index(struct rb_root *root, unsigned int idx) +{ + struct rb_node *p; + unsigned int i; + + p = rb_first(root); + i = 0; + while (i < idx && p) { + p = rb_next(p); + i++; + } + + return p; +} + +static int mtdswap_handle_badblock(struct mtdswap_dev *d, struct swap_eb *eb) +{ + int ret; + loff_t offset; + + d->spare_eblks--; + eb->flags |= EBLOCK_BAD; + mtdswap_eb_detach(d, eb); + eb->root = NULL; + + /* badblocks not supported */ + if (!mtd_can_have_bb(d->mtd)) + return 1; + + offset = mtdswap_eb_offset(d, eb); + dev_warn(d->dev, "Marking bad block at %08llx\n", offset); + ret = mtd_block_markbad(d->mtd, offset); + + if (ret) { + dev_warn(d->dev, "Mark block bad failed for block at %08llx " + "error %d\n", offset, ret); + return ret; + } + + return 1; + +} + +static int mtdswap_handle_write_error(struct mtdswap_dev *d, struct swap_eb *eb) +{ + unsigned int marked = eb->flags & EBLOCK_FAILED; + struct swap_eb *curr_write = d->curr_write; + + eb->flags |= EBLOCK_FAILED; + if (curr_write == eb) { + d->curr_write = NULL; + + if (!marked && d->curr_write_pos != 0) { + mtdswap_rb_add(d, eb, MTDSWAP_FAILING); + return 0; + } + } + + return mtdswap_handle_badblock(d, eb); +} + +static int mtdswap_read_oob(struct mtdswap_dev *d, loff_t from, + struct mtd_oob_ops *ops) +{ + int ret = mtd_read_oob(d->mtd, from, ops); + + if (mtd_is_bitflip(ret)) + return ret; + + if (ret) { + dev_warn(d->dev, "Read OOB failed %d for block at %08llx\n", + ret, from); + return ret; + } + + if (ops->oobretlen < ops->ooblen) { + dev_warn(d->dev, "Read OOB return short read (%zd bytes not " + "%zd) for block at %08llx\n", + ops->oobretlen, ops->ooblen, from); + return -EIO; + } + + return 0; +} + +static int mtdswap_read_markers(struct mtdswap_dev *d, struct swap_eb *eb) +{ + struct mtdswap_oobdata *data, *data2; + int ret; + loff_t offset; + struct mtd_oob_ops ops; + + offset = mtdswap_eb_offset(d, eb); + + /* Check first if the block is bad. */ + if (mtd_can_have_bb(d->mtd) && mtd_block_isbad(d->mtd, offset)) + return MTDSWAP_SCANNED_BAD; + + ops.ooblen = 2 * d->mtd->ecclayout->oobavail; + ops.oobbuf = d->oob_buf; + ops.ooboffs = 0; + ops.datbuf = NULL; + ops.mode = MTD_OPS_AUTO_OOB; + + ret = mtdswap_read_oob(d, offset, &ops); + + if (ret && !mtd_is_bitflip(ret)) + return ret; + + data = (struct mtdswap_oobdata *)d->oob_buf; + data2 = (struct mtdswap_oobdata *) + (d->oob_buf + d->mtd->ecclayout->oobavail); + + if (le16_to_cpu(data->magic) == MTDSWAP_MAGIC_CLEAN) { + eb->erase_count = le32_to_cpu(data->count); + if (mtd_is_bitflip(ret)) + ret = MTDSWAP_SCANNED_BITFLIP; + else { + if (le16_to_cpu(data2->magic) == MTDSWAP_MAGIC_DIRTY) + ret = MTDSWAP_SCANNED_DIRTY; + else + ret = MTDSWAP_SCANNED_CLEAN; + } + } else { + eb->flags |= EBLOCK_NOMAGIC; + ret = MTDSWAP_SCANNED_DIRTY; + } + + return ret; +} + +static int mtdswap_write_marker(struct mtdswap_dev *d, struct swap_eb *eb, + u16 marker) +{ + struct mtdswap_oobdata n; + int ret; + loff_t offset; + struct mtd_oob_ops ops; + + ops.ooboffs = 0; + ops.oobbuf = (uint8_t *)&n; + ops.mode = MTD_OPS_AUTO_OOB; + ops.datbuf = NULL; + + if (marker == MTDSWAP_TYPE_CLEAN) { + n.magic = cpu_to_le16(MTDSWAP_MAGIC_CLEAN); + n.count = cpu_to_le32(eb->erase_count); + ops.ooblen = MTDSWAP_OOBSIZE; + offset = mtdswap_eb_offset(d, eb); + } else { + n.magic = cpu_to_le16(MTDSWAP_MAGIC_DIRTY); + ops.ooblen = sizeof(n.magic); + offset = mtdswap_eb_offset(d, eb) + d->mtd->writesize; + } + + ret = mtd_write_oob(d->mtd, offset, &ops); + + if (ret) { + dev_warn(d->dev, "Write OOB failed for block at %08llx " + "error %d\n", offset, ret); + if (ret == -EIO || mtd_is_eccerr(ret)) + mtdswap_handle_write_error(d, eb); + return ret; + } + + if (ops.oobretlen != ops.ooblen) { + dev_warn(d->dev, "Short OOB write for block at %08llx: " + "%zd not %zd\n", + offset, ops.oobretlen, ops.ooblen); + return ret; + } + + return 0; +} + +/* + * Are there any erase blocks without MAGIC_CLEAN header, presumably + * because power was cut off after erase but before header write? We + * need to guestimate the erase count. + */ +static void mtdswap_check_counts(struct mtdswap_dev *d) +{ + struct rb_root hist_root = RB_ROOT; + struct rb_node *medrb; + struct swap_eb *eb; + unsigned int i, cnt, median; + + cnt = 0; + for (i = 0; i < d->eblks; i++) { + eb = d->eb_data + i; + + if (eb->flags & (EBLOCK_NOMAGIC | EBLOCK_BAD | EBLOCK_READERR)) + continue; + + __mtdswap_rb_add(&hist_root, eb); + cnt++; + } + + if (cnt == 0) + return; + + medrb = mtdswap_rb_index(&hist_root, cnt / 2); + median = rb_entry(medrb, struct swap_eb, rb)->erase_count; + + d->max_erase_count = MTDSWAP_ECNT_MAX(&hist_root); + + for (i = 0; i < d->eblks; i++) { + eb = d->eb_data + i; + + if (eb->flags & (EBLOCK_NOMAGIC | EBLOCK_READERR)) + eb->erase_count = median; + + if (eb->flags & (EBLOCK_NOMAGIC | EBLOCK_BAD | EBLOCK_READERR)) + continue; + + rb_erase(&eb->rb, &hist_root); + } +} + +static void mtdswap_scan_eblks(struct mtdswap_dev *d) +{ + int status; + unsigned int i, idx; + struct swap_eb *eb; + + for (i = 0; i < d->eblks; i++) { + eb = d->eb_data + i; + + status = mtdswap_read_markers(d, eb); + if (status < 0) + eb->flags |= EBLOCK_READERR; + else if (status == MTDSWAP_SCANNED_BAD) { + eb->flags |= EBLOCK_BAD; + continue; + } + + switch (status) { + case MTDSWAP_SCANNED_CLEAN: + idx = MTDSWAP_CLEAN; + break; + case MTDSWAP_SCANNED_DIRTY: + case MTDSWAP_SCANNED_BITFLIP: + idx = MTDSWAP_DIRTY; + break; + default: + idx = MTDSWAP_FAILING; + } + + eb->flags |= (idx << EBLOCK_IDX_SHIFT); + } + + mtdswap_check_counts(d); + + for (i = 0; i < d->eblks; i++) { + eb = d->eb_data + i; + + if (eb->flags & EBLOCK_BAD) + continue; + + idx = eb->flags >> EBLOCK_IDX_SHIFT; + mtdswap_rb_add(d, eb, idx); + } +} + +/* + * Place eblk into a tree corresponding to its number of active blocks + * it contains. + */ +static void mtdswap_store_eb(struct mtdswap_dev *d, struct swap_eb *eb) +{ + unsigned int weight = eb->active_count; + unsigned int maxweight = d->pages_per_eblk; + + if (eb == d->curr_write) + return; + + if (eb->flags & EBLOCK_BITFLIP) + mtdswap_rb_add(d, eb, MTDSWAP_BITFLIP); + else if (eb->flags & (EBLOCK_READERR | EBLOCK_FAILED)) + mtdswap_rb_add(d, eb, MTDSWAP_FAILING); + if (weight == maxweight) + mtdswap_rb_add(d, eb, MTDSWAP_USED); + else if (weight == 0) + mtdswap_rb_add(d, eb, MTDSWAP_DIRTY); + else if (weight > (maxweight/2)) + mtdswap_rb_add(d, eb, MTDSWAP_LOWFRAG); + else + mtdswap_rb_add(d, eb, MTDSWAP_HIFRAG); +} + + +static void mtdswap_erase_callback(struct erase_info *done) +{ + wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv; + wake_up(wait_q); +} + +static int mtdswap_erase_block(struct mtdswap_dev *d, struct swap_eb *eb) +{ + struct mtd_info *mtd = d->mtd; + struct erase_info erase; + wait_queue_head_t wq; + unsigned int retries = 0; + int ret; + + eb->erase_count++; + if (eb->erase_count > d->max_erase_count) + d->max_erase_count = eb->erase_count; + +retry: + init_waitqueue_head(&wq); + memset(&erase, 0, sizeof(struct erase_info)); + + erase.mtd = mtd; + erase.callback = mtdswap_erase_callback; + erase.addr = mtdswap_eb_offset(d, eb); + erase.len = mtd->erasesize; + erase.priv = (u_long)&wq; + + ret = mtd_erase(mtd, &erase); + if (ret) { + if (retries++ < MTDSWAP_ERASE_RETRIES) { + dev_warn(d->dev, + "erase of erase block %#llx on %s failed", + erase.addr, mtd->name); + yield(); + goto retry; + } + + dev_err(d->dev, "Cannot erase erase block %#llx on %s\n", + erase.addr, mtd->name); + + mtdswap_handle_badblock(d, eb); + return -EIO; + } + + ret = wait_event_interruptible(wq, erase.state == MTD_ERASE_DONE || + erase.state == MTD_ERASE_FAILED); + if (ret) { + dev_err(d->dev, "Interrupted erase block %#llx erassure on %s", + erase.addr, mtd->name); + return -EINTR; + } + + if (erase.state == MTD_ERASE_FAILED) { + if (retries++ < MTDSWAP_ERASE_RETRIES) { + dev_warn(d->dev, + "erase of erase block %#llx on %s failed", + erase.addr, mtd->name); + yield(); + goto retry; + } + + mtdswap_handle_badblock(d, eb); + return -EIO; + } + + return 0; +} + +static int mtdswap_map_free_block(struct mtdswap_dev *d, unsigned int page, + unsigned int *block) +{ + int ret; + struct swap_eb *old_eb = d->curr_write; + struct rb_root *clean_root; + struct swap_eb *eb; + + if (old_eb == NULL || d->curr_write_pos >= d->pages_per_eblk) { + do { + if (TREE_EMPTY(d, CLEAN)) + return -ENOSPC; + + clean_root = TREE_ROOT(d, CLEAN); + eb = rb_entry(rb_first(clean_root), struct swap_eb, rb); + rb_erase(&eb->rb, clean_root); + eb->root = NULL; + TREE_COUNT(d, CLEAN)--; + + ret = mtdswap_write_marker(d, eb, MTDSWAP_TYPE_DIRTY); + } while (ret == -EIO || mtd_is_eccerr(ret)); + + if (ret) + return ret; + + d->curr_write_pos = 0; + d->curr_write = eb; + if (old_eb) + mtdswap_store_eb(d, old_eb); + } + + *block = (d->curr_write - d->eb_data) * d->pages_per_eblk + + d->curr_write_pos; + + d->curr_write->active_count++; + d->revmap[*block] = page; + d->curr_write_pos++; + + return 0; +} + +static unsigned int mtdswap_free_page_cnt(struct mtdswap_dev *d) +{ + return TREE_COUNT(d, CLEAN) * d->pages_per_eblk + + d->pages_per_eblk - d->curr_write_pos; +} + +static unsigned int mtdswap_enough_free_pages(struct mtdswap_dev *d) +{ + return mtdswap_free_page_cnt(d) > d->pages_per_eblk; +} + +static int mtdswap_write_block(struct mtdswap_dev *d, char *buf, + unsigned int page, unsigned int *bp, int gc_context) +{ + struct mtd_info *mtd = d->mtd; + struct swap_eb *eb; + size_t retlen; + loff_t writepos; + int ret; + +retry: + if (!gc_context) + while (!mtdswap_enough_free_pages(d)) + if (mtdswap_gc(d, 0) > 0) + return -ENOSPC; + + ret = mtdswap_map_free_block(d, page, bp); + eb = d->eb_data + (*bp / d->pages_per_eblk); + + if (ret == -EIO || mtd_is_eccerr(ret)) { + d->curr_write = NULL; + eb->active_count--; + d->revmap[*bp] = PAGE_UNDEF; + goto retry; + } + + if (ret < 0) + return ret; + + writepos = (loff_t)*bp << PAGE_SHIFT; + ret = mtd_write(mtd, writepos, PAGE_SIZE, &retlen, buf); + if (ret == -EIO || mtd_is_eccerr(ret)) { + d->curr_write_pos--; + eb->active_count--; + d->revmap[*bp] = PAGE_UNDEF; + mtdswap_handle_write_error(d, eb); + goto retry; + } + + if (ret < 0) { + dev_err(d->dev, "Write to MTD device failed: %d (%zd written)", + ret, retlen); + goto err; + } + + if (retlen != PAGE_SIZE) { + dev_err(d->dev, "Short write to MTD device: %zd written", + retlen); + ret = -EIO; + goto err; + } + + return ret; + +err: + d->curr_write_pos--; + eb->active_count--; + d->revmap[*bp] = PAGE_UNDEF; + + return ret; +} + +static int mtdswap_move_block(struct mtdswap_dev *d, unsigned int oldblock, + unsigned int *newblock) +{ + struct mtd_info *mtd = d->mtd; + struct swap_eb *eb, *oldeb; + int ret; + size_t retlen; + unsigned int page, retries; + loff_t readpos; + + page = d->revmap[oldblock]; + readpos = (loff_t) oldblock << PAGE_SHIFT; + retries = 0; + +retry: + ret = mtd_read(mtd, readpos, PAGE_SIZE, &retlen, d->page_buf); + + if (ret < 0 && !mtd_is_bitflip(ret)) { + oldeb = d->eb_data + oldblock / d->pages_per_eblk; + oldeb->flags |= EBLOCK_READERR; + + dev_err(d->dev, "Read Error: %d (block %u)\n", ret, + oldblock); + retries++; + if (retries < MTDSWAP_IO_RETRIES) + goto retry; + + goto read_error; + } + + if (retlen != PAGE_SIZE) { + dev_err(d->dev, "Short read: %zd (block %u)\n", retlen, + oldblock); + ret = -EIO; + goto read_error; + } + + ret = mtdswap_write_block(d, d->page_buf, page, newblock, 1); + if (ret < 0) { + d->page_data[page] = BLOCK_ERROR; + dev_err(d->dev, "Write error: %d\n", ret); + return ret; + } + + eb = d->eb_data + *newblock / d->pages_per_eblk; + d->page_data[page] = *newblock; + d->revmap[oldblock] = PAGE_UNDEF; + eb = d->eb_data + oldblock / d->pages_per_eblk; + eb->active_count--; + + return 0; + +read_error: + d->page_data[page] = BLOCK_ERROR; + d->revmap[oldblock] = PAGE_UNDEF; + return ret; +} + +static int mtdswap_gc_eblock(struct mtdswap_dev *d, struct swap_eb *eb) +{ + unsigned int i, block, eblk_base, newblock; + int ret, errcode; + + errcode = 0; + eblk_base = (eb - d->eb_data) * d->pages_per_eblk; + + for (i = 0; i < d->pages_per_eblk; i++) { + if (d->spare_eblks < MIN_SPARE_EBLOCKS) + return -ENOSPC; + + block = eblk_base + i; + if (d->revmap[block] == PAGE_UNDEF) + continue; + + ret = mtdswap_move_block(d, block, &newblock); + if (ret < 0 && !errcode) + errcode = ret; + } + + return errcode; +} + +static int __mtdswap_choose_gc_tree(struct mtdswap_dev *d) +{ + int idx, stopat; + + if (TREE_COUNT(d, CLEAN) < LOW_FRAG_GC_TRESHOLD) + stopat = MTDSWAP_LOWFRAG; + else + stopat = MTDSWAP_HIFRAG; + + for (idx = MTDSWAP_BITFLIP; idx >= stopat; idx--) + if (d->trees[idx].root.rb_node != NULL) + return idx; + + return -1; +} + +static int mtdswap_wlfreq(unsigned int maxdiff) +{ + unsigned int h, x, y, dist, base; + + /* + * Calculate linear ramp down from f1 to f2 when maxdiff goes from + * MAX_ERASE_DIFF to MAX_ERASE_DIFF + COLLECT_NONDIRTY_BASE. Similar + * to triangle with height f1 - f1 and width COLLECT_NONDIRTY_BASE. + */ + + dist = maxdiff - MAX_ERASE_DIFF; + if (dist > COLLECT_NONDIRTY_BASE) + dist = COLLECT_NONDIRTY_BASE; + + /* + * Modelling the slop as right angular triangle with base + * COLLECT_NONDIRTY_BASE and height freq1 - freq2. The ratio y/x is + * equal to the ratio h/base. + */ + h = COLLECT_NONDIRTY_FREQ1 - COLLECT_NONDIRTY_FREQ2; + base = COLLECT_NONDIRTY_BASE; + + x = dist - base; + y = (x * h + base / 2) / base; + + return COLLECT_NONDIRTY_FREQ2 + y; +} + +static int mtdswap_choose_wl_tree(struct mtdswap_dev *d) +{ + static unsigned int pick_cnt; + unsigned int i, idx = -1, wear, max; + struct rb_root *root; + + max = 0; + for (i = 0; i <= MTDSWAP_DIRTY; i++) { + root = &d->trees[i].root; + if (root->rb_node == NULL) + continue; + + wear = d->max_erase_count - MTDSWAP_ECNT_MIN(root); + if (wear > max) { + max = wear; + idx = i; + } + } + + if (max > MAX_ERASE_DIFF && pick_cnt >= mtdswap_wlfreq(max) - 1) { + pick_cnt = 0; + return idx; + } + + pick_cnt++; + return -1; +} + +static int mtdswap_choose_gc_tree(struct mtdswap_dev *d, + unsigned int background) +{ + int idx; + + if (TREE_NONEMPTY(d, FAILING) && + (background || (TREE_EMPTY(d, CLEAN) && TREE_EMPTY(d, DIRTY)))) + return MTDSWAP_FAILING; + + idx = mtdswap_choose_wl_tree(d); + if (idx >= MTDSWAP_CLEAN) + return idx; + + return __mtdswap_choose_gc_tree(d); +} + +static struct swap_eb *mtdswap_pick_gc_eblk(struct mtdswap_dev *d, + unsigned int background) +{ + struct rb_root *rp = NULL; + struct swap_eb *eb = NULL; + int idx; + + if (background && TREE_COUNT(d, CLEAN) > CLEAN_BLOCK_THRESHOLD && + TREE_EMPTY(d, DIRTY) && TREE_EMPTY(d, FAILING)) + return NULL; + + idx = mtdswap_choose_gc_tree(d, background); + if (idx < 0) + return NULL; + + rp = &d->trees[idx].root; + eb = rb_entry(rb_first(rp), struct swap_eb, rb); + + rb_erase(&eb->rb, rp); + eb->root = NULL; + d->trees[idx].count--; + return eb; +} + +static unsigned int mtdswap_test_patt(unsigned int i) +{ + return i % 2 ? 0x55555555 : 0xAAAAAAAA; +} + +static unsigned int mtdswap_eblk_passes(struct mtdswap_dev *d, + struct swap_eb *eb) +{ + struct mtd_info *mtd = d->mtd; + unsigned int test, i, j, patt, mtd_pages; + loff_t base, pos; + unsigned int *p1 = (unsigned int *)d->page_buf; + unsigned char *p2 = (unsigned char *)d->oob_buf; + struct mtd_oob_ops ops; + int ret; + + ops.mode = MTD_OPS_AUTO_OOB; + ops.len = mtd->writesize; + ops.ooblen = mtd->ecclayout->oobavail; + ops.ooboffs = 0; + ops.datbuf = d->page_buf; + ops.oobbuf = d->oob_buf; + base = mtdswap_eb_offset(d, eb); + mtd_pages = d->pages_per_eblk * PAGE_SIZE / mtd->writesize; + + for (test = 0; test < 2; test++) { + pos = base; + for (i = 0; i < mtd_pages; i++) { + patt = mtdswap_test_patt(test + i); + memset(d->page_buf, patt, mtd->writesize); + memset(d->oob_buf, patt, mtd->ecclayout->oobavail); + ret = mtd_write_oob(mtd, pos, &ops); + if (ret) + goto error; + + pos += mtd->writesize; + } + + pos = base; + for (i = 0; i < mtd_pages; i++) { + ret = mtd_read_oob(mtd, pos, &ops); + if (ret) + goto error; + + patt = mtdswap_test_patt(test + i); + for (j = 0; j < mtd->writesize/sizeof(int); j++) + if (p1[j] != patt) + goto error; + + for (j = 0; j < mtd->ecclayout->oobavail; j++) + if (p2[j] != (unsigned char)patt) + goto error; + + pos += mtd->writesize; + } + + ret = mtdswap_erase_block(d, eb); + if (ret) + goto error; + } + + eb->flags &= ~EBLOCK_READERR; + return 1; + +error: + mtdswap_handle_badblock(d, eb); + return 0; +} + +static int mtdswap_gc(struct mtdswap_dev *d, unsigned int background) +{ + struct swap_eb *eb; + int ret; + + if (d->spare_eblks < MIN_SPARE_EBLOCKS) + return 1; + + eb = mtdswap_pick_gc_eblk(d, background); + if (!eb) + return 1; + + ret = mtdswap_gc_eblock(d, eb); + if (ret == -ENOSPC) + return 1; + + if (eb->flags & EBLOCK_FAILED) { + mtdswap_handle_badblock(d, eb); + return 0; + } + + eb->flags &= ~EBLOCK_BITFLIP; + ret = mtdswap_erase_block(d, eb); + if ((eb->flags & EBLOCK_READERR) && + (ret || !mtdswap_eblk_passes(d, eb))) + return 0; + + if (ret == 0) + ret = mtdswap_write_marker(d, eb, MTDSWAP_TYPE_CLEAN); + + if (ret == 0) + mtdswap_rb_add(d, eb, MTDSWAP_CLEAN); + else if (ret != -EIO && !mtd_is_eccerr(ret)) + mtdswap_rb_add(d, eb, MTDSWAP_DIRTY); + + return 0; +} + +static void mtdswap_background(struct mtd_blktrans_dev *dev) +{ + struct mtdswap_dev *d = MTDSWAP_MBD_TO_MTDSWAP(dev); + int ret; + + while (1) { + ret = mtdswap_gc(d, 1); + if (ret || mtd_blktrans_cease_background(dev)) + return; + } +} + +static void mtdswap_cleanup(struct mtdswap_dev *d) +{ + vfree(d->eb_data); + vfree(d->revmap); + vfree(d->page_data); + kfree(d->oob_buf); + kfree(d->page_buf); +} + +static int mtdswap_flush(struct mtd_blktrans_dev *dev) +{ + struct mtdswap_dev *d = MTDSWAP_MBD_TO_MTDSWAP(dev); + + mtd_sync(d->mtd); + return 0; +} + +static unsigned int mtdswap_badblocks(struct mtd_info *mtd, uint64_t size) +{ + loff_t offset; + unsigned int badcnt; + + badcnt = 0; + + if (mtd_can_have_bb(mtd)) + for (offset = 0; offset < size; offset += mtd->erasesize) + if (mtd_block_isbad(mtd, offset)) + badcnt++; + + return badcnt; +} + +static int mtdswap_writesect(struct mtd_blktrans_dev *dev, + unsigned long page, char *buf) +{ + struct mtdswap_dev *d = MTDSWAP_MBD_TO_MTDSWAP(dev); + unsigned int newblock, mapped; + struct swap_eb *eb; + int ret; + + d->sect_write_count++; + + if (d->spare_eblks < MIN_SPARE_EBLOCKS) + return -ENOSPC; + + if (header) { + /* Ignore writes to the header page */ + if (unlikely(page == 0)) + return 0; + + page--; + } + + mapped = d->page_data[page]; + if (mapped <= BLOCK_MAX) { + eb = d->eb_data + (mapped / d->pages_per_eblk); + eb->active_count--; + mtdswap_store_eb(d, eb); + d->page_data[page] = BLOCK_UNDEF; + d->revmap[mapped] = PAGE_UNDEF; + } + + ret = mtdswap_write_block(d, buf, page, &newblock, 0); + d->mtd_write_count++; + + if (ret < 0) + return ret; + + eb = d->eb_data + (newblock / d->pages_per_eblk); + d->page_data[page] = newblock; + + return 0; +} + +/* Provide a dummy swap header for the kernel */ +static int mtdswap_auto_header(struct mtdswap_dev *d, char *buf) +{ + union swap_header *hd = (union swap_header *)(buf); + + memset(buf, 0, PAGE_SIZE - 10); + + hd->info.version = 1; + hd->info.last_page = d->mbd_dev->size - 1; + hd->info.nr_badpages = 0; + + memcpy(buf + PAGE_SIZE - 10, "SWAPSPACE2", 10); + + return 0; +} + +static int mtdswap_readsect(struct mtd_blktrans_dev *dev, + unsigned long page, char *buf) +{ + struct mtdswap_dev *d = MTDSWAP_MBD_TO_MTDSWAP(dev); + struct mtd_info *mtd = d->mtd; + unsigned int realblock, retries; + loff_t readpos; + struct swap_eb *eb; + size_t retlen; + int ret; + + d->sect_read_count++; + + if (header) { + if (unlikely(page == 0)) + return mtdswap_auto_header(d, buf); + + page--; + } + + realblock = d->page_data[page]; + if (realblock > BLOCK_MAX) { + memset(buf, 0x0, PAGE_SIZE); + if (realblock == BLOCK_UNDEF) + return 0; + else + return -EIO; + } + + eb = d->eb_data + (realblock / d->pages_per_eblk); + BUG_ON(d->revmap[realblock] == PAGE_UNDEF); + + readpos = (loff_t)realblock << PAGE_SHIFT; + retries = 0; + +retry: + ret = mtd_read(mtd, readpos, PAGE_SIZE, &retlen, buf); + + d->mtd_read_count++; + if (mtd_is_bitflip(ret)) { + eb->flags |= EBLOCK_BITFLIP; + mtdswap_rb_add(d, eb, MTDSWAP_BITFLIP); + ret = 0; + } + + if (ret < 0) { + dev_err(d->dev, "Read error %d\n", ret); + eb->flags |= EBLOCK_READERR; + mtdswap_rb_add(d, eb, MTDSWAP_FAILING); + retries++; + if (retries < MTDSWAP_IO_RETRIES) + goto retry; + + return ret; + } + + if (retlen != PAGE_SIZE) { + dev_err(d->dev, "Short read %zd\n", retlen); + return -EIO; + } + + return 0; +} + +static int mtdswap_discard(struct mtd_blktrans_dev *dev, unsigned long first, + unsigned nr_pages) +{ + struct mtdswap_dev *d = MTDSWAP_MBD_TO_MTDSWAP(dev); + unsigned long page; + struct swap_eb *eb; + unsigned int mapped; + + d->discard_count++; + + for (page = first; page < first + nr_pages; page++) { + mapped = d->page_data[page]; + if (mapped <= BLOCK_MAX) { + eb = d->eb_data + (mapped / d->pages_per_eblk); + eb->active_count--; + mtdswap_store_eb(d, eb); + d->page_data[page] = BLOCK_UNDEF; + d->revmap[mapped] = PAGE_UNDEF; + d->discard_page_count++; + } else if (mapped == BLOCK_ERROR) { + d->page_data[page] = BLOCK_UNDEF; + d->discard_page_count++; + } + } + + return 0; +} + +static int mtdswap_show(struct seq_file *s, void *data) +{ + struct mtdswap_dev *d = (struct mtdswap_dev *) s->private; + unsigned long sum; + unsigned int count[MTDSWAP_TREE_CNT]; + unsigned int min[MTDSWAP_TREE_CNT]; + unsigned int max[MTDSWAP_TREE_CNT]; + unsigned int i, cw = 0, cwp = 0, cwecount = 0, bb_cnt, mapped, pages; + uint64_t use_size; + char *name[] = {"clean", "used", "low", "high", "dirty", "bitflip", + "failing"}; + + mutex_lock(&d->mbd_dev->lock); + + for (i = 0; i < MTDSWAP_TREE_CNT; i++) { + struct rb_root *root = &d->trees[i].root; + + if (root->rb_node) { + count[i] = d->trees[i].count; + min[i] = rb_entry(rb_first(root), struct swap_eb, + rb)->erase_count; + max[i] = rb_entry(rb_last(root), struct swap_eb, + rb)->erase_count; + } else + count[i] = 0; + } + + if (d->curr_write) { + cw = 1; + cwp = d->curr_write_pos; + cwecount = d->curr_write->erase_count; + } + + sum = 0; + for (i = 0; i < d->eblks; i++) + sum += d->eb_data[i].erase_count; + + use_size = (uint64_t)d->eblks * d->mtd->erasesize; + bb_cnt = mtdswap_badblocks(d->mtd, use_size); + + mapped = 0; + pages = d->mbd_dev->size; + for (i = 0; i < pages; i++) + if (d->page_data[i] != BLOCK_UNDEF) + mapped++; + + mutex_unlock(&d->mbd_dev->lock); + + for (i = 0; i < MTDSWAP_TREE_CNT; i++) { + if (!count[i]) + continue; + + if (min[i] != max[i]) + seq_printf(s, "%s:\t%5d erase blocks, erased min %d, " + "max %d times\n", + name[i], count[i], min[i], max[i]); + else + seq_printf(s, "%s:\t%5d erase blocks, all erased %d " + "times\n", name[i], count[i], min[i]); + } + + if (bb_cnt) + seq_printf(s, "bad:\t%5u erase blocks\n", bb_cnt); + + if (cw) + seq_printf(s, "current erase block: %u pages used, %u free, " + "erased %u times\n", + cwp, d->pages_per_eblk - cwp, cwecount); + + seq_printf(s, "total erasures: %lu\n", sum); + + seq_printf(s, "\n"); + + seq_printf(s, "mtdswap_readsect count: %llu\n", d->sect_read_count); + seq_printf(s, "mtdswap_writesect count: %llu\n", d->sect_write_count); + seq_printf(s, "mtdswap_discard count: %llu\n", d->discard_count); + seq_printf(s, "mtd read count: %llu\n", d->mtd_read_count); + seq_printf(s, "mtd write count: %llu\n", d->mtd_write_count); + seq_printf(s, "discarded pages count: %llu\n", d->discard_page_count); + + seq_printf(s, "\n"); + seq_printf(s, "total pages: %u\n", pages); + seq_printf(s, "pages mapped: %u\n", mapped); + + return 0; +} + +static int mtdswap_open(struct inode *inode, struct file *file) +{ + return single_open(file, mtdswap_show, inode->i_private); +} + +static const struct file_operations mtdswap_fops = { + .open = mtdswap_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int mtdswap_add_debugfs(struct mtdswap_dev *d) +{ + struct gendisk *gd = d->mbd_dev->disk; + struct device *dev = disk_to_dev(gd); + + struct dentry *root; + struct dentry *dent; + + root = debugfs_create_dir(gd->disk_name, NULL); + if (IS_ERR(root)) + return 0; + + if (!root) { + dev_err(dev, "failed to initialize debugfs\n"); + return -1; + } + + d->debugfs_root = root; + + dent = debugfs_create_file("stats", S_IRUSR, root, d, + &mtdswap_fops); + if (!dent) { + dev_err(d->dev, "debugfs_create_file failed\n"); + debugfs_remove_recursive(root); + d->debugfs_root = NULL; + return -1; + } + + return 0; +} + +static int mtdswap_init(struct mtdswap_dev *d, unsigned int eblocks, + unsigned int spare_cnt) +{ + struct mtd_info *mtd = d->mbd_dev->mtd; + unsigned int i, eblk_bytes, pages, blocks; + int ret = -ENOMEM; + + d->mtd = mtd; + d->eblks = eblocks; + d->spare_eblks = spare_cnt; + d->pages_per_eblk = mtd->erasesize >> PAGE_SHIFT; + + pages = d->mbd_dev->size; + blocks = eblocks * d->pages_per_eblk; + + for (i = 0; i < MTDSWAP_TREE_CNT; i++) + d->trees[i].root = RB_ROOT; + + d->page_data = vmalloc(sizeof(int)*pages); + if (!d->page_data) + goto page_data_fail; + + d->revmap = vmalloc(sizeof(int)*blocks); + if (!d->revmap) + goto revmap_fail; + + eblk_bytes = sizeof(struct swap_eb)*d->eblks; + d->eb_data = vzalloc(eblk_bytes); + if (!d->eb_data) + goto eb_data_fail; + + for (i = 0; i < pages; i++) + d->page_data[i] = BLOCK_UNDEF; + + for (i = 0; i < blocks; i++) + d->revmap[i] = PAGE_UNDEF; + + d->page_buf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!d->page_buf) + goto page_buf_fail; + + d->oob_buf = kmalloc(2 * mtd->ecclayout->oobavail, GFP_KERNEL); + if (!d->oob_buf) + goto oob_buf_fail; + + mtdswap_scan_eblks(d); + + return 0; + +oob_buf_fail: + kfree(d->page_buf); +page_buf_fail: + vfree(d->eb_data); +eb_data_fail: + vfree(d->revmap); +revmap_fail: + vfree(d->page_data); +page_data_fail: + printk(KERN_ERR "%s: init failed (%d)\n", MTDSWAP_PREFIX, ret); + return ret; +} + +static void mtdswap_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) +{ + struct mtdswap_dev *d; + struct mtd_blktrans_dev *mbd_dev; + char *parts; + char *this_opt; + unsigned long part; + unsigned int eblocks, eavailable, bad_blocks, spare_cnt; + uint64_t swap_size, use_size, size_limit; + struct nand_ecclayout *oinfo; + int ret; + + parts = &partitions[0]; + if (!*parts) + return; + + while ((this_opt = strsep(&parts, ",")) != NULL) { + if (kstrtoul(this_opt, 0, &part) < 0) + return; + + if (mtd->index == part) + break; + } + + if (mtd->index != part) + return; + + if (mtd->erasesize < PAGE_SIZE || mtd->erasesize % PAGE_SIZE) { + printk(KERN_ERR "%s: Erase size %u not multiple of PAGE_SIZE " + "%lu\n", MTDSWAP_PREFIX, mtd->erasesize, PAGE_SIZE); + return; + } + + if (PAGE_SIZE % mtd->writesize || mtd->writesize > PAGE_SIZE) { + printk(KERN_ERR "%s: PAGE_SIZE %lu not multiple of write size" + " %u\n", MTDSWAP_PREFIX, PAGE_SIZE, mtd->writesize); + return; + } + + oinfo = mtd->ecclayout; + if (!oinfo) { + printk(KERN_ERR "%s: mtd%d does not have OOB\n", + MTDSWAP_PREFIX, mtd->index); + return; + } + + if (!mtd->oobsize || oinfo->oobavail < MTDSWAP_OOBSIZE) { + printk(KERN_ERR "%s: Not enough free bytes in OOB, " + "%d available, %zu needed.\n", + MTDSWAP_PREFIX, oinfo->oobavail, MTDSWAP_OOBSIZE); + return; + } + + if (spare_eblocks > 100) + spare_eblocks = 100; + + use_size = mtd->size; + size_limit = (uint64_t) BLOCK_MAX * PAGE_SIZE; + + if (mtd->size > size_limit) { + printk(KERN_WARNING "%s: Device too large. Limiting size to " + "%llu bytes\n", MTDSWAP_PREFIX, size_limit); + use_size = size_limit; + } + + eblocks = mtd_div_by_eb(use_size, mtd); + use_size = eblocks * mtd->erasesize; + bad_blocks = mtdswap_badblocks(mtd, use_size); + eavailable = eblocks - bad_blocks; + + if (eavailable < MIN_ERASE_BLOCKS) { + printk(KERN_ERR "%s: Not enough erase blocks. %u available, " + "%d needed\n", MTDSWAP_PREFIX, eavailable, + MIN_ERASE_BLOCKS); + return; + } + + spare_cnt = div_u64((uint64_t)eavailable * spare_eblocks, 100); + + if (spare_cnt < MIN_SPARE_EBLOCKS) + spare_cnt = MIN_SPARE_EBLOCKS; + + if (spare_cnt > eavailable - 1) + spare_cnt = eavailable - 1; + + swap_size = (uint64_t)(eavailable - spare_cnt) * mtd->erasesize + + (header ? PAGE_SIZE : 0); + + printk(KERN_INFO "%s: Enabling MTD swap on device %lu, size %llu KB, " + "%u spare, %u bad blocks\n", + MTDSWAP_PREFIX, part, swap_size / 1024, spare_cnt, bad_blocks); + + d = kzalloc(sizeof(struct mtdswap_dev), GFP_KERNEL); + if (!d) + return; + + mbd_dev = kzalloc(sizeof(struct mtd_blktrans_dev), GFP_KERNEL); + if (!mbd_dev) { + kfree(d); + return; + } + + d->mbd_dev = mbd_dev; + mbd_dev->priv = d; + + mbd_dev->mtd = mtd; + mbd_dev->devnum = mtd->index; + mbd_dev->size = swap_size >> PAGE_SHIFT; + mbd_dev->tr = tr; + + if (!(mtd->flags & MTD_WRITEABLE)) + mbd_dev->readonly = 1; + + if (mtdswap_init(d, eblocks, spare_cnt) < 0) + goto init_failed; + + if (add_mtd_blktrans_dev(mbd_dev) < 0) + goto cleanup; + + d->dev = disk_to_dev(mbd_dev->disk); + + ret = mtdswap_add_debugfs(d); + if (ret < 0) + goto debugfs_failed; + + return; + +debugfs_failed: + del_mtd_blktrans_dev(mbd_dev); + +cleanup: + mtdswap_cleanup(d); + +init_failed: + kfree(mbd_dev); + kfree(d); +} + +static void mtdswap_remove_dev(struct mtd_blktrans_dev *dev) +{ + struct mtdswap_dev *d = MTDSWAP_MBD_TO_MTDSWAP(dev); + + debugfs_remove_recursive(d->debugfs_root); + del_mtd_blktrans_dev(dev); + mtdswap_cleanup(d); + kfree(d); +} + +static struct mtd_blktrans_ops mtdswap_ops = { + .name = "mtdswap", + .major = 0, + .part_bits = 0, + .blksize = PAGE_SIZE, + .flush = mtdswap_flush, + .readsect = mtdswap_readsect, + .writesect = mtdswap_writesect, + .discard = mtdswap_discard, + .background = mtdswap_background, + .add_mtd = mtdswap_add_mtd, + .remove_dev = mtdswap_remove_dev, + .owner = THIS_MODULE, +}; + +static int __init mtdswap_modinit(void) +{ + return register_mtd_blktrans(&mtdswap_ops); +} + +static void __exit mtdswap_modexit(void) +{ + deregister_mtd_blktrans(&mtdswap_ops); +} + +module_init(mtdswap_modinit); +module_exit(mtdswap_modexit); + + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jarkko Lavinen <jarkko.lavinen@nokia.com>"); +MODULE_DESCRIPTION("Block device access to an MTD suitable for using as " + "swap space"); diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index c99302ed382..f1cf503517f 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -1,142 +1,199 @@ -# drivers/mtd/nand/Kconfig -# $Id: Kconfig,v 1.35 2005/11/07 11:14:30 gleixner Exp $ +config MTD_NAND_ECC + tristate + +config MTD_NAND_ECC_SMC + bool "NAND ECC Smart Media byte order" + depends on MTD_NAND_ECC + default n + help + Software ECC according to the Smart Media Specification. + The original Linux implementation had byte 0 and 1 swapped. -menu "NAND Flash Device Drivers" - depends on MTD!=n -config MTD_NAND +menuconfig MTD_NAND tristate "NAND Device Support" depends on MTD select MTD_NAND_IDS + select MTD_NAND_ECC help This enables support for accessing all type of NAND flash devices. For further information see <http://www.linux-mtd.infradead.org/doc/nand.html>. -config MTD_NAND_VERIFY_WRITE - bool "Verify NAND page writes" - depends on MTD_NAND - help - This adds an extra check when data is written to the flash. The - NAND flash device internally checks only bits transitioning - from 1 to 0. There is a rare possibility that even though the - device thinks the write was successful, a bit could have been - flipped accidentaly due to device wear or something else. +if MTD_NAND -config MTD_NAND_ECC_SMC - bool "NAND ECC Smart Media byte order" - depends on MTD_NAND +config MTD_NAND_BCH + tristate + select BCH + depends on MTD_NAND_ECC_BCH + default MTD_NAND + +config MTD_NAND_ECC_BCH + bool "Support software BCH ECC" default n help - Software ECC according to the Smart Media Specification. - The original Linux implementation had byte 0 and 1 swapped. + This enables support for software BCH error correction. Binary BCH + codes are more powerful and cpu intensive than traditional Hamming + ECC codes. They are used with NAND devices requiring more than 1 bit + of error correction. -config MTD_NAND_AUTCPU12 - tristate "SmartMediaCard on autronix autcpu12 board" - depends on MTD_NAND && ARCH_AUTCPU12 - help - This enables the driver for the autronix autcpu12 board to - access the SmartMediaCard. +config MTD_SM_COMMON + tristate + default n -config MTD_NAND_EDB7312 - tristate "Support for Cirrus Logic EBD7312 evaluation board" - depends on MTD_NAND && ARCH_EDB7312 - help - This enables the driver for the Cirrus Logic EBD7312 evaluation - board to access the onboard NAND Flash. +config MTD_NAND_DENALI + tristate "Support Denali NAND controller" + depends on HAS_DMA + help + Enable support for the Denali NAND controller. This should be + combined with either the PCI or platform drivers to provide device + registration. + +config MTD_NAND_DENALI_PCI + tristate "Support Denali NAND controller on Intel Moorestown" + depends on PCI && MTD_NAND_DENALI + help + Enable the driver for NAND flash on Intel Moorestown, using the + Denali NAND controller core. -config MTD_NAND_H1900 - tristate "iPAQ H1900 flash" - depends on MTD_NAND && ARCH_PXA && MTD_PARTITIONS +config MTD_NAND_DENALI_DT + tristate "Support Denali NAND controller as a DT device" + depends on HAVE_CLK && MTD_NAND_DENALI help - This enables the driver for the iPAQ h1900 flash. + Enable the driver for NAND flash on platforms using a Denali NAND + controller as a DT device. -config MTD_NAND_SPIA - tristate "NAND Flash device on SPIA board" - depends on ARCH_P720T && MTD_NAND +config MTD_NAND_DENALI_SCRATCH_REG_ADDR + hex "Denali NAND size scratch register address" + default "0xFF108018" + depends on MTD_NAND_DENALI_PCI + help + Some platforms place the NAND chip size in a scratch register + because (some versions of) the driver aren't able to automatically + determine the size of certain chips. Set the address of the + scratch register here to enable this feature. On Intel Moorestown + boards, the scratch register is at 0xFF108018. + +config MTD_NAND_GPIO + tristate "GPIO NAND Flash driver" + depends on GPIOLIB help - If you had to ask, you don't have one. Say 'N'. + This enables a GPIO based NAND flash driver. config MTD_NAND_AMS_DELTA tristate "NAND Flash device on Amstrad E3" - depends on MACH_AMS_DELTA && MTD_NAND + depends on MACH_AMS_DELTA + default y help Support for NAND flash on Amstrad E3 (Delta). -config MTD_NAND_TOTO - tristate "NAND Flash device on TOTO board" - depends on ARCH_OMAP && MTD_NAND && BROKEN +config MTD_NAND_OMAP2 + tristate "NAND Flash device on OMAP2, OMAP3 and OMAP4" + depends on ARCH_OMAP2PLUS help - Support for NAND flash on Texas Instruments Toto platform. + Support for NAND flash on Texas Instruments OMAP2, OMAP3 and OMAP4 + platforms. -config MTD_NAND_TS7250 - tristate "NAND Flash device on TS-7250 board" - depends on MACH_TS72XX && MTD_NAND +config MTD_NAND_OMAP_BCH + depends on MTD_NAND_OMAP2 + tristate "Support hardware based BCH error correction" + default n + select BCH help - Support for NAND flash on Technologic Systems TS-7250 platform. + This config enables the ELM hardware engine, which can be used to + locate and correct errors when using BCH ECC scheme. This offloads + the cpu from doing ECC error searching and correction. However some + legacy OMAP families like OMAP2xxx, OMAP3xxx do not have ELM engine + so they should not enable this config symbol. config MTD_NAND_IDS tristate +config MTD_NAND_RICOH + tristate "Ricoh xD card reader" + default n + depends on PCI + select MTD_SM_COMMON + help + Enable support for Ricoh R5C852 xD card reader + You also need to enable ether + NAND SSFDC (SmartMedia) read only translation layer' or new + expermental, readwrite + 'SmartMedia/xD new translation layer' + config MTD_NAND_AU1550 tristate "Au1550/1200 NAND support" - depends on (SOC_AU1200 || SOC_AU1550) && MTD_NAND + depends on MIPS_ALCHEMY help This enables the driver for the NAND flash controller on the AMD/Alchemy 1550 SOC. -config MTD_NAND_RTC_FROM4 - tristate "Renesas Flash ROM 4-slot interface board (FROM_BOARD4)" - depends on MTD_NAND && SH_SOLUTION_ENGINE - select REED_SOLOMON - select REED_SOLOMON_DEC8 +config MTD_NAND_BF5XX + tristate "Blackfin on-chip NAND Flash Controller driver" + depends on BF54x || BF52x + help + This enables the Blackfin on-chip NAND flash controller + + No board specific support is done by this driver, each board + must advertise a platform_device for the driver to attach. + + This driver can also be built as a module. If so, the module + will be called bf5xx-nand. + +config MTD_NAND_BF5XX_HWECC + bool "BF5XX NAND Hardware ECC" + default y + depends on MTD_NAND_BF5XX help - This enables the driver for the Renesas Technology AG-AND - flash interface board (FROM_BOARD4) + Enable the use of the BF5XX's internal ECC generator when + using NAND. -config MTD_NAND_PPCHAMELEONEVB - tristate "NAND Flash device on PPChameleonEVB board" - depends on PPCHAMELEONEVB && MTD_NAND && BROKEN +config MTD_NAND_BF5XX_BOOTROM_ECC + bool "Use Blackfin BootROM ECC Layout" + default n + depends on MTD_NAND_BF5XX_HWECC help - This enables the NAND flash driver on the PPChameleon EVB Board. + If you wish to modify NAND pages and allow the Blackfin on-chip + BootROM to boot from them, say Y here. This is only necessary + if you are booting U-Boot out of NAND and you wish to update + U-Boot from Linux' userspace. Otherwise, you should say N here. + + If unsure, say N. config MTD_NAND_S3C2410 - tristate "NAND Flash support for S3C2410/S3C2440 SoC" - depends on ARCH_S3C2410 && MTD_NAND + tristate "NAND Flash support for Samsung S3C SoCs" + depends on ARCH_S3C24XX || ARCH_S3C64XX help - This enables the NAND flash controller on the S3C2410 and S3C2440 + This enables the NAND flash controller on the S3C24xx and S3C64xx SoCs No board specific support is done by this driver, each board must advertise a platform_device for the driver to attach. config MTD_NAND_S3C2410_DEBUG - bool "S3C2410 NAND driver debug" + bool "Samsung S3C NAND driver debug" depends on MTD_NAND_S3C2410 help - Enable debugging of the S3C2410 NAND driver + Enable debugging of the S3C NAND driver config MTD_NAND_S3C2410_HWECC - bool "S3C2410 NAND Hardware ECC" + bool "Samsung S3C NAND Hardware ECC" depends on MTD_NAND_S3C2410 help - Enable the use of the S3C2410's internal ECC generator when - using NAND. Early versions of the chip have had problems with + Enable the use of the controller's internal ECC generator when + using NAND. Early versions of the chips have had problems with incorrect ECC generation, and if using these, the default of software ECC is preferable. - If you lay down a device with the hardware ECC, then you will - currently not be able to switch to software, as there is no - implementation for ECC method used by the S3C2410 - config MTD_NAND_NDFC tristate "NDFC NanD Flash Controller" - depends on MTD_NAND && 44x + depends on 4xx + select MTD_NAND_ECC_SMC help - NDFC Nand Flash Controllers are integrated in EP44x SoCs + NDFC Nand Flash Controllers are integrated in IBM/AMCC's 4xx SoCs config MTD_NAND_S3C2410_CLKSTOP - bool "S3C2410 NAND IDLE clock stop" + bool "Samsung S3C NAND IDLE clock stop" depends on MTD_NAND_S3C2410 default n help @@ -146,8 +203,8 @@ config MTD_NAND_S3C2410_CLKSTOP approximately 5mA of power when there is nothing happening. config MTD_NAND_DISKONCHIP - tristate "DiskOnChip 2000, Millennium and Millennium Plus (NAND reimplementation) (EXPERIMENTAL)" - depends on MTD_NAND && EXPERIMENTAL + tristate "DiskOnChip 2000, Millennium and Millennium Plus (NAND reimplementation)" + depends on HAS_IOMEM select REED_SOLOMON select REED_SOLOMON_DEC16 help @@ -215,13 +272,42 @@ config MTD_NAND_DISKONCHIP_BBTWRITE load time (assuming you build diskonchip as a module) with the module parameter "inftl_bbt_write=1". +config MTD_NAND_DOCG4 + tristate "Support for DiskOnChip G4" + depends on HAS_IOMEM + select BCH + select BITREVERSE + help + Support for diskonchip G4 nand flash, found in various smartphones and + PDAs, among them the Palm Treo680, HTC Prophet and Wizard, Toshiba + Portege G900, Asus P526, and O2 XDA Zinc. + + With this driver you will be able to use UBI and create a ubifs on the + device, so you may wish to consider enabling UBI and UBIFS as well. + + These devices ship with the Mys/Sandisk SAFTL formatting, for which + there is currently no mtd parser, so you may want to use command line + partitioning to segregate write-protected blocks. On the Treo680, the + first five erase blocks (256KiB each) are write-protected, followed + by the block containing the saftl partition table. This is probably + typical. + config MTD_NAND_SHARPSL tristate "Support for NAND Flash on Sharp SL Series (C7xx + others)" - depends on MTD_NAND && ARCH_PXA + depends on ARCH_PXA + +config MTD_NAND_CAFE + tristate "NAND support for OLPC CAFÉ chip" + depends on PCI + select REED_SOLOMON + select REED_SOLOMON_DEC16 + help + Use NAND flash attached to the CAFÉ chip designed for the OLPC + laptop. config MTD_NAND_CS553X tristate "NAND support for CS5535/CS5536 (AMD Geode companion chip)" - depends on MTD_NAND && X86_32 && (X86_PC || X86_GENERICARCH) + depends on X86_32 help The CS553x companion chips for the AMD Geode processor include NAND flash controllers with built-in hardware ECC @@ -230,13 +316,201 @@ config MTD_NAND_CS553X controller is enabled for NAND, and currently requires that the controller be in MMIO mode. - If you say "m", the module will be called "cs553x_nand.ko". + If you say "m", the module will be called cs553x_nand. + +config MTD_NAND_ATMEL + tristate "Support for NAND Flash / SmartMedia on AT91 and AVR32" + depends on ARCH_AT91 || AVR32 + help + Enables support for NAND Flash / Smart Media Card interface + on Atmel AT91 and AVR32 processors. + +config MTD_NAND_PXA3xx + tristate "NAND support on PXA3xx and Armada 370/XP" + depends on PXA3xx || ARCH_MMP || PLAT_ORION + help + This enables the driver for the NAND flash device found on + PXA3xx processors (NFCv1) and also on Armada 370/XP (NFCv2). + +config MTD_NAND_SLC_LPC32XX + tristate "NXP LPC32xx SLC Controller" + depends on ARCH_LPC32XX + help + Enables support for NXP's LPC32XX SLC (i.e. for Single Level Cell + chips) NAND controller. This is the default for the PHYTEC 3250 + reference board which contains a NAND256R3A2CZA6 chip. + + Please check the actual NAND chip connected and its support + by the SLC NAND controller. + +config MTD_NAND_MLC_LPC32XX + tristate "NXP LPC32xx MLC Controller" + depends on ARCH_LPC32XX + help + Uses the LPC32XX MLC (i.e. for Multi Level Cell chips) NAND + controller. This is the default for the WORK92105 controller + board. + + Please check the actual NAND chip connected and its support + by the MLC NAND controller. + +config MTD_NAND_CM_X270 + tristate "Support for NAND Flash on CM-X270 modules" + depends on MACH_ARMCORE + +config MTD_NAND_PASEMI + tristate "NAND support for PA Semi PWRficient" + depends on PPC_PASEMI + help + Enables support for NAND Flash interface on PA Semi PWRficient + based boards + +config MTD_NAND_TMIO + tristate "NAND Flash device on Toshiba Mobile IO Controller" + depends on MFD_TMIO + help + Support for NAND flash connected to a Toshiba Mobile IO + Controller in some PDAs, including the Sharp SL6000x. config MTD_NAND_NANDSIM tristate "Support for NAND Flash Simulator" - depends on MTD_NAND && MTD_PARTITIONS help The simulator may simulate various NAND flash chips for the MTD nand layer. -endmenu +config MTD_NAND_GPMI_NAND + tristate "GPMI NAND Flash Controller driver" + depends on MTD_NAND && MXS_DMA + help + Enables NAND Flash support for IMX23, IMX28 or IMX6. + The GPMI controller is very powerful, with the help of BCH + module, it can do the hardware ECC. The GPMI supports several + NAND flashs at the same time. The GPMI may conflicts with other + block, such as SD card. So pay attention to it when you enable + the GPMI. + +config MTD_NAND_BCM47XXNFLASH + tristate "Support for NAND flash on BCM4706 BCMA bus" + depends on BCMA_NFLASH + help + BCMA bus can have various flash memories attached, they are + registered by bcma as platform devices. This enables driver for + NAND flash memories. For now only BCM4706 is supported. + +config MTD_NAND_PLATFORM + tristate "Support for generic platform NAND driver" + depends on HAS_IOMEM + help + This implements a generic NAND driver for on-SOC platform + devices. You will need to provide platform-specific functions + via platform_data. + +config MTD_NAND_ORION + tristate "NAND Flash support for Marvell Orion SoC" + depends on PLAT_ORION + help + This enables the NAND flash controller on Orion machines. + + No board specific support is done by this driver, each board + must advertise a platform_device for the driver to attach. + +config MTD_NAND_FSL_ELBC + tristate "NAND support for Freescale eLBC controllers" + depends on PPC_OF + select FSL_LBC + help + Various Freescale chips, including the 8313, include a NAND Flash + Controller Module with built-in hardware ECC capabilities. + Enabling this option will enable you to use this to control + external NAND devices. + +config MTD_NAND_FSL_IFC + tristate "NAND support for Freescale IFC controller" + depends on MTD_NAND && FSL_SOC + select FSL_IFC + select MEMORY + help + Various Freescale chips e.g P1010, include a NAND Flash machine + with built-in hardware ECC capabilities. + Enabling this option will enable you to use this to control + external NAND devices. + +config MTD_NAND_FSL_UPM + tristate "Support for NAND on Freescale UPM" + depends on PPC_83xx || PPC_85xx + select FSL_LBC + help + Enables support for NAND Flash chips wired onto Freescale PowerPC + processor localbus with User-Programmable Machine support. + +config MTD_NAND_MPC5121_NFC + tristate "MPC5121 built-in NAND Flash Controller support" + depends on PPC_MPC512x + help + This enables the driver for the NAND flash controller on the + MPC5121 SoC. + +config MTD_NAND_MXC + tristate "MXC NAND support" + depends on ARCH_MXC + help + This enables the driver for the NAND flash controller on the + MXC processors. + +config MTD_NAND_SH_FLCTL + tristate "Support for NAND on Renesas SuperH FLCTL" + depends on SUPERH || ARCH_SHMOBILE || COMPILE_TEST + depends on HAS_IOMEM + depends on HAS_DMA + help + Several Renesas SuperH CPU has FLCTL. This option enables support + for NAND Flash using FLCTL. + +config MTD_NAND_DAVINCI + tristate "Support NAND on DaVinci/Keystone SoC" + depends on ARCH_DAVINCI || (ARCH_KEYSTONE && TI_AEMIF) + help + Enable the driver for NAND flash chips on Texas Instruments + DaVinci/Keystone 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 SOCRATES + help + Enables support for NAND Flash chips wired onto Socrates board. + +config MTD_NAND_NUC900 + tristate "Support for NAND on Nuvoton NUC9xx/w90p910 evaluation boards." + depends on ARCH_W90X900 + help + This enables the driver for the NAND Flash on evaluation board based + on w90p910 / NUC9xx. + +config MTD_NAND_JZ4740 + tristate "Support for JZ4740 SoC NAND controller" + depends on MACH_JZ4740 + help + Enables support for NAND Flash on JZ4740 SoC based boards. + +config MTD_NAND_FSMC + tristate "Support for NAND on ST Micros FSMC" + depends on PLAT_SPEAR || ARCH_NOMADIK || ARCH_U8500 || MACH_U300 + help + Enables support for NAND Flash chips on the ST Microelectronics + Flexible Static Memory Controller (FSMC) + +config MTD_NAND_XWAY + tristate "Support for NAND on Lantiq XWAY SoC" + depends on LANTIQ && SOC_TYPE_XWAY + select MTD_NAND_PLATFORM + help + Enables support for NAND Flash chips on Lantiq XWAY SoCs. NAND is attached + to the External Bus Unit (EBU). + +endif # MTD_NAND diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index f74759351c9..542b5689eb6 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -1,26 +1,53 @@ # # linux/drivers/nand/Makefile # -# $Id: Makefile.common,v 1.15 2004/11/26 12:28:22 dedekind Exp $ -obj-$(CONFIG_MTD_NAND) += nand.o nand_ecc.o +obj-$(CONFIG_MTD_NAND) += nand.o +obj-$(CONFIG_MTD_NAND_ECC) += nand_ecc.o +obj-$(CONFIG_MTD_NAND_BCH) += nand_bch.o obj-$(CONFIG_MTD_NAND_IDS) += nand_ids.o +obj-$(CONFIG_MTD_SM_COMMON) += sm_common.o -obj-$(CONFIG_MTD_NAND_SPIA) += spia.o +obj-$(CONFIG_MTD_NAND_CAFE) += cafe_nand.o obj-$(CONFIG_MTD_NAND_AMS_DELTA) += ams-delta.o -obj-$(CONFIG_MTD_NAND_TOTO) += toto.o -obj-$(CONFIG_MTD_NAND_AUTCPU12) += autcpu12.o -obj-$(CONFIG_MTD_NAND_EDB7312) += edb7312.o +obj-$(CONFIG_MTD_NAND_DENALI) += denali.o +obj-$(CONFIG_MTD_NAND_DENALI_PCI) += denali_pci.o +obj-$(CONFIG_MTD_NAND_DENALI_DT) += denali_dt.o obj-$(CONFIG_MTD_NAND_AU1550) += au1550nd.o -obj-$(CONFIG_MTD_NAND_PPCHAMELEONEVB) += ppchameleonevb.o +obj-$(CONFIG_MTD_NAND_BF5XX) += bf5xx_nand.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 +obj-$(CONFIG_MTD_NAND_DOCG4) += docg4.o +obj-$(CONFIG_MTD_NAND_FSMC) += fsmc_nand.o obj-$(CONFIG_MTD_NAND_SHARPSL) += sharpsl.o -obj-$(CONFIG_MTD_NAND_TS7250) += ts7250.o obj-$(CONFIG_MTD_NAND_NANDSIM) += nandsim.o obj-$(CONFIG_MTD_NAND_CS553X) += cs553x_nand.o obj-$(CONFIG_MTD_NAND_NDFC) += ndfc.o +obj-$(CONFIG_MTD_NAND_ATMEL) += atmel_nand.o +obj-$(CONFIG_MTD_NAND_GPIO) += gpio.o +obj-$(CONFIG_MTD_NAND_OMAP2) += omap2.o +obj-$(CONFIG_MTD_NAND_CM_X270) += cmx270_nand.o +obj-$(CONFIG_MTD_NAND_PXA3xx) += pxa3xx_nand.o +obj-$(CONFIG_MTD_NAND_TMIO) += tmio_nand.o +obj-$(CONFIG_MTD_NAND_PLATFORM) += plat_nand.o +obj-$(CONFIG_MTD_NAND_PASEMI) += pasemi_nand.o +obj-$(CONFIG_MTD_NAND_ORION) += orion_nand.o +obj-$(CONFIG_MTD_NAND_FSL_ELBC) += fsl_elbc_nand.o +obj-$(CONFIG_MTD_NAND_FSL_IFC) += fsl_ifc_nand.o +obj-$(CONFIG_MTD_NAND_FSL_UPM) += fsl_upm.o +obj-$(CONFIG_MTD_NAND_SLC_LPC32XX) += lpc32xx_slc.o +obj-$(CONFIG_MTD_NAND_MLC_LPC32XX) += lpc32xx_mlc.o +obj-$(CONFIG_MTD_NAND_SH_FLCTL) += sh_flctl.o +obj-$(CONFIG_MTD_NAND_MXC) += mxc_nand.o +obj-$(CONFIG_MTD_NAND_SOCRATES) += socrates_nand.o +obj-$(CONFIG_MTD_NAND_TXX9NDFMC) += txx9ndfmc.o +obj-$(CONFIG_MTD_NAND_NUC900) += nuc900_nand.o +obj-$(CONFIG_MTD_NAND_MPC5121_NFC) += mpc5121_nfc.o +obj-$(CONFIG_MTD_NAND_RICOH) += r852.o +obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o +obj-$(CONFIG_MTD_NAND_GPMI_NAND) += gpmi-nand/ +obj-$(CONFIG_MTD_NAND_XWAY) += xway_nand.o +obj-$(CONFIG_MTD_NAND_BCM47XXNFLASH) += bcm47xxnflash/ -nand-objs = nand_base.o nand_bbt.o +nand-objs := nand_base.o nand_bbt.o diff --git a/drivers/mtd/nand/ams-delta.c b/drivers/mtd/nand/ams-delta.c index a0ba07c36ee..4936e9e0002 100644 --- a/drivers/mtd/nand/ams-delta.c +++ b/drivers/mtd/nand/ams-delta.c @@ -4,6 +4,8 @@ * Copyright (C) 2006 Jonathan McDowell <noodles@earth.li> * * Derived from drivers/mtd/toto.c + * Converted to platform driver by Janusz Krzysztofik <jkrzyszt@tis.icnet.pl> + * Partially stolen from drivers/mtd/nand/plat_nand.c * * 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 @@ -15,25 +17,26 @@ */ #include <linux/slab.h> -#include <linux/init.h> #include <linux/module.h> #include <linux/delay.h> #include <linux/mtd/mtd.h> #include <linux/mtd/nand.h> #include <linux/mtd/partitions.h> +#include <linux/gpio.h> +#include <linux/platform_data/gpio-omap.h> + #include <asm/io.h> -#include <asm/arch/hardware.h> #include <asm/sizes.h> -#include <asm/arch/gpio.h> -#include <asm/arch/board-ams-delta.h> + +#include <mach/board-ams-delta.h> + +#include <mach/hardware.h> /* * MTD structure for E3 (Delta) */ static struct mtd_info *ams_delta_mtd = NULL; -#define NAND_MASK (AMS_DELTA_LATCH2_NAND_NRE | AMS_DELTA_LATCH2_NAND_NWE | AMS_DELTA_LATCH2_NAND_CLE | AMS_DELTA_LATCH2_NAND_ALE | AMS_DELTA_LATCH2_NAND_NCE | AMS_DELTA_LATCH2_NAND_NWP) - /* * Define partitions for flash devices */ @@ -62,26 +65,26 @@ static struct mtd_partition partition_info[] = { static void ams_delta_write_byte(struct mtd_info *mtd, u_char byte) { struct nand_chip *this = mtd->priv; + void __iomem *io_base = this->priv; - omap_writew(0, (OMAP_MPUIO_BASE + OMAP_MPUIO_IO_CNTL)); - omap_writew(byte, this->IO_ADDR_W); - ams_delta_latch2_write(AMS_DELTA_LATCH2_NAND_NWE, 0); + writew(0, io_base + OMAP_MPUIO_IO_CNTL); + writew(byte, this->IO_ADDR_W); + gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_NWE, 0); ndelay(40); - ams_delta_latch2_write(AMS_DELTA_LATCH2_NAND_NWE, - AMS_DELTA_LATCH2_NAND_NWE); + gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_NWE, 1); } static u_char ams_delta_read_byte(struct mtd_info *mtd) { u_char res; struct nand_chip *this = mtd->priv; + void __iomem *io_base = this->priv; - ams_delta_latch2_write(AMS_DELTA_LATCH2_NAND_NRE, 0); + gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_NRE, 0); ndelay(40); - omap_writew(~0, (OMAP_MPUIO_BASE + OMAP_MPUIO_IO_CNTL)); - res = omap_readw(this->IO_ADDR_R); - ams_delta_latch2_write(AMS_DELTA_LATCH2_NAND_NRE, - AMS_DELTA_LATCH2_NAND_NRE); + writew(~0, io_base + OMAP_MPUIO_IO_CNTL); + res = readw(this->IO_ADDR_R); + gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_NRE, 1); return res; } @@ -103,18 +106,6 @@ static void ams_delta_read_buf(struct mtd_info *mtd, u_char *buf, int len) buf[i] = ams_delta_read_byte(mtd); } -static int ams_delta_verify_buf(struct mtd_info *mtd, const u_char *buf, - int len) -{ - int i; - - for (i=0; i<len; i++) - if (buf[i] != ams_delta_read_byte(mtd)) - return -EFAULT; - - return 0; -} - /* * Command control function * @@ -128,15 +119,12 @@ static void ams_delta_hwcontrol(struct mtd_info *mtd, int cmd, { if (ctrl & NAND_CTRL_CHANGE) { - unsigned long bits; - - bits = (~ctrl & NAND_NCE) ? AMS_DELTA_LATCH2_NAND_NCE : 0; - bits |= (ctrl & NAND_CLE) ? AMS_DELTA_LATCH2_NAND_CLE : 0; - bits |= (ctrl & NAND_ALE) ? AMS_DELTA_LATCH2_NAND_ALE : 0; - - ams_delta_latch2_write(AMS_DELTA_LATCH2_NAND_CLE | - AMS_DELTA_LATCH2_NAND_ALE | - AMS_DELTA_LATCH2_NAND_NCE, bits); + gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_NCE, + (ctrl & NAND_NCE) == 0); + gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_CLE, + (ctrl & NAND_CLE) != 0); + gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_ALE, + (ctrl & NAND_ALE) != 0); } if (cmd != NAND_CMD_NONE) @@ -145,17 +133,55 @@ static void ams_delta_hwcontrol(struct mtd_info *mtd, int cmd, static int ams_delta_nand_ready(struct mtd_info *mtd) { - return omap_get_gpio_datain(AMS_DELTA_GPIO_PIN_NAND_RB); + return gpio_get_value(AMS_DELTA_GPIO_PIN_NAND_RB); } +static const struct gpio _mandatory_gpio[] = { + { + .gpio = AMS_DELTA_GPIO_PIN_NAND_NCE, + .flags = GPIOF_OUT_INIT_HIGH, + .label = "nand_nce", + }, + { + .gpio = AMS_DELTA_GPIO_PIN_NAND_NRE, + .flags = GPIOF_OUT_INIT_HIGH, + .label = "nand_nre", + }, + { + .gpio = AMS_DELTA_GPIO_PIN_NAND_NWP, + .flags = GPIOF_OUT_INIT_HIGH, + .label = "nand_nwp", + }, + { + .gpio = AMS_DELTA_GPIO_PIN_NAND_NWE, + .flags = GPIOF_OUT_INIT_HIGH, + .label = "nand_nwe", + }, + { + .gpio = AMS_DELTA_GPIO_PIN_NAND_ALE, + .flags = GPIOF_OUT_INIT_LOW, + .label = "nand_ale", + }, + { + .gpio = AMS_DELTA_GPIO_PIN_NAND_CLE, + .flags = GPIOF_OUT_INIT_LOW, + .label = "nand_cle", + }, +}; + /* * Main initialization routine */ -static int __init ams_delta_init(void) +static int ams_delta_init(struct platform_device *pdev) { struct nand_chip *this; + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + void __iomem *io_base; int err = 0; + if (!res) + return -ENXIO; + /* Allocate memory for MTD device structure and private data */ ams_delta_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL); @@ -177,15 +203,29 @@ static int __init ams_delta_init(void) /* Link the private data with the MTD structure */ ams_delta_mtd->priv = this; + /* + * Don't try to request the memory region from here, + * it should have been already requested from the + * gpio-omap driver and requesting it again would fail. + */ + + io_base = ioremap(res->start, resource_size(res)); + if (io_base == NULL) { + dev_err(&pdev->dev, "ioremap failed\n"); + err = -EIO; + goto out_free; + } + + this->priv = io_base; + /* Set address of NAND IO lines */ - this->IO_ADDR_R = (OMAP_MPUIO_BASE + OMAP_MPUIO_INPUT_LATCH); - this->IO_ADDR_W = (OMAP_MPUIO_BASE + OMAP_MPUIO_OUTPUT); + this->IO_ADDR_R = io_base + OMAP_MPUIO_INPUT_LATCH; + this->IO_ADDR_W = io_base + OMAP_MPUIO_OUTPUT; this->read_byte = ams_delta_read_byte; this->write_buf = ams_delta_write_buf; this->read_buf = ams_delta_read_buf; - this->verify_buf = ams_delta_verify_buf; this->cmd_ctrl = ams_delta_hwcontrol; - if (!omap_request_gpio(AMS_DELTA_GPIO_PIN_NAND_RB)) { + if (gpio_request(AMS_DELTA_GPIO_PIN_NAND_RB, "nand_rdy") == 0) { this->dev_ready = ams_delta_nand_ready; } else { this->dev_ready = NULL; @@ -195,44 +235,66 @@ static int __init ams_delta_init(void) this->chip_delay = 30; this->ecc.mode = NAND_ECC_SOFT; + platform_set_drvdata(pdev, io_base); + /* Set chip enabled, but */ - ams_delta_latch2_write(NAND_MASK, AMS_DELTA_LATCH2_NAND_NRE | - AMS_DELTA_LATCH2_NAND_NWE | - AMS_DELTA_LATCH2_NAND_NCE | - AMS_DELTA_LATCH2_NAND_NWP); + err = gpio_request_array(_mandatory_gpio, ARRAY_SIZE(_mandatory_gpio)); + if (err) + goto out_gpio; - /* Scan to find existance of the device */ + /* Scan to find existence of the device */ if (nand_scan(ams_delta_mtd, 1)) { err = -ENXIO; goto out_mtd; } /* Register the partitions */ - add_mtd_partitions(ams_delta_mtd, partition_info, - ARRAY_SIZE(partition_info)); + mtd_device_register(ams_delta_mtd, partition_info, + ARRAY_SIZE(partition_info)); goto out; out_mtd: + gpio_free_array(_mandatory_gpio, ARRAY_SIZE(_mandatory_gpio)); +out_gpio: + gpio_free(AMS_DELTA_GPIO_PIN_NAND_RB); + iounmap(io_base); +out_free: kfree(ams_delta_mtd); out: return err; } -module_init(ams_delta_init); - /* * Clean up routine */ -static void __exit ams_delta_cleanup(void) +static int ams_delta_cleanup(struct platform_device *pdev) { + void __iomem *io_base = platform_get_drvdata(pdev); + /* Release resources, unregister device */ nand_release(ams_delta_mtd); + gpio_free_array(_mandatory_gpio, ARRAY_SIZE(_mandatory_gpio)); + gpio_free(AMS_DELTA_GPIO_PIN_NAND_RB); + iounmap(io_base); + /* Free the MTD device structure */ kfree(ams_delta_mtd); + + return 0; } -module_exit(ams_delta_cleanup); + +static struct platform_driver ams_delta_nand_driver = { + .probe = ams_delta_init, + .remove = ams_delta_cleanup, + .driver = { + .name = "ams-delta-nand", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(ams_delta_nand_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Jonathan McDowell <noodles@earth.li>"); diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c new file mode 100644 index 00000000000..4ce181a35bc --- /dev/null +++ b/drivers/mtd/nand/atmel_nand.c @@ -0,0 +1,2247 @@ +/* + * Copyright © 2003 Rick Bronson + * + * Derived from drivers/mtd/nand/autcpu12.c + * Copyright © 2001 Thomas Gleixner (gleixner@autronix.de) + * + * Derived from drivers/mtd/spia.c + * Copyright © 2000 Steven J. Hill (sjhill@cotw.com) + * + * + * Add Hardware ECC support for AT91SAM9260 / AT91SAM9263 + * Richard Genoud (richard.genoud@gmail.com), Adeneo Copyright © 2007 + * + * Derived from Das U-Boot source code + * (u-boot-1.1.5/board/atmel/at91sam9263ek/nand.c) + * © Copyright 2006 ATMEL Rousset, Lacressonniere Nicolas + * + * Add Programmable Multibit ECC support for various AT91 SoC + * © Copyright 2012 ATMEL, Hong Xu + * + * Add Nand Flash Controller support for SAMA5 SoC + * © Copyright 2013 ATMEL, Josh Wu (josh.wu@atmel.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/of_mtd.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> + +#include <linux/delay.h> +#include <linux/dmaengine.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/platform_data/atmel.h> + +static int use_dma = 1; +module_param(use_dma, int, 0); + +static int on_flash_bbt = 0; +module_param(on_flash_bbt, int, 0); + +/* Register access macros */ +#define ecc_readl(add, reg) \ + __raw_readl(add + ATMEL_ECC_##reg) +#define ecc_writel(add, reg, value) \ + __raw_writel((value), add + ATMEL_ECC_##reg) + +#include "atmel_nand_ecc.h" /* Hardware ECC registers */ +#include "atmel_nand_nfc.h" /* Nand Flash Controller definition */ + +/* oob layout for large page size + * bad block info is on bytes 0 and 1 + * the bytes have to be consecutives to avoid + * several NAND_CMD_RNDOUT during read + */ +static struct nand_ecclayout atmel_oobinfo_large = { + .eccbytes = 4, + .eccpos = {60, 61, 62, 63}, + .oobfree = { + {2, 58} + }, +}; + +/* oob layout for small page size + * bad block info is on bytes 4 and 5 + * the bytes have to be consecutives to avoid + * several NAND_CMD_RNDOUT during read + */ +static struct nand_ecclayout atmel_oobinfo_small = { + .eccbytes = 4, + .eccpos = {0, 1, 2, 3}, + .oobfree = { + {6, 10} + }, +}; + +struct atmel_nfc { + void __iomem *base_cmd_regs; + void __iomem *hsmc_regs; + void __iomem *sram_bank0; + dma_addr_t sram_bank0_phys; + bool use_nfc_sram; + bool write_by_sram; + + bool is_initialized; + struct completion comp_nfc; + + /* Point to the sram bank which include readed data via NFC */ + void __iomem *data_in_sram; + bool will_write_sram; +}; +static struct atmel_nfc nand_nfc; + +struct atmel_nand_host { + struct nand_chip nand_chip; + struct mtd_info mtd; + void __iomem *io_base; + dma_addr_t io_phys; + struct atmel_nand_data board; + struct device *dev; + void __iomem *ecc; + + struct completion comp; + struct dma_chan *dma_chan; + + struct atmel_nfc *nfc; + + bool has_pmecc; + u8 pmecc_corr_cap; + u16 pmecc_sector_size; + u32 pmecc_lookup_table_offset; + u32 pmecc_lookup_table_offset_512; + u32 pmecc_lookup_table_offset_1024; + + int pmecc_bytes_per_sector; + int pmecc_sector_number; + int pmecc_degree; /* Degree of remainders */ + int pmecc_cw_len; /* Length of codeword */ + + void __iomem *pmerrloc_base; + void __iomem *pmecc_rom_base; + + /* lookup table for alpha_to and index_of */ + void __iomem *pmecc_alpha_to; + void __iomem *pmecc_index_of; + + /* data for pmecc computation */ + int16_t *pmecc_partial_syn; + int16_t *pmecc_si; + int16_t *pmecc_smu; /* Sigma table */ + int16_t *pmecc_lmu; /* polynomal order */ + int *pmecc_mu; + int *pmecc_dmu; + int *pmecc_delta; +}; + +static struct nand_ecclayout atmel_pmecc_oobinfo; + +/* + * Enable NAND. + */ +static void atmel_nand_enable(struct atmel_nand_host *host) +{ + if (gpio_is_valid(host->board.enable_pin)) + gpio_set_value(host->board.enable_pin, 0); +} + +/* + * Disable NAND. + */ +static void atmel_nand_disable(struct atmel_nand_host *host) +{ + if (gpio_is_valid(host->board.enable_pin)) + gpio_set_value(host->board.enable_pin, 1); +} + +/* + * Hardware specific access to control-lines + */ +static void atmel_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl) +{ + struct nand_chip *nand_chip = mtd->priv; + struct atmel_nand_host *host = nand_chip->priv; + + if (ctrl & NAND_CTRL_CHANGE) { + if (ctrl & NAND_NCE) + atmel_nand_enable(host); + else + atmel_nand_disable(host); + } + if (cmd == NAND_CMD_NONE) + return; + + if (ctrl & NAND_CLE) + writeb(cmd, host->io_base + (1 << host->board.cle)); + else + writeb(cmd, host->io_base + (1 << host->board.ale)); +} + +/* + * Read the Device Ready pin. + */ +static int atmel_nand_device_ready(struct mtd_info *mtd) +{ + struct nand_chip *nand_chip = mtd->priv; + struct atmel_nand_host *host = nand_chip->priv; + + return gpio_get_value(host->board.rdy_pin) ^ + !!host->board.rdy_pin_active_low; +} + +/* Set up for hardware ready pin and enable pin. */ +static int atmel_nand_set_enable_ready_pins(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct atmel_nand_host *host = chip->priv; + int res = 0; + + if (gpio_is_valid(host->board.rdy_pin)) { + res = devm_gpio_request(host->dev, + host->board.rdy_pin, "nand_rdy"); + if (res < 0) { + dev_err(host->dev, + "can't request rdy gpio %d\n", + host->board.rdy_pin); + return res; + } + + res = gpio_direction_input(host->board.rdy_pin); + if (res < 0) { + dev_err(host->dev, + "can't request input direction rdy gpio %d\n", + host->board.rdy_pin); + return res; + } + + chip->dev_ready = atmel_nand_device_ready; + } + + if (gpio_is_valid(host->board.enable_pin)) { + res = devm_gpio_request(host->dev, + host->board.enable_pin, "nand_enable"); + if (res < 0) { + dev_err(host->dev, + "can't request enable gpio %d\n", + host->board.enable_pin); + return res; + } + + res = gpio_direction_output(host->board.enable_pin, 1); + if (res < 0) { + dev_err(host->dev, + "can't request output direction enable gpio %d\n", + host->board.enable_pin); + return res; + } + } + + return res; +} + +static void memcpy32_fromio(void *trg, const void __iomem *src, size_t size) +{ + int i; + u32 *t = trg; + const __iomem u32 *s = src; + + for (i = 0; i < (size >> 2); i++) + *t++ = readl_relaxed(s++); +} + +static void memcpy32_toio(void __iomem *trg, const void *src, int size) +{ + int i; + u32 __iomem *t = trg; + const u32 *s = src; + + for (i = 0; i < (size >> 2); i++) + writel_relaxed(*s++, t++); +} + +/* + * Minimal-overhead PIO for data access. + */ +static void atmel_read_buf8(struct mtd_info *mtd, u8 *buf, int len) +{ + struct nand_chip *nand_chip = mtd->priv; + struct atmel_nand_host *host = nand_chip->priv; + + if (host->nfc && host->nfc->use_nfc_sram && host->nfc->data_in_sram) { + memcpy32_fromio(buf, host->nfc->data_in_sram, len); + host->nfc->data_in_sram += len; + } else { + __raw_readsb(nand_chip->IO_ADDR_R, buf, len); + } +} + +static void atmel_read_buf16(struct mtd_info *mtd, u8 *buf, int len) +{ + struct nand_chip *nand_chip = mtd->priv; + struct atmel_nand_host *host = nand_chip->priv; + + if (host->nfc && host->nfc->use_nfc_sram && host->nfc->data_in_sram) { + memcpy32_fromio(buf, host->nfc->data_in_sram, len); + host->nfc->data_in_sram += len; + } else { + __raw_readsw(nand_chip->IO_ADDR_R, buf, len / 2); + } +} + +static void atmel_write_buf8(struct mtd_info *mtd, const u8 *buf, int len) +{ + struct nand_chip *nand_chip = mtd->priv; + + __raw_writesb(nand_chip->IO_ADDR_W, buf, len); +} + +static void atmel_write_buf16(struct mtd_info *mtd, const u8 *buf, int len) +{ + struct nand_chip *nand_chip = mtd->priv; + + __raw_writesw(nand_chip->IO_ADDR_W, buf, len / 2); +} + +static void dma_complete_func(void *completion) +{ + complete(completion); +} + +static int nfc_set_sram_bank(struct atmel_nand_host *host, unsigned int bank) +{ + /* NFC only has two banks. Must be 0 or 1 */ + if (bank > 1) + return -EINVAL; + + if (bank) { + /* Only for a 2k-page or lower flash, NFC can handle 2 banks */ + if (host->mtd.writesize > 2048) + return -EINVAL; + nfc_writel(host->nfc->hsmc_regs, BANK, ATMEL_HSMC_NFC_BANK1); + } else { + nfc_writel(host->nfc->hsmc_regs, BANK, ATMEL_HSMC_NFC_BANK0); + } + + return 0; +} + +static uint nfc_get_sram_off(struct atmel_nand_host *host) +{ + if (nfc_readl(host->nfc->hsmc_regs, BANK) & ATMEL_HSMC_NFC_BANK1) + return NFC_SRAM_BANK1_OFFSET; + else + return 0; +} + +static dma_addr_t nfc_sram_phys(struct atmel_nand_host *host) +{ + if (nfc_readl(host->nfc->hsmc_regs, BANK) & ATMEL_HSMC_NFC_BANK1) + return host->nfc->sram_bank0_phys + NFC_SRAM_BANK1_OFFSET; + else + return host->nfc->sram_bank0_phys; +} + +static int atmel_nand_dma_op(struct mtd_info *mtd, void *buf, int len, + int is_read) +{ + struct dma_device *dma_dev; + enum dma_ctrl_flags flags; + dma_addr_t dma_src_addr, dma_dst_addr, phys_addr; + struct dma_async_tx_descriptor *tx = NULL; + dma_cookie_t cookie; + struct nand_chip *chip = mtd->priv; + struct atmel_nand_host *host = chip->priv; + void *p = buf; + int err = -EIO; + enum dma_data_direction dir = is_read ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + struct atmel_nfc *nfc = host->nfc; + + if (buf >= high_memory) + goto err_buf; + + dma_dev = host->dma_chan->device; + + flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT; + + phys_addr = dma_map_single(dma_dev->dev, p, len, dir); + if (dma_mapping_error(dma_dev->dev, phys_addr)) { + dev_err(host->dev, "Failed to dma_map_single\n"); + goto err_buf; + } + + if (is_read) { + if (nfc && nfc->data_in_sram) + dma_src_addr = nfc_sram_phys(host) + (nfc->data_in_sram + - (nfc->sram_bank0 + nfc_get_sram_off(host))); + else + dma_src_addr = host->io_phys; + + dma_dst_addr = phys_addr; + } else { + dma_src_addr = phys_addr; + + if (nfc && nfc->write_by_sram) + dma_dst_addr = nfc_sram_phys(host); + else + dma_dst_addr = host->io_phys; + } + + tx = dma_dev->device_prep_dma_memcpy(host->dma_chan, dma_dst_addr, + dma_src_addr, len, flags); + if (!tx) { + dev_err(host->dev, "Failed to prepare DMA memcpy\n"); + goto err_dma; + } + + init_completion(&host->comp); + tx->callback = dma_complete_func; + tx->callback_param = &host->comp; + + cookie = tx->tx_submit(tx); + if (dma_submit_error(cookie)) { + dev_err(host->dev, "Failed to do DMA tx_submit\n"); + goto err_dma; + } + + dma_async_issue_pending(host->dma_chan); + wait_for_completion(&host->comp); + + if (is_read && nfc && nfc->data_in_sram) + /* After read data from SRAM, need to increase the position */ + nfc->data_in_sram += len; + + err = 0; + +err_dma: + dma_unmap_single(dma_dev->dev, phys_addr, len, dir); +err_buf: + if (err != 0) + dev_dbg(host->dev, "Fall back to CPU I/O\n"); + return err; +} + +static void atmel_read_buf(struct mtd_info *mtd, u8 *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct atmel_nand_host *host = chip->priv; + + if (use_dma && len > mtd->oobsize) + /* only use DMA for bigger than oob size: better performances */ + if (atmel_nand_dma_op(mtd, buf, len, 1) == 0) + return; + + if (host->board.bus_width_16) + atmel_read_buf16(mtd, buf, len); + else + atmel_read_buf8(mtd, buf, len); +} + +static void atmel_write_buf(struct mtd_info *mtd, const u8 *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct atmel_nand_host *host = chip->priv; + + if (use_dma && len > mtd->oobsize) + /* only use DMA for bigger than oob size: better performances */ + if (atmel_nand_dma_op(mtd, (void *)buf, len, 0) == 0) + return; + + if (host->board.bus_width_16) + atmel_write_buf16(mtd, buf, len); + else + atmel_write_buf8(mtd, buf, len); +} + +/* + * Return number of ecc bytes per sector according to sector size and + * correction capability + * + * Following table shows what at91 PMECC supported: + * Correction Capability Sector_512_bytes Sector_1024_bytes + * ===================== ================ ================= + * 2-bits 4-bytes 4-bytes + * 4-bits 7-bytes 7-bytes + * 8-bits 13-bytes 14-bytes + * 12-bits 20-bytes 21-bytes + * 24-bits 39-bytes 42-bytes + */ +static int pmecc_get_ecc_bytes(int cap, int sector_size) +{ + int m = 12 + sector_size / 512; + return (m * cap + 7) / 8; +} + +static void pmecc_config_ecc_layout(struct nand_ecclayout *layout, + int oobsize, int ecc_len) +{ + int i; + + layout->eccbytes = ecc_len; + + /* ECC will occupy the last ecc_len bytes continuously */ + for (i = 0; i < ecc_len; i++) + layout->eccpos[i] = oobsize - ecc_len + i; + + layout->oobfree[0].offset = 2; + layout->oobfree[0].length = + oobsize - ecc_len - layout->oobfree[0].offset; +} + +static void __iomem *pmecc_get_alpha_to(struct atmel_nand_host *host) +{ + int table_size; + + table_size = host->pmecc_sector_size == 512 ? + PMECC_LOOKUP_TABLE_SIZE_512 : PMECC_LOOKUP_TABLE_SIZE_1024; + + return host->pmecc_rom_base + host->pmecc_lookup_table_offset + + table_size * sizeof(int16_t); +} + +static int pmecc_data_alloc(struct atmel_nand_host *host) +{ + const int cap = host->pmecc_corr_cap; + int size; + + size = (2 * cap + 1) * sizeof(int16_t); + host->pmecc_partial_syn = devm_kzalloc(host->dev, size, GFP_KERNEL); + host->pmecc_si = devm_kzalloc(host->dev, size, GFP_KERNEL); + host->pmecc_lmu = devm_kzalloc(host->dev, + (cap + 1) * sizeof(int16_t), GFP_KERNEL); + host->pmecc_smu = devm_kzalloc(host->dev, + (cap + 2) * size, GFP_KERNEL); + + size = (cap + 1) * sizeof(int); + host->pmecc_mu = devm_kzalloc(host->dev, size, GFP_KERNEL); + host->pmecc_dmu = devm_kzalloc(host->dev, size, GFP_KERNEL); + host->pmecc_delta = devm_kzalloc(host->dev, size, GFP_KERNEL); + + if (!host->pmecc_partial_syn || + !host->pmecc_si || + !host->pmecc_lmu || + !host->pmecc_smu || + !host->pmecc_mu || + !host->pmecc_dmu || + !host->pmecc_delta) + return -ENOMEM; + + return 0; +} + +static void pmecc_gen_syndrome(struct mtd_info *mtd, int sector) +{ + struct nand_chip *nand_chip = mtd->priv; + struct atmel_nand_host *host = nand_chip->priv; + int i; + uint32_t value; + + /* Fill odd syndromes */ + for (i = 0; i < host->pmecc_corr_cap; i++) { + value = pmecc_readl_rem_relaxed(host->ecc, sector, i / 2); + if (i & 1) + value >>= 16; + value &= 0xffff; + host->pmecc_partial_syn[(2 * i) + 1] = (int16_t)value; + } +} + +static void pmecc_substitute(struct mtd_info *mtd) +{ + struct nand_chip *nand_chip = mtd->priv; + struct atmel_nand_host *host = nand_chip->priv; + int16_t __iomem *alpha_to = host->pmecc_alpha_to; + int16_t __iomem *index_of = host->pmecc_index_of; + int16_t *partial_syn = host->pmecc_partial_syn; + const int cap = host->pmecc_corr_cap; + int16_t *si; + int i, j; + + /* si[] is a table that holds the current syndrome value, + * an element of that table belongs to the field + */ + si = host->pmecc_si; + + memset(&si[1], 0, sizeof(int16_t) * (2 * cap - 1)); + + /* Computation 2t syndromes based on S(x) */ + /* Odd syndromes */ + for (i = 1; i < 2 * cap; i += 2) { + for (j = 0; j < host->pmecc_degree; j++) { + if (partial_syn[i] & ((unsigned short)0x1 << j)) + si[i] = readw_relaxed(alpha_to + i * j) ^ si[i]; + } + } + /* Even syndrome = (Odd syndrome) ** 2 */ + for (i = 2, j = 1; j <= cap; i = ++j << 1) { + if (si[j] == 0) { + si[i] = 0; + } else { + int16_t tmp; + + tmp = readw_relaxed(index_of + si[j]); + tmp = (tmp * 2) % host->pmecc_cw_len; + si[i] = readw_relaxed(alpha_to + tmp); + } + } + + return; +} + +static void pmecc_get_sigma(struct mtd_info *mtd) +{ + struct nand_chip *nand_chip = mtd->priv; + struct atmel_nand_host *host = nand_chip->priv; + + int16_t *lmu = host->pmecc_lmu; + int16_t *si = host->pmecc_si; + int *mu = host->pmecc_mu; + int *dmu = host->pmecc_dmu; /* Discrepancy */ + int *delta = host->pmecc_delta; /* Delta order */ + int cw_len = host->pmecc_cw_len; + const int16_t cap = host->pmecc_corr_cap; + const int num = 2 * cap + 1; + int16_t __iomem *index_of = host->pmecc_index_of; + int16_t __iomem *alpha_to = host->pmecc_alpha_to; + int i, j, k; + uint32_t dmu_0_count, tmp; + int16_t *smu = host->pmecc_smu; + + /* index of largest delta */ + int ro; + int largest; + int diff; + + dmu_0_count = 0; + + /* First Row */ + + /* Mu */ + mu[0] = -1; + + memset(smu, 0, sizeof(int16_t) * num); + smu[0] = 1; + + /* discrepancy set to 1 */ + dmu[0] = 1; + /* polynom order set to 0 */ + lmu[0] = 0; + delta[0] = (mu[0] * 2 - lmu[0]) >> 1; + + /* Second Row */ + + /* Mu */ + mu[1] = 0; + /* Sigma(x) set to 1 */ + memset(&smu[num], 0, sizeof(int16_t) * num); + smu[num] = 1; + + /* discrepancy set to S1 */ + dmu[1] = si[1]; + + /* polynom order set to 0 */ + lmu[1] = 0; + + delta[1] = (mu[1] * 2 - lmu[1]) >> 1; + + /* Init the Sigma(x) last row */ + memset(&smu[(cap + 1) * num], 0, sizeof(int16_t) * num); + + for (i = 1; i <= cap; i++) { + mu[i + 1] = i << 1; + /* Begin Computing Sigma (Mu+1) and L(mu) */ + /* check if discrepancy is set to 0 */ + if (dmu[i] == 0) { + dmu_0_count++; + + tmp = ((cap - (lmu[i] >> 1) - 1) / 2); + if ((cap - (lmu[i] >> 1) - 1) & 0x1) + tmp += 2; + else + tmp += 1; + + if (dmu_0_count == tmp) { + for (j = 0; j <= (lmu[i] >> 1) + 1; j++) + smu[(cap + 1) * num + j] = + smu[i * num + j]; + + lmu[cap + 1] = lmu[i]; + return; + } + + /* copy polynom */ + for (j = 0; j <= lmu[i] >> 1; j++) + smu[(i + 1) * num + j] = smu[i * num + j]; + + /* copy previous polynom order to the next */ + lmu[i + 1] = lmu[i]; + } else { + ro = 0; + largest = -1; + /* find largest delta with dmu != 0 */ + for (j = 0; j < i; j++) { + if ((dmu[j]) && (delta[j] > largest)) { + largest = delta[j]; + ro = j; + } + } + + /* compute difference */ + diff = (mu[i] - mu[ro]); + + /* Compute degree of the new smu polynomial */ + if ((lmu[i] >> 1) > ((lmu[ro] >> 1) + diff)) + lmu[i + 1] = lmu[i]; + else + lmu[i + 1] = ((lmu[ro] >> 1) + diff) * 2; + + /* Init smu[i+1] with 0 */ + for (k = 0; k < num; k++) + smu[(i + 1) * num + k] = 0; + + /* Compute smu[i+1] */ + for (k = 0; k <= lmu[ro] >> 1; k++) { + int16_t a, b, c; + + if (!(smu[ro * num + k] && dmu[i])) + continue; + a = readw_relaxed(index_of + dmu[i]); + b = readw_relaxed(index_of + dmu[ro]); + c = readw_relaxed(index_of + smu[ro * num + k]); + tmp = a + (cw_len - b) + c; + a = readw_relaxed(alpha_to + tmp % cw_len); + smu[(i + 1) * num + (k + diff)] = a; + } + + for (k = 0; k <= lmu[i] >> 1; k++) + smu[(i + 1) * num + k] ^= smu[i * num + k]; + } + + /* End Computing Sigma (Mu+1) and L(mu) */ + /* In either case compute delta */ + delta[i + 1] = (mu[i + 1] * 2 - lmu[i + 1]) >> 1; + + /* Do not compute discrepancy for the last iteration */ + if (i >= cap) + continue; + + for (k = 0; k <= (lmu[i + 1] >> 1); k++) { + tmp = 2 * (i - 1); + if (k == 0) { + dmu[i + 1] = si[tmp + 3]; + } else if (smu[(i + 1) * num + k] && si[tmp + 3 - k]) { + int16_t a, b, c; + a = readw_relaxed(index_of + + smu[(i + 1) * num + k]); + b = si[2 * (i - 1) + 3 - k]; + c = readw_relaxed(index_of + b); + tmp = a + c; + tmp %= cw_len; + dmu[i + 1] = readw_relaxed(alpha_to + tmp) ^ + dmu[i + 1]; + } + } + } + + return; +} + +static int pmecc_err_location(struct mtd_info *mtd) +{ + struct nand_chip *nand_chip = mtd->priv; + struct atmel_nand_host *host = nand_chip->priv; + unsigned long end_time; + const int cap = host->pmecc_corr_cap; + const int num = 2 * cap + 1; + int sector_size = host->pmecc_sector_size; + int err_nbr = 0; /* number of error */ + int roots_nbr; /* number of roots */ + int i; + uint32_t val; + int16_t *smu = host->pmecc_smu; + + pmerrloc_writel(host->pmerrloc_base, ELDIS, PMERRLOC_DISABLE); + + for (i = 0; i <= host->pmecc_lmu[cap + 1] >> 1; i++) { + pmerrloc_writel_sigma_relaxed(host->pmerrloc_base, i, + smu[(cap + 1) * num + i]); + err_nbr++; + } + + val = (err_nbr - 1) << 16; + if (sector_size == 1024) + val |= 1; + + pmerrloc_writel(host->pmerrloc_base, ELCFG, val); + pmerrloc_writel(host->pmerrloc_base, ELEN, + sector_size * 8 + host->pmecc_degree * cap); + + end_time = jiffies + msecs_to_jiffies(PMECC_MAX_TIMEOUT_MS); + while (!(pmerrloc_readl_relaxed(host->pmerrloc_base, ELISR) + & PMERRLOC_CALC_DONE)) { + if (unlikely(time_after(jiffies, end_time))) { + dev_err(host->dev, "PMECC: Timeout to calculate error location.\n"); + return -1; + } + cpu_relax(); + } + + roots_nbr = (pmerrloc_readl_relaxed(host->pmerrloc_base, ELISR) + & PMERRLOC_ERR_NUM_MASK) >> 8; + /* Number of roots == degree of smu hence <= cap */ + if (roots_nbr == host->pmecc_lmu[cap + 1] >> 1) + return err_nbr - 1; + + /* Number of roots does not match the degree of smu + * unable to correct error */ + return -1; +} + +static void pmecc_correct_data(struct mtd_info *mtd, uint8_t *buf, uint8_t *ecc, + int sector_num, int extra_bytes, int err_nbr) +{ + struct nand_chip *nand_chip = mtd->priv; + struct atmel_nand_host *host = nand_chip->priv; + int i = 0; + int byte_pos, bit_pos, sector_size, pos; + uint32_t tmp; + uint8_t err_byte; + + sector_size = host->pmecc_sector_size; + + while (err_nbr) { + tmp = pmerrloc_readl_el_relaxed(host->pmerrloc_base, i) - 1; + byte_pos = tmp / 8; + bit_pos = tmp % 8; + + if (byte_pos >= (sector_size + extra_bytes)) + BUG(); /* should never happen */ + + if (byte_pos < sector_size) { + err_byte = *(buf + byte_pos); + *(buf + byte_pos) ^= (1 << bit_pos); + + pos = sector_num * host->pmecc_sector_size + byte_pos; + dev_info(host->dev, "Bit flip in data area, byte_pos: %d, bit_pos: %d, 0x%02x -> 0x%02x\n", + pos, bit_pos, err_byte, *(buf + byte_pos)); + } else { + /* Bit flip in OOB area */ + tmp = sector_num * host->pmecc_bytes_per_sector + + (byte_pos - sector_size); + err_byte = ecc[tmp]; + ecc[tmp] ^= (1 << bit_pos); + + pos = tmp + nand_chip->ecc.layout->eccpos[0]; + dev_info(host->dev, "Bit flip in OOB, oob_byte_pos: %d, bit_pos: %d, 0x%02x -> 0x%02x\n", + pos, bit_pos, err_byte, ecc[tmp]); + } + + i++; + err_nbr--; + } + + return; +} + +static int pmecc_correction(struct mtd_info *mtd, u32 pmecc_stat, uint8_t *buf, + u8 *ecc) +{ + struct nand_chip *nand_chip = mtd->priv; + struct atmel_nand_host *host = nand_chip->priv; + int i, err_nbr, eccbytes; + uint8_t *buf_pos; + int total_err = 0; + + eccbytes = nand_chip->ecc.bytes; + for (i = 0; i < eccbytes; i++) + if (ecc[i] != 0xff) + goto normal_check; + /* Erased page, return OK */ + return 0; + +normal_check: + for (i = 0; i < host->pmecc_sector_number; i++) { + err_nbr = 0; + if (pmecc_stat & 0x1) { + buf_pos = buf + i * host->pmecc_sector_size; + + pmecc_gen_syndrome(mtd, i); + pmecc_substitute(mtd); + pmecc_get_sigma(mtd); + + err_nbr = pmecc_err_location(mtd); + if (err_nbr == -1) { + dev_err(host->dev, "PMECC: Too many errors\n"); + mtd->ecc_stats.failed++; + return -EIO; + } else { + pmecc_correct_data(mtd, buf_pos, ecc, i, + host->pmecc_bytes_per_sector, err_nbr); + mtd->ecc_stats.corrected += err_nbr; + total_err += err_nbr; + } + } + pmecc_stat >>= 1; + } + + return total_err; +} + +static void pmecc_enable(struct atmel_nand_host *host, int ecc_op) +{ + u32 val; + + if (ecc_op != NAND_ECC_READ && ecc_op != NAND_ECC_WRITE) { + dev_err(host->dev, "atmel_nand: wrong pmecc operation type!"); + return; + } + + pmecc_writel(host->ecc, CTRL, PMECC_CTRL_RST); + pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE); + val = pmecc_readl_relaxed(host->ecc, CFG); + + if (ecc_op == NAND_ECC_READ) + pmecc_writel(host->ecc, CFG, (val & ~PMECC_CFG_WRITE_OP) + | PMECC_CFG_AUTO_ENABLE); + else + pmecc_writel(host->ecc, CFG, (val | PMECC_CFG_WRITE_OP) + & ~PMECC_CFG_AUTO_ENABLE); + + pmecc_writel(host->ecc, CTRL, PMECC_CTRL_ENABLE); + pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DATA); +} + +static int atmel_nand_pmecc_read_page(struct mtd_info *mtd, + struct nand_chip *chip, uint8_t *buf, int oob_required, int page) +{ + struct atmel_nand_host *host = chip->priv; + int eccsize = chip->ecc.size; + uint8_t *oob = chip->oob_poi; + uint32_t *eccpos = chip->ecc.layout->eccpos; + uint32_t stat; + unsigned long end_time; + int bitflips = 0; + + if (!host->nfc || !host->nfc->use_nfc_sram) + pmecc_enable(host, NAND_ECC_READ); + + chip->read_buf(mtd, buf, eccsize); + chip->read_buf(mtd, oob, mtd->oobsize); + + end_time = jiffies + msecs_to_jiffies(PMECC_MAX_TIMEOUT_MS); + while ((pmecc_readl_relaxed(host->ecc, SR) & PMECC_SR_BUSY)) { + if (unlikely(time_after(jiffies, end_time))) { + dev_err(host->dev, "PMECC: Timeout to get error status.\n"); + return -EIO; + } + cpu_relax(); + } + + stat = pmecc_readl_relaxed(host->ecc, ISR); + if (stat != 0) { + bitflips = pmecc_correction(mtd, stat, buf, &oob[eccpos[0]]); + if (bitflips < 0) + /* uncorrectable errors */ + return 0; + } + + return bitflips; +} + +static int atmel_nand_pmecc_write_page(struct mtd_info *mtd, + struct nand_chip *chip, const uint8_t *buf, int oob_required) +{ + struct atmel_nand_host *host = chip->priv; + uint32_t *eccpos = chip->ecc.layout->eccpos; + int i, j; + unsigned long end_time; + + if (!host->nfc || !host->nfc->write_by_sram) { + pmecc_enable(host, NAND_ECC_WRITE); + chip->write_buf(mtd, (u8 *)buf, mtd->writesize); + } + + end_time = jiffies + msecs_to_jiffies(PMECC_MAX_TIMEOUT_MS); + while ((pmecc_readl_relaxed(host->ecc, SR) & PMECC_SR_BUSY)) { + if (unlikely(time_after(jiffies, end_time))) { + dev_err(host->dev, "PMECC: Timeout to get ECC value.\n"); + return -EIO; + } + cpu_relax(); + } + + for (i = 0; i < host->pmecc_sector_number; i++) { + for (j = 0; j < host->pmecc_bytes_per_sector; j++) { + int pos; + + pos = i * host->pmecc_bytes_per_sector + j; + chip->oob_poi[eccpos[pos]] = + pmecc_readb_ecc_relaxed(host->ecc, i, j); + } + } + chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); + + return 0; +} + +static void atmel_pmecc_core_init(struct mtd_info *mtd) +{ + struct nand_chip *nand_chip = mtd->priv; + struct atmel_nand_host *host = nand_chip->priv; + uint32_t val = 0; + struct nand_ecclayout *ecc_layout; + + pmecc_writel(host->ecc, CTRL, PMECC_CTRL_RST); + pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE); + + switch (host->pmecc_corr_cap) { + case 2: + val = PMECC_CFG_BCH_ERR2; + break; + case 4: + val = PMECC_CFG_BCH_ERR4; + break; + case 8: + val = PMECC_CFG_BCH_ERR8; + break; + case 12: + val = PMECC_CFG_BCH_ERR12; + break; + case 24: + val = PMECC_CFG_BCH_ERR24; + break; + } + + if (host->pmecc_sector_size == 512) + val |= PMECC_CFG_SECTOR512; + else if (host->pmecc_sector_size == 1024) + val |= PMECC_CFG_SECTOR1024; + + switch (host->pmecc_sector_number) { + case 1: + val |= PMECC_CFG_PAGE_1SECTOR; + break; + case 2: + val |= PMECC_CFG_PAGE_2SECTORS; + break; + case 4: + val |= PMECC_CFG_PAGE_4SECTORS; + break; + case 8: + val |= PMECC_CFG_PAGE_8SECTORS; + break; + } + + val |= (PMECC_CFG_READ_OP | PMECC_CFG_SPARE_DISABLE + | PMECC_CFG_AUTO_DISABLE); + pmecc_writel(host->ecc, CFG, val); + + ecc_layout = nand_chip->ecc.layout; + pmecc_writel(host->ecc, SAREA, mtd->oobsize - 1); + pmecc_writel(host->ecc, SADDR, ecc_layout->eccpos[0]); + pmecc_writel(host->ecc, EADDR, + ecc_layout->eccpos[ecc_layout->eccbytes - 1]); + /* See datasheet about PMECC Clock Control Register */ + pmecc_writel(host->ecc, CLK, 2); + pmecc_writel(host->ecc, IDR, 0xff); + pmecc_writel(host->ecc, CTRL, PMECC_CTRL_ENABLE); +} + +/* + * Get minimum ecc requirements from NAND. + * If pmecc-cap, pmecc-sector-size in DTS are not specified, this function + * will set them according to minimum ecc requirement. Otherwise, use the + * value in DTS file. + * return 0 if success. otherwise return error code. + */ +static int pmecc_choose_ecc(struct atmel_nand_host *host, + int *cap, int *sector_size) +{ + /* Get minimum ECC requirements */ + if (host->nand_chip.ecc_strength_ds) { + *cap = host->nand_chip.ecc_strength_ds; + *sector_size = host->nand_chip.ecc_step_ds; + dev_info(host->dev, "minimum ECC: %d bits in %d bytes\n", + *cap, *sector_size); + } else { + *cap = 2; + *sector_size = 512; + dev_info(host->dev, "can't detect min. ECC, assume 2 bits in 512 bytes\n"); + } + + /* If device tree doesn't specify, use NAND's minimum ECC parameters */ + if (host->pmecc_corr_cap == 0) { + /* use the most fitable ecc bits (the near bigger one ) */ + if (*cap <= 2) + host->pmecc_corr_cap = 2; + else if (*cap <= 4) + host->pmecc_corr_cap = 4; + else if (*cap <= 8) + host->pmecc_corr_cap = 8; + else if (*cap <= 12) + host->pmecc_corr_cap = 12; + else if (*cap <= 24) + host->pmecc_corr_cap = 24; + else + return -EINVAL; + } + if (host->pmecc_sector_size == 0) { + /* use the most fitable sector size (the near smaller one ) */ + if (*sector_size >= 1024) + host->pmecc_sector_size = 1024; + else if (*sector_size >= 512) + host->pmecc_sector_size = 512; + else + return -EINVAL; + } + return 0; +} + +static int atmel_pmecc_nand_init_params(struct platform_device *pdev, + struct atmel_nand_host *host) +{ + struct mtd_info *mtd = &host->mtd; + struct nand_chip *nand_chip = &host->nand_chip; + struct resource *regs, *regs_pmerr, *regs_rom; + int cap, sector_size, err_no; + + err_no = pmecc_choose_ecc(host, &cap, §or_size); + if (err_no) { + dev_err(host->dev, "The NAND flash's ECC requirement are not support!"); + return err_no; + } + + if (cap > host->pmecc_corr_cap || + sector_size != host->pmecc_sector_size) + dev_info(host->dev, "WARNING: Be Caution! Using different PMECC parameters from Nand ONFI ECC reqirement.\n"); + + cap = host->pmecc_corr_cap; + sector_size = host->pmecc_sector_size; + host->pmecc_lookup_table_offset = (sector_size == 512) ? + host->pmecc_lookup_table_offset_512 : + host->pmecc_lookup_table_offset_1024; + + dev_info(host->dev, "Initialize PMECC params, cap: %d, sector: %d\n", + cap, sector_size); + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!regs) { + dev_warn(host->dev, + "Can't get I/O resource regs for PMECC controller, rolling back on software ECC\n"); + nand_chip->ecc.mode = NAND_ECC_SOFT; + return 0; + } + + host->ecc = devm_ioremap_resource(&pdev->dev, regs); + if (IS_ERR(host->ecc)) { + dev_err(host->dev, "ioremap failed\n"); + err_no = PTR_ERR(host->ecc); + goto err; + } + + regs_pmerr = platform_get_resource(pdev, IORESOURCE_MEM, 2); + host->pmerrloc_base = devm_ioremap_resource(&pdev->dev, regs_pmerr); + if (IS_ERR(host->pmerrloc_base)) { + dev_err(host->dev, + "Can not get I/O resource for PMECC ERRLOC controller!\n"); + err_no = PTR_ERR(host->pmerrloc_base); + goto err; + } + + regs_rom = platform_get_resource(pdev, IORESOURCE_MEM, 3); + host->pmecc_rom_base = devm_ioremap_resource(&pdev->dev, regs_rom); + if (IS_ERR(host->pmecc_rom_base)) { + dev_err(host->dev, "Can not get I/O resource for ROM!\n"); + err_no = PTR_ERR(host->pmecc_rom_base); + goto err; + } + + /* ECC is calculated for the whole page (1 step) */ + nand_chip->ecc.size = mtd->writesize; + + /* set ECC page size and oob layout */ + switch (mtd->writesize) { + case 2048: + host->pmecc_degree = (sector_size == 512) ? + PMECC_GF_DIMENSION_13 : PMECC_GF_DIMENSION_14; + host->pmecc_cw_len = (1 << host->pmecc_degree) - 1; + host->pmecc_sector_number = mtd->writesize / sector_size; + host->pmecc_bytes_per_sector = pmecc_get_ecc_bytes( + cap, sector_size); + host->pmecc_alpha_to = pmecc_get_alpha_to(host); + host->pmecc_index_of = host->pmecc_rom_base + + host->pmecc_lookup_table_offset; + + nand_chip->ecc.steps = 1; + nand_chip->ecc.strength = cap; + nand_chip->ecc.bytes = host->pmecc_bytes_per_sector * + host->pmecc_sector_number; + if (nand_chip->ecc.bytes > mtd->oobsize - 2) { + dev_err(host->dev, "No room for ECC bytes\n"); + err_no = -EINVAL; + goto err; + } + pmecc_config_ecc_layout(&atmel_pmecc_oobinfo, + mtd->oobsize, + nand_chip->ecc.bytes); + nand_chip->ecc.layout = &atmel_pmecc_oobinfo; + break; + case 512: + case 1024: + case 4096: + /* TODO */ + dev_warn(host->dev, + "Unsupported page size for PMECC, use Software ECC\n"); + default: + /* page size not handled by HW ECC */ + /* switching back to soft ECC */ + nand_chip->ecc.mode = NAND_ECC_SOFT; + return 0; + } + + /* Allocate data for PMECC computation */ + err_no = pmecc_data_alloc(host); + if (err_no) { + dev_err(host->dev, + "Cannot allocate memory for PMECC computation!\n"); + goto err; + } + + nand_chip->options |= NAND_NO_SUBPAGE_WRITE; + nand_chip->ecc.read_page = atmel_nand_pmecc_read_page; + nand_chip->ecc.write_page = atmel_nand_pmecc_write_page; + + atmel_pmecc_core_init(mtd); + + return 0; + +err: + return err_no; +} + +/* + * Calculate HW ECC + * + * function called after a write + * + * mtd: MTD block structure + * dat: raw data (unused) + * ecc_code: buffer for ECC + */ +static int atmel_nand_calculate(struct mtd_info *mtd, + const u_char *dat, unsigned char *ecc_code) +{ + struct nand_chip *nand_chip = mtd->priv; + struct atmel_nand_host *host = nand_chip->priv; + unsigned int ecc_value; + + /* get the first 2 ECC bytes */ + ecc_value = ecc_readl(host->ecc, PR); + + ecc_code[0] = ecc_value & 0xFF; + ecc_code[1] = (ecc_value >> 8) & 0xFF; + + /* get the last 2 ECC bytes */ + ecc_value = ecc_readl(host->ecc, NPR) & ATMEL_ECC_NPARITY; + + ecc_code[2] = ecc_value & 0xFF; + ecc_code[3] = (ecc_value >> 8) & 0xFF; + + return 0; +} + +/* + * HW ECC read page function + * + * mtd: mtd info structure + * chip: nand chip info structure + * buf: buffer to store read data + * oob_required: caller expects OOB data read to chip->oob_poi + */ +static int atmel_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf, int oob_required, int page) +{ + int eccsize = chip->ecc.size; + int eccbytes = chip->ecc.bytes; + uint32_t *eccpos = chip->ecc.layout->eccpos; + uint8_t *p = buf; + uint8_t *oob = chip->oob_poi; + uint8_t *ecc_pos; + int stat; + unsigned int max_bitflips = 0; + + /* + * Errata: ALE is incorrectly wired up to the ECC controller + * on the AP7000, so it will include the address cycles in the + * ECC calculation. + * + * Workaround: Reset the parity registers before reading the + * actual data. + */ + struct atmel_nand_host *host = chip->priv; + if (host->board.need_reset_workaround) + ecc_writel(host->ecc, CR, ATMEL_ECC_RST); + + /* read the page */ + chip->read_buf(mtd, p, eccsize); + + /* move to ECC position if needed */ + if (eccpos[0] != 0) { + /* This only works on large pages + * because the ECC controller waits for + * NAND_CMD_RNDOUTSTART after the + * NAND_CMD_RNDOUT. + * anyway, for small pages, the eccpos[0] == 0 + */ + chip->cmdfunc(mtd, NAND_CMD_RNDOUT, + mtd->writesize + eccpos[0], -1); + } + + /* the ECC controller needs to read the ECC just after the data */ + ecc_pos = oob + eccpos[0]; + chip->read_buf(mtd, ecc_pos, eccbytes); + + /* check if there's an error */ + stat = chip->ecc.correct(mtd, p, oob, NULL); + + if (stat < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } + + /* get back to oob start (end of page) */ + chip->cmdfunc(mtd, NAND_CMD_RNDOUT, mtd->writesize, -1); + + /* read the oob */ + chip->read_buf(mtd, oob, mtd->oobsize); + + return max_bitflips; +} + +/* + * HW ECC Correction + * + * function called after a read + * + * mtd: MTD block structure + * dat: raw data read from the chip + * read_ecc: ECC from the chip (unused) + * isnull: unused + * + * Detect and correct a 1 bit error for a page + */ +static int atmel_nand_correct(struct mtd_info *mtd, u_char *dat, + u_char *read_ecc, u_char *isnull) +{ + struct nand_chip *nand_chip = mtd->priv; + struct atmel_nand_host *host = nand_chip->priv; + unsigned int ecc_status; + unsigned int ecc_word, ecc_bit; + + /* get the status from the Status Register */ + ecc_status = ecc_readl(host->ecc, SR); + + /* if there's no error */ + if (likely(!(ecc_status & ATMEL_ECC_RECERR))) + return 0; + + /* get error bit offset (4 bits) */ + ecc_bit = ecc_readl(host->ecc, PR) & ATMEL_ECC_BITADDR; + /* get word address (12 bits) */ + ecc_word = ecc_readl(host->ecc, PR) & ATMEL_ECC_WORDADDR; + ecc_word >>= 4; + + /* if there are multiple errors */ + if (ecc_status & ATMEL_ECC_MULERR) { + /* check if it is a freshly erased block + * (filled with 0xff) */ + if ((ecc_bit == ATMEL_ECC_BITADDR) + && (ecc_word == (ATMEL_ECC_WORDADDR >> 4))) { + /* the block has just been erased, return OK */ + return 0; + } + /* it doesn't seems to be a freshly + * erased block. + * We can't correct so many errors */ + dev_dbg(host->dev, "atmel_nand : multiple errors detected." + " Unable to correct.\n"); + return -EIO; + } + + /* if there's a single bit error : we can correct it */ + if (ecc_status & ATMEL_ECC_ECCERR) { + /* there's nothing much to do here. + * the bit error is on the ECC itself. + */ + dev_dbg(host->dev, "atmel_nand : one bit error on ECC code." + " Nothing to correct\n"); + return 0; + } + + dev_dbg(host->dev, "atmel_nand : one bit error on data." + " (word offset in the page :" + " 0x%x bit offset : 0x%x)\n", + ecc_word, ecc_bit); + /* correct the error */ + if (nand_chip->options & NAND_BUSWIDTH_16) { + /* 16 bits words */ + ((unsigned short *) dat)[ecc_word] ^= (1 << ecc_bit); + } else { + /* 8 bits words */ + dat[ecc_word] ^= (1 << ecc_bit); + } + dev_dbg(host->dev, "atmel_nand : error corrected\n"); + return 1; +} + +/* + * Enable HW ECC : unused on most chips + */ +static void atmel_nand_hwctl(struct mtd_info *mtd, int mode) +{ + struct nand_chip *nand_chip = mtd->priv; + struct atmel_nand_host *host = nand_chip->priv; + + if (host->board.need_reset_workaround) + ecc_writel(host->ecc, CR, ATMEL_ECC_RST); +} + +static int atmel_of_init_port(struct atmel_nand_host *host, + struct device_node *np) +{ + u32 val; + u32 offset[2]; + int ecc_mode; + struct atmel_nand_data *board = &host->board; + enum of_gpio_flags flags = 0; + + if (of_property_read_u32(np, "atmel,nand-addr-offset", &val) == 0) { + if (val >= 32) { + dev_err(host->dev, "invalid addr-offset %u\n", val); + return -EINVAL; + } + board->ale = val; + } + + if (of_property_read_u32(np, "atmel,nand-cmd-offset", &val) == 0) { + if (val >= 32) { + dev_err(host->dev, "invalid cmd-offset %u\n", val); + return -EINVAL; + } + board->cle = val; + } + + ecc_mode = of_get_nand_ecc_mode(np); + + board->ecc_mode = ecc_mode < 0 ? NAND_ECC_SOFT : ecc_mode; + + board->on_flash_bbt = of_get_nand_on_flash_bbt(np); + + board->has_dma = of_property_read_bool(np, "atmel,nand-has-dma"); + + if (of_get_nand_bus_width(np) == 16) + board->bus_width_16 = 1; + + board->rdy_pin = of_get_gpio_flags(np, 0, &flags); + board->rdy_pin_active_low = (flags == OF_GPIO_ACTIVE_LOW); + + board->enable_pin = of_get_gpio(np, 1); + board->det_pin = of_get_gpio(np, 2); + + host->has_pmecc = of_property_read_bool(np, "atmel,has-pmecc"); + + /* load the nfc driver if there is */ + of_platform_populate(np, NULL, NULL, host->dev); + + if (!(board->ecc_mode == NAND_ECC_HW) || !host->has_pmecc) + return 0; /* Not using PMECC */ + + /* use PMECC, get correction capability, sector size and lookup + * table offset. + * If correction bits and sector size are not specified, then find + * them from NAND ONFI parameters. + */ + if (of_property_read_u32(np, "atmel,pmecc-cap", &val) == 0) { + if ((val != 2) && (val != 4) && (val != 8) && (val != 12) && + (val != 24)) { + dev_err(host->dev, + "Unsupported PMECC correction capability: %d; should be 2, 4, 8, 12 or 24\n", + val); + return -EINVAL; + } + host->pmecc_corr_cap = (u8)val; + } + + if (of_property_read_u32(np, "atmel,pmecc-sector-size", &val) == 0) { + if ((val != 512) && (val != 1024)) { + dev_err(host->dev, + "Unsupported PMECC sector size: %d; should be 512 or 1024 bytes\n", + val); + return -EINVAL; + } + host->pmecc_sector_size = (u16)val; + } + + if (of_property_read_u32_array(np, "atmel,pmecc-lookup-table-offset", + offset, 2) != 0) { + dev_err(host->dev, "Cannot get PMECC lookup table offset\n"); + return -EINVAL; + } + if (!offset[0] && !offset[1]) { + dev_err(host->dev, "Invalid PMECC lookup table offset\n"); + return -EINVAL; + } + host->pmecc_lookup_table_offset_512 = offset[0]; + host->pmecc_lookup_table_offset_1024 = offset[1]; + + return 0; +} + +static int atmel_hw_nand_init_params(struct platform_device *pdev, + struct atmel_nand_host *host) +{ + struct mtd_info *mtd = &host->mtd; + struct nand_chip *nand_chip = &host->nand_chip; + struct resource *regs; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!regs) { + dev_err(host->dev, + "Can't get I/O resource regs, use software ECC\n"); + nand_chip->ecc.mode = NAND_ECC_SOFT; + return 0; + } + + host->ecc = devm_ioremap_resource(&pdev->dev, regs); + if (IS_ERR(host->ecc)) { + dev_err(host->dev, "ioremap failed\n"); + return PTR_ERR(host->ecc); + } + + /* ECC is calculated for the whole page (1 step) */ + nand_chip->ecc.size = mtd->writesize; + + /* set ECC page size and oob layout */ + switch (mtd->writesize) { + case 512: + nand_chip->ecc.layout = &atmel_oobinfo_small; + ecc_writel(host->ecc, MR, ATMEL_ECC_PAGESIZE_528); + break; + case 1024: + nand_chip->ecc.layout = &atmel_oobinfo_large; + ecc_writel(host->ecc, MR, ATMEL_ECC_PAGESIZE_1056); + break; + case 2048: + nand_chip->ecc.layout = &atmel_oobinfo_large; + ecc_writel(host->ecc, MR, ATMEL_ECC_PAGESIZE_2112); + break; + case 4096: + nand_chip->ecc.layout = &atmel_oobinfo_large; + ecc_writel(host->ecc, MR, ATMEL_ECC_PAGESIZE_4224); + break; + default: + /* page size not handled by HW ECC */ + /* switching back to soft ECC */ + nand_chip->ecc.mode = NAND_ECC_SOFT; + return 0; + } + + /* set up for HW ECC */ + nand_chip->ecc.calculate = atmel_nand_calculate; + nand_chip->ecc.correct = atmel_nand_correct; + nand_chip->ecc.hwctl = atmel_nand_hwctl; + nand_chip->ecc.read_page = atmel_nand_read_page; + nand_chip->ecc.bytes = 4; + nand_chip->ecc.strength = 1; + + return 0; +} + +/* SMC interrupt service routine */ +static irqreturn_t hsmc_interrupt(int irq, void *dev_id) +{ + struct atmel_nand_host *host = dev_id; + u32 status, mask, pending; + irqreturn_t ret = IRQ_HANDLED; + + status = nfc_readl(host->nfc->hsmc_regs, SR); + mask = nfc_readl(host->nfc->hsmc_regs, IMR); + pending = status & mask; + + if (pending & NFC_SR_XFR_DONE) { + complete(&host->nfc->comp_nfc); + nfc_writel(host->nfc->hsmc_regs, IDR, NFC_SR_XFR_DONE); + } else if (pending & NFC_SR_RB_EDGE) { + complete(&host->nfc->comp_nfc); + nfc_writel(host->nfc->hsmc_regs, IDR, NFC_SR_RB_EDGE); + } else if (pending & NFC_SR_CMD_DONE) { + complete(&host->nfc->comp_nfc); + nfc_writel(host->nfc->hsmc_regs, IDR, NFC_SR_CMD_DONE); + } else { + ret = IRQ_NONE; + } + + return ret; +} + +/* NFC(Nand Flash Controller) related functions */ +static int nfc_wait_interrupt(struct atmel_nand_host *host, u32 flag) +{ + unsigned long timeout; + init_completion(&host->nfc->comp_nfc); + + /* Enable interrupt that need to wait for */ + nfc_writel(host->nfc->hsmc_regs, IER, flag); + + timeout = wait_for_completion_timeout(&host->nfc->comp_nfc, + msecs_to_jiffies(NFC_TIME_OUT_MS)); + if (timeout) + return 0; + + /* Time out to wait for the interrupt */ + dev_err(host->dev, "Time out to wait for interrupt: 0x%08x\n", flag); + return -ETIMEDOUT; +} + +static int nfc_send_command(struct atmel_nand_host *host, + unsigned int cmd, unsigned int addr, unsigned char cycle0) +{ + unsigned long timeout; + dev_dbg(host->dev, + "nfc_cmd: 0x%08x, addr1234: 0x%08x, cycle0: 0x%02x\n", + cmd, addr, cycle0); + + timeout = jiffies + msecs_to_jiffies(NFC_TIME_OUT_MS); + while (nfc_cmd_readl(NFCADDR_CMD_NFCBUSY, host->nfc->base_cmd_regs) + & NFCADDR_CMD_NFCBUSY) { + if (time_after(jiffies, timeout)) { + dev_err(host->dev, + "Time out to wait CMD_NFCBUSY ready!\n"); + return -ETIMEDOUT; + } + } + nfc_writel(host->nfc->hsmc_regs, CYCLE0, cycle0); + nfc_cmd_addr1234_writel(cmd, addr, host->nfc->base_cmd_regs); + return nfc_wait_interrupt(host, NFC_SR_CMD_DONE); +} + +static int nfc_device_ready(struct mtd_info *mtd) +{ + struct nand_chip *nand_chip = mtd->priv; + struct atmel_nand_host *host = nand_chip->priv; + if (!nfc_wait_interrupt(host, NFC_SR_RB_EDGE)) + return 1; + return 0; +} + +static void nfc_select_chip(struct mtd_info *mtd, int chip) +{ + struct nand_chip *nand_chip = mtd->priv; + struct atmel_nand_host *host = nand_chip->priv; + + if (chip == -1) + nfc_writel(host->nfc->hsmc_regs, CTRL, NFC_CTRL_DISABLE); + else + nfc_writel(host->nfc->hsmc_regs, CTRL, NFC_CTRL_ENABLE); +} + +static int nfc_make_addr(struct mtd_info *mtd, int command, int column, + int page_addr, unsigned int *addr1234, unsigned int *cycle0) +{ + struct nand_chip *chip = mtd->priv; + + int acycle = 0; + unsigned char addr_bytes[8]; + int index = 0, bit_shift; + + BUG_ON(addr1234 == NULL || cycle0 == NULL); + + *cycle0 = 0; + *addr1234 = 0; + + if (column != -1) { + if (chip->options & NAND_BUSWIDTH_16 && + !nand_opcode_8bits(command)) + column >>= 1; + addr_bytes[acycle++] = column & 0xff; + if (mtd->writesize > 512) + addr_bytes[acycle++] = (column >> 8) & 0xff; + } + + if (page_addr != -1) { + addr_bytes[acycle++] = page_addr & 0xff; + addr_bytes[acycle++] = (page_addr >> 8) & 0xff; + if (chip->chipsize > (128 << 20)) + addr_bytes[acycle++] = (page_addr >> 16) & 0xff; + } + + if (acycle > 4) + *cycle0 = addr_bytes[index++]; + + for (bit_shift = 0; index < acycle; bit_shift += 8) + *addr1234 += addr_bytes[index++] << bit_shift; + + /* return acycle in cmd register */ + return acycle << NFCADDR_CMD_ACYCLE_BIT_POS; +} + +static void nfc_nand_command(struct mtd_info *mtd, unsigned int command, + int column, int page_addr) +{ + struct nand_chip *chip = mtd->priv; + struct atmel_nand_host *host = chip->priv; + unsigned long timeout; + unsigned int nfc_addr_cmd = 0; + + unsigned int cmd1 = command << NFCADDR_CMD_CMD1_BIT_POS; + + /* Set default settings: no cmd2, no addr cycle. read from nand */ + unsigned int cmd2 = 0; + unsigned int vcmd2 = 0; + int acycle = NFCADDR_CMD_ACYCLE_NONE; + int csid = NFCADDR_CMD_CSID_3; + int dataen = NFCADDR_CMD_DATADIS; + int nfcwr = NFCADDR_CMD_NFCRD; + unsigned int addr1234 = 0; + unsigned int cycle0 = 0; + bool do_addr = true; + host->nfc->data_in_sram = NULL; + + dev_dbg(host->dev, "%s: cmd = 0x%02x, col = 0x%08x, page = 0x%08x\n", + __func__, command, column, page_addr); + + switch (command) { + case NAND_CMD_RESET: + nfc_addr_cmd = cmd1 | acycle | csid | dataen | nfcwr; + nfc_send_command(host, nfc_addr_cmd, addr1234, cycle0); + udelay(chip->chip_delay); + + nfc_nand_command(mtd, NAND_CMD_STATUS, -1, -1); + timeout = jiffies + msecs_to_jiffies(NFC_TIME_OUT_MS); + while (!(chip->read_byte(mtd) & NAND_STATUS_READY)) { + if (time_after(jiffies, timeout)) { + dev_err(host->dev, + "Time out to wait status ready!\n"); + break; + } + } + return; + case NAND_CMD_STATUS: + do_addr = false; + break; + case NAND_CMD_PARAM: + case NAND_CMD_READID: + do_addr = false; + acycle = NFCADDR_CMD_ACYCLE_1; + if (column != -1) + addr1234 = column; + break; + case NAND_CMD_RNDOUT: + cmd2 = NAND_CMD_RNDOUTSTART << NFCADDR_CMD_CMD2_BIT_POS; + vcmd2 = NFCADDR_CMD_VCMD2; + break; + case NAND_CMD_READ0: + case NAND_CMD_READOOB: + if (command == NAND_CMD_READOOB) { + column += mtd->writesize; + command = NAND_CMD_READ0; /* only READ0 is valid */ + cmd1 = command << NFCADDR_CMD_CMD1_BIT_POS; + } + if (host->nfc->use_nfc_sram) { + /* Enable Data transfer to sram */ + dataen = NFCADDR_CMD_DATAEN; + + /* Need enable PMECC now, since NFC will transfer + * data in bus after sending nfc read command. + */ + if (chip->ecc.mode == NAND_ECC_HW && host->has_pmecc) + pmecc_enable(host, NAND_ECC_READ); + } + + cmd2 = NAND_CMD_READSTART << NFCADDR_CMD_CMD2_BIT_POS; + vcmd2 = NFCADDR_CMD_VCMD2; + break; + /* For prgramming command, the cmd need set to write enable */ + case NAND_CMD_PAGEPROG: + case NAND_CMD_SEQIN: + case NAND_CMD_RNDIN: + nfcwr = NFCADDR_CMD_NFCWR; + if (host->nfc->will_write_sram && command == NAND_CMD_SEQIN) + dataen = NFCADDR_CMD_DATAEN; + break; + default: + break; + } + + if (do_addr) + acycle = nfc_make_addr(mtd, command, column, page_addr, + &addr1234, &cycle0); + + nfc_addr_cmd = cmd1 | cmd2 | vcmd2 | acycle | csid | dataen | nfcwr; + nfc_send_command(host, nfc_addr_cmd, addr1234, cycle0); + + if (dataen == NFCADDR_CMD_DATAEN) + if (nfc_wait_interrupt(host, NFC_SR_XFR_DONE)) + dev_err(host->dev, "something wrong, No XFR_DONE interrupt comes.\n"); + + /* + * Program and erase have their own busy handlers status, sequential + * in, and deplete1 need no delay. + */ + switch (command) { + case NAND_CMD_CACHEDPROG: + case NAND_CMD_PAGEPROG: + case NAND_CMD_ERASE1: + case NAND_CMD_ERASE2: + case NAND_CMD_RNDIN: + case NAND_CMD_STATUS: + case NAND_CMD_RNDOUT: + case NAND_CMD_SEQIN: + case NAND_CMD_READID: + return; + + case NAND_CMD_READ0: + if (dataen == NFCADDR_CMD_DATAEN) { + host->nfc->data_in_sram = host->nfc->sram_bank0 + + nfc_get_sram_off(host); + return; + } + /* fall through */ + default: + nfc_wait_interrupt(host, NFC_SR_RB_EDGE); + } +} + +static int nfc_sram_write_page(struct mtd_info *mtd, struct nand_chip *chip, + uint32_t offset, int data_len, const uint8_t *buf, + int oob_required, int page, int cached, int raw) +{ + int cfg, len; + int status = 0; + struct atmel_nand_host *host = chip->priv; + void __iomem *sram = host->nfc->sram_bank0 + nfc_get_sram_off(host); + + /* Subpage write is not supported */ + if (offset || (data_len < mtd->writesize)) + return -EINVAL; + + cfg = nfc_readl(host->nfc->hsmc_regs, CFG); + len = mtd->writesize; + + if (unlikely(raw)) { + len += mtd->oobsize; + nfc_writel(host->nfc->hsmc_regs, CFG, cfg | NFC_CFG_WSPARE); + } else + nfc_writel(host->nfc->hsmc_regs, CFG, cfg & ~NFC_CFG_WSPARE); + + /* Copy page data to sram that will write to nand via NFC */ + if (use_dma) { + if (atmel_nand_dma_op(mtd, (void *)buf, len, 0) != 0) + /* Fall back to use cpu copy */ + memcpy32_toio(sram, buf, len); + } else { + memcpy32_toio(sram, buf, len); + } + + if (chip->ecc.mode == NAND_ECC_HW && host->has_pmecc) + /* + * When use NFC sram, need set up PMECC before send + * NAND_CMD_SEQIN command. Since when the nand command + * is sent, nfc will do transfer from sram and nand. + */ + pmecc_enable(host, NAND_ECC_WRITE); + + host->nfc->will_write_sram = true; + chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page); + host->nfc->will_write_sram = false; + + if (likely(!raw)) + /* Need to write ecc into oob */ + status = chip->ecc.write_page(mtd, chip, buf, oob_required); + + if (status < 0) + return status; + + chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + status = chip->waitfunc(mtd, chip); + + if ((status & NAND_STATUS_FAIL) && (chip->errstat)) + status = chip->errstat(mtd, chip, FL_WRITING, status, page); + + if (status & NAND_STATUS_FAIL) + return -EIO; + + return 0; +} + +static int nfc_sram_init(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct atmel_nand_host *host = chip->priv; + int res = 0; + + /* Initialize the NFC CFG register */ + unsigned int cfg_nfc = 0; + + /* set page size and oob layout */ + switch (mtd->writesize) { + case 512: + cfg_nfc = NFC_CFG_PAGESIZE_512; + break; + case 1024: + cfg_nfc = NFC_CFG_PAGESIZE_1024; + break; + case 2048: + cfg_nfc = NFC_CFG_PAGESIZE_2048; + break; + case 4096: + cfg_nfc = NFC_CFG_PAGESIZE_4096; + break; + case 8192: + cfg_nfc = NFC_CFG_PAGESIZE_8192; + break; + default: + dev_err(host->dev, "Unsupported page size for NFC.\n"); + res = -ENXIO; + return res; + } + + /* oob bytes size = (NFCSPARESIZE + 1) * 4 + * Max support spare size is 512 bytes. */ + cfg_nfc |= (((mtd->oobsize / 4) - 1) << NFC_CFG_NFC_SPARESIZE_BIT_POS + & NFC_CFG_NFC_SPARESIZE); + /* default set a max timeout */ + cfg_nfc |= NFC_CFG_RSPARE | + NFC_CFG_NFC_DTOCYC | NFC_CFG_NFC_DTOMUL; + + nfc_writel(host->nfc->hsmc_regs, CFG, cfg_nfc); + + host->nfc->will_write_sram = false; + nfc_set_sram_bank(host, 0); + + /* Use Write page with NFC SRAM only for PMECC or ECC NONE. */ + if (host->nfc->write_by_sram) { + if ((chip->ecc.mode == NAND_ECC_HW && host->has_pmecc) || + chip->ecc.mode == NAND_ECC_NONE) + chip->write_page = nfc_sram_write_page; + else + host->nfc->write_by_sram = false; + } + + dev_info(host->dev, "Using NFC Sram read %s\n", + host->nfc->write_by_sram ? "and write" : ""); + return 0; +} + +static struct platform_driver atmel_nand_nfc_driver; +/* + * Probe for the NAND device. + */ +static int atmel_nand_probe(struct platform_device *pdev) +{ + struct atmel_nand_host *host; + struct mtd_info *mtd; + struct nand_chip *nand_chip; + struct resource *mem; + struct mtd_part_parser_data ppdata = {}; + int res, irq; + + /* Allocate memory for the device structure (and zero it) */ + host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + res = platform_driver_register(&atmel_nand_nfc_driver); + if (res) + dev_err(&pdev->dev, "atmel_nand: can't register NFC driver\n"); + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + host->io_base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(host->io_base)) { + dev_err(&pdev->dev, "atmel_nand: ioremap resource failed\n"); + res = PTR_ERR(host->io_base); + goto err_nand_ioremap; + } + host->io_phys = (dma_addr_t)mem->start; + + mtd = &host->mtd; + nand_chip = &host->nand_chip; + host->dev = &pdev->dev; + if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { + /* Only when CONFIG_OF is enabled of_node can be parsed */ + res = atmel_of_init_port(host, pdev->dev.of_node); + if (res) + goto err_nand_ioremap; + } else { + memcpy(&host->board, dev_get_platdata(&pdev->dev), + sizeof(struct atmel_nand_data)); + } + + nand_chip->priv = host; /* link the private data structures */ + mtd->priv = nand_chip; + mtd->owner = THIS_MODULE; + + /* Set address of NAND IO lines */ + nand_chip->IO_ADDR_R = host->io_base; + nand_chip->IO_ADDR_W = host->io_base; + + if (nand_nfc.is_initialized) { + /* NFC driver is probed and initialized */ + host->nfc = &nand_nfc; + + nand_chip->select_chip = nfc_select_chip; + nand_chip->dev_ready = nfc_device_ready; + nand_chip->cmdfunc = nfc_nand_command; + + /* Initialize the interrupt for NFC */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(host->dev, "Cannot get HSMC irq!\n"); + res = irq; + goto err_nand_ioremap; + } + + res = devm_request_irq(&pdev->dev, irq, hsmc_interrupt, + 0, "hsmc", host); + if (res) { + dev_err(&pdev->dev, "Unable to request HSMC irq %d\n", + irq); + goto err_nand_ioremap; + } + } else { + res = atmel_nand_set_enable_ready_pins(mtd); + if (res) + goto err_nand_ioremap; + + nand_chip->cmd_ctrl = atmel_nand_cmd_ctrl; + } + + nand_chip->ecc.mode = host->board.ecc_mode; + nand_chip->chip_delay = 20; /* 20us command delay time */ + + if (host->board.bus_width_16) /* 16-bit bus width */ + nand_chip->options |= NAND_BUSWIDTH_16; + + nand_chip->read_buf = atmel_read_buf; + nand_chip->write_buf = atmel_write_buf; + + platform_set_drvdata(pdev, host); + atmel_nand_enable(host); + + if (gpio_is_valid(host->board.det_pin)) { + res = devm_gpio_request(&pdev->dev, + host->board.det_pin, "nand_det"); + if (res < 0) { + dev_err(&pdev->dev, + "can't request det gpio %d\n", + host->board.det_pin); + goto err_no_card; + } + + res = gpio_direction_input(host->board.det_pin); + if (res < 0) { + dev_err(&pdev->dev, + "can't request input direction det gpio %d\n", + host->board.det_pin); + goto err_no_card; + } + + if (gpio_get_value(host->board.det_pin)) { + dev_info(&pdev->dev, "No SmartMedia card inserted.\n"); + res = -ENXIO; + goto err_no_card; + } + } + + if (host->board.on_flash_bbt || on_flash_bbt) { + dev_info(&pdev->dev, "Use On Flash BBT\n"); + nand_chip->bbt_options |= NAND_BBT_USE_FLASH; + } + + if (!host->board.has_dma) + use_dma = 0; + + if (use_dma) { + dma_cap_mask_t mask; + + dma_cap_zero(mask); + dma_cap_set(DMA_MEMCPY, mask); + host->dma_chan = dma_request_channel(mask, NULL, NULL); + if (!host->dma_chan) { + dev_err(host->dev, "Failed to request DMA channel\n"); + use_dma = 0; + } + } + if (use_dma) + dev_info(host->dev, "Using %s for DMA transfers.\n", + dma_chan_name(host->dma_chan)); + else + dev_info(host->dev, "No DMA support for NAND access.\n"); + + /* first scan to find the device and get the page size */ + if (nand_scan_ident(mtd, 1, NULL)) { + res = -ENXIO; + goto err_scan_ident; + } + + if (nand_chip->ecc.mode == NAND_ECC_HW) { + if (host->has_pmecc) + res = atmel_pmecc_nand_init_params(pdev, host); + else + res = atmel_hw_nand_init_params(pdev, host); + + if (res != 0) + goto err_hw_ecc; + } + + /* initialize the nfc configuration register */ + if (host->nfc && host->nfc->use_nfc_sram) { + res = nfc_sram_init(mtd); + if (res) { + host->nfc->use_nfc_sram = false; + dev_err(host->dev, "Disable use nfc sram for data transfer.\n"); + } + } + + /* second phase scan */ + if (nand_scan_tail(mtd)) { + res = -ENXIO; + goto err_scan_tail; + } + + mtd->name = "atmel_nand"; + ppdata.of_node = pdev->dev.of_node; + res = mtd_device_parse_register(mtd, NULL, &ppdata, + host->board.parts, host->board.num_parts); + if (!res) + return res; + +err_scan_tail: + if (host->has_pmecc && host->nand_chip.ecc.mode == NAND_ECC_HW) + pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE); +err_hw_ecc: +err_scan_ident: +err_no_card: + atmel_nand_disable(host); + if (host->dma_chan) + dma_release_channel(host->dma_chan); +err_nand_ioremap: + return res; +} + +/* + * Remove a NAND device. + */ +static int atmel_nand_remove(struct platform_device *pdev) +{ + struct atmel_nand_host *host = platform_get_drvdata(pdev); + struct mtd_info *mtd = &host->mtd; + + nand_release(mtd); + + atmel_nand_disable(host); + + if (host->has_pmecc && host->nand_chip.ecc.mode == NAND_ECC_HW) { + pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE); + pmerrloc_writel(host->pmerrloc_base, ELDIS, + PMERRLOC_DISABLE); + } + + if (host->dma_chan) + dma_release_channel(host->dma_chan); + + platform_driver_unregister(&atmel_nand_nfc_driver); + + return 0; +} + +static const struct of_device_id atmel_nand_dt_ids[] = { + { .compatible = "atmel,at91rm9200-nand" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, atmel_nand_dt_ids); + +static int atmel_nand_nfc_probe(struct platform_device *pdev) +{ + struct atmel_nfc *nfc = &nand_nfc; + struct resource *nfc_cmd_regs, *nfc_hsmc_regs, *nfc_sram; + + nfc_cmd_regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + nfc->base_cmd_regs = devm_ioremap_resource(&pdev->dev, nfc_cmd_regs); + if (IS_ERR(nfc->base_cmd_regs)) + return PTR_ERR(nfc->base_cmd_regs); + + nfc_hsmc_regs = platform_get_resource(pdev, IORESOURCE_MEM, 1); + nfc->hsmc_regs = devm_ioremap_resource(&pdev->dev, nfc_hsmc_regs); + if (IS_ERR(nfc->hsmc_regs)) + return PTR_ERR(nfc->hsmc_regs); + + nfc_sram = platform_get_resource(pdev, IORESOURCE_MEM, 2); + if (nfc_sram) { + nfc->sram_bank0 = devm_ioremap_resource(&pdev->dev, nfc_sram); + if (IS_ERR(nfc->sram_bank0)) { + dev_warn(&pdev->dev, "Fail to ioremap the NFC sram with error: %ld. So disable NFC sram.\n", + PTR_ERR(nfc->sram_bank0)); + } else { + nfc->use_nfc_sram = true; + nfc->sram_bank0_phys = (dma_addr_t)nfc_sram->start; + + if (pdev->dev.of_node) + nfc->write_by_sram = of_property_read_bool( + pdev->dev.of_node, + "atmel,write-by-sram"); + } + } + + nfc->is_initialized = true; + dev_info(&pdev->dev, "NFC is probed.\n"); + return 0; +} + +static const struct of_device_id atmel_nand_nfc_match[] = { + { .compatible = "atmel,sama5d3-nfc" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, atmel_nand_nfc_match); + +static struct platform_driver atmel_nand_nfc_driver = { + .driver = { + .name = "atmel_nand_nfc", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(atmel_nand_nfc_match), + }, + .probe = atmel_nand_nfc_probe, +}; + +static struct platform_driver atmel_nand_driver = { + .probe = atmel_nand_probe, + .remove = atmel_nand_remove, + .driver = { + .name = "atmel_nand", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(atmel_nand_dt_ids), + }, +}; + +module_platform_driver(atmel_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Rick Bronson"); +MODULE_DESCRIPTION("NAND/SmartMedia driver for AT91 / AVR32"); +MODULE_ALIAS("platform:atmel_nand"); diff --git a/drivers/mtd/nand/atmel_nand_ecc.h b/drivers/mtd/nand/atmel_nand_ecc.h new file mode 100644 index 00000000000..8a1e9a68675 --- /dev/null +++ b/drivers/mtd/nand/atmel_nand_ecc.h @@ -0,0 +1,151 @@ +/* + * Error Corrected Code Controller (ECC) - System peripherals regsters. + * Based on AT91SAM9260 datasheet revision B. + * + * Copyright (C) 2007 Andrew Victor + * Copyright (C) 2007 - 2012 Atmel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef ATMEL_NAND_ECC_H +#define ATMEL_NAND_ECC_H + +#define ATMEL_ECC_CR 0x00 /* Control register */ +#define ATMEL_ECC_RST (1 << 0) /* Reset parity */ + +#define ATMEL_ECC_MR 0x04 /* Mode register */ +#define ATMEL_ECC_PAGESIZE (3 << 0) /* Page Size */ +#define ATMEL_ECC_PAGESIZE_528 (0) +#define ATMEL_ECC_PAGESIZE_1056 (1) +#define ATMEL_ECC_PAGESIZE_2112 (2) +#define ATMEL_ECC_PAGESIZE_4224 (3) + +#define ATMEL_ECC_SR 0x08 /* Status register */ +#define ATMEL_ECC_RECERR (1 << 0) /* Recoverable Error */ +#define ATMEL_ECC_ECCERR (1 << 1) /* ECC Single Bit Error */ +#define ATMEL_ECC_MULERR (1 << 2) /* Multiple Errors */ + +#define ATMEL_ECC_PR 0x0c /* Parity register */ +#define ATMEL_ECC_BITADDR (0xf << 0) /* Bit Error Address */ +#define ATMEL_ECC_WORDADDR (0xfff << 4) /* Word Error Address */ + +#define ATMEL_ECC_NPR 0x10 /* NParity register */ +#define ATMEL_ECC_NPARITY (0xffff << 0) /* NParity */ + +/* PMECC Register Definitions */ +#define ATMEL_PMECC_CFG 0x000 /* Configuration Register */ +#define PMECC_CFG_BCH_ERR2 (0 << 0) +#define PMECC_CFG_BCH_ERR4 (1 << 0) +#define PMECC_CFG_BCH_ERR8 (2 << 0) +#define PMECC_CFG_BCH_ERR12 (3 << 0) +#define PMECC_CFG_BCH_ERR24 (4 << 0) + +#define PMECC_CFG_SECTOR512 (0 << 4) +#define PMECC_CFG_SECTOR1024 (1 << 4) + +#define PMECC_CFG_PAGE_1SECTOR (0 << 8) +#define PMECC_CFG_PAGE_2SECTORS (1 << 8) +#define PMECC_CFG_PAGE_4SECTORS (2 << 8) +#define PMECC_CFG_PAGE_8SECTORS (3 << 8) + +#define PMECC_CFG_READ_OP (0 << 12) +#define PMECC_CFG_WRITE_OP (1 << 12) + +#define PMECC_CFG_SPARE_ENABLE (1 << 16) +#define PMECC_CFG_SPARE_DISABLE (0 << 16) + +#define PMECC_CFG_AUTO_ENABLE (1 << 20) +#define PMECC_CFG_AUTO_DISABLE (0 << 20) + +#define ATMEL_PMECC_SAREA 0x004 /* Spare area size */ +#define ATMEL_PMECC_SADDR 0x008 /* PMECC starting address */ +#define ATMEL_PMECC_EADDR 0x00c /* PMECC ending address */ +#define ATMEL_PMECC_CLK 0x010 /* PMECC clock control */ +#define PMECC_CLK_133MHZ (2 << 0) + +#define ATMEL_PMECC_CTRL 0x014 /* PMECC control register */ +#define PMECC_CTRL_RST (1 << 0) +#define PMECC_CTRL_DATA (1 << 1) +#define PMECC_CTRL_USER (1 << 2) +#define PMECC_CTRL_ENABLE (1 << 4) +#define PMECC_CTRL_DISABLE (1 << 5) + +#define ATMEL_PMECC_SR 0x018 /* PMECC status register */ +#define PMECC_SR_BUSY (1 << 0) +#define PMECC_SR_ENABLE (1 << 4) + +#define ATMEL_PMECC_IER 0x01c /* PMECC interrupt enable */ +#define PMECC_IER_ENABLE (1 << 0) +#define ATMEL_PMECC_IDR 0x020 /* PMECC interrupt disable */ +#define PMECC_IER_DISABLE (1 << 0) +#define ATMEL_PMECC_IMR 0x024 /* PMECC interrupt mask */ +#define PMECC_IER_MASK (1 << 0) +#define ATMEL_PMECC_ISR 0x028 /* PMECC interrupt status */ +#define ATMEL_PMECC_ECCx 0x040 /* PMECC ECC x */ +#define ATMEL_PMECC_REMx 0x240 /* PMECC REM x */ + +/* PMERRLOC Register Definitions */ +#define ATMEL_PMERRLOC_ELCFG 0x000 /* Error location config */ +#define PMERRLOC_ELCFG_SECTOR_512 (0 << 0) +#define PMERRLOC_ELCFG_SECTOR_1024 (1 << 0) +#define PMERRLOC_ELCFG_NUM_ERRORS(n) ((n) << 16) + +#define ATMEL_PMERRLOC_ELPRIM 0x004 /* Error location primitive */ +#define ATMEL_PMERRLOC_ELEN 0x008 /* Error location enable */ +#define ATMEL_PMERRLOC_ELDIS 0x00c /* Error location disable */ +#define PMERRLOC_DISABLE (1 << 0) + +#define ATMEL_PMERRLOC_ELSR 0x010 /* Error location status */ +#define PMERRLOC_ELSR_BUSY (1 << 0) +#define ATMEL_PMERRLOC_ELIER 0x014 /* Error location int enable */ +#define ATMEL_PMERRLOC_ELIDR 0x018 /* Error location int disable */ +#define ATMEL_PMERRLOC_ELIMR 0x01c /* Error location int mask */ +#define ATMEL_PMERRLOC_ELISR 0x020 /* Error location int status */ +#define PMERRLOC_ERR_NUM_MASK (0x1f << 8) +#define PMERRLOC_CALC_DONE (1 << 0) +#define ATMEL_PMERRLOC_SIGMAx 0x028 /* Error location SIGMA x */ +#define ATMEL_PMERRLOC_ELx 0x08c /* Error location x */ + +/* Register access macros for PMECC */ +#define pmecc_readl_relaxed(addr, reg) \ + readl_relaxed((addr) + ATMEL_PMECC_##reg) + +#define pmecc_writel(addr, reg, value) \ + writel((value), (addr) + ATMEL_PMECC_##reg) + +#define pmecc_readb_ecc_relaxed(addr, sector, n) \ + readb_relaxed((addr) + ATMEL_PMECC_ECCx + ((sector) * 0x40) + (n)) + +#define pmecc_readl_rem_relaxed(addr, sector, n) \ + readl_relaxed((addr) + ATMEL_PMECC_REMx + ((sector) * 0x40) + ((n) * 4)) + +#define pmerrloc_readl_relaxed(addr, reg) \ + readl_relaxed((addr) + ATMEL_PMERRLOC_##reg) + +#define pmerrloc_writel(addr, reg, value) \ + writel((value), (addr) + ATMEL_PMERRLOC_##reg) + +#define pmerrloc_writel_sigma_relaxed(addr, n, value) \ + writel_relaxed((value), (addr) + ATMEL_PMERRLOC_SIGMAx + ((n) * 4)) + +#define pmerrloc_readl_sigma_relaxed(addr, n) \ + readl_relaxed((addr) + ATMEL_PMERRLOC_SIGMAx + ((n) * 4)) + +#define pmerrloc_readl_el_relaxed(addr, n) \ + readl_relaxed((addr) + ATMEL_PMERRLOC_ELx + ((n) * 4)) + +/* Galois field dimension */ +#define PMECC_GF_DIMENSION_13 13 +#define PMECC_GF_DIMENSION_14 14 + +#define PMECC_LOOKUP_TABLE_SIZE_512 0x2000 +#define PMECC_LOOKUP_TABLE_SIZE_1024 0x4000 + +/* Time out value for reading PMECC status register */ +#define PMECC_MAX_TIMEOUT_MS 100 + +#endif diff --git a/drivers/mtd/nand/atmel_nand_nfc.h b/drivers/mtd/nand/atmel_nand_nfc.h new file mode 100644 index 00000000000..4efd117cd3a --- /dev/null +++ b/drivers/mtd/nand/atmel_nand_nfc.h @@ -0,0 +1,98 @@ +/* + * Atmel Nand Flash Controller (NFC) - System peripherals regsters. + * Based on SAMA5D3 datasheet. + * + * © Copyright 2013 Atmel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef ATMEL_NAND_NFC_H +#define ATMEL_NAND_NFC_H + +/* + * HSMC NFC registers + */ +#define ATMEL_HSMC_NFC_CFG 0x00 /* NFC Configuration Register */ +#define NFC_CFG_PAGESIZE (7 << 0) +#define NFC_CFG_PAGESIZE_512 (0 << 0) +#define NFC_CFG_PAGESIZE_1024 (1 << 0) +#define NFC_CFG_PAGESIZE_2048 (2 << 0) +#define NFC_CFG_PAGESIZE_4096 (3 << 0) +#define NFC_CFG_PAGESIZE_8192 (4 << 0) +#define NFC_CFG_WSPARE (1 << 8) +#define NFC_CFG_RSPARE (1 << 9) +#define NFC_CFG_NFC_DTOCYC (0xf << 16) +#define NFC_CFG_NFC_DTOMUL (0x7 << 20) +#define NFC_CFG_NFC_SPARESIZE (0x7f << 24) +#define NFC_CFG_NFC_SPARESIZE_BIT_POS 24 + +#define ATMEL_HSMC_NFC_CTRL 0x04 /* NFC Control Register */ +#define NFC_CTRL_ENABLE (1 << 0) +#define NFC_CTRL_DISABLE (1 << 1) + +#define ATMEL_HSMC_NFC_SR 0x08 /* NFC Status Register */ +#define NFC_SR_XFR_DONE (1 << 16) +#define NFC_SR_CMD_DONE (1 << 17) +#define NFC_SR_RB_EDGE (1 << 24) + +#define ATMEL_HSMC_NFC_IER 0x0c +#define ATMEL_HSMC_NFC_IDR 0x10 +#define ATMEL_HSMC_NFC_IMR 0x14 +#define ATMEL_HSMC_NFC_CYCLE0 0x18 /* NFC Address Cycle Zero */ +#define ATMEL_HSMC_NFC_ADDR_CYCLE0 (0xff) + +#define ATMEL_HSMC_NFC_BANK 0x1c /* NFC Bank Register */ +#define ATMEL_HSMC_NFC_BANK0 (0 << 0) +#define ATMEL_HSMC_NFC_BANK1 (1 << 0) + +#define nfc_writel(addr, reg, value) \ + writel((value), (addr) + ATMEL_HSMC_NFC_##reg) + +#define nfc_readl(addr, reg) \ + readl_relaxed((addr) + ATMEL_HSMC_NFC_##reg) + +/* + * NFC Address Command definitions + */ +#define NFCADDR_CMD_CMD1 (0xff << 2) /* Command for Cycle 1 */ +#define NFCADDR_CMD_CMD1_BIT_POS 2 +#define NFCADDR_CMD_CMD2 (0xff << 10) /* Command for Cycle 2 */ +#define NFCADDR_CMD_CMD2_BIT_POS 10 +#define NFCADDR_CMD_VCMD2 (0x1 << 18) /* Valid Cycle 2 Command */ +#define NFCADDR_CMD_ACYCLE (0x7 << 19) /* Number of Address required */ +#define NFCADDR_CMD_ACYCLE_NONE (0x0 << 19) +#define NFCADDR_CMD_ACYCLE_1 (0x1 << 19) +#define NFCADDR_CMD_ACYCLE_2 (0x2 << 19) +#define NFCADDR_CMD_ACYCLE_3 (0x3 << 19) +#define NFCADDR_CMD_ACYCLE_4 (0x4 << 19) +#define NFCADDR_CMD_ACYCLE_5 (0x5 << 19) +#define NFCADDR_CMD_ACYCLE_BIT_POS 19 +#define NFCADDR_CMD_CSID (0x7 << 22) /* Chip Select Identifier */ +#define NFCADDR_CMD_CSID_0 (0x0 << 22) +#define NFCADDR_CMD_CSID_1 (0x1 << 22) +#define NFCADDR_CMD_CSID_2 (0x2 << 22) +#define NFCADDR_CMD_CSID_3 (0x3 << 22) +#define NFCADDR_CMD_CSID_4 (0x4 << 22) +#define NFCADDR_CMD_CSID_5 (0x5 << 22) +#define NFCADDR_CMD_CSID_6 (0x6 << 22) +#define NFCADDR_CMD_CSID_7 (0x7 << 22) +#define NFCADDR_CMD_DATAEN (0x1 << 25) /* Data Transfer Enable */ +#define NFCADDR_CMD_DATADIS (0x0 << 25) /* Data Transfer Disable */ +#define NFCADDR_CMD_NFCRD (0x0 << 26) /* NFC Read Enable */ +#define NFCADDR_CMD_NFCWR (0x1 << 26) /* NFC Write Enable */ +#define NFCADDR_CMD_NFCBUSY (0x1 << 27) /* NFC Busy */ + +#define nfc_cmd_addr1234_writel(cmd, addr1234, nfc_base) \ + writel((addr1234), (cmd) + nfc_base) + +#define nfc_cmd_readl(bitstatus, nfc_base) \ + readl_relaxed((bitstatus) + nfc_base) + +#define NFC_TIME_OUT_MS 100 +#define NFC_SRAM_BANK1_OFFSET 0x1200 + +#endif diff --git a/drivers/mtd/nand/au1550nd.c b/drivers/mtd/nand/au1550nd.c index 09e421a9689..bc5c518828d 100644 --- a/drivers/mtd/nand/au1550nd.c +++ b/drivers/mtd/nand/au1550nd.c @@ -3,8 +3,6 @@ * * Copyright (C) 2004 Embedded Edge, LLC * - * $Id: au1550nd.c,v 1.13 2005/11/07 11:14:30 gleixner Exp $ - * * 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. @@ -12,44 +10,32 @@ */ #include <linux/slab.h> -#include <linux/init.h> +#include <linux/gpio.h> #include <linux/module.h> #include <linux/interrupt.h> #include <linux/mtd/mtd.h> #include <linux/mtd/nand.h> #include <linux/mtd/partitions.h> -#include <linux/version.h> +#include <linux/platform_device.h> #include <asm/io.h> +#include <asm/mach-au1x00/au1000.h> +#include <asm/mach-au1x00/au1550nd.h> -#include <asm/mach-au1x00/au1xxx.h> -/* - * MTD structure for NAND controller - */ -static struct mtd_info *au1550_mtd = NULL; -static void __iomem *p_nand; -static int nand_width = 1; /* default x8 */ -static void (*au1550_write_byte)(struct mtd_info *, u_char); +struct au1550nd_ctx { + struct mtd_info info; + struct nand_chip chip; -/* - * Define partitions for flash device - */ -static const struct mtd_partition partition_info[] = { - { - .name = "NAND FS 0", - .offset = 0, - .size = 8 * 1024 * 1024}, - { - .name = "NAND FS 1", - .offset = MTDPART_OFS_APPEND, - .size = MTDPART_SIZ_FULL} + int cs; + void __iomem *base; + void (*write_byte)(struct mtd_info *, u_char); }; /** * au_read_byte - read one byte from the chip * @mtd: MTD device structure * - * read function for 8bit buswith + * read function for 8bit buswidth */ static u_char au_read_byte(struct mtd_info *mtd) { @@ -64,7 +50,7 @@ static u_char au_read_byte(struct mtd_info *mtd) * @mtd: MTD device structure * @byte: pointer to data byte to write * - * write function for 8it buswith + * write function for 8it buswidth */ static void au_write_byte(struct mtd_info *mtd, u_char byte) { @@ -74,11 +60,10 @@ static void au_write_byte(struct mtd_info *mtd, u_char byte) } /** - * au_read_byte16 - read one byte endianess aware from the chip + * au_read_byte16 - read one byte endianness aware from the chip * @mtd: MTD device structure * - * read function for 16bit buswith with - * endianess conversion + * read function for 16bit buswidth with endianness conversion */ static u_char au_read_byte16(struct mtd_info *mtd) { @@ -89,12 +74,11 @@ static u_char au_read_byte16(struct mtd_info *mtd) } /** - * au_write_byte16 - write one byte endianess aware to the chip + * au_write_byte16 - write one byte endianness aware to the chip * @mtd: MTD device structure * @byte: pointer to data byte to write * - * write function for 16bit buswith with - * endianess conversion + * write function for 16bit buswidth with endianness conversion */ static void au_write_byte16(struct mtd_info *mtd, u_char byte) { @@ -107,8 +91,7 @@ static void au_write_byte16(struct mtd_info *mtd, u_char byte) * au_read_word - read one word from the chip * @mtd: MTD device structure * - * read function for 16bit buswith without - * endianess conversion + * read function for 16bit buswidth without endianness conversion */ static u16 au_read_word(struct mtd_info *mtd) { @@ -124,7 +107,7 @@ static u16 au_read_word(struct mtd_info *mtd) * @buf: data buffer * @len: number of bytes to write * - * write function for 8bit buswith + * write function for 8bit buswidth */ static void au_write_buf(struct mtd_info *mtd, const u_char *buf, int len) { @@ -143,7 +126,7 @@ static void au_write_buf(struct mtd_info *mtd, const u_char *buf, int len) * @buf: buffer to store date * @len: number of bytes to read * - * read function for 8bit buswith + * read function for 8bit buswidth */ static void au_read_buf(struct mtd_info *mtd, u_char *buf, int len) { @@ -157,34 +140,12 @@ static void au_read_buf(struct mtd_info *mtd, u_char *buf, int len) } /** - * au_verify_buf - Verify chip data against buffer - * @mtd: MTD device structure - * @buf: buffer containing the data to compare - * @len: number of bytes to compare - * - * verify function for 8bit buswith - */ -static int au_verify_buf(struct mtd_info *mtd, const u_char *buf, int len) -{ - int i; - struct nand_chip *this = mtd->priv; - - for (i = 0; i < len; i++) { - if (buf[i] != readb(this->IO_ADDR_R)) - return -EFAULT; - au_sync(); - } - - return 0; -} - -/** * au_write_buf16 - write buffer to chip * @mtd: MTD device structure * @buf: data buffer * @len: number of bytes to write * - * write function for 16bit buswith + * write function for 16bit buswidth */ static void au_write_buf16(struct mtd_info *mtd, const u_char *buf, int len) { @@ -206,7 +167,7 @@ static void au_write_buf16(struct mtd_info *mtd, const u_char *buf, int len) * @buf: buffer to store date * @len: number of bytes to read * - * read function for 16bit buswith + * read function for 16bit buswidth */ static void au_read_buf16(struct mtd_info *mtd, u_char *buf, int len) { @@ -221,29 +182,6 @@ static void au_read_buf16(struct mtd_info *mtd, u_char *buf, int len) } } -/** - * au_verify_buf16 - Verify chip data against buffer - * @mtd: MTD device structure - * @buf: buffer containing the data to compare - * @len: number of bytes to compare - * - * verify function for 16bit buswith - */ -static int au_verify_buf16(struct mtd_info *mtd, const u_char *buf, int len) -{ - int i; - struct nand_chip *this = mtd->priv; - u16 *p = (u16 *) buf; - len >>= 1; - - for (i = 0; i < len; i++) { - if (p[i] != readw(this->IO_ADDR_R)) - return -EFAULT; - au_sync(); - } - return 0; -} - /* Select the chip by setting nCE to low */ #define NAND_CTL_SETNCE 1 /* Deselect the chip by setting nCE to high */ @@ -259,24 +197,25 @@ static int au_verify_buf16(struct mtd_info *mtd, const u_char *buf, int len) static void au1550_hwcontrol(struct mtd_info *mtd, int cmd) { - register struct nand_chip *this = mtd->priv; + struct au1550nd_ctx *ctx = container_of(mtd, struct au1550nd_ctx, info); + struct nand_chip *this = mtd->priv; switch (cmd) { case NAND_CTL_SETCLE: - this->IO_ADDR_W = p_nand + MEM_STNAND_CMD; + this->IO_ADDR_W = ctx->base + MEM_STNAND_CMD; break; case NAND_CTL_CLRCLE: - this->IO_ADDR_W = p_nand + MEM_STNAND_DATA; + this->IO_ADDR_W = ctx->base + MEM_STNAND_DATA; break; case NAND_CTL_SETALE: - this->IO_ADDR_W = p_nand + MEM_STNAND_ADDR; + this->IO_ADDR_W = ctx->base + MEM_STNAND_ADDR; break; case NAND_CTL_CLRALE: - this->IO_ADDR_W = p_nand + MEM_STNAND_DATA; + this->IO_ADDR_W = ctx->base + MEM_STNAND_DATA; /* FIXME: Nobody knows why this is necessary, * but it works only that way */ udelay(1); @@ -284,7 +223,7 @@ static void au1550_hwcontrol(struct mtd_info *mtd, int cmd) case NAND_CTL_SETNCE: /* assert (force assert) chip enable */ - au_writel((1 << (4 + NAND_CS)), MEM_STNDCTL); + au_writel((1 << (4 + ctx->cs)), MEM_STNDCTL); break; case NAND_CTL_CLRNCE: @@ -331,9 +270,10 @@ static void au1550_select_chip(struct mtd_info *mtd, int chip) */ static void au1550_command(struct mtd_info *mtd, unsigned command, int column, int page_addr) { - register struct nand_chip *this = mtd->priv; + struct au1550nd_ctx *ctx = container_of(mtd, struct au1550nd_ctx, info); + struct nand_chip *this = mtd->priv; int ce_override = 0, i; - ulong flags; + unsigned long flags = 0; /* Begin command latch cycle */ au1550_hwcontrol(mtd, NAND_CTL_SETCLE); @@ -354,9 +294,9 @@ static void au1550_command(struct mtd_info *mtd, unsigned command, int column, i column -= 256; readcmd = NAND_CMD_READ1; } - au1550_write_byte(mtd, readcmd); + ctx->write_byte(mtd, readcmd); } - au1550_write_byte(mtd, command); + ctx->write_byte(mtd, command); /* Set ALE and clear CLE to start address cycle */ au1550_hwcontrol(mtd, NAND_CTL_CLRCLE); @@ -367,12 +307,13 @@ static void au1550_command(struct mtd_info *mtd, unsigned command, int column, i /* Serially input address */ if (column != -1) { /* Adjust columns for 16 bit buswidth */ - if (this->options & NAND_BUSWIDTH_16) + if (this->options & NAND_BUSWIDTH_16 && + !nand_opcode_8bits(command)) column >>= 1; - au1550_write_byte(mtd, column); + ctx->write_byte(mtd, column); } if (page_addr != -1) { - au1550_write_byte(mtd, (u8)(page_addr & 0xff)); + ctx->write_byte(mtd, (u8)(page_addr & 0xff)); if (command == NAND_CMD_READ0 || command == NAND_CMD_READ1 || @@ -390,11 +331,12 @@ static void au1550_command(struct mtd_info *mtd, unsigned command, int column, i au1550_hwcontrol(mtd, NAND_CTL_SETNCE); } - au1550_write_byte(mtd, (u8)(page_addr >> 8)); + ctx->write_byte(mtd, (u8)(page_addr >> 8)); /* One more address cycle for devices > 32MiB */ if (this->chipsize > (32 << 20)) - au1550_write_byte(mtd, (u8)((page_addr >> 16) & 0x0f)); + ctx->write_byte(mtd, + ((page_addr >> 16) & 0x0f)); } /* Latch in address */ au1550_hwcontrol(mtd, NAND_CTL_CLRALE); @@ -440,124 +382,77 @@ static void au1550_command(struct mtd_info *mtd, unsigned command, int column, i while(!this->dev_ready(mtd)); } - -/* - * Main initialization routine - */ -static int __init au1xxx_nand_init(void) +static int find_nand_cs(unsigned long nand_base) { - struct nand_chip *this; - u16 boot_swapboot = 0; /* default value */ - int retval; - u32 mem_staddr; - u32 nand_phys; - - /* Allocate memory for MTD device structure and private data */ - au1550_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL); - if (!au1550_mtd) { - printk("Unable to allocate NAND MTD dev structure.\n"); - return -ENOMEM; - } - - /* Get pointer to private data */ - this = (struct nand_chip *)(&au1550_mtd[1]); + void __iomem *base = + (void __iomem *)KSEG1ADDR(AU1000_STATIC_MEM_PHYS_ADDR); + unsigned long addr, staddr, start, mask, end; + int i; - /* Initialize structures */ - memset(au1550_mtd, 0, sizeof(struct mtd_info)); - memset(this, 0, sizeof(struct nand_chip)); + for (i = 0; i < 4; i++) { + addr = 0x1000 + (i * 0x10); /* CSx */ + staddr = __raw_readl(base + addr + 0x08); /* STADDRx */ + /* figure out the decoded range of this CS */ + start = (staddr << 4) & 0xfffc0000; + mask = (staddr << 18) & 0xfffc0000; + end = (start | (start - 1)) & ~(start ^ mask); + if ((nand_base >= start) && (nand_base < end)) + return i; + } - /* Link the private data with the MTD structure */ - au1550_mtd->priv = this; - au1550_mtd->owner = THIS_MODULE; + return -ENODEV; +} +static int au1550nd_probe(struct platform_device *pdev) +{ + struct au1550nd_platdata *pd; + struct au1550nd_ctx *ctx; + struct nand_chip *this; + struct resource *r; + int ret, cs; - /* MEM_STNDCTL: disable ints, disable nand boot */ - au_writel(0, MEM_STNDCTL); + pd = dev_get_platdata(&pdev->dev); + if (!pd) { + dev_err(&pdev->dev, "missing platform data\n"); + return -ENODEV; + } -#ifdef CONFIG_MIPS_PB1550 - /* set gpio206 high */ - au_writel(au_readl(GPIO2_DIR) & ~(1 << 6), GPIO2_DIR); + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; - boot_swapboot = (au_readl(MEM_STSTAT) & (0x7 << 1)) | ((bcsr->status >> 6) & 0x1); - switch (boot_swapboot) { - case 0: - case 2: - case 8: - case 0xC: - case 0xD: - /* x16 NAND Flash */ - nand_width = 0; - break; - case 1: - case 9: - case 3: - case 0xE: - case 0xF: - /* x8 NAND Flash */ - nand_width = 1; - break; - default: - printk("Pb1550 NAND: bad boot:swap\n"); - retval = -EINVAL; - goto outmem; + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "no NAND memory resource\n"); + ret = -ENODEV; + goto out1; } -#endif - - /* Configure chip-select; normally done by boot code, e.g. YAMON */ -#ifdef NAND_STCFG - if (NAND_CS == 0) { - au_writel(NAND_STCFG, MEM_STCFG0); - au_writel(NAND_STTIME, MEM_STTIME0); - au_writel(NAND_STADDR, MEM_STADDR0); + if (request_mem_region(r->start, resource_size(r), "au1550-nand")) { + dev_err(&pdev->dev, "cannot claim NAND memory area\n"); + ret = -ENOMEM; + goto out1; } - if (NAND_CS == 1) { - au_writel(NAND_STCFG, MEM_STCFG1); - au_writel(NAND_STTIME, MEM_STTIME1); - au_writel(NAND_STADDR, MEM_STADDR1); - } - if (NAND_CS == 2) { - au_writel(NAND_STCFG, MEM_STCFG2); - au_writel(NAND_STTIME, MEM_STTIME2); - au_writel(NAND_STADDR, MEM_STADDR2); - } - if (NAND_CS == 3) { - au_writel(NAND_STCFG, MEM_STCFG3); - au_writel(NAND_STTIME, MEM_STTIME3); - au_writel(NAND_STADDR, MEM_STADDR3); - } -#endif - - /* Locate NAND chip-select in order to determine NAND phys address */ - mem_staddr = 0x00000000; - if (((au_readl(MEM_STCFG0) & 0x7) == 0x5) && (NAND_CS == 0)) - mem_staddr = au_readl(MEM_STADDR0); - else if (((au_readl(MEM_STCFG1) & 0x7) == 0x5) && (NAND_CS == 1)) - mem_staddr = au_readl(MEM_STADDR1); - else if (((au_readl(MEM_STCFG2) & 0x7) == 0x5) && (NAND_CS == 2)) - mem_staddr = au_readl(MEM_STADDR2); - else if (((au_readl(MEM_STCFG3) & 0x7) == 0x5) && (NAND_CS == 3)) - mem_staddr = au_readl(MEM_STADDR3); - - if (mem_staddr == 0x00000000) { - printk("Au1xxx NAND: ERROR WITH NAND CHIP-SELECT\n"); - kfree(au1550_mtd); - return 1; + + ctx->base = ioremap_nocache(r->start, 0x1000); + if (!ctx->base) { + dev_err(&pdev->dev, "cannot remap NAND memory area\n"); + ret = -ENODEV; + goto out2; } - nand_phys = (mem_staddr << 4) & 0xFFFC0000; - p_nand = (void __iomem *)ioremap(nand_phys, 0x1000); + this = &ctx->chip; + ctx->info.priv = this; + ctx->info.owner = THIS_MODULE; - /* make controller and MTD agree */ - if (NAND_CS == 0) - nand_width = au_readl(MEM_STCFG0) & (1 << 22); - if (NAND_CS == 1) - nand_width = au_readl(MEM_STCFG1) & (1 << 22); - if (NAND_CS == 2) - nand_width = au_readl(MEM_STCFG2) & (1 << 22); - if (NAND_CS == 3) - nand_width = au_readl(MEM_STCFG3) & (1 << 22); + /* figure out which CS# r->start belongs to */ + cs = find_nand_cs(r->start); + if (cs < 0) { + dev_err(&pdev->dev, "cannot detect NAND chipselect\n"); + ret = -ENODEV; + goto out3; + } + ctx->cs = cs; - /* Set address of hardware control function */ this->dev_ready = au1550_device_ready; this->select_chip = au1550_select_chip; this->cmdfunc = au1550_command; @@ -566,57 +461,58 @@ static int __init au1xxx_nand_init(void) this->chip_delay = 30; this->ecc.mode = NAND_ECC_SOFT; - this->options = NAND_NO_AUTOINCR; - - if (!nand_width) + if (pd->devwidth) this->options |= NAND_BUSWIDTH_16; - this->read_byte = (!nand_width) ? au_read_byte16 : au_read_byte; - au1550_write_byte = (!nand_width) ? au_write_byte16 : au_write_byte; + this->read_byte = (pd->devwidth) ? au_read_byte16 : au_read_byte; + ctx->write_byte = (pd->devwidth) ? au_write_byte16 : au_write_byte; this->read_word = au_read_word; - this->write_buf = (!nand_width) ? au_write_buf16 : au_write_buf; - this->read_buf = (!nand_width) ? au_read_buf16 : au_read_buf; - this->verify_buf = (!nand_width) ? au_verify_buf16 : au_verify_buf; - - /* Scan to find existence of the device */ - if (nand_scan(au1550_mtd, 1)) { - retval = -ENXIO; - goto outio; + this->write_buf = (pd->devwidth) ? au_write_buf16 : au_write_buf; + this->read_buf = (pd->devwidth) ? au_read_buf16 : au_read_buf; + + ret = nand_scan(&ctx->info, 1); + if (ret) { + dev_err(&pdev->dev, "NAND scan failed with %d\n", ret); + goto out3; } - /* Register the partitions */ - add_mtd_partitions(au1550_mtd, partition_info, ARRAY_SIZE(partition_info)); + mtd_device_register(&ctx->info, pd->parts, pd->num_parts); - return 0; + platform_set_drvdata(pdev, ctx); - outio: - iounmap((void *)p_nand); + return 0; - outmem: - kfree(au1550_mtd); - return retval; +out3: + iounmap(ctx->base); +out2: + release_mem_region(r->start, resource_size(r)); +out1: + kfree(ctx); + return ret; } -module_init(au1xxx_nand_init); - -/* - * Clean up routine - */ -static void __exit au1550_cleanup(void) +static int au1550nd_remove(struct platform_device *pdev) { - struct nand_chip *this = (struct nand_chip *)&au1550_mtd[1]; - - /* Release resources, unregister device */ - nand_release(au1550_mtd); + struct au1550nd_ctx *ctx = platform_get_drvdata(pdev); + struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - /* Free the MTD device structure */ - kfree(au1550_mtd); - - /* Unmap */ - iounmap((void *)p_nand); + nand_release(&ctx->info); + iounmap(ctx->base); + release_mem_region(r->start, 0x1000); + kfree(ctx); + return 0; } -module_exit(au1550_cleanup); +static struct platform_driver au1550nd_driver = { + .driver = { + .name = "au1550-nand", + .owner = THIS_MODULE, + }, + .probe = au1550nd_probe, + .remove = au1550nd_remove, +}; + +module_platform_driver(au1550nd_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Embedded Edge, LLC"); diff --git a/drivers/mtd/nand/autcpu12.c b/drivers/mtd/nand/autcpu12.c deleted file mode 100644 index fe94ae9ae1f..00000000000 --- a/drivers/mtd/nand/autcpu12.c +++ /dev/null @@ -1,241 +0,0 @@ -/* - * drivers/mtd/autcpu12.c - * - * Copyright (c) 2002 Thomas Gleixner <tgxl@linutronix.de> - * - * Derived from drivers/mtd/spia.c - * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com) - * - * $Id: autcpu12.c,v 1.23 2005/11/07 11:14:30 gleixner Exp $ - * - * 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. - * - * Overview: - * This is a device driver for the NAND flash device found on the - * autronix autcpu12 board, which is a SmartMediaCard. It supports - * 16MiB, 32MiB and 64MiB cards. - * - * - * 02-12-2002 TG Cleanup of module params - * - * 02-20-2002 TG adjusted for different rd/wr adress support - * added support for read device ready/busy line - * added page_cache - * - * 10-06-2002 TG 128K card support added - */ - -#include <linux/slab.h> -#include <linux/init.h> -#include <linux/module.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/nand.h> -#include <linux/mtd/partitions.h> -#include <asm/io.h> -#include <asm/arch/hardware.h> -#include <asm/sizes.h> -#include <asm/arch/autcpu12.h> - -/* - * MTD structure for AUTCPU12 board - */ -static struct mtd_info *autcpu12_mtd = NULL; -static void __iomem *autcpu12_fio_base; - -/* - * Define partitions for flash devices - */ -static struct mtd_partition partition_info16k[] = { - { .name = "AUTCPU12 flash partition 1", - .offset = 0, - .size = 8 * SZ_1M }, - { .name = "AUTCPU12 flash partition 2", - .offset = 8 * SZ_1M, - .size = 8 * SZ_1M }, -}; - -static struct mtd_partition partition_info32k[] = { - { .name = "AUTCPU12 flash partition 1", - .offset = 0, - .size = 8 * SZ_1M }, - { .name = "AUTCPU12 flash partition 2", - .offset = 8 * SZ_1M, - .size = 24 * SZ_1M }, -}; - -static struct mtd_partition partition_info64k[] = { - { .name = "AUTCPU12 flash partition 1", - .offset = 0, - .size = 16 * SZ_1M }, - { .name = "AUTCPU12 flash partition 2", - .offset = 16 * SZ_1M, - .size = 48 * SZ_1M }, -}; - -static struct mtd_partition partition_info128k[] = { - { .name = "AUTCPU12 flash partition 1", - .offset = 0, - .size = 16 * SZ_1M }, - { .name = "AUTCPU12 flash partition 2", - .offset = 16 * SZ_1M, - .size = 112 * SZ_1M }, -}; - -#define NUM_PARTITIONS16K 2 -#define NUM_PARTITIONS32K 2 -#define NUM_PARTITIONS64K 2 -#define NUM_PARTITIONS128K 2 -/* - * hardware specific access to control-lines - * - * ALE bit 4 autcpu12_pedr - * CLE bit 5 autcpu12_pedr - * NCE bit 0 fio_ctrl - * - */ -static void autcpu12_hwcontrol(struct mtd_info *mtd, int cmd, - unsigned int ctrl) -{ - struct nand_chip *chip = mtd->priv; - - if (ctrl & NAND_CTRL_CHANGE) { - void __iomem *addr - unsigned char bits; - - addr = CS89712_VIRT_BASE + AUTCPU12_SMC_PORT_OFFSET; - bits = (ctrl & NAND_CLE) << 4; - bits |= (ctrl & NAND_ALE) << 2; - writeb((readb(addr) & ~0x30) | bits, addr); - - addr = autcpu12_fio_base + AUTCPU12_SMC_SELECT_OFFSET; - writeb((readb(addr) & ~0x1) | (ctrl & NAND_NCE), addr); - } - - if (cmd != NAND_CMD_NONE) - writeb(cmd, chip->IO_ADDR_W); -} - -/* - * read device ready pin - */ -int autcpu12_device_ready(struct mtd_info *mtd) -{ - void __iomem *addr = CS89712_VIRT_BASE + AUTCPU12_SMC_PORT_OFFSET; - - return readb(addr) & AUTCPU12_SMC_RDY; -} - -/* - * Main initialization routine - */ -static int __init autcpu12_init(void) -{ - struct nand_chip *this; - int err = 0; - - /* Allocate memory for MTD device structure and private data */ - autcpu12_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), - GFP_KERNEL); - if (!autcpu12_mtd) { - printk("Unable to allocate AUTCPU12 NAND MTD device structure.\n"); - err = -ENOMEM; - goto out; - } - - /* map physical adress */ - autcpu12_fio_base = ioremap(AUTCPU12_PHYS_SMC, SZ_1K); - if (!autcpu12_fio_base) { - printk("Ioremap autcpu12 SmartMedia Card failed\n"); - err = -EIO; - goto out_mtd; - } - - /* Get pointer to private data */ - this = (struct nand_chip *)(&autcpu12_mtd[1]); - - /* Initialize structures */ - memset(autcpu12_mtd, 0, sizeof(struct mtd_info)); - memset(this, 0, sizeof(struct nand_chip)); - - /* Link the private data with the MTD structure */ - autcpu12_mtd->priv = this; - autcpu12_mtd->owner = THIS_MODULE; - - /* Set address of NAND IO lines */ - this->IO_ADDR_R = autcpu12_fio_base; - this->IO_ADDR_W = autcpu12_fio_base; - this->cmd_ctrl = autcpu12_hwcontrol; - this->dev_ready = autcpu12_device_ready; - /* 20 us command delay time */ - this->chip_delay = 20; - this->ecc.mode = NAND_ECC_SOFT; - - /* Enable the following for a flash based bad block table */ - /* - this->options = NAND_USE_FLASH_BBT; - */ - this->options = NAND_USE_FLASH_BBT; - - /* Scan to find existance of the device */ - if (nand_scan(autcpu12_mtd, 1)) { - err = -ENXIO; - goto out_ior; - } - - /* Register the partitions */ - switch (autcpu12_mtd->size) { - case SZ_16M: - add_mtd_partitions(autcpu12_mtd, partition_info16k, - NUM_PARTITIONS16K); - break; - case SZ_32M: - add_mtd_partitions(autcpu12_mtd, partition_info32k, - NUM_PARTITIONS32K); - break; - case SZ_64M: - add_mtd_partitions(autcpu12_mtd, partition_info64k, - NUM_PARTITIONS64K); - break; - case SZ_128M: - add_mtd_partitions(autcpu12_mtd, partition_info128k, - NUM_PARTITIONS128K); - break; - default: - printk("Unsupported SmartMedia device\n"); - err = -ENXIO; - goto out_ior; - } - goto out; - - out_ior: - iounmap(autcpu12_fio_base); - out_mtd: - kfree(autcpu12_mtd); - out: - return err; -} - -module_init(autcpu12_init); - -/* - * Clean up routine - */ -static void __exit autcpu12_cleanup(void) -{ - /* Release resources, unregister device */ - nand_release(autcpu12_mtd); - - /* unmap physical adress */ - iounmap(autcpu12_fio_base); - - /* Free the MTD device structure */ - kfree(autcpu12_mtd); -} - -module_exit(autcpu12_cleanup); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Thomas Gleixner <tglx@linutronix.de>"); -MODULE_DESCRIPTION("Glue layer for SmartMediaCard on autronix autcpu12"); diff --git a/drivers/mtd/nand/bcm47xxnflash/Makefile b/drivers/mtd/nand/bcm47xxnflash/Makefile new file mode 100644 index 00000000000..f05b119e134 --- /dev/null +++ b/drivers/mtd/nand/bcm47xxnflash/Makefile @@ -0,0 +1,4 @@ +bcm47xxnflash-y += main.o +bcm47xxnflash-y += ops_bcm4706.o + +obj-$(CONFIG_MTD_NAND_BCM47XXNFLASH) += bcm47xxnflash.o diff --git a/drivers/mtd/nand/bcm47xxnflash/bcm47xxnflash.h b/drivers/mtd/nand/bcm47xxnflash/bcm47xxnflash.h new file mode 100644 index 00000000000..c005a62330b --- /dev/null +++ b/drivers/mtd/nand/bcm47xxnflash/bcm47xxnflash.h @@ -0,0 +1,26 @@ +#ifndef __BCM47XXNFLASH_H +#define __BCM47XXNFLASH_H + +#ifndef pr_fmt +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#endif + +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> + +struct bcm47xxnflash { + struct bcma_drv_cc *cc; + + struct nand_chip nand_chip; + struct mtd_info mtd; + + unsigned curr_command; + int curr_page_addr; + int curr_column; + + u8 id_data[8]; +}; + +int bcm47xxnflash_ops_bcm4706_init(struct bcm47xxnflash *b47n); + +#endif /* BCM47XXNFLASH */ diff --git a/drivers/mtd/nand/bcm47xxnflash/main.c b/drivers/mtd/nand/bcm47xxnflash/main.c new file mode 100644 index 00000000000..10744591131 --- /dev/null +++ b/drivers/mtd/nand/bcm47xxnflash/main.c @@ -0,0 +1,80 @@ +/* + * BCM47XX NAND flash driver + * + * Copyright (C) 2012 Rafał Miłecki <zajec5@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include "bcm47xxnflash.h" + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/bcma/bcma.h> + +MODULE_DESCRIPTION("NAND flash driver for BCMA bus"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Rafał Miłecki"); + +static const char *probes[] = { "bcm47xxpart", NULL }; + +static int bcm47xxnflash_probe(struct platform_device *pdev) +{ + struct bcma_nflash *nflash = dev_get_platdata(&pdev->dev); + struct bcm47xxnflash *b47n; + int err = 0; + + b47n = devm_kzalloc(&pdev->dev, sizeof(*b47n), GFP_KERNEL); + if (!b47n) + return -ENOMEM; + + b47n->nand_chip.priv = b47n; + b47n->mtd.owner = THIS_MODULE; + b47n->mtd.priv = &b47n->nand_chip; /* Required */ + b47n->cc = container_of(nflash, struct bcma_drv_cc, nflash); + + if (b47n->cc->core->bus->chipinfo.id == BCMA_CHIP_ID_BCM4706) { + err = bcm47xxnflash_ops_bcm4706_init(b47n); + } else { + pr_err("Device not supported\n"); + err = -ENOTSUPP; + } + if (err) { + pr_err("Initialization failed: %d\n", err); + return err; + } + + err = mtd_device_parse_register(&b47n->mtd, probes, NULL, NULL, 0); + if (err) { + pr_err("Failed to register MTD device: %d\n", err); + return err; + } + + return 0; +} + +static int bcm47xxnflash_remove(struct platform_device *pdev) +{ + struct bcma_nflash *nflash = dev_get_platdata(&pdev->dev); + + if (nflash->mtd) + mtd_device_unregister(nflash->mtd); + + return 0; +} + +static struct platform_driver bcm47xxnflash_driver = { + .probe = bcm47xxnflash_probe, + .remove = bcm47xxnflash_remove, + .driver = { + .name = "bcma_nflash", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(bcm47xxnflash_driver); diff --git a/drivers/mtd/nand/bcm47xxnflash/ops_bcm4706.c b/drivers/mtd/nand/bcm47xxnflash/ops_bcm4706.c new file mode 100644 index 00000000000..b2ab373c9ee --- /dev/null +++ b/drivers/mtd/nand/bcm47xxnflash/ops_bcm4706.c @@ -0,0 +1,413 @@ +/* + * BCM47XX NAND flash driver + * + * Copyright (C) 2012 Rafał Miłecki <zajec5@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include "bcm47xxnflash.h" + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/bcma/bcma.h> + +/* Broadcom uses 1'000'000 but it seems to be too many. Tests on WNDR4500 has + * shown ~1000 retries as maxiumum. */ +#define NFLASH_READY_RETRIES 10000 + +#define NFLASH_SECTOR_SIZE 512 + +#define NCTL_CMD0 0x00010000 +#define NCTL_CMD1W 0x00080000 +#define NCTL_READ 0x00100000 +#define NCTL_WRITE 0x00200000 +#define NCTL_SPECADDR 0x01000000 +#define NCTL_READY 0x04000000 +#define NCTL_ERR 0x08000000 +#define NCTL_CSA 0x40000000 +#define NCTL_START 0x80000000 + +/************************************************** + * Various helpers + **************************************************/ + +static inline u8 bcm47xxnflash_ops_bcm4706_ns_to_cycle(u16 ns, u16 clock) +{ + return ((ns * 1000 * clock) / 1000000) + 1; +} + +static int bcm47xxnflash_ops_bcm4706_ctl_cmd(struct bcma_drv_cc *cc, u32 code) +{ + int i = 0; + + bcma_cc_write32(cc, BCMA_CC_NFLASH_CTL, NCTL_START | code); + for (i = 0; i < NFLASH_READY_RETRIES; i++) { + if (!(bcma_cc_read32(cc, BCMA_CC_NFLASH_CTL) & NCTL_START)) { + i = 0; + break; + } + } + if (i) { + pr_err("NFLASH control command not ready!\n"); + return -EBUSY; + } + return 0; +} + +static int bcm47xxnflash_ops_bcm4706_poll(struct bcma_drv_cc *cc) +{ + int i; + + for (i = 0; i < NFLASH_READY_RETRIES; i++) { + if (bcma_cc_read32(cc, BCMA_CC_NFLASH_CTL) & NCTL_READY) { + if (bcma_cc_read32(cc, BCMA_CC_NFLASH_CTL) & + BCMA_CC_NFLASH_CTL_ERR) { + pr_err("Error on polling\n"); + return -EBUSY; + } else { + return 0; + } + } + } + + pr_err("Polling timeout!\n"); + return -EBUSY; +} + +/************************************************** + * R/W + **************************************************/ + +static void bcm47xxnflash_ops_bcm4706_read(struct mtd_info *mtd, uint8_t *buf, + int len) +{ + struct nand_chip *nand_chip = (struct nand_chip *)mtd->priv; + struct bcm47xxnflash *b47n = (struct bcm47xxnflash *)nand_chip->priv; + + u32 ctlcode; + u32 *dest = (u32 *)buf; + int i; + int toread; + + BUG_ON(b47n->curr_page_addr & ~nand_chip->pagemask); + /* Don't validate column using nand_chip->page_shift, it may be bigger + * when accessing OOB */ + + while (len) { + /* We can read maximum of 0x200 bytes at once */ + toread = min(len, 0x200); + + /* Set page and column */ + bcma_cc_write32(b47n->cc, BCMA_CC_NFLASH_COL_ADDR, + b47n->curr_column); + bcma_cc_write32(b47n->cc, BCMA_CC_NFLASH_ROW_ADDR, + b47n->curr_page_addr); + + /* Prepare to read */ + ctlcode = NCTL_CSA | NCTL_CMD1W | 0x00040000 | 0x00020000 | + NCTL_CMD0; + ctlcode |= NAND_CMD_READSTART << 8; + if (bcm47xxnflash_ops_bcm4706_ctl_cmd(b47n->cc, ctlcode)) + return; + if (bcm47xxnflash_ops_bcm4706_poll(b47n->cc)) + return; + + /* Eventually read some data :) */ + for (i = 0; i < toread; i += 4, dest++) { + ctlcode = NCTL_CSA | 0x30000000 | NCTL_READ; + if (i == toread - 4) /* Last read goes without that */ + ctlcode &= ~NCTL_CSA; + if (bcm47xxnflash_ops_bcm4706_ctl_cmd(b47n->cc, + ctlcode)) + return; + *dest = bcma_cc_read32(b47n->cc, BCMA_CC_NFLASH_DATA); + } + + b47n->curr_column += toread; + len -= toread; + } +} + +static void bcm47xxnflash_ops_bcm4706_write(struct mtd_info *mtd, + const uint8_t *buf, int len) +{ + struct nand_chip *nand_chip = (struct nand_chip *)mtd->priv; + struct bcm47xxnflash *b47n = (struct bcm47xxnflash *)nand_chip->priv; + struct bcma_drv_cc *cc = b47n->cc; + + u32 ctlcode; + const u32 *data = (u32 *)buf; + int i; + + BUG_ON(b47n->curr_page_addr & ~nand_chip->pagemask); + /* Don't validate column using nand_chip->page_shift, it may be bigger + * when accessing OOB */ + + for (i = 0; i < len; i += 4, data++) { + bcma_cc_write32(cc, BCMA_CC_NFLASH_DATA, *data); + + ctlcode = NCTL_CSA | 0x30000000 | NCTL_WRITE; + if (i == len - 4) /* Last read goes without that */ + ctlcode &= ~NCTL_CSA; + if (bcm47xxnflash_ops_bcm4706_ctl_cmd(cc, ctlcode)) { + pr_err("%s ctl_cmd didn't work!\n", __func__); + return; + } + } + + b47n->curr_column += len; +} + +/************************************************** + * NAND chip ops + **************************************************/ + +/* Default nand_select_chip calls cmd_ctrl, which is not used in BCM4706 */ +static void bcm47xxnflash_ops_bcm4706_select_chip(struct mtd_info *mtd, + int chip) +{ + return; +} + +/* + * Default nand_command and nand_command_lp don't match BCM4706 hardware layout. + * For example, reading chip id is performed in a non-standard way. + * Setting column and page is also handled differently, we use a special + * registers of ChipCommon core. Hacking cmd_ctrl to understand and convert + * standard commands would be much more complicated. + */ +static void bcm47xxnflash_ops_bcm4706_cmdfunc(struct mtd_info *mtd, + unsigned command, int column, + int page_addr) +{ + struct nand_chip *nand_chip = (struct nand_chip *)mtd->priv; + struct bcm47xxnflash *b47n = (struct bcm47xxnflash *)nand_chip->priv; + struct bcma_drv_cc *cc = b47n->cc; + u32 ctlcode; + int i; + + if (column != -1) + b47n->curr_column = column; + if (page_addr != -1) + b47n->curr_page_addr = page_addr; + + switch (command) { + case NAND_CMD_RESET: + pr_warn("Chip reset not implemented yet\n"); + break; + case NAND_CMD_READID: + ctlcode = NCTL_CSA | 0x01000000 | NCTL_CMD1W | NCTL_CMD0; + ctlcode |= NAND_CMD_READID; + if (bcm47xxnflash_ops_bcm4706_ctl_cmd(b47n->cc, ctlcode)) { + pr_err("READID error\n"); + break; + } + + /* + * Reading is specific, last one has to go without NCTL_CSA + * bit. We don't know how many reads NAND subsystem is going + * to perform, so cache everything. + */ + for (i = 0; i < ARRAY_SIZE(b47n->id_data); i++) { + ctlcode = NCTL_CSA | NCTL_READ; + if (i == ARRAY_SIZE(b47n->id_data) - 1) + ctlcode &= ~NCTL_CSA; + if (bcm47xxnflash_ops_bcm4706_ctl_cmd(b47n->cc, + ctlcode)) { + pr_err("READID error\n"); + break; + } + b47n->id_data[i] = + bcma_cc_read32(b47n->cc, BCMA_CC_NFLASH_DATA) + & 0xFF; + } + + break; + case NAND_CMD_STATUS: + ctlcode = NCTL_CSA | NCTL_CMD0 | NAND_CMD_STATUS; + if (bcm47xxnflash_ops_bcm4706_ctl_cmd(cc, ctlcode)) + pr_err("STATUS command error\n"); + break; + case NAND_CMD_READ0: + break; + case NAND_CMD_READOOB: + if (page_addr != -1) + b47n->curr_column += mtd->writesize; + break; + case NAND_CMD_ERASE1: + bcma_cc_write32(cc, BCMA_CC_NFLASH_ROW_ADDR, + b47n->curr_page_addr); + ctlcode = 0x00040000 | NCTL_CMD1W | NCTL_CMD0 | + NAND_CMD_ERASE1 | (NAND_CMD_ERASE2 << 8); + if (bcm47xxnflash_ops_bcm4706_ctl_cmd(cc, ctlcode)) + pr_err("ERASE1 failed\n"); + break; + case NAND_CMD_ERASE2: + break; + case NAND_CMD_SEQIN: + /* Set page and column */ + bcma_cc_write32(cc, BCMA_CC_NFLASH_COL_ADDR, + b47n->curr_column); + bcma_cc_write32(cc, BCMA_CC_NFLASH_ROW_ADDR, + b47n->curr_page_addr); + + /* Prepare to write */ + ctlcode = 0x40000000 | 0x00040000 | 0x00020000 | 0x00010000; + ctlcode |= NAND_CMD_SEQIN; + if (bcm47xxnflash_ops_bcm4706_ctl_cmd(cc, ctlcode)) + pr_err("SEQIN failed\n"); + break; + case NAND_CMD_PAGEPROG: + if (bcm47xxnflash_ops_bcm4706_ctl_cmd(cc, 0x00010000 | + NAND_CMD_PAGEPROG)) + pr_err("PAGEPROG failed\n"); + if (bcm47xxnflash_ops_bcm4706_poll(cc)) + pr_err("PAGEPROG not ready\n"); + break; + default: + pr_err("Command 0x%X unsupported\n", command); + break; + } + b47n->curr_command = command; +} + +static u8 bcm47xxnflash_ops_bcm4706_read_byte(struct mtd_info *mtd) +{ + struct nand_chip *nand_chip = (struct nand_chip *)mtd->priv; + struct bcm47xxnflash *b47n = (struct bcm47xxnflash *)nand_chip->priv; + struct bcma_drv_cc *cc = b47n->cc; + u32 tmp = 0; + + switch (b47n->curr_command) { + case NAND_CMD_READID: + if (b47n->curr_column >= ARRAY_SIZE(b47n->id_data)) { + pr_err("Requested invalid id_data: %d\n", + b47n->curr_column); + return 0; + } + return b47n->id_data[b47n->curr_column++]; + case NAND_CMD_STATUS: + if (bcm47xxnflash_ops_bcm4706_ctl_cmd(cc, NCTL_READ)) + return 0; + return bcma_cc_read32(cc, BCMA_CC_NFLASH_DATA) & 0xff; + case NAND_CMD_READOOB: + bcm47xxnflash_ops_bcm4706_read(mtd, (u8 *)&tmp, 4); + return tmp & 0xFF; + } + + pr_err("Invalid command for byte read: 0x%X\n", b47n->curr_command); + return 0; +} + +static void bcm47xxnflash_ops_bcm4706_read_buf(struct mtd_info *mtd, + uint8_t *buf, int len) +{ + struct nand_chip *nand_chip = (struct nand_chip *)mtd->priv; + struct bcm47xxnflash *b47n = (struct bcm47xxnflash *)nand_chip->priv; + + switch (b47n->curr_command) { + case NAND_CMD_READ0: + case NAND_CMD_READOOB: + bcm47xxnflash_ops_bcm4706_read(mtd, buf, len); + return; + } + + pr_err("Invalid command for buf read: 0x%X\n", b47n->curr_command); +} + +static void bcm47xxnflash_ops_bcm4706_write_buf(struct mtd_info *mtd, + const uint8_t *buf, int len) +{ + struct nand_chip *nand_chip = (struct nand_chip *)mtd->priv; + struct bcm47xxnflash *b47n = (struct bcm47xxnflash *)nand_chip->priv; + + switch (b47n->curr_command) { + case NAND_CMD_SEQIN: + bcm47xxnflash_ops_bcm4706_write(mtd, buf, len); + return; + } + + pr_err("Invalid command for buf write: 0x%X\n", b47n->curr_command); +} + +/************************************************** + * Init + **************************************************/ + +int bcm47xxnflash_ops_bcm4706_init(struct bcm47xxnflash *b47n) +{ + int err; + u32 freq; + u16 clock; + u8 w0, w1, w2, w3, w4; + + unsigned long chipsize; /* MiB */ + u8 tbits, col_bits, col_size, row_bits, row_bsize; + u32 val; + + b47n->nand_chip.select_chip = bcm47xxnflash_ops_bcm4706_select_chip; + b47n->nand_chip.cmdfunc = bcm47xxnflash_ops_bcm4706_cmdfunc; + b47n->nand_chip.read_byte = bcm47xxnflash_ops_bcm4706_read_byte; + b47n->nand_chip.read_buf = bcm47xxnflash_ops_bcm4706_read_buf; + b47n->nand_chip.write_buf = bcm47xxnflash_ops_bcm4706_write_buf; + b47n->nand_chip.bbt_options = NAND_BBT_USE_FLASH; + b47n->nand_chip.ecc.mode = NAND_ECC_NONE; /* TODO: implement ECC */ + + /* Enable NAND flash access */ + bcma_cc_set32(b47n->cc, BCMA_CC_4706_FLASHSCFG, + BCMA_CC_4706_FLASHSCFG_NF1); + + /* Configure wait counters */ + if (b47n->cc->status & BCMA_CC_CHIPST_4706_PKG_OPTION) { + freq = 100000000; + } else { + freq = bcma_chipco_pll_read(b47n->cc, 4); + freq = (freq * 0xFFF) >> 3; + freq = (freq * 25000000) >> 3; + } + clock = freq / 1000000; + w0 = bcm47xxnflash_ops_bcm4706_ns_to_cycle(15, clock); + w1 = bcm47xxnflash_ops_bcm4706_ns_to_cycle(20, clock); + w2 = bcm47xxnflash_ops_bcm4706_ns_to_cycle(10, clock); + w3 = bcm47xxnflash_ops_bcm4706_ns_to_cycle(10, clock); + w4 = bcm47xxnflash_ops_bcm4706_ns_to_cycle(100, clock); + bcma_cc_write32(b47n->cc, BCMA_CC_NFLASH_WAITCNT0, + (w4 << 24 | w3 << 18 | w2 << 12 | w1 << 6 | w0)); + + /* Scan NAND */ + err = nand_scan(&b47n->mtd, 1); + if (err) { + pr_err("Could not scan NAND flash: %d\n", err); + goto exit; + } + + /* Configure FLASH */ + chipsize = b47n->nand_chip.chipsize >> 20; + tbits = ffs(chipsize); /* find first bit set */ + if (!tbits || tbits != fls(chipsize)) { + pr_err("Invalid flash size: 0x%lX\n", chipsize); + err = -ENOTSUPP; + goto exit; + } + tbits += 19; /* Broadcom increases *index* by 20, we increase *pos* */ + + col_bits = b47n->nand_chip.page_shift + 1; + col_size = (col_bits + 7) / 8; + + row_bits = tbits - col_bits + 1; + row_bsize = (row_bits + 7) / 8; + + val = ((row_bsize - 1) << 6) | ((col_size - 1) << 4) | 2; + bcma_cc_write32(b47n->cc, BCMA_CC_NFLASH_CONF, val); + +exit: + if (err) + bcma_cc_mask32(b47n->cc, BCMA_CC_4706_FLASHSCFG, + ~BCMA_CC_4706_FLASHSCFG_NF1); + return err; +} diff --git a/drivers/mtd/nand/bf5xx_nand.c b/drivers/mtd/nand/bf5xx_nand.c new file mode 100644 index 00000000000..722898aea7a --- /dev/null +++ b/drivers/mtd/nand/bf5xx_nand.c @@ -0,0 +1,872 @@ +/* linux/drivers/mtd/nand/bf5xx_nand.c + * + * Copyright 2006-2008 Analog Devices Inc. + * http://blackfin.uclinux.org/ + * Bryan Wu <bryan.wu@analog.com> + * + * Blackfin BF5xx on-chip NAND flash controller driver + * + * Derived from drivers/mtd/nand/s3c2410.c + * Copyright (c) 2007 Ben Dooks <ben@simtec.co.uk> + * + * Derived from drivers/mtd/nand/cafe.c + * Copyright © 2006 Red Hat, Inc. + * Copyright © 2006 David Woodhouse <dwmw2@infradead.org> + * + * Changelog: + * 12-Jun-2007 Bryan Wu: Initial version + * 18-Jul-2007 Bryan Wu: + * - ECC_HW and ECC_SW supported + * - DMA supported in ECC_HW + * - YAFFS tested as rootfs in both ECC_HW and ECC_SW + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/bitops.h> + +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/nand_ecc.h> +#include <linux/mtd/partitions.h> + +#include <asm/blackfin.h> +#include <asm/dma.h> +#include <asm/cacheflush.h> +#include <asm/nand.h> +#include <asm/portmux.h> + +#define DRV_NAME "bf5xx-nand" +#define DRV_VERSION "1.2" +#define DRV_AUTHOR "Bryan Wu <bryan.wu@analog.com>" +#define DRV_DESC "BF5xx on-chip NAND FLash Controller Driver" + +/* NFC_STAT Masks */ +#define NBUSY 0x01 /* Not Busy */ +#define WB_FULL 0x02 /* Write Buffer Full */ +#define PG_WR_STAT 0x04 /* Page Write Pending */ +#define PG_RD_STAT 0x08 /* Page Read Pending */ +#define WB_EMPTY 0x10 /* Write Buffer Empty */ + +/* NFC_IRQSTAT Masks */ +#define NBUSYIRQ 0x01 /* Not Busy IRQ */ +#define WB_OVF 0x02 /* Write Buffer Overflow */ +#define WB_EDGE 0x04 /* Write Buffer Edge Detect */ +#define RD_RDY 0x08 /* Read Data Ready */ +#define WR_DONE 0x10 /* Page Write Done */ + +/* NFC_RST Masks */ +#define ECC_RST 0x01 /* ECC (and NFC counters) Reset */ + +/* NFC_PGCTL Masks */ +#define PG_RD_START 0x01 /* Page Read Start */ +#define PG_WR_START 0x02 /* Page Write Start */ + +#ifdef CONFIG_MTD_NAND_BF5XX_HWECC +static int hardware_ecc = 1; +#else +static int hardware_ecc; +#endif + +static const unsigned short bfin_nfc_pin_req[] = + {P_NAND_CE, + P_NAND_RB, + P_NAND_D0, + P_NAND_D1, + P_NAND_D2, + P_NAND_D3, + P_NAND_D4, + P_NAND_D5, + P_NAND_D6, + P_NAND_D7, + P_NAND_WE, + P_NAND_RE, + P_NAND_CLE, + P_NAND_ALE, + 0}; + +#ifdef CONFIG_MTD_NAND_BF5XX_BOOTROM_ECC +static struct nand_ecclayout bootrom_ecclayout = { + .eccbytes = 24, + .eccpos = { + 0x8 * 0, 0x8 * 0 + 1, 0x8 * 0 + 2, + 0x8 * 1, 0x8 * 1 + 1, 0x8 * 1 + 2, + 0x8 * 2, 0x8 * 2 + 1, 0x8 * 2 + 2, + 0x8 * 3, 0x8 * 3 + 1, 0x8 * 3 + 2, + 0x8 * 4, 0x8 * 4 + 1, 0x8 * 4 + 2, + 0x8 * 5, 0x8 * 5 + 1, 0x8 * 5 + 2, + 0x8 * 6, 0x8 * 6 + 1, 0x8 * 6 + 2, + 0x8 * 7, 0x8 * 7 + 1, 0x8 * 7 + 2 + }, + .oobfree = { + { 0x8 * 0 + 3, 5 }, + { 0x8 * 1 + 3, 5 }, + { 0x8 * 2 + 3, 5 }, + { 0x8 * 3 + 3, 5 }, + { 0x8 * 4 + 3, 5 }, + { 0x8 * 5 + 3, 5 }, + { 0x8 * 6 + 3, 5 }, + { 0x8 * 7 + 3, 5 }, + } +}; +#endif + +/* + * Data structures for bf5xx nand flash controller driver + */ + +/* bf5xx nand info */ +struct bf5xx_nand_info { + /* mtd info */ + struct nand_hw_control controller; + struct mtd_info mtd; + struct nand_chip chip; + + /* platform info */ + struct bf5xx_nand_platform *platform; + + /* device info */ + struct device *device; + + /* DMA stuff */ + struct completion dma_completion; +}; + +/* + * Conversion functions + */ +static struct bf5xx_nand_info *mtd_to_nand_info(struct mtd_info *mtd) +{ + return container_of(mtd, struct bf5xx_nand_info, mtd); +} + +static struct bf5xx_nand_info *to_nand_info(struct platform_device *pdev) +{ + return platform_get_drvdata(pdev); +} + +static struct bf5xx_nand_platform *to_nand_plat(struct platform_device *pdev) +{ + return dev_get_platdata(&pdev->dev); +} + +/* + * struct nand_chip interface function pointers + */ + +/* + * bf5xx_nand_hwcontrol + * + * Issue command and address cycles to the chip + */ +static void bf5xx_nand_hwcontrol(struct mtd_info *mtd, int cmd, + unsigned int ctrl) +{ + if (cmd == NAND_CMD_NONE) + return; + + while (bfin_read_NFC_STAT() & WB_FULL) + cpu_relax(); + + if (ctrl & NAND_CLE) + bfin_write_NFC_CMD(cmd); + else if (ctrl & NAND_ALE) + bfin_write_NFC_ADDR(cmd); + SSYNC(); +} + +/* + * bf5xx_nand_devready() + * + * returns 0 if the nand is busy, 1 if it is ready + */ +static int bf5xx_nand_devready(struct mtd_info *mtd) +{ + unsigned short val = bfin_read_NFC_STAT(); + + if ((val & NBUSY) == NBUSY) + return 1; + else + return 0; +} + +/* + * ECC functions + * These allow the bf5xx to use the controller's ECC + * generator block to ECC the data as it passes through + */ + +/* + * ECC error correction function + */ +static int bf5xx_nand_correct_data_256(struct mtd_info *mtd, u_char *dat, + u_char *read_ecc, u_char *calc_ecc) +{ + struct bf5xx_nand_info *info = mtd_to_nand_info(mtd); + u32 syndrome[5]; + u32 calced, stored; + int i; + unsigned short failing_bit, failing_byte; + u_char data; + + calced = calc_ecc[0] | (calc_ecc[1] << 8) | (calc_ecc[2] << 16); + stored = read_ecc[0] | (read_ecc[1] << 8) | (read_ecc[2] << 16); + + syndrome[0] = (calced ^ stored); + + /* + * syndrome 0: all zero + * No error in data + * No action + */ + if (!syndrome[0] || !calced || !stored) + return 0; + + /* + * sysdrome 0: only one bit is one + * ECC data was incorrect + * No action + */ + if (hweight32(syndrome[0]) == 1) { + dev_err(info->device, "ECC data was incorrect!\n"); + return 1; + } + + syndrome[1] = (calced & 0x7FF) ^ (stored & 0x7FF); + syndrome[2] = (calced & 0x7FF) ^ ((calced >> 11) & 0x7FF); + syndrome[3] = (stored & 0x7FF) ^ ((stored >> 11) & 0x7FF); + syndrome[4] = syndrome[2] ^ syndrome[3]; + + for (i = 0; i < 5; i++) + dev_info(info->device, "syndrome[%d] 0x%08x\n", i, syndrome[i]); + + dev_info(info->device, + "calced[0x%08x], stored[0x%08x]\n", + calced, stored); + + /* + * sysdrome 0: exactly 11 bits are one, each parity + * and parity' pair is 1 & 0 or 0 & 1. + * 1-bit correctable error + * Correct the error + */ + if (hweight32(syndrome[0]) == 11 && syndrome[4] == 0x7FF) { + dev_info(info->device, + "1-bit correctable error, correct it.\n"); + dev_info(info->device, + "syndrome[1] 0x%08x\n", syndrome[1]); + + failing_bit = syndrome[1] & 0x7; + failing_byte = syndrome[1] >> 0x3; + data = *(dat + failing_byte); + data = data ^ (0x1 << failing_bit); + *(dat + failing_byte) = data; + + return 0; + } + + /* + * sysdrome 0: random data + * More than 1-bit error, non-correctable error + * Discard data, mark bad block + */ + dev_err(info->device, + "More than 1-bit error, non-correctable error.\n"); + dev_err(info->device, + "Please discard data, mark bad block\n"); + + return 1; +} + +static int bf5xx_nand_correct_data(struct mtd_info *mtd, u_char *dat, + u_char *read_ecc, u_char *calc_ecc) +{ + struct nand_chip *chip = mtd->priv; + int ret; + + ret = bf5xx_nand_correct_data_256(mtd, dat, read_ecc, calc_ecc); + + /* If ecc size is 512, correct second 256 bytes */ + if (chip->ecc.size == 512) { + dat += 256; + read_ecc += 3; + calc_ecc += 3; + ret |= bf5xx_nand_correct_data_256(mtd, dat, read_ecc, calc_ecc); + } + + return ret; +} + +static void bf5xx_nand_enable_hwecc(struct mtd_info *mtd, int mode) +{ + return; +} + +static int bf5xx_nand_calculate_ecc(struct mtd_info *mtd, + const u_char *dat, u_char *ecc_code) +{ + struct bf5xx_nand_info *info = mtd_to_nand_info(mtd); + struct nand_chip *chip = mtd->priv; + u16 ecc0, ecc1; + u32 code[2]; + u8 *p; + + /* first 3 bytes ECC code for 256 page size */ + ecc0 = bfin_read_NFC_ECC0(); + ecc1 = bfin_read_NFC_ECC1(); + + code[0] = (ecc0 & 0x7ff) | ((ecc1 & 0x7ff) << 11); + + dev_dbg(info->device, "returning ecc 0x%08x\n", code[0]); + + p = (u8 *) code; + memcpy(ecc_code, p, 3); + + /* second 3 bytes ECC code for 512 ecc size */ + if (chip->ecc.size == 512) { + ecc0 = bfin_read_NFC_ECC2(); + ecc1 = bfin_read_NFC_ECC3(); + code[1] = (ecc0 & 0x7ff) | ((ecc1 & 0x7ff) << 11); + + /* second 3 bytes in ecc_code for second 256 + * bytes of 512 page size + */ + p = (u8 *) (code + 1); + memcpy((ecc_code + 3), p, 3); + dev_dbg(info->device, "returning ecc 0x%08x\n", code[1]); + } + + return 0; +} + +/* + * PIO mode for buffer writing and reading + */ +static void bf5xx_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + int i; + unsigned short val; + + /* + * Data reads are requested by first writing to NFC_DATA_RD + * and then reading back from NFC_READ. + */ + for (i = 0; i < len; i++) { + while (bfin_read_NFC_STAT() & WB_FULL) + cpu_relax(); + + /* Contents do not matter */ + bfin_write_NFC_DATA_RD(0x0000); + SSYNC(); + + while ((bfin_read_NFC_IRQSTAT() & RD_RDY) != RD_RDY) + cpu_relax(); + + buf[i] = bfin_read_NFC_READ(); + + val = bfin_read_NFC_IRQSTAT(); + val |= RD_RDY; + bfin_write_NFC_IRQSTAT(val); + SSYNC(); + } +} + +static uint8_t bf5xx_nand_read_byte(struct mtd_info *mtd) +{ + uint8_t val; + + bf5xx_nand_read_buf(mtd, &val, 1); + + return val; +} + +static void bf5xx_nand_write_buf(struct mtd_info *mtd, + const uint8_t *buf, int len) +{ + int i; + + for (i = 0; i < len; i++) { + while (bfin_read_NFC_STAT() & WB_FULL) + cpu_relax(); + + bfin_write_NFC_DATA_WR(buf[i]); + SSYNC(); + } +} + +static void bf5xx_nand_read_buf16(struct mtd_info *mtd, uint8_t *buf, int len) +{ + int i; + u16 *p = (u16 *) buf; + len >>= 1; + + /* + * Data reads are requested by first writing to NFC_DATA_RD + * and then reading back from NFC_READ. + */ + bfin_write_NFC_DATA_RD(0x5555); + + SSYNC(); + + for (i = 0; i < len; i++) + p[i] = bfin_read_NFC_READ(); +} + +static void bf5xx_nand_write_buf16(struct mtd_info *mtd, + const uint8_t *buf, int len) +{ + int i; + u16 *p = (u16 *) buf; + len >>= 1; + + for (i = 0; i < len; i++) + bfin_write_NFC_DATA_WR(p[i]); + + SSYNC(); +} + +/* + * DMA functions for buffer writing and reading + */ +static irqreturn_t bf5xx_nand_dma_irq(int irq, void *dev_id) +{ + struct bf5xx_nand_info *info = dev_id; + + clear_dma_irqstat(CH_NFC); + disable_dma(CH_NFC); + complete(&info->dma_completion); + + return IRQ_HANDLED; +} + +static void bf5xx_nand_dma_rw(struct mtd_info *mtd, + uint8_t *buf, int is_read) +{ + struct bf5xx_nand_info *info = mtd_to_nand_info(mtd); + struct nand_chip *chip = mtd->priv; + unsigned short val; + + dev_dbg(info->device, " mtd->%p, buf->%p, is_read %d\n", + mtd, buf, is_read); + + /* + * Before starting a dma transfer, be sure to invalidate/flush + * the cache over the address range of your DMA buffer to + * prevent cache coherency problems. Otherwise very subtle bugs + * can be introduced to your driver. + */ + if (is_read) + invalidate_dcache_range((unsigned int)buf, + (unsigned int)(buf + chip->ecc.size)); + else + flush_dcache_range((unsigned int)buf, + (unsigned int)(buf + chip->ecc.size)); + + /* + * This register must be written before each page is + * transferred to generate the correct ECC register + * values. + */ + bfin_write_NFC_RST(ECC_RST); + SSYNC(); + while (bfin_read_NFC_RST() & ECC_RST) + cpu_relax(); + + disable_dma(CH_NFC); + clear_dma_irqstat(CH_NFC); + + /* setup DMA register with Blackfin DMA API */ + set_dma_config(CH_NFC, 0x0); + set_dma_start_addr(CH_NFC, (unsigned long) buf); + + /* The DMAs have different size on BF52x and BF54x */ +#ifdef CONFIG_BF52x + set_dma_x_count(CH_NFC, (chip->ecc.size >> 1)); + set_dma_x_modify(CH_NFC, 2); + val = DI_EN | WDSIZE_16; +#endif + +#ifdef CONFIG_BF54x + set_dma_x_count(CH_NFC, (chip->ecc.size >> 2)); + set_dma_x_modify(CH_NFC, 4); + val = DI_EN | WDSIZE_32; +#endif + /* setup write or read operation */ + if (is_read) + val |= WNR; + set_dma_config(CH_NFC, val); + enable_dma(CH_NFC); + + /* Start PAGE read/write operation */ + if (is_read) + bfin_write_NFC_PGCTL(PG_RD_START); + else + bfin_write_NFC_PGCTL(PG_WR_START); + wait_for_completion(&info->dma_completion); +} + +static void bf5xx_nand_dma_read_buf(struct mtd_info *mtd, + uint8_t *buf, int len) +{ + struct bf5xx_nand_info *info = mtd_to_nand_info(mtd); + struct nand_chip *chip = mtd->priv; + + dev_dbg(info->device, "mtd->%p, buf->%p, int %d\n", mtd, buf, len); + + if (len == chip->ecc.size) + bf5xx_nand_dma_rw(mtd, buf, 1); + else + bf5xx_nand_read_buf(mtd, buf, len); +} + +static void bf5xx_nand_dma_write_buf(struct mtd_info *mtd, + const uint8_t *buf, int len) +{ + struct bf5xx_nand_info *info = mtd_to_nand_info(mtd); + struct nand_chip *chip = mtd->priv; + + dev_dbg(info->device, "mtd->%p, buf->%p, len %d\n", mtd, buf, len); + + if (len == chip->ecc.size) + bf5xx_nand_dma_rw(mtd, (uint8_t *)buf, 0); + else + bf5xx_nand_write_buf(mtd, buf, len); +} + +static int bf5xx_nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf, int oob_required, int page) +{ + bf5xx_nand_read_buf(mtd, buf, mtd->writesize); + bf5xx_nand_read_buf(mtd, chip->oob_poi, mtd->oobsize); + + return 0; +} + +static int bf5xx_nand_write_page_raw(struct mtd_info *mtd, + struct nand_chip *chip, const uint8_t *buf, int oob_required) +{ + bf5xx_nand_write_buf(mtd, buf, mtd->writesize); + bf5xx_nand_write_buf(mtd, chip->oob_poi, mtd->oobsize); + + return 0; +} + +/* + * System initialization functions + */ +static int bf5xx_nand_dma_init(struct bf5xx_nand_info *info) +{ + int ret; + + /* Do not use dma */ + if (!hardware_ecc) + return 0; + + init_completion(&info->dma_completion); + + /* Request NFC DMA channel */ + ret = request_dma(CH_NFC, "BF5XX NFC driver"); + if (ret < 0) { + dev_err(info->device, " unable to get DMA channel\n"); + return ret; + } + +#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); + return 0; +} + +static void bf5xx_nand_dma_remove(struct bf5xx_nand_info *info) +{ + /* Free NFC DMA channel */ + if (hardware_ecc) + free_dma(CH_NFC); +} + +/* + * BF5XX NFC hardware initialization + * - pin mux setup + * - clear interrupt status + */ +static int bf5xx_nand_hw_init(struct bf5xx_nand_info *info) +{ + int err = 0; + unsigned short val; + struct bf5xx_nand_platform *plat = info->platform; + + /* setup NFC_CTL register */ + dev_info(info->device, + "data_width=%d, wr_dly=%d, rd_dly=%d\n", + (plat->data_width ? 16 : 8), + plat->wr_dly, plat->rd_dly); + + val = (1 << NFC_PG_SIZE_OFFSET) | + (plat->data_width << NFC_NWIDTH_OFFSET) | + (plat->rd_dly << NFC_RDDLY_OFFSET) | + (plat->wr_dly << NFC_WRDLY_OFFSET); + dev_dbg(info->device, "NFC_CTL is 0x%04x\n", val); + + bfin_write_NFC_CTL(val); + SSYNC(); + + /* clear interrupt status */ + bfin_write_NFC_IRQMASK(0x0); + SSYNC(); + val = bfin_read_NFC_IRQSTAT(); + bfin_write_NFC_IRQSTAT(val); + SSYNC(); + + /* DMA initialization */ + if (bf5xx_nand_dma_init(info)) + err = -ENXIO; + + return err; +} + +/* + * Device management interface + */ +static int bf5xx_nand_add_partition(struct bf5xx_nand_info *info) +{ + struct mtd_info *mtd = &info->mtd; + struct mtd_partition *parts = info->platform->partitions; + int nr = info->platform->nr_partitions; + + return mtd_device_register(mtd, parts, nr); +} + +static int bf5xx_nand_remove(struct platform_device *pdev) +{ + struct bf5xx_nand_info *info = to_nand_info(pdev); + + /* first thing we need to do is release all our mtds + * and their partitions, then go through freeing the + * resources used + */ + nand_release(&info->mtd); + + peripheral_free_list(bfin_nfc_pin_req); + bf5xx_nand_dma_remove(info); + + return 0; +} + +static int bf5xx_nand_scan(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + int ret; + + ret = nand_scan_ident(mtd, 1, NULL); + if (ret) + return ret; + + if (hardware_ecc) { + /* + * for nand with page size > 512B, think it as several sections with 512B + */ + if (likely(mtd->writesize >= 512)) { + chip->ecc.size = 512; + chip->ecc.bytes = 6; + chip->ecc.strength = 2; + } else { + chip->ecc.size = 256; + chip->ecc.bytes = 3; + chip->ecc.strength = 1; + bfin_write_NFC_CTL(bfin_read_NFC_CTL() & ~(1 << NFC_PG_SIZE_OFFSET)); + SSYNC(); + } + } + + return nand_scan_tail(mtd); +} + +/* + * bf5xx_nand_probe + * + * called by device layer when it finds a device matching + * one our driver can handled. This code checks to see if + * it can allocate all necessary resources then calls the + * nand layer to look for devices + */ +static int bf5xx_nand_probe(struct platform_device *pdev) +{ + struct bf5xx_nand_platform *plat = to_nand_plat(pdev); + struct bf5xx_nand_info *info = NULL; + struct nand_chip *chip = NULL; + struct mtd_info *mtd = NULL; + int err = 0; + + dev_dbg(&pdev->dev, "(%p)\n", pdev); + + if (!plat) { + dev_err(&pdev->dev, "no platform specific information\n"); + return -EINVAL; + } + + if (peripheral_request_list(bfin_nfc_pin_req, DRV_NAME)) { + dev_err(&pdev->dev, "requesting Peripherals failed\n"); + return -EFAULT; + } + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (info == NULL) { + err = -ENOMEM; + goto out_err; + } + + platform_set_drvdata(pdev, info); + + spin_lock_init(&info->controller.lock); + init_waitqueue_head(&info->controller.wq); + + info->device = &pdev->dev; + info->platform = plat; + + /* initialise chip data struct */ + chip = &info->chip; + + if (plat->data_width) + chip->options |= NAND_BUSWIDTH_16; + + chip->options |= NAND_CACHEPRG | NAND_SKIP_BBTSCAN; + + chip->read_buf = (plat->data_width) ? + bf5xx_nand_read_buf16 : bf5xx_nand_read_buf; + chip->write_buf = (plat->data_width) ? + bf5xx_nand_write_buf16 : bf5xx_nand_write_buf; + + chip->read_byte = bf5xx_nand_read_byte; + + chip->cmd_ctrl = bf5xx_nand_hwcontrol; + chip->dev_ready = bf5xx_nand_devready; + + chip->priv = &info->mtd; + chip->controller = &info->controller; + + chip->IO_ADDR_R = (void __iomem *) NFC_READ; + chip->IO_ADDR_W = (void __iomem *) NFC_DATA_WR; + + chip->chip_delay = 0; + + /* initialise mtd info data struct */ + mtd = &info->mtd; + mtd->priv = chip; + mtd->owner = THIS_MODULE; + + /* initialise the hardware */ + err = bf5xx_nand_hw_init(info); + if (err) + goto out_err; + + /* setup hardware ECC data struct */ + if (hardware_ecc) { +#ifdef CONFIG_MTD_NAND_BF5XX_BOOTROM_ECC + chip->ecc.layout = &bootrom_ecclayout; +#endif + chip->read_buf = bf5xx_nand_dma_read_buf; + chip->write_buf = bf5xx_nand_dma_write_buf; + chip->ecc.calculate = bf5xx_nand_calculate_ecc; + chip->ecc.correct = bf5xx_nand_correct_data; + chip->ecc.mode = NAND_ECC_HW; + chip->ecc.hwctl = bf5xx_nand_enable_hwecc; + chip->ecc.read_page_raw = bf5xx_nand_read_page_raw; + chip->ecc.write_page_raw = bf5xx_nand_write_page_raw; + } else { + chip->ecc.mode = NAND_ECC_SOFT; + } + + /* scan hardware nand chip and setup mtd info data struct */ + if (bf5xx_nand_scan(mtd)) { + err = -ENXIO; + goto out_err_nand_scan; + } + +#ifdef CONFIG_MTD_NAND_BF5XX_BOOTROM_ECC + chip->badblockpos = 63; +#endif + + /* add NAND partition */ + bf5xx_nand_add_partition(info); + + dev_dbg(&pdev->dev, "initialised ok\n"); + return 0; + +out_err_nand_scan: + bf5xx_nand_dma_remove(info); +out_err: + peripheral_free_list(bfin_nfc_pin_req); + + return err; +} + +/* PM Support */ +#ifdef CONFIG_PM + +static int bf5xx_nand_suspend(struct platform_device *dev, pm_message_t pm) +{ + struct bf5xx_nand_info *info = platform_get_drvdata(dev); + + return 0; +} + +static int bf5xx_nand_resume(struct platform_device *dev) +{ + struct bf5xx_nand_info *info = platform_get_drvdata(dev); + + return 0; +} + +#else +#define bf5xx_nand_suspend NULL +#define bf5xx_nand_resume NULL +#endif + +/* driver device registration */ +static struct platform_driver bf5xx_nand_driver = { + .probe = bf5xx_nand_probe, + .remove = bf5xx_nand_remove, + .suspend = bf5xx_nand_suspend, + .resume = bf5xx_nand_resume, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(bf5xx_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_DESCRIPTION(DRV_DESC); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/mtd/nand/cafe_nand.c b/drivers/mtd/nand/cafe_nand.c new file mode 100644 index 00000000000..4e66726da9a --- /dev/null +++ b/drivers/mtd/nand/cafe_nand.c @@ -0,0 +1,914 @@ +/* + * Driver for One Laptop Per Child ‘CAFÉ’ controller, aka Marvell 88ALP01 + * + * The data sheet for this device can be found at: + * http://wiki.laptop.org/go/Datasheets + * + * Copyright © 2006 Red Hat, Inc. + * Copyright © 2006 David Woodhouse <dwmw2@infradead.org> + */ + +#define DEBUG + +#include <linux/device.h> +#undef DEBUG +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/rslib.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <asm/io.h> + +#define CAFE_NAND_CTRL1 0x00 +#define CAFE_NAND_CTRL2 0x04 +#define CAFE_NAND_CTRL3 0x08 +#define CAFE_NAND_STATUS 0x0c +#define CAFE_NAND_IRQ 0x10 +#define CAFE_NAND_IRQ_MASK 0x14 +#define CAFE_NAND_DATA_LEN 0x18 +#define CAFE_NAND_ADDR1 0x1c +#define CAFE_NAND_ADDR2 0x20 +#define CAFE_NAND_TIMING1 0x24 +#define CAFE_NAND_TIMING2 0x28 +#define CAFE_NAND_TIMING3 0x2c +#define CAFE_NAND_NONMEM 0x30 +#define CAFE_NAND_ECC_RESULT 0x3C +#define CAFE_NAND_DMA_CTRL 0x40 +#define CAFE_NAND_DMA_ADDR0 0x44 +#define CAFE_NAND_DMA_ADDR1 0x48 +#define CAFE_NAND_ECC_SYN01 0x50 +#define CAFE_NAND_ECC_SYN23 0x54 +#define CAFE_NAND_ECC_SYN45 0x58 +#define CAFE_NAND_ECC_SYN67 0x5c +#define CAFE_NAND_READ_DATA 0x1000 +#define CAFE_NAND_WRITE_DATA 0x2000 + +#define CAFE_GLOBAL_CTRL 0x3004 +#define CAFE_GLOBAL_IRQ 0x3008 +#define CAFE_GLOBAL_IRQ_MASK 0x300c +#define CAFE_NAND_RESET 0x3034 + +/* Missing from the datasheet: bit 19 of CTRL1 sets CE0 vs. CE1 */ +#define CTRL1_CHIPSELECT (1<<19) + +struct cafe_priv { + struct nand_chip nand; + struct pci_dev *pdev; + void __iomem *mmio; + struct rs_control *rs; + uint32_t ctl1; + uint32_t ctl2; + int datalen; + int nr_data; + int data_pos; + int page_addr; + dma_addr_t dmaaddr; + unsigned char *dmabuf; +}; + +static int usedma = 1; +module_param(usedma, int, 0644); + +static int skipbbt = 0; +module_param(skipbbt, int, 0644); + +static int debug = 0; +module_param(debug, int, 0644); + +static int regdebug = 0; +module_param(regdebug, int, 0644); + +static int checkecc = 1; +module_param(checkecc, int, 0644); + +static unsigned int numtimings; +static int timing[3]; +module_param_array(timing, int, &numtimings, 0644); + +static const char *part_probes[] = { "cmdlinepart", "RedBoot", NULL }; + +/* Hrm. Why isn't this already conditional on something in the struct device? */ +#define cafe_dev_dbg(dev, args...) do { if (debug) dev_dbg(dev, ##args); } while(0) + +/* Make it easier to switch to PIO if we need to */ +#define cafe_readl(cafe, addr) readl((cafe)->mmio + CAFE_##addr) +#define cafe_writel(cafe, datum, addr) writel(datum, (cafe)->mmio + CAFE_##addr) + +static int cafe_device_ready(struct mtd_info *mtd) +{ + struct cafe_priv *cafe = mtd->priv; + int result = !!(cafe_readl(cafe, NAND_STATUS) & 0x40000000); + uint32_t irqs = cafe_readl(cafe, NAND_IRQ); + + cafe_writel(cafe, irqs, NAND_IRQ); + + cafe_dev_dbg(&cafe->pdev->dev, "NAND device is%s ready, IRQ %x (%x) (%x,%x)\n", + result?"":" not", irqs, cafe_readl(cafe, NAND_IRQ), + cafe_readl(cafe, GLOBAL_IRQ), cafe_readl(cafe, GLOBAL_IRQ_MASK)); + + return result; +} + + +static void cafe_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) +{ + struct cafe_priv *cafe = mtd->priv; + + if (usedma) + memcpy(cafe->dmabuf + cafe->datalen, buf, len); + else + memcpy_toio(cafe->mmio + CAFE_NAND_WRITE_DATA + cafe->datalen, buf, len); + + cafe->datalen += len; + + cafe_dev_dbg(&cafe->pdev->dev, "Copy 0x%x bytes to write buffer. datalen 0x%x\n", + len, cafe->datalen); +} + +static void cafe_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + struct cafe_priv *cafe = mtd->priv; + + if (usedma) + memcpy(buf, cafe->dmabuf + cafe->datalen, len); + else + memcpy_fromio(buf, cafe->mmio + CAFE_NAND_READ_DATA + cafe->datalen, len); + + cafe_dev_dbg(&cafe->pdev->dev, "Copy 0x%x bytes from position 0x%x in read buffer.\n", + len, cafe->datalen); + cafe->datalen += len; +} + +static uint8_t cafe_read_byte(struct mtd_info *mtd) +{ + struct cafe_priv *cafe = mtd->priv; + uint8_t d; + + cafe_read_buf(mtd, &d, 1); + cafe_dev_dbg(&cafe->pdev->dev, "Read %02x\n", d); + + return d; +} + +static void cafe_nand_cmdfunc(struct mtd_info *mtd, unsigned command, + int column, int page_addr) +{ + struct cafe_priv *cafe = mtd->priv; + int adrbytes = 0; + uint32_t ctl1; + uint32_t doneint = 0x80000000; + + cafe_dev_dbg(&cafe->pdev->dev, "cmdfunc %02x, 0x%x, 0x%x\n", + command, column, page_addr); + + if (command == NAND_CMD_ERASE2 || command == NAND_CMD_PAGEPROG) { + /* Second half of a command we already calculated */ + cafe_writel(cafe, cafe->ctl2 | 0x100 | command, NAND_CTRL2); + ctl1 = cafe->ctl1; + cafe->ctl2 &= ~(1<<30); + cafe_dev_dbg(&cafe->pdev->dev, "Continue command, ctl1 %08x, #data %d\n", + cafe->ctl1, cafe->nr_data); + goto do_command; + } + /* Reset ECC engine */ + cafe_writel(cafe, 0, NAND_CTRL2); + + /* Emulate NAND_CMD_READOOB on large-page chips */ + if (mtd->writesize > 512 && + command == NAND_CMD_READOOB) { + column += mtd->writesize; + command = NAND_CMD_READ0; + } + + /* FIXME: Do we need to send read command before sending data + for small-page chips, to position the buffer correctly? */ + + if (column != -1) { + cafe_writel(cafe, column, NAND_ADDR1); + adrbytes = 2; + if (page_addr != -1) + goto write_adr2; + } else if (page_addr != -1) { + cafe_writel(cafe, page_addr & 0xffff, NAND_ADDR1); + page_addr >>= 16; + write_adr2: + cafe_writel(cafe, page_addr, NAND_ADDR2); + adrbytes += 2; + if (mtd->size > mtd->writesize << 16) + adrbytes++; + } + + cafe->data_pos = cafe->datalen = 0; + + /* Set command valid bit, mask in the chip select bit */ + ctl1 = 0x80000000 | command | (cafe->ctl1 & CTRL1_CHIPSELECT); + + /* Set RD or WR bits as appropriate */ + if (command == NAND_CMD_READID || command == NAND_CMD_STATUS) { + ctl1 |= (1<<26); /* rd */ + /* Always 5 bytes, for now */ + cafe->datalen = 4; + /* And one address cycle -- even for STATUS, since the controller doesn't work without */ + adrbytes = 1; + } else if (command == NAND_CMD_READ0 || command == NAND_CMD_READ1 || + command == NAND_CMD_READOOB || command == NAND_CMD_RNDOUT) { + ctl1 |= 1<<26; /* rd */ + /* For now, assume just read to end of page */ + cafe->datalen = mtd->writesize + mtd->oobsize - column; + } else if (command == NAND_CMD_SEQIN) + ctl1 |= 1<<25; /* wr */ + + /* Set number of address bytes */ + if (adrbytes) + ctl1 |= ((adrbytes-1)|8) << 27; + + if (command == NAND_CMD_SEQIN || command == NAND_CMD_ERASE1) { + /* Ignore the first command of a pair; the hardware + deals with them both at once, later */ + cafe->ctl1 = ctl1; + cafe_dev_dbg(&cafe->pdev->dev, "Setup for delayed command, ctl1 %08x, dlen %x\n", + cafe->ctl1, cafe->datalen); + return; + } + /* RNDOUT and READ0 commands need a following byte */ + if (command == NAND_CMD_RNDOUT) + cafe_writel(cafe, cafe->ctl2 | 0x100 | NAND_CMD_RNDOUTSTART, NAND_CTRL2); + else if (command == NAND_CMD_READ0 && mtd->writesize > 512) + cafe_writel(cafe, cafe->ctl2 | 0x100 | NAND_CMD_READSTART, NAND_CTRL2); + + do_command: + cafe_dev_dbg(&cafe->pdev->dev, "dlen %x, ctl1 %x, ctl2 %x\n", + cafe->datalen, ctl1, cafe_readl(cafe, NAND_CTRL2)); + + /* NB: The datasheet lies -- we really should be subtracting 1 here */ + cafe_writel(cafe, cafe->datalen, NAND_DATA_LEN); + cafe_writel(cafe, 0x90000000, NAND_IRQ); + if (usedma && (ctl1 & (3<<25))) { + uint32_t dmactl = 0xc0000000 + cafe->datalen; + /* If WR or RD bits set, set up DMA */ + if (ctl1 & (1<<26)) { + /* It's a read */ + dmactl |= (1<<29); + /* ... so it's done when the DMA is done, not just + the command. */ + doneint = 0x10000000; + } + cafe_writel(cafe, dmactl, NAND_DMA_CTRL); + } + cafe->datalen = 0; + + if (unlikely(regdebug)) { + int i; + printk("About to write command %08x to register 0\n", ctl1); + for (i=4; i< 0x5c; i+=4) + printk("Register %x: %08x\n", i, readl(cafe->mmio + i)); + } + + cafe_writel(cafe, ctl1, NAND_CTRL1); + /* Apply this short delay always to ensure that we do wait tWB in + * any case on any machine. */ + ndelay(100); + + if (1) { + int c; + uint32_t irqs; + + for (c = 500000; c != 0; c--) { + irqs = cafe_readl(cafe, NAND_IRQ); + if (irqs & doneint) + break; + udelay(1); + if (!(c % 100000)) + cafe_dev_dbg(&cafe->pdev->dev, "Wait for ready, IRQ %x\n", irqs); + cpu_relax(); + } + cafe_writel(cafe, doneint, NAND_IRQ); + cafe_dev_dbg(&cafe->pdev->dev, "Command %x completed after %d usec, irqs %x (%x)\n", + command, 500000-c, irqs, cafe_readl(cafe, NAND_IRQ)); + } + + WARN_ON(cafe->ctl2 & (1<<30)); + + switch (command) { + + case NAND_CMD_CACHEDPROG: + case NAND_CMD_PAGEPROG: + case NAND_CMD_ERASE1: + case NAND_CMD_ERASE2: + case NAND_CMD_SEQIN: + case NAND_CMD_RNDIN: + case NAND_CMD_STATUS: + case NAND_CMD_RNDOUT: + cafe_writel(cafe, cafe->ctl2, NAND_CTRL2); + return; + } + nand_wait_ready(mtd); + cafe_writel(cafe, cafe->ctl2, NAND_CTRL2); +} + +static void cafe_select_chip(struct mtd_info *mtd, int chipnr) +{ + struct cafe_priv *cafe = mtd->priv; + + cafe_dev_dbg(&cafe->pdev->dev, "select_chip %d\n", chipnr); + + /* Mask the appropriate bit into the stored value of ctl1 + which will be used by cafe_nand_cmdfunc() */ + if (chipnr) + cafe->ctl1 |= CTRL1_CHIPSELECT; + else + cafe->ctl1 &= ~CTRL1_CHIPSELECT; +} + +static irqreturn_t cafe_nand_interrupt(int irq, void *id) +{ + struct mtd_info *mtd = id; + struct cafe_priv *cafe = mtd->priv; + uint32_t irqs = cafe_readl(cafe, NAND_IRQ); + cafe_writel(cafe, irqs & ~0x90000000, NAND_IRQ); + if (!irqs) + return IRQ_NONE; + + cafe_dev_dbg(&cafe->pdev->dev, "irq, bits %x (%x)\n", irqs, cafe_readl(cafe, NAND_IRQ)); + return IRQ_HANDLED; +} + +static void cafe_nand_bug(struct mtd_info *mtd) +{ + BUG(); +} + +static int cafe_nand_write_oob(struct mtd_info *mtd, + struct nand_chip *chip, int page) +{ + int status = 0; + + chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize, page); + chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); + chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + status = chip->waitfunc(mtd, chip); + + return status & NAND_STATUS_FAIL ? -EIO : 0; +} + +/* Don't use -- use nand_read_oob_std for now */ +static int cafe_nand_read_oob(struct mtd_info *mtd, struct nand_chip *chip, + int page) +{ + chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page); + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + return 0; +} +/** + * cafe_nand_read_page_syndrome - [REPLACEABLE] hardware ecc syndrome based page read + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: buffer to store read data + * @oob_required: caller expects OOB data read to chip->oob_poi + * + * The hw generator calculates the error syndrome automatically. Therefore + * we need a special oob layout and handling. + */ +static int cafe_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf, int oob_required, int page) +{ + struct cafe_priv *cafe = mtd->priv; + unsigned int max_bitflips = 0; + + cafe_dev_dbg(&cafe->pdev->dev, "ECC result %08x SYN1,2 %08x\n", + cafe_readl(cafe, NAND_ECC_RESULT), + cafe_readl(cafe, NAND_ECC_SYN01)); + + chip->read_buf(mtd, buf, mtd->writesize); + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + + if (checkecc && cafe_readl(cafe, NAND_ECC_RESULT) & (1<<18)) { + unsigned short syn[8], pat[4]; + int pos[4]; + u8 *oob = chip->oob_poi; + int i, n; + + for (i=0; i<8; i+=2) { + uint32_t tmp = cafe_readl(cafe, NAND_ECC_SYN01 + (i*2)); + syn[i] = cafe->rs->index_of[tmp & 0xfff]; + syn[i+1] = cafe->rs->index_of[(tmp >> 16) & 0xfff]; + } + + n = decode_rs16(cafe->rs, NULL, NULL, 1367, syn, 0, pos, 0, + pat); + + for (i = 0; i < n; i++) { + int p = pos[i]; + + /* The 12-bit symbols are mapped to bytes here */ + + if (p > 1374) { + /* out of range */ + n = -1374; + } else if (p == 0) { + /* high four bits do not correspond to data */ + if (pat[i] > 0xff) + n = -2048; + else + buf[0] ^= pat[i]; + } else if (p == 1365) { + buf[2047] ^= pat[i] >> 4; + oob[0] ^= pat[i] << 4; + } else if (p > 1365) { + if ((p & 1) == 1) { + oob[3*p/2 - 2048] ^= pat[i] >> 4; + oob[3*p/2 - 2047] ^= pat[i] << 4; + } else { + oob[3*p/2 - 2049] ^= pat[i] >> 8; + oob[3*p/2 - 2048] ^= pat[i]; + } + } else if ((p & 1) == 1) { + buf[3*p/2] ^= pat[i] >> 4; + buf[3*p/2 + 1] ^= pat[i] << 4; + } else { + buf[3*p/2 - 1] ^= pat[i] >> 8; + buf[3*p/2] ^= pat[i]; + } + } + + if (n < 0) { + dev_dbg(&cafe->pdev->dev, "Failed to correct ECC at %08x\n", + cafe_readl(cafe, NAND_ADDR2) * 2048); + for (i = 0; i < 0x5c; i += 4) + printk("Register %x: %08x\n", i, readl(cafe->mmio + i)); + mtd->ecc_stats.failed++; + } else { + dev_dbg(&cafe->pdev->dev, "Corrected %d symbol errors\n", n); + mtd->ecc_stats.corrected += n; + max_bitflips = max_t(unsigned int, max_bitflips, n); + } + } + + return max_bitflips; +} + +static struct nand_ecclayout cafe_oobinfo_2048 = { + .eccbytes = 14, + .eccpos = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}, + .oobfree = {{14, 50}} +}; + +/* Ick. The BBT code really ought to be able to work this bit out + for itself from the above, at least for the 2KiB case */ +static uint8_t cafe_bbt_pattern_2048[] = { 'B', 'b', 't', '0' }; +static uint8_t cafe_mirror_pattern_2048[] = { '1', 't', 'b', 'B' }; + +static uint8_t cafe_bbt_pattern_512[] = { 0xBB }; +static uint8_t cafe_mirror_pattern_512[] = { 0xBC }; + + +static struct nand_bbt_descr cafe_bbt_main_descr_2048 = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 14, + .len = 4, + .veroffs = 18, + .maxblocks = 4, + .pattern = cafe_bbt_pattern_2048 +}; + +static struct nand_bbt_descr cafe_bbt_mirror_descr_2048 = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 14, + .len = 4, + .veroffs = 18, + .maxblocks = 4, + .pattern = cafe_mirror_pattern_2048 +}; + +static struct nand_ecclayout cafe_oobinfo_512 = { + .eccbytes = 14, + .eccpos = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}, + .oobfree = {{14, 2}} +}; + +static struct nand_bbt_descr cafe_bbt_main_descr_512 = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 14, + .len = 1, + .veroffs = 15, + .maxblocks = 4, + .pattern = cafe_bbt_pattern_512 +}; + +static struct nand_bbt_descr cafe_bbt_mirror_descr_512 = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 14, + .len = 1, + .veroffs = 15, + .maxblocks = 4, + .pattern = cafe_mirror_pattern_512 +}; + + +static int cafe_nand_write_page_lowlevel(struct mtd_info *mtd, + struct nand_chip *chip, + const uint8_t *buf, int oob_required) +{ + struct cafe_priv *cafe = mtd->priv; + + chip->write_buf(mtd, buf, mtd->writesize); + chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); + + /* Set up ECC autogeneration */ + cafe->ctl2 |= (1<<30); + + return 0; +} + +static int cafe_nand_write_page(struct mtd_info *mtd, struct nand_chip *chip, + uint32_t offset, int data_len, const uint8_t *buf, + int oob_required, int page, int cached, int raw) +{ + int status; + + chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page); + + if (unlikely(raw)) + status = chip->ecc.write_page_raw(mtd, chip, buf, oob_required); + else + status = chip->ecc.write_page(mtd, chip, buf, oob_required); + + if (status < 0) + return status; + + /* + * Cached progamming disabled for now, Not sure if its worth the + * trouble. The speed gain is not very impressive. (2.3->2.6Mib/s) + */ + cached = 0; + + if (!cached || !(chip->options & NAND_CACHEPRG)) { + + chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + status = chip->waitfunc(mtd, chip); + /* + * See if operation failed and additional status checks are + * available + */ + if ((status & NAND_STATUS_FAIL) && (chip->errstat)) + status = chip->errstat(mtd, chip, FL_WRITING, status, + page); + + if (status & NAND_STATUS_FAIL) + return -EIO; + } else { + chip->cmdfunc(mtd, NAND_CMD_CACHEDPROG, -1, -1); + status = chip->waitfunc(mtd, chip); + } + + return 0; +} + +static int cafe_nand_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip) +{ + return 0; +} + +/* F_2[X]/(X**6+X+1) */ +static unsigned short gf64_mul(u8 a, u8 b) +{ + u8 c; + unsigned int i; + + c = 0; + for (i = 0; i < 6; i++) { + if (a & 1) + c ^= b; + a >>= 1; + b <<= 1; + if ((b & 0x40) != 0) + b ^= 0x43; + } + + return c; +} + +/* F_64[X]/(X**2+X+A**-1) with A the generator of F_64[X] */ +static u16 gf4096_mul(u16 a, u16 b) +{ + u8 ah, al, bh, bl, ch, cl; + + ah = a >> 6; + al = a & 0x3f; + bh = b >> 6; + bl = b & 0x3f; + + ch = gf64_mul(ah ^ al, bh ^ bl) ^ gf64_mul(al, bl); + cl = gf64_mul(gf64_mul(ah, bh), 0x21) ^ gf64_mul(al, bl); + + return (ch << 6) ^ cl; +} + +static int cafe_mul(int x) +{ + if (x == 0) + return 1; + return gf4096_mul(x, 0xe01); +} + +static int cafe_nand_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct mtd_info *mtd; + struct cafe_priv *cafe; + uint32_t ctrl; + int err = 0; + int old_dma; + struct nand_buffers *nbuf; + + /* Very old versions shared the same PCI ident for all three + functions on the chip. Verify the class too... */ + if ((pdev->class >> 8) != PCI_CLASS_MEMORY_FLASH) + return -ENODEV; + + err = pci_enable_device(pdev); + if (err) + return err; + + pci_set_master(pdev); + + mtd = kzalloc(sizeof(*mtd) + sizeof(struct cafe_priv), GFP_KERNEL); + if (!mtd) + return -ENOMEM; + cafe = (void *)(&mtd[1]); + + mtd->dev.parent = &pdev->dev; + mtd->priv = cafe; + mtd->owner = THIS_MODULE; + + cafe->pdev = pdev; + cafe->mmio = pci_iomap(pdev, 0, 0); + if (!cafe->mmio) { + dev_warn(&pdev->dev, "failed to iomap\n"); + err = -ENOMEM; + goto out_free_mtd; + } + + cafe->rs = init_rs_non_canonical(12, &cafe_mul, 0, 1, 8); + if (!cafe->rs) { + err = -ENOMEM; + goto out_ior; + } + + cafe->nand.cmdfunc = cafe_nand_cmdfunc; + cafe->nand.dev_ready = cafe_device_ready; + cafe->nand.read_byte = cafe_read_byte; + cafe->nand.read_buf = cafe_read_buf; + cafe->nand.write_buf = cafe_write_buf; + cafe->nand.select_chip = cafe_select_chip; + + cafe->nand.chip_delay = 0; + + /* Enable the following for a flash based bad block table */ + cafe->nand.bbt_options = NAND_BBT_USE_FLASH; + cafe->nand.options = NAND_OWN_BUFFERS; + + if (skipbbt) { + cafe->nand.options |= NAND_SKIP_BBTSCAN; + cafe->nand.block_bad = cafe_nand_block_bad; + } + + if (numtimings && numtimings != 3) { + dev_warn(&cafe->pdev->dev, "%d timing register values ignored; precisely three are required\n", numtimings); + } + + if (numtimings == 3) { + cafe_dev_dbg(&cafe->pdev->dev, "Using provided timings (%08x %08x %08x)\n", + timing[0], timing[1], timing[2]); + } else { + timing[0] = cafe_readl(cafe, NAND_TIMING1); + timing[1] = cafe_readl(cafe, NAND_TIMING2); + timing[2] = cafe_readl(cafe, NAND_TIMING3); + + if (timing[0] | timing[1] | timing[2]) { + cafe_dev_dbg(&cafe->pdev->dev, "Timing registers already set (%08x %08x %08x)\n", + timing[0], timing[1], timing[2]); + } else { + dev_warn(&cafe->pdev->dev, "Timing registers unset; using most conservative defaults\n"); + timing[0] = timing[1] = timing[2] = 0xffffffff; + } + } + + /* Start off by resetting the NAND controller completely */ + cafe_writel(cafe, 1, NAND_RESET); + cafe_writel(cafe, 0, NAND_RESET); + + cafe_writel(cafe, timing[0], NAND_TIMING1); + cafe_writel(cafe, timing[1], NAND_TIMING2); + cafe_writel(cafe, timing[2], NAND_TIMING3); + + cafe_writel(cafe, 0xffffffff, NAND_IRQ_MASK); + err = request_irq(pdev->irq, &cafe_nand_interrupt, IRQF_SHARED, + "CAFE NAND", mtd); + if (err) { + dev_warn(&pdev->dev, "Could not register IRQ %d\n", pdev->irq); + goto out_ior; + } + + /* Disable master reset, enable NAND clock */ + ctrl = cafe_readl(cafe, GLOBAL_CTRL); + ctrl &= 0xffffeff0; + ctrl |= 0x00007000; + cafe_writel(cafe, ctrl | 0x05, GLOBAL_CTRL); + cafe_writel(cafe, ctrl | 0x0a, GLOBAL_CTRL); + cafe_writel(cafe, 0, NAND_DMA_CTRL); + + cafe_writel(cafe, 0x7006, GLOBAL_CTRL); + cafe_writel(cafe, 0x700a, GLOBAL_CTRL); + + /* Enable NAND IRQ in global IRQ mask register */ + cafe_writel(cafe, 0x80000007, GLOBAL_IRQ_MASK); + cafe_dev_dbg(&cafe->pdev->dev, "Control %x, IRQ mask %x\n", + cafe_readl(cafe, GLOBAL_CTRL), + cafe_readl(cafe, GLOBAL_IRQ_MASK)); + + /* Do not use the DMA for the nand_scan_ident() */ + old_dma = usedma; + usedma = 0; + + /* Scan to find existence of the device */ + if (nand_scan_ident(mtd, 2, NULL)) { + err = -ENXIO; + goto out_irq; + } + + cafe->dmabuf = dma_alloc_coherent(&cafe->pdev->dev, + 2112 + sizeof(struct nand_buffers) + + mtd->writesize + mtd->oobsize, + &cafe->dmaaddr, GFP_KERNEL); + if (!cafe->dmabuf) { + err = -ENOMEM; + goto out_irq; + } + cafe->nand.buffers = nbuf = (void *)cafe->dmabuf + 2112; + + /* Set up DMA address */ + cafe_writel(cafe, cafe->dmaaddr & 0xffffffff, NAND_DMA_ADDR0); + if (sizeof(cafe->dmaaddr) > 4) + /* Shift in two parts to shut the compiler up */ + cafe_writel(cafe, (cafe->dmaaddr >> 16) >> 16, NAND_DMA_ADDR1); + else + cafe_writel(cafe, 0, NAND_DMA_ADDR1); + + cafe_dev_dbg(&cafe->pdev->dev, "Set DMA address to %x (virt %p)\n", + cafe_readl(cafe, NAND_DMA_ADDR0), cafe->dmabuf); + + /* this driver does not need the @ecccalc and @ecccode */ + nbuf->ecccalc = NULL; + nbuf->ecccode = NULL; + nbuf->databuf = (uint8_t *)(nbuf + 1); + + /* Restore the DMA flag */ + usedma = old_dma; + + cafe->ctl2 = 1<<27; /* Reed-Solomon ECC */ + if (mtd->writesize == 2048) + cafe->ctl2 |= 1<<29; /* 2KiB page size */ + + /* Set up ECC according to the type of chip we found */ + if (mtd->writesize == 2048) { + cafe->nand.ecc.layout = &cafe_oobinfo_2048; + cafe->nand.bbt_td = &cafe_bbt_main_descr_2048; + cafe->nand.bbt_md = &cafe_bbt_mirror_descr_2048; + } else if (mtd->writesize == 512) { + cafe->nand.ecc.layout = &cafe_oobinfo_512; + cafe->nand.bbt_td = &cafe_bbt_main_descr_512; + cafe->nand.bbt_md = &cafe_bbt_mirror_descr_512; + } else { + printk(KERN_WARNING "Unexpected NAND flash writesize %d. Aborting\n", + mtd->writesize); + goto out_free_dma; + } + cafe->nand.ecc.mode = NAND_ECC_HW_SYNDROME; + cafe->nand.ecc.size = mtd->writesize; + cafe->nand.ecc.bytes = 14; + cafe->nand.ecc.strength = 4; + cafe->nand.ecc.hwctl = (void *)cafe_nand_bug; + cafe->nand.ecc.calculate = (void *)cafe_nand_bug; + cafe->nand.ecc.correct = (void *)cafe_nand_bug; + cafe->nand.write_page = cafe_nand_write_page; + cafe->nand.ecc.write_page = cafe_nand_write_page_lowlevel; + cafe->nand.ecc.write_oob = cafe_nand_write_oob; + cafe->nand.ecc.read_page = cafe_nand_read_page; + cafe->nand.ecc.read_oob = cafe_nand_read_oob; + + err = nand_scan_tail(mtd); + if (err) + goto out_free_dma; + + pci_set_drvdata(pdev, mtd); + + mtd->name = "cafe_nand"; + mtd_device_parse_register(mtd, part_probes, NULL, NULL, 0); + + goto out; + + out_free_dma: + dma_free_coherent(&cafe->pdev->dev, + 2112 + sizeof(struct nand_buffers) + + mtd->writesize + mtd->oobsize, + cafe->dmabuf, cafe->dmaaddr); + out_irq: + /* Disable NAND IRQ in global IRQ mask register */ + cafe_writel(cafe, ~1 & cafe_readl(cafe, GLOBAL_IRQ_MASK), GLOBAL_IRQ_MASK); + free_irq(pdev->irq, mtd); + out_ior: + pci_iounmap(pdev, cafe->mmio); + out_free_mtd: + kfree(mtd); + out: + return err; +} + +static void cafe_nand_remove(struct pci_dev *pdev) +{ + struct mtd_info *mtd = pci_get_drvdata(pdev); + struct cafe_priv *cafe = mtd->priv; + + /* Disable NAND IRQ in global IRQ mask register */ + cafe_writel(cafe, ~1 & cafe_readl(cafe, GLOBAL_IRQ_MASK), GLOBAL_IRQ_MASK); + free_irq(pdev->irq, mtd); + nand_release(mtd); + free_rs(cafe->rs); + pci_iounmap(pdev, cafe->mmio); + dma_free_coherent(&cafe->pdev->dev, + 2112 + sizeof(struct nand_buffers) + + mtd->writesize + mtd->oobsize, + cafe->dmabuf, cafe->dmaaddr); + kfree(mtd); +} + +static const struct pci_device_id cafe_nand_tbl[] = { + { PCI_VENDOR_ID_MARVELL, PCI_DEVICE_ID_MARVELL_88ALP01_NAND, + PCI_ANY_ID, PCI_ANY_ID }, + { } +}; + +MODULE_DEVICE_TABLE(pci, cafe_nand_tbl); + +static int cafe_nand_resume(struct pci_dev *pdev) +{ + uint32_t ctrl; + struct mtd_info *mtd = pci_get_drvdata(pdev); + struct cafe_priv *cafe = mtd->priv; + + /* Start off by resetting the NAND controller completely */ + cafe_writel(cafe, 1, NAND_RESET); + cafe_writel(cafe, 0, NAND_RESET); + cafe_writel(cafe, 0xffffffff, NAND_IRQ_MASK); + + /* Restore timing configuration */ + cafe_writel(cafe, timing[0], NAND_TIMING1); + cafe_writel(cafe, timing[1], NAND_TIMING2); + cafe_writel(cafe, timing[2], NAND_TIMING3); + + /* Disable master reset, enable NAND clock */ + ctrl = cafe_readl(cafe, GLOBAL_CTRL); + ctrl &= 0xffffeff0; + ctrl |= 0x00007000; + cafe_writel(cafe, ctrl | 0x05, GLOBAL_CTRL); + cafe_writel(cafe, ctrl | 0x0a, GLOBAL_CTRL); + cafe_writel(cafe, 0, NAND_DMA_CTRL); + cafe_writel(cafe, 0x7006, GLOBAL_CTRL); + cafe_writel(cafe, 0x700a, GLOBAL_CTRL); + + /* Set up DMA address */ + cafe_writel(cafe, cafe->dmaaddr & 0xffffffff, NAND_DMA_ADDR0); + if (sizeof(cafe->dmaaddr) > 4) + /* Shift in two parts to shut the compiler up */ + cafe_writel(cafe, (cafe->dmaaddr >> 16) >> 16, NAND_DMA_ADDR1); + else + cafe_writel(cafe, 0, NAND_DMA_ADDR1); + + /* Enable NAND IRQ in global IRQ mask register */ + cafe_writel(cafe, 0x80000007, GLOBAL_IRQ_MASK); + return 0; +} + +static struct pci_driver cafe_nand_pci_driver = { + .name = "CAFÉ NAND", + .id_table = cafe_nand_tbl, + .probe = cafe_nand_probe, + .remove = cafe_nand_remove, + .resume = cafe_nand_resume, +}; + +module_pci_driver(cafe_nand_pci_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); +MODULE_DESCRIPTION("NAND flash driver for OLPC CAFÉ chip"); diff --git a/drivers/mtd/nand/cmx270_nand.c b/drivers/mtd/nand/cmx270_nand.c new file mode 100644 index 00000000000..66ec95e6ca6 --- /dev/null +++ b/drivers/mtd/nand/cmx270_nand.c @@ -0,0 +1,250 @@ +/* + * linux/drivers/mtd/nand/cmx270-nand.c + * + * Copyright (C) 2006 Compulab, Ltd. + * Mike Rapoport <mike@compulab.co.il> + * + * Derived from drivers/mtd/nand/h1910.c + * Copyright (C) 2002 Marius Gröger (mag@sysgo.de) + * Copyright (c) 2001 Thomas Gleixner (gleixner@autronix.de) + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Overview: + * This is a device driver for the NAND flash device found on the + * CM-X270 board. + */ + +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/module.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/mach-types.h> + +#include <mach/pxa2xx-regs.h> + +#define GPIO_NAND_CS (11) +#define GPIO_NAND_RB (89) + +/* MTD structure for CM-X270 board */ +static struct mtd_info *cmx270_nand_mtd; + +/* remaped IO address of the device */ +static void __iomem *cmx270_nand_io; + +/* + * Define static partitions for flash device + */ +static struct mtd_partition partition_info[] = { + [0] = { + .name = "cmx270-0", + .offset = 0, + .size = MTDPART_SIZ_FULL + } +}; +#define NUM_PARTITIONS (ARRAY_SIZE(partition_info)) + +static u_char cmx270_read_byte(struct mtd_info *mtd) +{ + struct nand_chip *this = mtd->priv; + + return (readl(this->IO_ADDR_R) >> 16); +} + +static void cmx270_write_buf(struct mtd_info *mtd, const u_char *buf, int len) +{ + int i; + struct nand_chip *this = mtd->priv; + + for (i=0; i<len; i++) + writel((*buf++ << 16), this->IO_ADDR_W); +} + +static void cmx270_read_buf(struct mtd_info *mtd, u_char *buf, int len) +{ + int i; + struct nand_chip *this = mtd->priv; + + for (i=0; i<len; i++) + *buf++ = readl(this->IO_ADDR_R) >> 16; +} + +static inline void nand_cs_on(void) +{ + gpio_set_value(GPIO_NAND_CS, 0); +} + +static void nand_cs_off(void) +{ + dsb(); + + gpio_set_value(GPIO_NAND_CS, 1); +} + +/* + * hardware specific access to control-lines + */ +static void cmx270_hwcontrol(struct mtd_info *mtd, int dat, + unsigned int ctrl) +{ + struct nand_chip* this = mtd->priv; + unsigned int nandaddr = (unsigned int)this->IO_ADDR_W; + + dsb(); + + if (ctrl & NAND_CTRL_CHANGE) { + if ( ctrl & NAND_ALE ) + nandaddr |= (1 << 3); + else + nandaddr &= ~(1 << 3); + if ( ctrl & NAND_CLE ) + nandaddr |= (1 << 2); + else + nandaddr &= ~(1 << 2); + if ( ctrl & NAND_NCE ) + nand_cs_on(); + else + nand_cs_off(); + } + + dsb(); + this->IO_ADDR_W = (void __iomem*)nandaddr; + if (dat != NAND_CMD_NONE) + writel((dat << 16), this->IO_ADDR_W); + + dsb(); +} + +/* + * read device ready pin + */ +static int cmx270_device_ready(struct mtd_info *mtd) +{ + dsb(); + + return (gpio_get_value(GPIO_NAND_RB)); +} + +/* + * Main initialization routine + */ +static int __init cmx270_init(void) +{ + struct nand_chip *this; + int ret; + + if (!(machine_is_armcore() && cpu_is_pxa27x())) + return -ENODEV; + + ret = gpio_request(GPIO_NAND_CS, "NAND CS"); + if (ret) { + pr_warning("CM-X270: failed to request NAND CS gpio\n"); + return ret; + } + + gpio_direction_output(GPIO_NAND_CS, 1); + + ret = gpio_request(GPIO_NAND_RB, "NAND R/B"); + if (ret) { + pr_warning("CM-X270: failed to request NAND R/B gpio\n"); + goto err_gpio_request; + } + + gpio_direction_input(GPIO_NAND_RB); + + /* Allocate memory for MTD device structure and private data */ + cmx270_nand_mtd = kzalloc(sizeof(struct mtd_info) + + sizeof(struct nand_chip), + GFP_KERNEL); + if (!cmx270_nand_mtd) { + ret = -ENOMEM; + goto err_kzalloc; + } + + cmx270_nand_io = ioremap(PXA_CS1_PHYS, 12); + if (!cmx270_nand_io) { + pr_debug("Unable to ioremap NAND device\n"); + ret = -EINVAL; + goto err_ioremap; + } + + /* Get pointer to private data */ + this = (struct nand_chip *)(&cmx270_nand_mtd[1]); + + /* Link the private data with the MTD structure */ + cmx270_nand_mtd->owner = THIS_MODULE; + cmx270_nand_mtd->priv = this; + + /* insert callbacks */ + this->IO_ADDR_R = cmx270_nand_io; + this->IO_ADDR_W = cmx270_nand_io; + this->cmd_ctrl = cmx270_hwcontrol; + this->dev_ready = cmx270_device_ready; + + /* 15 us command delay time */ + this->chip_delay = 20; + this->ecc.mode = NAND_ECC_SOFT; + + /* read/write functions */ + this->read_byte = cmx270_read_byte; + this->read_buf = cmx270_read_buf; + this->write_buf = cmx270_write_buf; + + /* Scan to find existence of the device */ + if (nand_scan (cmx270_nand_mtd, 1)) { + pr_notice("No NAND device\n"); + ret = -ENXIO; + goto err_scan; + } + + /* Register the partitions */ + ret = mtd_device_parse_register(cmx270_nand_mtd, NULL, NULL, + partition_info, NUM_PARTITIONS); + if (ret) + goto err_scan; + + /* Return happy */ + return 0; + +err_scan: + iounmap(cmx270_nand_io); +err_ioremap: + kfree(cmx270_nand_mtd); +err_kzalloc: + gpio_free(GPIO_NAND_RB); +err_gpio_request: + gpio_free(GPIO_NAND_CS); + + return ret; + +} +module_init(cmx270_init); + +/* + * Clean up routine + */ +static void __exit cmx270_cleanup(void) +{ + /* Release resources, unregister device */ + nand_release(cmx270_nand_mtd); + + gpio_free(GPIO_NAND_RB); + gpio_free(GPIO_NAND_CS); + + iounmap(cmx270_nand_io); + + /* Free the MTD device structure */ + kfree (cmx270_nand_mtd); +} +module_exit(cmx270_cleanup); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>"); +MODULE_DESCRIPTION("NAND flash driver for Compulab CM-X270 Module"); diff --git a/drivers/mtd/nand/cs553x_nand.c b/drivers/mtd/nand/cs553x_nand.c index e0a1d386e58..88109d375ae 100644 --- a/drivers/mtd/nand/cs553x_nand.c +++ b/drivers/mtd/nand/cs553x_nand.c @@ -11,16 +11,18 @@ * published by the Free Software Foundation. * * Overview: - * This is a device driver for the NAND flash controller found on + * This is a device driver for the NAND flash controller found on * the AMD CS5535/CS5536 companion chipsets for the Geode processor. + * mtd-id for command line partitioning is cs553x_nand_cs[0-3] + * where 0-3 reflects the chip select for NAND. * */ +#include <linux/kernel.h> #include <linux/slab.h> #include <linux/init.h> #include <linux/module.h> #include <linux/delay.h> -#include <linux/pci.h> #include <linux/mtd/mtd.h> #include <linux/mtd/nand.h> #include <linux/mtd/nand_ecc.h> @@ -195,9 +197,8 @@ static int __init cs553x_init_one(int cs, int mmio, unsigned long adr) } /* Allocate memory for MTD device structure and private data */ - new_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL); + new_mtd = kzalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL); if (!new_mtd) { - printk(KERN_WARNING "Unable to allocate CS553X NAND MTD device structure.\n"); err = -ENOMEM; goto out; } @@ -205,10 +206,6 @@ static int __init cs553x_init_one(int cs, int mmio, unsigned long adr) /* Get pointer to private data */ this = (struct nand_chip *)(&new_mtd[1]); - /* Initialize structures */ - memset(new_mtd, 0, sizeof(struct mtd_info)); - memset(this, 0, sizeof(struct nand_chip)); - /* Link the private data with the MTD structure */ new_mtd->priv = this; new_mtd->owner = THIS_MODULE; @@ -235,21 +232,24 @@ static int __init cs553x_init_one(int cs, int mmio, unsigned long adr) this->ecc.hwctl = cs_enable_hwecc; this->ecc.calculate = cs_calculate_ecc; this->ecc.correct = nand_correct_data; + this->ecc.strength = 1; /* Enable the following for a flash based bad block table */ - this->options = NAND_USE_FLASH_BBT | NAND_NO_AUTOINCR; + this->bbt_options = NAND_BBT_USE_FLASH; - /* Scan to find existance of the device */ + /* Scan to find existence of the device */ if (nand_scan(new_mtd, 1)) { err = -ENXIO; goto out_ior; } + new_mtd->name = kasprintf(GFP_KERNEL, "cs553x_nand_cs%d", cs); + cs553x_mtd[cs] = new_mtd; goto out; out_ior: - iounmap((void *)this->IO_ADDR_R); + iounmap(this->IO_ADDR_R); out_mtd: kfree(new_mtd); out: @@ -291,7 +291,7 @@ static int __init cs553x_init(void) /* If it doesn't have the NAND controller enabled, abort */ rdmsrl(MSR_DIVIL_BALL_OPTS, val); - if (val & 1) { + if (val & PIN_OPT_IDE) { printk(KERN_INFO "CS553x NAND controller: Flash I/O not enabled in MSR_DIVIL_BALL_OPTS.\n"); return -ENXIO; } @@ -303,13 +303,13 @@ static int __init cs553x_init(void) err = cs553x_init_one(i, !!(val & FLSH_MEM_IO), val & 0xFFFFFFFF); } - /* Register all devices together here. This means we can easily hack it to + /* Register all devices together here. This means we can easily hack it to do mtdconcat etc. if we want to. */ for (i = 0; i < NR_CS553X_CONTROLLERS; i++) { if (cs553x_mtd[i]) { - add_mtd_device(cs553x_mtd[i]); - /* If any devices registered, return success. Else the last error. */ + mtd_device_parse_register(cs553x_mtd[i], NULL, NULL, + NULL, 0); err = 0; } } @@ -329,16 +329,17 @@ static void __exit cs553x_cleanup(void) void __iomem *mmio_base; if (!mtd) - break; + continue; this = cs553x_mtd[i]->priv; mmio_base = this->IO_ADDR_R; /* Release resources, unregister device */ nand_release(cs553x_mtd[i]); + kfree(cs553x_mtd[i]->name); cs553x_mtd[i] = NULL; - /* unmap physical adress */ + /* unmap physical address */ iounmap(mmio_base); /* Free the MTD device structure */ diff --git a/drivers/mtd/nand/davinci_nand.c b/drivers/mtd/nand/davinci_nand.c new file mode 100644 index 00000000000..b922c8efcf4 --- /dev/null +++ b/drivers/mtd/nand/davinci_nand.c @@ -0,0 +1,884 @@ +/* + * 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/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 <linux/slab.h> +#include <linux/of_device.h> +#include <linux/of.h> +#include <linux/of_mtd.h> + +#include <linux/platform_data/mtd-davinci.h> +#include <linux/platform_data/mtd-davinci-aemif.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, as well as 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 nand_ecclayout ecclayout; + + struct device *dev; + struct clk *clk; + + bool is_readmode; + + 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; + + struct davinci_aemif_timing *timing; +}; + +static DEFINE_SPINLOCK(davinci_nand_lock); +static bool ecc4_busy; + +#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; +} + +/*----------------------------------------------------------------------*/ + +/* + * 4-bit hardware ECC ... context maintained over entire AEMIF + * + * This is a syndrome engine, but we avoid NAND_ECC_HW_SYNDROME + * since that forces use of a problematic "infix OOB" layout. + * Among other things, it trashes manufacturer bad block markers. + * Also, and specific to this hardware, it ECC-protects the "prepad" + * in the OOB ... while having ECC protection for parts of OOB would + * seem useful, the current MTD stack sometimes wants to update the + * OOB without recomputing ECC. + */ + +static void nand_davinci_hwctl_4bit(struct mtd_info *mtd, int mode) +{ + struct davinci_nand_info *info = to_davinci_nand(mtd); + unsigned long flags; + u32 val; + + spin_lock_irqsave(&davinci_nand_lock, flags); + + /* Start 4-bit ECC calculation for read/write */ + val = davinci_nand_readl(info, NANDFCR_OFFSET); + val &= ~(0x03 << 4); + val |= (info->core_chipsel << 4) | BIT(12); + davinci_nand_writel(info, NANDFCR_OFFSET, val); + + info->is_readmode = (mode == NAND_ECC_READ); + + spin_unlock_irqrestore(&davinci_nand_lock, flags); +} + +/* Read raw ECC code after writing to NAND. */ +static void +nand_davinci_readecc_4bit(struct davinci_nand_info *info, u32 code[4]) +{ + const u32 mask = 0x03ff03ff; + + code[0] = davinci_nand_readl(info, NAND_4BIT_ECC1_OFFSET) & mask; + code[1] = davinci_nand_readl(info, NAND_4BIT_ECC2_OFFSET) & mask; + code[2] = davinci_nand_readl(info, NAND_4BIT_ECC3_OFFSET) & mask; + code[3] = davinci_nand_readl(info, NAND_4BIT_ECC4_OFFSET) & mask; +} + +/* Terminate read ECC; or return ECC (as bytes) of data written to NAND. */ +static int nand_davinci_calculate_4bit(struct mtd_info *mtd, + const u_char *dat, u_char *ecc_code) +{ + struct davinci_nand_info *info = to_davinci_nand(mtd); + u32 raw_ecc[4], *p; + unsigned i; + + /* After a read, terminate ECC calculation by a dummy read + * of some 4-bit ECC register. ECC covers everything that + * was read; correct() just uses the hardware state, so + * ecc_code is not needed. + */ + if (info->is_readmode) { + davinci_nand_readl(info, NAND_4BIT_ECC1_OFFSET); + return 0; + } + + /* Pack eight raw 10-bit ecc values into ten bytes, making + * two passes which each convert four values (in upper and + * lower halves of two 32-bit words) into five bytes. The + * ROM boot loader uses this same packing scheme. + */ + nand_davinci_readecc_4bit(info, raw_ecc); + for (i = 0, p = raw_ecc; i < 2; i++, p += 2) { + *ecc_code++ = p[0] & 0xff; + *ecc_code++ = ((p[0] >> 8) & 0x03) | ((p[0] >> 14) & 0xfc); + *ecc_code++ = ((p[0] >> 22) & 0x0f) | ((p[1] << 4) & 0xf0); + *ecc_code++ = ((p[1] >> 4) & 0x3f) | ((p[1] >> 10) & 0xc0); + *ecc_code++ = (p[1] >> 18) & 0xff; + } + + return 0; +} + +/* Correct up to 4 bits in data we just read, using state left in the + * hardware plus the ecc_code computed when it was first written. + */ +static int nand_davinci_correct_4bit(struct mtd_info *mtd, + u_char *data, u_char *ecc_code, u_char *null) +{ + int i; + struct davinci_nand_info *info = to_davinci_nand(mtd); + unsigned short ecc10[8]; + unsigned short *ecc16; + u32 syndrome[4]; + u32 ecc_state; + unsigned num_errors, corrected; + unsigned long timeo; + + /* All bytes 0xff? It's an erased page; ignore its ECC. */ + for (i = 0; i < 10; i++) { + if (ecc_code[i] != 0xff) + goto compare; + } + return 0; + +compare: + /* Unpack ten bytes into eight 10 bit values. We know we're + * little-endian, and use type punning for less shifting/masking. + */ + if (WARN_ON(0x01 & (unsigned) ecc_code)) + return -EINVAL; + ecc16 = (unsigned short *)ecc_code; + + ecc10[0] = (ecc16[0] >> 0) & 0x3ff; + ecc10[1] = ((ecc16[0] >> 10) & 0x3f) | ((ecc16[1] << 6) & 0x3c0); + ecc10[2] = (ecc16[1] >> 4) & 0x3ff; + ecc10[3] = ((ecc16[1] >> 14) & 0x3) | ((ecc16[2] << 2) & 0x3fc); + ecc10[4] = (ecc16[2] >> 8) | ((ecc16[3] << 8) & 0x300); + ecc10[5] = (ecc16[3] >> 2) & 0x3ff; + ecc10[6] = ((ecc16[3] >> 12) & 0xf) | ((ecc16[4] << 4) & 0x3f0); + ecc10[7] = (ecc16[4] >> 6) & 0x3ff; + + /* Tell ECC controller about the expected ECC codes. */ + for (i = 7; i >= 0; i--) + davinci_nand_writel(info, NAND_4BIT_ECC_LOAD_OFFSET, ecc10[i]); + + /* Allow time for syndrome calculation ... then read it. + * A syndrome of all zeroes 0 means no detected errors. + */ + davinci_nand_readl(info, NANDFSR_OFFSET); + nand_davinci_readecc_4bit(info, syndrome); + if (!(syndrome[0] | syndrome[1] | syndrome[2] | syndrome[3])) + return 0; + + /* + * Clear any previous address calculation by doing a dummy read of an + * error address register. + */ + davinci_nand_readl(info, NAND_ERR_ADD1_OFFSET); + + /* Start address calculation, and wait for it to complete. + * We _could_ start reading more data while this is working, + * to speed up the overall page read. + */ + davinci_nand_writel(info, NANDFCR_OFFSET, + davinci_nand_readl(info, NANDFCR_OFFSET) | BIT(13)); + + /* + * ECC_STATE field reads 0x3 (Error correction complete) immediately + * after setting the 4BITECC_ADD_CALC_START bit. So if you immediately + * begin trying to poll for the state, you may fall right out of your + * loop without any of the correction calculations having taken place. + * The recommendation from the hardware team is to initially delay as + * long as ECC_STATE reads less than 4. After that, ECC HW has entered + * correction state. + */ + timeo = jiffies + usecs_to_jiffies(100); + do { + ecc_state = (davinci_nand_readl(info, + NANDFSR_OFFSET) >> 8) & 0x0f; + cpu_relax(); + } while ((ecc_state < 4) && time_before(jiffies, timeo)); + + for (;;) { + u32 fsr = davinci_nand_readl(info, NANDFSR_OFFSET); + + switch ((fsr >> 8) & 0x0f) { + case 0: /* no error, should not happen */ + davinci_nand_readl(info, NAND_ERR_ERRVAL1_OFFSET); + return 0; + case 1: /* five or more errors detected */ + davinci_nand_readl(info, NAND_ERR_ERRVAL1_OFFSET); + return -EIO; + case 2: /* error addresses computed */ + case 3: + num_errors = 1 + ((fsr >> 16) & 0x03); + goto correct; + default: /* still working on it */ + cpu_relax(); + continue; + } + } + +correct: + /* correct each error */ + for (i = 0, corrected = 0; i < num_errors; i++) { + int error_address, error_value; + + if (i > 1) { + error_address = davinci_nand_readl(info, + NAND_ERR_ADD2_OFFSET); + error_value = davinci_nand_readl(info, + NAND_ERR_ERRVAL2_OFFSET); + } else { + error_address = davinci_nand_readl(info, + NAND_ERR_ADD1_OFFSET); + error_value = davinci_nand_readl(info, + NAND_ERR_ERRVAL1_OFFSET); + } + + if (i & 1) { + error_address >>= 16; + error_value >>= 16; + } + error_address &= 0x3ff; + error_address = (512 + 7) - error_address; + + if (error_address < 512) { + data[error_address] ^= error_value; + corrected++; + } + } + + return corrected; +} + +/*----------------------------------------------------------------------*/ + +/* + * 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); +} + +/*----------------------------------------------------------------------*/ + +/* An ECC layout for using 4-bit ECC with small-page flash, storing + * ten ECC bytes plus the manufacturer's bad block marker byte, and + * and not overlapping the default BBT markers. + */ +static struct nand_ecclayout hwecc4_small = { + .eccbytes = 10, + .eccpos = { 0, 1, 2, 3, 4, + /* offset 5 holds the badblock marker */ + 6, 7, + 13, 14, 15, }, + .oobfree = { + {.offset = 8, .length = 5, }, + {.offset = 16, }, + }, +}; + +/* An ECC layout for using 4-bit ECC with large-page (2048bytes) flash, + * storing ten ECC bytes plus the manufacturer's bad block marker byte, + * and not overlapping the default BBT markers. + */ +static struct nand_ecclayout hwecc4_2048 = { + .eccbytes = 40, + .eccpos = { + /* at the end of spare sector */ + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, + 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + }, + .oobfree = { + /* 2 bytes at offset 0 hold manufacturer badblock markers */ + {.offset = 2, .length = 22, }, + /* 5 bytes at offset 8 hold BBT markers */ + /* 8 bytes at offset 16 hold JFFS2 clean markers */ + }, +}; + +#if defined(CONFIG_OF) +static const struct of_device_id davinci_nand_of_match[] = { + {.compatible = "ti,davinci-nand", }, + {.compatible = "ti,keystone-nand", }, + {}, +}; +MODULE_DEVICE_TABLE(of, davinci_nand_of_match); + +static struct davinci_nand_pdata + *nand_davinci_get_pdata(struct platform_device *pdev) +{ + if (!dev_get_platdata(&pdev->dev) && pdev->dev.of_node) { + struct davinci_nand_pdata *pdata; + const char *mode; + u32 prop; + + pdata = devm_kzalloc(&pdev->dev, + sizeof(struct davinci_nand_pdata), + GFP_KERNEL); + pdev->dev.platform_data = pdata; + if (!pdata) + return ERR_PTR(-ENOMEM); + if (!of_property_read_u32(pdev->dev.of_node, + "ti,davinci-chipselect", &prop)) + pdev->id = prop; + else + return ERR_PTR(-EINVAL); + + if (!of_property_read_u32(pdev->dev.of_node, + "ti,davinci-mask-ale", &prop)) + pdata->mask_ale = prop; + if (!of_property_read_u32(pdev->dev.of_node, + "ti,davinci-mask-cle", &prop)) + pdata->mask_cle = prop; + if (!of_property_read_u32(pdev->dev.of_node, + "ti,davinci-mask-chipsel", &prop)) + pdata->mask_chipsel = prop; + if (!of_property_read_string(pdev->dev.of_node, + "nand-ecc-mode", &mode) || + !of_property_read_string(pdev->dev.of_node, + "ti,davinci-ecc-mode", &mode)) { + if (!strncmp("none", mode, 4)) + pdata->ecc_mode = NAND_ECC_NONE; + if (!strncmp("soft", mode, 4)) + pdata->ecc_mode = NAND_ECC_SOFT; + if (!strncmp("hw", mode, 2)) + pdata->ecc_mode = NAND_ECC_HW; + } + if (!of_property_read_u32(pdev->dev.of_node, + "ti,davinci-ecc-bits", &prop)) + pdata->ecc_bits = prop; + + prop = of_get_nand_bus_width(pdev->dev.of_node); + if (0 < prop || !of_property_read_u32(pdev->dev.of_node, + "ti,davinci-nand-buswidth", &prop)) + if (prop == 16) + pdata->options |= NAND_BUSWIDTH_16; + if (of_property_read_bool(pdev->dev.of_node, + "nand-on-flash-bbt") || + of_property_read_bool(pdev->dev.of_node, + "ti,davinci-nand-use-bbt")) + pdata->bbt_options = NAND_BBT_USE_FLASH; + + if (of_device_is_compatible(pdev->dev.of_node, + "ti,keystone-nand")) { + pdata->options |= NAND_NO_SUBPAGE_WRITE; + } + } + + return dev_get_platdata(&pdev->dev); +} +#else +static struct davinci_nand_pdata + *nand_davinci_get_pdata(struct platform_device *pdev) +{ + return dev_get_platdata(&pdev->dev); +} +#endif + +static int nand_davinci_probe(struct platform_device *pdev) +{ + struct davinci_nand_pdata *pdata; + 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; + + pdata = nand_davinci_get_pdata(pdev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + + /* insist on board-specific configuration */ + if (!pdata) + return -ENODEV; + + /* which external chipselect will we be managing? */ + if (pdev->id < 0 || pdev->id > 3) + return -ENODEV; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + 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"); + return -EINVAL; + } + + vaddr = devm_ioremap_resource(&pdev->dev, res1); + if (IS_ERR(vaddr)) + return PTR_ERR(vaddr); + + /* + * This registers range is used to setup NAND settings. In case with + * TI AEMIF driver, the same memory address range is requested already + * by AEMIF, so we cannot request it twice, just ioremap. + * The AEMIF and NAND drivers not use the same registers in this range. + */ + base = devm_ioremap(&pdev->dev, res2->start, resource_size(res2)); + if (!base) { + dev_err(&pdev->dev, "ioremap failed for resource %pR\n", res2); + return -EADDRNOTAVAIL; + } + + 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_BBT_USE_FLASH */ + info->chip.bbt_options = pdata->bbt_options; + /* options such as 16-bit widths */ + info->chip.options = pdata->options; + info->chip.bbt_td = pdata->bbt_td; + info->chip.bbt_md = pdata->bbt_md; + info->timing = pdata->timing; + + 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 */ + info->mask_ale = pdata->mask_ale ? : MASK_ALE; + info->mask_cle = pdata->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 */ + ecc_mode = pdata->ecc_mode; + + ret = -EINVAL; + switch (ecc_mode) { + case NAND_ECC_NONE: + case NAND_ECC_SOFT: + pdata->ecc_bits = 0; + break; + case NAND_ECC_HW: + if (pdata->ecc_bits == 4) { + /* No sanity checks: CPUs must support this, + * and the chips may not use NAND_BUSWIDTH_16. + */ + + /* No sharing 4-bit hardware between chipselects yet */ + spin_lock_irq(&davinci_nand_lock); + if (ecc4_busy) + ret = -EBUSY; + else + ecc4_busy = true; + spin_unlock_irq(&davinci_nand_lock); + + if (ret == -EBUSY) + return ret; + + info->chip.ecc.calculate = nand_davinci_calculate_4bit; + info->chip.ecc.correct = nand_davinci_correct_4bit; + info->chip.ecc.hwctl = nand_davinci_hwctl_4bit; + info->chip.ecc.bytes = 10; + } else { + 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.bytes = 3; + } + info->chip.ecc.size = 512; + info->chip.ecc.strength = pdata->ecc_bits; + break; + default: + return -EINVAL; + } + info->chip.ecc.mode = ecc_mode; + + info->clk = devm_clk_get(&pdev->dev, "aemif"); + if (IS_ERR(info->clk)) { + ret = PTR_ERR(info->clk); + dev_dbg(&pdev->dev, "unable to get AEMIF clock, err %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(info->clk); + if (ret < 0) { + dev_dbg(&pdev->dev, "unable to enable AEMIF clock, err %d\n", + ret); + goto err_clk_enable; + } + + 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_ident(&info->mtd, pdata->mask_chipsel ? 2 : 1, NULL); + if (ret < 0) { + dev_dbg(&pdev->dev, "no NAND chip(s) found\n"); + goto err; + } + + /* Update ECC layout if needed ... for 1-bit HW ECC, the default + * is OK, but it allocates 6 bytes when only 3 are needed (for + * each 512 bytes). For the 4-bit HW ECC, that default is not + * usable: 10 bytes are needed, not 6. + */ + if (pdata->ecc_bits == 4) { + int chunks = info->mtd.writesize / 512; + + if (!chunks || info->mtd.oobsize < 16) { + dev_dbg(&pdev->dev, "too small\n"); + ret = -EINVAL; + goto err; + } + + /* For small page chips, preserve the manufacturer's + * badblock marking data ... and make sure a flash BBT + * table marker fits in the free bytes. + */ + if (chunks == 1) { + info->ecclayout = hwecc4_small; + info->ecclayout.oobfree[1].length = + info->mtd.oobsize - 16; + goto syndrome_done; + } + if (chunks == 4) { + info->ecclayout = hwecc4_2048; + info->chip.ecc.mode = NAND_ECC_HW_OOB_FIRST; + goto syndrome_done; + } + + /* 4KiB page chips are not yet supported. The eccpos from + * nand_ecclayout cannot hold 80 bytes and change to eccpos[] + * breaks userspace ioctl interface with mtd-utils. Once we + * resolve this issue, NAND_ECC_HW_OOB_FIRST mode can be used + * for the 4KiB page chips. + * + * TODO: Note that nand_ecclayout has now been expanded and can + * hold plenty of OOB entries. + */ + dev_warn(&pdev->dev, "no 4-bit ECC support yet " + "for 4KiB-page NAND\n"); + ret = -EIO; + goto err; + +syndrome_done: + info->chip.ecc.layout = &info->ecclayout; + } + + ret = nand_scan_tail(&info->mtd); + if (ret < 0) + goto err; + + if (pdata->parts) + ret = mtd_device_parse_register(&info->mtd, NULL, NULL, + pdata->parts, pdata->nr_parts); + else { + struct mtd_part_parser_data ppdata; + + ppdata.of_node = pdev->dev.of_node; + ret = mtd_device_parse_register(&info->mtd, NULL, &ppdata, + NULL, 0); + } + if (ret < 0) + goto err; + + val = davinci_nand_readl(info, NRCSR_OFFSET); + dev_info(&pdev->dev, "controller rev. %d.%d\n", + (val >> 8) & 0xff, val & 0xff); + + return 0; + +err: + clk_disable_unprepare(info->clk); + +err_clk_enable: + spin_lock_irq(&davinci_nand_lock); + if (ecc_mode == NAND_ECC_HW_SYNDROME) + ecc4_busy = false; + spin_unlock_irq(&davinci_nand_lock); + return ret; +} + +static int nand_davinci_remove(struct platform_device *pdev) +{ + struct davinci_nand_info *info = platform_get_drvdata(pdev); + + spin_lock_irq(&davinci_nand_lock); + if (info->chip.ecc.mode == NAND_ECC_HW_SYNDROME) + ecc4_busy = false; + spin_unlock_irq(&davinci_nand_lock); + + nand_release(&info->mtd); + + clk_disable_unprepare(info->clk); + + return 0; +} + +static struct platform_driver nand_davinci_driver = { + .probe = nand_davinci_probe, + .remove = nand_davinci_remove, + .driver = { + .name = "davinci_nand", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(davinci_nand_of_match), + }, +}; +MODULE_ALIAS("platform:davinci_nand"); + +module_platform_driver(nand_davinci_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("Davinci NAND flash driver"); + diff --git a/drivers/mtd/nand/denali.c b/drivers/mtd/nand/denali.c new file mode 100644 index 00000000000..9f2012a3e76 --- /dev/null +++ b/drivers/mtd/nand/denali.c @@ -0,0 +1,1616 @@ +/* + * NAND Flash Controller Device Driver + * Copyright © 2009-2010, Intel Corporation and its suppliers. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/wait.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/mtd/mtd.h> +#include <linux/module.h> + +#include "denali.h" + +MODULE_LICENSE("GPL"); + +/* We define a module parameter that allows the user to override + * the hardware and decide what timing mode should be used. + */ +#define NAND_DEFAULT_TIMINGS -1 + +static int onfi_timing_mode = NAND_DEFAULT_TIMINGS; +module_param(onfi_timing_mode, int, S_IRUGO); +MODULE_PARM_DESC(onfi_timing_mode, "Overrides default ONFI setting." + " -1 indicates use default timings"); + +#define DENALI_NAND_NAME "denali-nand" + +/* We define a macro here that combines all interrupts this driver uses into + * a single constant value, for convenience. */ +#define DENALI_IRQ_ALL (INTR_STATUS__DMA_CMD_COMP | \ + INTR_STATUS__ECC_TRANSACTION_DONE | \ + INTR_STATUS__ECC_ERR | \ + INTR_STATUS__PROGRAM_FAIL | \ + INTR_STATUS__LOAD_COMP | \ + INTR_STATUS__PROGRAM_COMP | \ + INTR_STATUS__TIME_OUT | \ + INTR_STATUS__ERASE_FAIL | \ + INTR_STATUS__RST_COMP | \ + INTR_STATUS__ERASE_COMP) + +/* indicates whether or not the internal value for the flash bank is + * valid or not */ +#define CHIP_SELECT_INVALID -1 + +#define SUPPORT_8BITECC 1 + +/* This macro divides two integers and rounds fractional values up + * to the nearest integer value. */ +#define CEIL_DIV(X, Y) (((X)%(Y)) ? ((X)/(Y)+1) : ((X)/(Y))) + +/* this macro allows us to convert from an MTD structure to our own + * device context (denali) structure. + */ +#define mtd_to_denali(m) container_of(m, struct denali_nand_info, mtd) + +/* These constants are defined by the driver to enable common driver + * configuration options. */ +#define SPARE_ACCESS 0x41 +#define MAIN_ACCESS 0x42 +#define MAIN_SPARE_ACCESS 0x43 + +#define DENALI_READ 0 +#define DENALI_WRITE 0x100 + +/* types of device accesses. We can issue commands and get status */ +#define COMMAND_CYCLE 0 +#define ADDR_CYCLE 1 +#define STATUS_CYCLE 2 + +/* this is a helper macro that allows us to + * format the bank into the proper bits for the controller */ +#define BANK(x) ((x) << 24) + +/* forward declarations */ +static void clear_interrupts(struct denali_nand_info *denali); +static uint32_t wait_for_irq(struct denali_nand_info *denali, + uint32_t irq_mask); +static void denali_irq_enable(struct denali_nand_info *denali, + uint32_t int_mask); +static uint32_t read_interrupt_status(struct denali_nand_info *denali); + +/* Certain operations for the denali NAND controller use + * an indexed mode to read/write data. The operation is + * performed by writing the address value of the command + * to the device memory followed by the data. This function + * abstracts this common operation. +*/ +static void index_addr(struct denali_nand_info *denali, + uint32_t address, uint32_t data) +{ + iowrite32(address, denali->flash_mem); + iowrite32(data, denali->flash_mem + 0x10); +} + +/* Perform an indexed read of the device */ +static void index_addr_read_data(struct denali_nand_info *denali, + uint32_t address, uint32_t *pdata) +{ + iowrite32(address, denali->flash_mem); + *pdata = ioread32(denali->flash_mem + 0x10); +} + +/* We need to buffer some data for some of the NAND core routines. + * The operations manage buffering that data. */ +static void reset_buf(struct denali_nand_info *denali) +{ + denali->buf.head = denali->buf.tail = 0; +} + +static void write_byte_to_buf(struct denali_nand_info *denali, uint8_t byte) +{ + denali->buf.buf[denali->buf.tail++] = byte; +} + +/* reads the status of the device */ +static void read_status(struct denali_nand_info *denali) +{ + uint32_t cmd = 0x0; + + /* initialize the data buffer to store status */ + reset_buf(denali); + + cmd = ioread32(denali->flash_reg + WRITE_PROTECT); + if (cmd) + write_byte_to_buf(denali, NAND_STATUS_WP); + else + write_byte_to_buf(denali, 0); +} + +/* resets a specific device connected to the core */ +static void reset_bank(struct denali_nand_info *denali) +{ + uint32_t irq_status = 0; + uint32_t irq_mask = INTR_STATUS__RST_COMP | + INTR_STATUS__TIME_OUT; + + clear_interrupts(denali); + + iowrite32(1 << denali->flash_bank, denali->flash_reg + DEVICE_RESET); + + irq_status = wait_for_irq(denali, irq_mask); + + if (irq_status & INTR_STATUS__TIME_OUT) + dev_err(denali->dev, "reset bank failed.\n"); +} + +/* Reset the flash controller */ +static uint16_t denali_nand_reset(struct denali_nand_info *denali) +{ + uint32_t i; + + dev_dbg(denali->dev, "%s, Line %d, Function: %s\n", + __FILE__, __LINE__, __func__); + + for (i = 0 ; i < denali->max_banks; i++) + iowrite32(INTR_STATUS__RST_COMP | INTR_STATUS__TIME_OUT, + denali->flash_reg + INTR_STATUS(i)); + + for (i = 0 ; i < denali->max_banks; i++) { + iowrite32(1 << i, denali->flash_reg + DEVICE_RESET); + while (!(ioread32(denali->flash_reg + + INTR_STATUS(i)) & + (INTR_STATUS__RST_COMP | INTR_STATUS__TIME_OUT))) + cpu_relax(); + if (ioread32(denali->flash_reg + INTR_STATUS(i)) & + INTR_STATUS__TIME_OUT) + dev_dbg(denali->dev, + "NAND Reset operation timed out on bank %d\n", i); + } + + for (i = 0; i < denali->max_banks; i++) + iowrite32(INTR_STATUS__RST_COMP | INTR_STATUS__TIME_OUT, + denali->flash_reg + INTR_STATUS(i)); + + return PASS; +} + +/* this routine calculates the ONFI timing values for a given mode and + * programs the clocking register accordingly. The mode is determined by + * the get_onfi_nand_para routine. + */ +static void nand_onfi_timing_set(struct denali_nand_info *denali, + uint16_t mode) +{ + uint16_t Trea[6] = {40, 30, 25, 20, 20, 16}; + uint16_t Trp[6] = {50, 25, 17, 15, 12, 10}; + uint16_t Treh[6] = {30, 15, 15, 10, 10, 7}; + uint16_t Trc[6] = {100, 50, 35, 30, 25, 20}; + uint16_t Trhoh[6] = {0, 15, 15, 15, 15, 15}; + uint16_t Trloh[6] = {0, 0, 0, 0, 5, 5}; + uint16_t Tcea[6] = {100, 45, 30, 25, 25, 25}; + uint16_t Tadl[6] = {200, 100, 100, 100, 70, 70}; + uint16_t Trhw[6] = {200, 100, 100, 100, 100, 100}; + uint16_t Trhz[6] = {200, 100, 100, 100, 100, 100}; + uint16_t Twhr[6] = {120, 80, 80, 60, 60, 60}; + uint16_t Tcs[6] = {70, 35, 25, 25, 20, 15}; + + uint16_t TclsRising = 1; + uint16_t data_invalid_rhoh, data_invalid_rloh, data_invalid; + uint16_t dv_window = 0; + uint16_t en_lo, en_hi; + uint16_t acc_clks; + uint16_t addr_2_data, re_2_we, re_2_re, we_2_re, cs_cnt; + + dev_dbg(denali->dev, "%s, Line %d, Function: %s\n", + __FILE__, __LINE__, __func__); + + en_lo = CEIL_DIV(Trp[mode], CLK_X); + en_hi = CEIL_DIV(Treh[mode], CLK_X); +#if ONFI_BLOOM_TIME + if ((en_hi * CLK_X) < (Treh[mode] + 2)) + en_hi++; +#endif + + if ((en_lo + en_hi) * CLK_X < Trc[mode]) + en_lo += CEIL_DIV((Trc[mode] - (en_lo + en_hi) * CLK_X), CLK_X); + + if ((en_lo + en_hi) < CLK_MULTI) + en_lo += CLK_MULTI - en_lo - en_hi; + + while (dv_window < 8) { + data_invalid_rhoh = en_lo * CLK_X + Trhoh[mode]; + + data_invalid_rloh = (en_lo + en_hi) * CLK_X + Trloh[mode]; + + data_invalid = + data_invalid_rhoh < + data_invalid_rloh ? data_invalid_rhoh : data_invalid_rloh; + + dv_window = data_invalid - Trea[mode]; + + if (dv_window < 8) + en_lo++; + } + + acc_clks = CEIL_DIV(Trea[mode], CLK_X); + + while (((acc_clks * CLK_X) - Trea[mode]) < 3) + acc_clks++; + + if ((data_invalid - acc_clks * CLK_X) < 2) + dev_warn(denali->dev, "%s, Line %d: Warning!\n", + __FILE__, __LINE__); + + addr_2_data = CEIL_DIV(Tadl[mode], CLK_X); + re_2_we = CEIL_DIV(Trhw[mode], CLK_X); + re_2_re = CEIL_DIV(Trhz[mode], CLK_X); + we_2_re = CEIL_DIV(Twhr[mode], CLK_X); + cs_cnt = CEIL_DIV((Tcs[mode] - Trp[mode]), CLK_X); + if (!TclsRising) + cs_cnt = CEIL_DIV(Tcs[mode], CLK_X); + if (cs_cnt == 0) + cs_cnt = 1; + + if (Tcea[mode]) { + while (((cs_cnt * CLK_X) + Trea[mode]) < Tcea[mode]) + cs_cnt++; + } + +#if MODE5_WORKAROUND + if (mode == 5) + acc_clks = 5; +#endif + + /* Sighting 3462430: Temporary hack for MT29F128G08CJABAWP:B */ + if ((ioread32(denali->flash_reg + MANUFACTURER_ID) == 0) && + (ioread32(denali->flash_reg + DEVICE_ID) == 0x88)) + acc_clks = 6; + + iowrite32(acc_clks, denali->flash_reg + ACC_CLKS); + iowrite32(re_2_we, denali->flash_reg + RE_2_WE); + iowrite32(re_2_re, denali->flash_reg + RE_2_RE); + iowrite32(we_2_re, denali->flash_reg + WE_2_RE); + iowrite32(addr_2_data, denali->flash_reg + ADDR_2_DATA); + iowrite32(en_lo, denali->flash_reg + RDWR_EN_LO_CNT); + iowrite32(en_hi, denali->flash_reg + RDWR_EN_HI_CNT); + iowrite32(cs_cnt, denali->flash_reg + CS_SETUP_CNT); +} + +/* queries the NAND device to see what ONFI modes it supports. */ +static uint16_t get_onfi_nand_para(struct denali_nand_info *denali) +{ + int i; + /* we needn't to do a reset here because driver has already + * reset all the banks before + * */ + if (!(ioread32(denali->flash_reg + ONFI_TIMING_MODE) & + ONFI_TIMING_MODE__VALUE)) + return FAIL; + + for (i = 5; i > 0; i--) { + if (ioread32(denali->flash_reg + ONFI_TIMING_MODE) & + (0x01 << i)) + break; + } + + nand_onfi_timing_set(denali, i); + + /* By now, all the ONFI devices we know support the page cache */ + /* rw feature. So here we enable the pipeline_rw_ahead feature */ + /* iowrite32(1, denali->flash_reg + CACHE_WRITE_ENABLE); */ + /* iowrite32(1, denali->flash_reg + CACHE_READ_ENABLE); */ + + return PASS; +} + +static void get_samsung_nand_para(struct denali_nand_info *denali, + uint8_t device_id) +{ + if (device_id == 0xd3) { /* Samsung K9WAG08U1A */ + /* Set timing register values according to datasheet */ + iowrite32(5, denali->flash_reg + ACC_CLKS); + iowrite32(20, denali->flash_reg + RE_2_WE); + iowrite32(12, denali->flash_reg + WE_2_RE); + iowrite32(14, denali->flash_reg + ADDR_2_DATA); + iowrite32(3, denali->flash_reg + RDWR_EN_LO_CNT); + iowrite32(2, denali->flash_reg + RDWR_EN_HI_CNT); + iowrite32(2, denali->flash_reg + CS_SETUP_CNT); + } +} + +static void get_toshiba_nand_para(struct denali_nand_info *denali) +{ + uint32_t tmp; + + /* Workaround to fix a controller bug which reports a wrong */ + /* spare area size for some kind of Toshiba NAND device */ + if ((ioread32(denali->flash_reg + DEVICE_MAIN_AREA_SIZE) == 4096) && + (ioread32(denali->flash_reg + DEVICE_SPARE_AREA_SIZE) == 64)) { + iowrite32(216, denali->flash_reg + DEVICE_SPARE_AREA_SIZE); + tmp = ioread32(denali->flash_reg + DEVICES_CONNECTED) * + ioread32(denali->flash_reg + DEVICE_SPARE_AREA_SIZE); + iowrite32(tmp, + denali->flash_reg + LOGICAL_PAGE_SPARE_SIZE); +#if SUPPORT_15BITECC + iowrite32(15, denali->flash_reg + ECC_CORRECTION); +#elif SUPPORT_8BITECC + iowrite32(8, denali->flash_reg + ECC_CORRECTION); +#endif + } +} + +static void get_hynix_nand_para(struct denali_nand_info *denali, + uint8_t device_id) +{ + uint32_t main_size, spare_size; + + switch (device_id) { + case 0xD5: /* Hynix H27UAG8T2A, H27UBG8U5A or H27UCG8VFA */ + case 0xD7: /* Hynix H27UDG8VEM, H27UCG8UDM or H27UCG8V5A */ + iowrite32(128, denali->flash_reg + PAGES_PER_BLOCK); + iowrite32(4096, denali->flash_reg + DEVICE_MAIN_AREA_SIZE); + iowrite32(224, denali->flash_reg + DEVICE_SPARE_AREA_SIZE); + main_size = 4096 * + ioread32(denali->flash_reg + DEVICES_CONNECTED); + spare_size = 224 * + ioread32(denali->flash_reg + DEVICES_CONNECTED); + iowrite32(main_size, + denali->flash_reg + LOGICAL_PAGE_DATA_SIZE); + iowrite32(spare_size, + denali->flash_reg + LOGICAL_PAGE_SPARE_SIZE); + iowrite32(0, denali->flash_reg + DEVICE_WIDTH); +#if SUPPORT_15BITECC + iowrite32(15, denali->flash_reg + ECC_CORRECTION); +#elif SUPPORT_8BITECC + iowrite32(8, denali->flash_reg + ECC_CORRECTION); +#endif + break; + default: + dev_warn(denali->dev, + "Spectra: Unknown Hynix NAND (Device ID: 0x%x)." + "Will use default parameter values instead.\n", + device_id); + } +} + +/* determines how many NAND chips are connected to the controller. Note for + * Intel CE4100 devices we don't support more than one device. + */ +static void find_valid_banks(struct denali_nand_info *denali) +{ + uint32_t id[denali->max_banks]; + int i; + + denali->total_used_banks = 1; + for (i = 0; i < denali->max_banks; i++) { + index_addr(denali, (uint32_t)(MODE_11 | (i << 24) | 0), 0x90); + index_addr(denali, (uint32_t)(MODE_11 | (i << 24) | 1), 0); + index_addr_read_data(denali, + (uint32_t)(MODE_11 | (i << 24) | 2), &id[i]); + + dev_dbg(denali->dev, + "Return 1st ID for bank[%d]: %x\n", i, id[i]); + + if (i == 0) { + if (!(id[i] & 0x0ff)) + break; /* WTF? */ + } else { + if ((id[i] & 0x0ff) == (id[0] & 0x0ff)) + denali->total_used_banks++; + else + break; + } + } + + if (denali->platform == INTEL_CE4100) { + /* Platform limitations of the CE4100 device limit + * users to a single chip solution for NAND. + * Multichip support is not enabled. + */ + if (denali->total_used_banks != 1) { + dev_err(denali->dev, + "Sorry, Intel CE4100 only supports " + "a single NAND device.\n"); + BUG(); + } + } + dev_dbg(denali->dev, + "denali->total_used_banks: %d\n", denali->total_used_banks); +} + +/* + * Use the configuration feature register to determine the maximum number of + * banks that the hardware supports. + */ +static void detect_max_banks(struct denali_nand_info *denali) +{ + uint32_t features = ioread32(denali->flash_reg + FEATURES); + + denali->max_banks = 2 << (features & FEATURES__N_BANKS); +} + +static void detect_partition_feature(struct denali_nand_info *denali) +{ + /* For MRST platform, denali->fwblks represent the + * number of blocks firmware is taken, + * FW is in protect partition and MTD driver has no + * permission to access it. So let driver know how many + * blocks it can't touch. + * */ + if (ioread32(denali->flash_reg + FEATURES) & FEATURES__PARTITION) { + if ((ioread32(denali->flash_reg + PERM_SRC_ID(1)) & + PERM_SRC_ID__SRCID) == SPECTRA_PARTITION_ID) { + denali->fwblks = + ((ioread32(denali->flash_reg + MIN_MAX_BANK(1)) & + MIN_MAX_BANK__MIN_VALUE) * + denali->blksperchip) + + + (ioread32(denali->flash_reg + MIN_BLK_ADDR(1)) & + MIN_BLK_ADDR__VALUE); + } else + denali->fwblks = SPECTRA_START_BLOCK; + } else + denali->fwblks = SPECTRA_START_BLOCK; +} + +static uint16_t denali_nand_timing_set(struct denali_nand_info *denali) +{ + uint16_t status = PASS; + uint32_t id_bytes[5], addr; + uint8_t i, maf_id, device_id; + + dev_dbg(denali->dev, + "%s, Line %d, Function: %s\n", + __FILE__, __LINE__, __func__); + + /* Use read id method to get device ID and other + * params. For some NAND chips, controller can't + * report the correct device ID by reading from + * DEVICE_ID register + * */ + addr = (uint32_t)MODE_11 | BANK(denali->flash_bank); + index_addr(denali, (uint32_t)addr | 0, 0x90); + index_addr(denali, (uint32_t)addr | 1, 0); + for (i = 0; i < 5; i++) + index_addr_read_data(denali, addr | 2, &id_bytes[i]); + maf_id = id_bytes[0]; + device_id = id_bytes[1]; + + if (ioread32(denali->flash_reg + ONFI_DEVICE_NO_OF_LUNS) & + ONFI_DEVICE_NO_OF_LUNS__ONFI_DEVICE) { /* ONFI 1.0 NAND */ + if (FAIL == get_onfi_nand_para(denali)) + return FAIL; + } else if (maf_id == 0xEC) { /* Samsung NAND */ + get_samsung_nand_para(denali, device_id); + } else if (maf_id == 0x98) { /* Toshiba NAND */ + get_toshiba_nand_para(denali); + } else if (maf_id == 0xAD) { /* Hynix NAND */ + get_hynix_nand_para(denali, device_id); + } + + dev_info(denali->dev, + "Dump timing register values:" + "acc_clks: %d, re_2_we: %d, re_2_re: %d\n" + "we_2_re: %d, addr_2_data: %d, rdwr_en_lo_cnt: %d\n" + "rdwr_en_hi_cnt: %d, cs_setup_cnt: %d\n", + ioread32(denali->flash_reg + ACC_CLKS), + ioread32(denali->flash_reg + RE_2_WE), + ioread32(denali->flash_reg + RE_2_RE), + ioread32(denali->flash_reg + WE_2_RE), + ioread32(denali->flash_reg + ADDR_2_DATA), + ioread32(denali->flash_reg + RDWR_EN_LO_CNT), + ioread32(denali->flash_reg + RDWR_EN_HI_CNT), + ioread32(denali->flash_reg + CS_SETUP_CNT)); + + find_valid_banks(denali); + + detect_partition_feature(denali); + + /* If the user specified to override the default timings + * with a specific ONFI mode, we apply those changes here. + */ + if (onfi_timing_mode != NAND_DEFAULT_TIMINGS) + nand_onfi_timing_set(denali, onfi_timing_mode); + + return status; +} + +static void denali_set_intr_modes(struct denali_nand_info *denali, + uint16_t INT_ENABLE) +{ + dev_dbg(denali->dev, "%s, Line %d, Function: %s\n", + __FILE__, __LINE__, __func__); + + if (INT_ENABLE) + iowrite32(1, denali->flash_reg + GLOBAL_INT_ENABLE); + else + iowrite32(0, denali->flash_reg + GLOBAL_INT_ENABLE); +} + +/* validation function to verify that the controlling software is making + * a valid request + */ +static inline bool is_flash_bank_valid(int flash_bank) +{ + return (flash_bank >= 0 && flash_bank < 4); +} + +static void denali_irq_init(struct denali_nand_info *denali) +{ + uint32_t int_mask = 0; + int i; + + /* Disable global interrupts */ + denali_set_intr_modes(denali, false); + + int_mask = DENALI_IRQ_ALL; + + /* Clear all status bits */ + for (i = 0; i < denali->max_banks; ++i) + iowrite32(0xFFFF, denali->flash_reg + INTR_STATUS(i)); + + denali_irq_enable(denali, int_mask); +} + +static void denali_irq_cleanup(int irqnum, struct denali_nand_info *denali) +{ + denali_set_intr_modes(denali, false); + free_irq(irqnum, denali); +} + +static void denali_irq_enable(struct denali_nand_info *denali, + uint32_t int_mask) +{ + int i; + + for (i = 0; i < denali->max_banks; ++i) + iowrite32(int_mask, denali->flash_reg + INTR_EN(i)); +} + +/* This function only returns when an interrupt that this driver cares about + * occurs. This is to reduce the overhead of servicing interrupts + */ +static inline uint32_t denali_irq_detected(struct denali_nand_info *denali) +{ + return read_interrupt_status(denali) & DENALI_IRQ_ALL; +} + +/* Interrupts are cleared by writing a 1 to the appropriate status bit */ +static inline void clear_interrupt(struct denali_nand_info *denali, + uint32_t irq_mask) +{ + uint32_t intr_status_reg = 0; + + intr_status_reg = INTR_STATUS(denali->flash_bank); + + iowrite32(irq_mask, denali->flash_reg + intr_status_reg); +} + +static void clear_interrupts(struct denali_nand_info *denali) +{ + uint32_t status = 0x0; + spin_lock_irq(&denali->irq_lock); + + status = read_interrupt_status(denali); + clear_interrupt(denali, status); + + denali->irq_status = 0x0; + spin_unlock_irq(&denali->irq_lock); +} + +static uint32_t read_interrupt_status(struct denali_nand_info *denali) +{ + uint32_t intr_status_reg = 0; + + intr_status_reg = INTR_STATUS(denali->flash_bank); + + return ioread32(denali->flash_reg + intr_status_reg); +} + +/* This is the interrupt service routine. It handles all interrupts + * sent to this device. Note that on CE4100, this is a shared + * interrupt. + */ +static irqreturn_t denali_isr(int irq, void *dev_id) +{ + struct denali_nand_info *denali = dev_id; + uint32_t irq_status = 0x0; + irqreturn_t result = IRQ_NONE; + + spin_lock(&denali->irq_lock); + + /* check to see if a valid NAND chip has + * been selected. + */ + if (is_flash_bank_valid(denali->flash_bank)) { + /* check to see if controller generated + * the interrupt, since this is a shared interrupt */ + irq_status = denali_irq_detected(denali); + if (irq_status != 0) { + /* handle interrupt */ + /* first acknowledge it */ + clear_interrupt(denali, irq_status); + /* store the status in the device context for someone + to read */ + denali->irq_status |= irq_status; + /* notify anyone who cares that it happened */ + complete(&denali->complete); + /* tell the OS that we've handled this */ + result = IRQ_HANDLED; + } + } + spin_unlock(&denali->irq_lock); + return result; +} +#define BANK(x) ((x) << 24) + +static uint32_t wait_for_irq(struct denali_nand_info *denali, uint32_t irq_mask) +{ + unsigned long comp_res = 0; + uint32_t intr_status = 0; + bool retry = false; + unsigned long timeout = msecs_to_jiffies(1000); + + do { + comp_res = + wait_for_completion_timeout(&denali->complete, timeout); + spin_lock_irq(&denali->irq_lock); + intr_status = denali->irq_status; + + if (intr_status & irq_mask) { + denali->irq_status &= ~irq_mask; + spin_unlock_irq(&denali->irq_lock); + /* our interrupt was detected */ + break; + } else { + /* these are not the interrupts you are looking for - + * need to wait again */ + spin_unlock_irq(&denali->irq_lock); + retry = true; + } + } while (comp_res != 0); + + if (comp_res == 0) { + /* timeout */ + pr_err("timeout occurred, status = 0x%x, mask = 0x%x\n", + intr_status, irq_mask); + + intr_status = 0; + } + return intr_status; +} + +/* This helper function setups the registers for ECC and whether or not + * the spare area will be transferred. */ +static void setup_ecc_for_xfer(struct denali_nand_info *denali, bool ecc_en, + bool transfer_spare) +{ + int ecc_en_flag = 0, transfer_spare_flag = 0; + + /* set ECC, transfer spare bits if needed */ + ecc_en_flag = ecc_en ? ECC_ENABLE__FLAG : 0; + transfer_spare_flag = transfer_spare ? TRANSFER_SPARE_REG__FLAG : 0; + + /* Enable spare area/ECC per user's request. */ + iowrite32(ecc_en_flag, denali->flash_reg + ECC_ENABLE); + iowrite32(transfer_spare_flag, + denali->flash_reg + TRANSFER_SPARE_REG); +} + +/* sends a pipeline command operation to the controller. See the Denali NAND + * controller's user guide for more information (section 4.2.3.6). + */ +static int denali_send_pipeline_cmd(struct denali_nand_info *denali, + bool ecc_en, + bool transfer_spare, + int access_type, + int op) +{ + int status = PASS; + uint32_t addr = 0x0, cmd = 0x0, page_count = 1, irq_status = 0, + irq_mask = 0; + + if (op == DENALI_READ) + irq_mask = INTR_STATUS__LOAD_COMP; + else if (op == DENALI_WRITE) + irq_mask = 0; + else + BUG(); + + setup_ecc_for_xfer(denali, ecc_en, transfer_spare); + + /* clear interrupts */ + clear_interrupts(denali); + + addr = BANK(denali->flash_bank) | denali->page; + + if (op == DENALI_WRITE && access_type != SPARE_ACCESS) { + cmd = MODE_01 | addr; + iowrite32(cmd, denali->flash_mem); + } else if (op == DENALI_WRITE && access_type == SPARE_ACCESS) { + /* read spare area */ + cmd = MODE_10 | addr; + index_addr(denali, (uint32_t)cmd, access_type); + + cmd = MODE_01 | addr; + iowrite32(cmd, denali->flash_mem); + } else if (op == DENALI_READ) { + /* setup page read request for access type */ + cmd = MODE_10 | addr; + index_addr(denali, (uint32_t)cmd, access_type); + + /* page 33 of the NAND controller spec indicates we should not + use the pipeline commands in Spare area only mode. So we + don't. + */ + if (access_type == SPARE_ACCESS) { + cmd = MODE_01 | addr; + iowrite32(cmd, denali->flash_mem); + } else { + index_addr(denali, (uint32_t)cmd, + 0x2000 | op | page_count); + + /* wait for command to be accepted + * can always use status0 bit as the + * mask is identical for each + * bank. */ + irq_status = wait_for_irq(denali, irq_mask); + + if (irq_status == 0) { + dev_err(denali->dev, + "cmd, page, addr on timeout " + "(0x%x, 0x%x, 0x%x)\n", + cmd, denali->page, addr); + status = FAIL; + } else { + cmd = MODE_01 | addr; + iowrite32(cmd, denali->flash_mem); + } + } + } + return status; +} + +/* helper function that simply writes a buffer to the flash */ +static int write_data_to_flash_mem(struct denali_nand_info *denali, + const uint8_t *buf, + int len) +{ + uint32_t i = 0, *buf32; + + /* verify that the len is a multiple of 4. see comment in + * read_data_from_flash_mem() */ + BUG_ON((len % 4) != 0); + + /* write the data to the flash memory */ + buf32 = (uint32_t *)buf; + for (i = 0; i < len / 4; i++) + iowrite32(*buf32++, denali->flash_mem + 0x10); + return i*4; /* intent is to return the number of bytes read */ +} + +/* helper function that simply reads a buffer from the flash */ +static int read_data_from_flash_mem(struct denali_nand_info *denali, + uint8_t *buf, + int len) +{ + uint32_t i = 0, *buf32; + + /* we assume that len will be a multiple of 4, if not + * it would be nice to know about it ASAP rather than + * have random failures... + * This assumption is based on the fact that this + * function is designed to be used to read flash pages, + * which are typically multiples of 4... + */ + + BUG_ON((len % 4) != 0); + + /* transfer the data from the flash */ + buf32 = (uint32_t *)buf; + for (i = 0; i < len / 4; i++) + *buf32++ = ioread32(denali->flash_mem + 0x10); + return i*4; /* intent is to return the number of bytes read */ +} + +/* writes OOB data to the device */ +static int write_oob_data(struct mtd_info *mtd, uint8_t *buf, int page) +{ + struct denali_nand_info *denali = mtd_to_denali(mtd); + uint32_t irq_status = 0; + uint32_t irq_mask = INTR_STATUS__PROGRAM_COMP | + INTR_STATUS__PROGRAM_FAIL; + int status = 0; + + denali->page = page; + + if (denali_send_pipeline_cmd(denali, false, false, SPARE_ACCESS, + DENALI_WRITE) == PASS) { + write_data_to_flash_mem(denali, buf, mtd->oobsize); + + /* wait for operation to complete */ + irq_status = wait_for_irq(denali, irq_mask); + + if (irq_status == 0) { + dev_err(denali->dev, "OOB write failed\n"); + status = -EIO; + } + } else { + dev_err(denali->dev, "unable to send pipeline command\n"); + status = -EIO; + } + return status; +} + +/* reads OOB data from the device */ +static void read_oob_data(struct mtd_info *mtd, uint8_t *buf, int page) +{ + struct denali_nand_info *denali = mtd_to_denali(mtd); + uint32_t irq_mask = INTR_STATUS__LOAD_COMP, + irq_status = 0, addr = 0x0, cmd = 0x0; + + denali->page = page; + + if (denali_send_pipeline_cmd(denali, false, true, SPARE_ACCESS, + DENALI_READ) == PASS) { + read_data_from_flash_mem(denali, buf, mtd->oobsize); + + /* wait for command to be accepted + * can always use status0 bit as the mask is identical for each + * bank. */ + irq_status = wait_for_irq(denali, irq_mask); + + if (irq_status == 0) + dev_err(denali->dev, "page on OOB timeout %d\n", + denali->page); + + /* We set the device back to MAIN_ACCESS here as I observed + * instability with the controller if you do a block erase + * and the last transaction was a SPARE_ACCESS. Block erase + * is reliable (according to the MTD test infrastructure) + * if you are in MAIN_ACCESS. + */ + addr = BANK(denali->flash_bank) | denali->page; + cmd = MODE_10 | addr; + index_addr(denali, (uint32_t)cmd, MAIN_ACCESS); + } +} + +/* this function examines buffers to see if they contain data that + * indicate that the buffer is part of an erased region of flash. + */ +static bool is_erased(uint8_t *buf, int len) +{ + int i = 0; + for (i = 0; i < len; i++) + if (buf[i] != 0xFF) + return false; + return true; +} +#define ECC_SECTOR_SIZE 512 + +#define ECC_SECTOR(x) (((x) & ECC_ERROR_ADDRESS__SECTOR_NR) >> 12) +#define ECC_BYTE(x) (((x) & ECC_ERROR_ADDRESS__OFFSET)) +#define ECC_CORRECTION_VALUE(x) ((x) & ERR_CORRECTION_INFO__BYTEMASK) +#define ECC_ERROR_CORRECTABLE(x) (!((x) & ERR_CORRECTION_INFO__ERROR_TYPE)) +#define ECC_ERR_DEVICE(x) (((x) & ERR_CORRECTION_INFO__DEVICE_NR) >> 8) +#define ECC_LAST_ERR(x) ((x) & ERR_CORRECTION_INFO__LAST_ERR_INFO) + +static bool handle_ecc(struct denali_nand_info *denali, uint8_t *buf, + uint32_t irq_status, unsigned int *max_bitflips) +{ + bool check_erased_page = false; + unsigned int bitflips = 0; + + if (irq_status & INTR_STATUS__ECC_ERR) { + /* read the ECC errors. we'll ignore them for now */ + uint32_t err_address = 0, err_correction_info = 0; + uint32_t err_byte = 0, err_sector = 0, err_device = 0; + uint32_t err_correction_value = 0; + denali_set_intr_modes(denali, false); + + do { + err_address = ioread32(denali->flash_reg + + ECC_ERROR_ADDRESS); + err_sector = ECC_SECTOR(err_address); + err_byte = ECC_BYTE(err_address); + + err_correction_info = ioread32(denali->flash_reg + + ERR_CORRECTION_INFO); + err_correction_value = + ECC_CORRECTION_VALUE(err_correction_info); + err_device = ECC_ERR_DEVICE(err_correction_info); + + if (ECC_ERROR_CORRECTABLE(err_correction_info)) { + /* If err_byte is larger than ECC_SECTOR_SIZE, + * means error happened in OOB, so we ignore + * it. It's no need for us to correct it + * err_device is represented the NAND error + * bits are happened in if there are more + * than one NAND connected. + * */ + if (err_byte < ECC_SECTOR_SIZE) { + int offset; + offset = (err_sector * + ECC_SECTOR_SIZE + + err_byte) * + denali->devnum + + err_device; + /* correct the ECC error */ + buf[offset] ^= err_correction_value; + denali->mtd.ecc_stats.corrected++; + bitflips++; + } + } else { + /* if the error is not correctable, need to + * look at the page to see if it is an erased + * page. if so, then it's not a real ECC error + * */ + check_erased_page = true; + } + } while (!ECC_LAST_ERR(err_correction_info)); + /* Once handle all ecc errors, controller will triger + * a ECC_TRANSACTION_DONE interrupt, so here just wait + * for a while for this interrupt + * */ + while (!(read_interrupt_status(denali) & + INTR_STATUS__ECC_TRANSACTION_DONE)) + cpu_relax(); + clear_interrupts(denali); + denali_set_intr_modes(denali, true); + } + *max_bitflips = bitflips; + return check_erased_page; +} + +/* programs the controller to either enable/disable DMA transfers */ +static void denali_enable_dma(struct denali_nand_info *denali, bool en) +{ + uint32_t reg_val = 0x0; + + if (en) + reg_val = DMA_ENABLE__FLAG; + + iowrite32(reg_val, denali->flash_reg + DMA_ENABLE); + ioread32(denali->flash_reg + DMA_ENABLE); +} + +/* setups the HW to perform the data DMA */ +static void denali_setup_dma(struct denali_nand_info *denali, int op) +{ + uint32_t mode = 0x0; + const int page_count = 1; + dma_addr_t addr = denali->buf.dma_buf; + + mode = MODE_10 | BANK(denali->flash_bank); + + /* DMA is a four step process */ + + /* 1. setup transfer type and # of pages */ + index_addr(denali, mode | denali->page, 0x2000 | op | page_count); + + /* 2. set memory high address bits 23:8 */ + index_addr(denali, mode | ((uint16_t)(addr >> 16) << 8), 0x2200); + + /* 3. set memory low address bits 23:8 */ + index_addr(denali, mode | ((uint16_t)addr << 8), 0x2300); + + /* 4. interrupt when complete, burst len = 64 bytes*/ + index_addr(denali, mode | 0x14000, 0x2400); +} + +/* writes a page. user specifies type, and this function handles the + * configuration details. */ +static int write_page(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf, bool raw_xfer) +{ + struct denali_nand_info *denali = mtd_to_denali(mtd); + + dma_addr_t addr = denali->buf.dma_buf; + size_t size = denali->mtd.writesize + denali->mtd.oobsize; + + uint32_t irq_status = 0; + uint32_t irq_mask = INTR_STATUS__DMA_CMD_COMP | + INTR_STATUS__PROGRAM_FAIL; + + /* if it is a raw xfer, we want to disable ecc, and send + * the spare area. + * !raw_xfer - enable ecc + * raw_xfer - transfer spare + */ + setup_ecc_for_xfer(denali, !raw_xfer, raw_xfer); + + /* copy buffer into DMA buffer */ + memcpy(denali->buf.buf, buf, mtd->writesize); + + if (raw_xfer) { + /* transfer the data to the spare area */ + memcpy(denali->buf.buf + mtd->writesize, + chip->oob_poi, + mtd->oobsize); + } + + dma_sync_single_for_device(denali->dev, addr, size, DMA_TO_DEVICE); + + clear_interrupts(denali); + denali_enable_dma(denali, true); + + denali_setup_dma(denali, DENALI_WRITE); + + /* wait for operation to complete */ + irq_status = wait_for_irq(denali, irq_mask); + + if (irq_status == 0) { + dev_err(denali->dev, + "timeout on write_page (type = %d)\n", + raw_xfer); + denali->status = + (irq_status & INTR_STATUS__PROGRAM_FAIL) ? + NAND_STATUS_FAIL : PASS; + } + + denali_enable_dma(denali, false); + dma_sync_single_for_cpu(denali->dev, addr, size, DMA_TO_DEVICE); + + return 0; +} + +/* NAND core entry points */ + +/* this is the callback that the NAND core calls to write a page. Since + * writing a page with ECC or without is similar, all the work is done + * by write_page above. + * */ +static int denali_write_page(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf, int oob_required) +{ + /* for regular page writes, we let HW handle all the ECC + * data written to the device. */ + return write_page(mtd, chip, buf, false); +} + +/* This is the callback that the NAND core calls to write a page without ECC. + * raw access is similar to ECC page writes, so all the work is done in the + * write_page() function above. + */ +static int denali_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf, int oob_required) +{ + /* for raw page writes, we want to disable ECC and simply write + whatever data is in the buffer. */ + return write_page(mtd, chip, buf, true); +} + +static int denali_write_oob(struct mtd_info *mtd, struct nand_chip *chip, + int page) +{ + return write_oob_data(mtd, chip->oob_poi, page); +} + +static int denali_read_oob(struct mtd_info *mtd, struct nand_chip *chip, + int page) +{ + read_oob_data(mtd, chip->oob_poi, page); + + return 0; +} + +static int denali_read_page(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf, int oob_required, int page) +{ + unsigned int max_bitflips; + struct denali_nand_info *denali = mtd_to_denali(mtd); + + dma_addr_t addr = denali->buf.dma_buf; + size_t size = denali->mtd.writesize + denali->mtd.oobsize; + + uint32_t irq_status = 0; + uint32_t irq_mask = INTR_STATUS__ECC_TRANSACTION_DONE | + INTR_STATUS__ECC_ERR; + bool check_erased_page = false; + + if (page != denali->page) { + dev_err(denali->dev, "IN %s: page %d is not" + " equal to denali->page %d, investigate!!", + __func__, page, denali->page); + BUG(); + } + + setup_ecc_for_xfer(denali, true, false); + + denali_enable_dma(denali, true); + dma_sync_single_for_device(denali->dev, addr, size, DMA_FROM_DEVICE); + + clear_interrupts(denali); + denali_setup_dma(denali, DENALI_READ); + + /* wait for operation to complete */ + irq_status = wait_for_irq(denali, irq_mask); + + dma_sync_single_for_cpu(denali->dev, addr, size, DMA_FROM_DEVICE); + + memcpy(buf, denali->buf.buf, mtd->writesize); + + check_erased_page = handle_ecc(denali, buf, irq_status, &max_bitflips); + denali_enable_dma(denali, false); + + if (check_erased_page) { + read_oob_data(&denali->mtd, chip->oob_poi, denali->page); + + /* check ECC failures that may have occurred on erased pages */ + if (check_erased_page) { + if (!is_erased(buf, denali->mtd.writesize)) + denali->mtd.ecc_stats.failed++; + if (!is_erased(buf, denali->mtd.oobsize)) + denali->mtd.ecc_stats.failed++; + } + } + return max_bitflips; +} + +static int denali_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf, int oob_required, int page) +{ + struct denali_nand_info *denali = mtd_to_denali(mtd); + + dma_addr_t addr = denali->buf.dma_buf; + size_t size = denali->mtd.writesize + denali->mtd.oobsize; + + uint32_t irq_status = 0; + uint32_t irq_mask = INTR_STATUS__DMA_CMD_COMP; + + if (page != denali->page) { + dev_err(denali->dev, "IN %s: page %d is not" + " equal to denali->page %d, investigate!!", + __func__, page, denali->page); + BUG(); + } + + setup_ecc_for_xfer(denali, false, true); + denali_enable_dma(denali, true); + + dma_sync_single_for_device(denali->dev, addr, size, DMA_FROM_DEVICE); + + clear_interrupts(denali); + denali_setup_dma(denali, DENALI_READ); + + /* wait for operation to complete */ + irq_status = wait_for_irq(denali, irq_mask); + + dma_sync_single_for_cpu(denali->dev, addr, size, DMA_FROM_DEVICE); + + denali_enable_dma(denali, false); + + memcpy(buf, denali->buf.buf, mtd->writesize); + memcpy(chip->oob_poi, denali->buf.buf + mtd->writesize, mtd->oobsize); + + return 0; +} + +static uint8_t denali_read_byte(struct mtd_info *mtd) +{ + struct denali_nand_info *denali = mtd_to_denali(mtd); + uint8_t result = 0xff; + + if (denali->buf.head < denali->buf.tail) + result = denali->buf.buf[denali->buf.head++]; + + return result; +} + +static void denali_select_chip(struct mtd_info *mtd, int chip) +{ + struct denali_nand_info *denali = mtd_to_denali(mtd); + + spin_lock_irq(&denali->irq_lock); + denali->flash_bank = chip; + spin_unlock_irq(&denali->irq_lock); +} + +static int denali_waitfunc(struct mtd_info *mtd, struct nand_chip *chip) +{ + struct denali_nand_info *denali = mtd_to_denali(mtd); + int status = denali->status; + denali->status = 0; + + return status; +} + +static int denali_erase(struct mtd_info *mtd, int page) +{ + struct denali_nand_info *denali = mtd_to_denali(mtd); + + uint32_t cmd = 0x0, irq_status = 0; + + /* clear interrupts */ + clear_interrupts(denali); + + /* setup page read request for access type */ + cmd = MODE_10 | BANK(denali->flash_bank) | page; + index_addr(denali, (uint32_t)cmd, 0x1); + + /* wait for erase to complete or failure to occur */ + irq_status = wait_for_irq(denali, INTR_STATUS__ERASE_COMP | + INTR_STATUS__ERASE_FAIL); + + return (irq_status & INTR_STATUS__ERASE_FAIL) ? NAND_STATUS_FAIL : PASS; +} + +static void denali_cmdfunc(struct mtd_info *mtd, unsigned int cmd, int col, + int page) +{ + struct denali_nand_info *denali = mtd_to_denali(mtd); + uint32_t addr, id; + int i; + + switch (cmd) { + case NAND_CMD_PAGEPROG: + break; + case NAND_CMD_STATUS: + read_status(denali); + break; + case NAND_CMD_READID: + case NAND_CMD_PARAM: + reset_buf(denali); + /*sometimes ManufactureId read from register is not right + * e.g. some of Micron MT29F32G08QAA MLC NAND chips + * So here we send READID cmd to NAND insteand + * */ + addr = (uint32_t)MODE_11 | BANK(denali->flash_bank); + index_addr(denali, (uint32_t)addr | 0, 0x90); + index_addr(denali, (uint32_t)addr | 1, 0); + for (i = 0; i < 5; i++) { + index_addr_read_data(denali, + (uint32_t)addr | 2, + &id); + write_byte_to_buf(denali, id); + } + break; + case NAND_CMD_READ0: + case NAND_CMD_SEQIN: + denali->page = page; + break; + case NAND_CMD_RESET: + reset_bank(denali); + break; + case NAND_CMD_READOOB: + /* TODO: Read OOB data */ + break; + default: + pr_err(": unsupported command received 0x%x\n", cmd); + break; + } +} + +/* stubs for ECC functions not used by the NAND core */ +static int denali_ecc_calculate(struct mtd_info *mtd, const uint8_t *data, + uint8_t *ecc_code) +{ + struct denali_nand_info *denali = mtd_to_denali(mtd); + dev_err(denali->dev, + "denali_ecc_calculate called unexpectedly\n"); + BUG(); + return -EIO; +} + +static int denali_ecc_correct(struct mtd_info *mtd, uint8_t *data, + uint8_t *read_ecc, uint8_t *calc_ecc) +{ + struct denali_nand_info *denali = mtd_to_denali(mtd); + dev_err(denali->dev, + "denali_ecc_correct called unexpectedly\n"); + BUG(); + return -EIO; +} + +static void denali_ecc_hwctl(struct mtd_info *mtd, int mode) +{ + struct denali_nand_info *denali = mtd_to_denali(mtd); + dev_err(denali->dev, + "denali_ecc_hwctl called unexpectedly\n"); + BUG(); +} +/* end NAND core entry points */ + +/* Initialization code to bring the device up to a known good state */ +static void denali_hw_init(struct denali_nand_info *denali) +{ + /* tell driver how many bit controller will skip before + * writing ECC code in OOB, this register may be already + * set by firmware. So we read this value out. + * if this value is 0, just let it be. + * */ + denali->bbtskipbytes = ioread32(denali->flash_reg + + SPARE_AREA_SKIP_BYTES); + detect_max_banks(denali); + denali_nand_reset(denali); + iowrite32(0x0F, denali->flash_reg + RB_PIN_ENABLED); + iowrite32(CHIP_EN_DONT_CARE__FLAG, + denali->flash_reg + CHIP_ENABLE_DONT_CARE); + + iowrite32(0xffff, denali->flash_reg + SPARE_AREA_MARKER); + + /* Should set value for these registers when init */ + iowrite32(0, denali->flash_reg + TWO_ROW_ADDR_CYCLES); + iowrite32(1, denali->flash_reg + ECC_ENABLE); + denali_nand_timing_set(denali); + denali_irq_init(denali); +} + +/* Althogh controller spec said SLC ECC is forceb to be 4bit, + * but denali controller in MRST only support 15bit and 8bit ECC + * correction + * */ +#define ECC_8BITS 14 +static struct nand_ecclayout nand_8bit_oob = { + .eccbytes = 14, +}; + +#define ECC_15BITS 26 +static struct nand_ecclayout nand_15bit_oob = { + .eccbytes = 26, +}; + +static uint8_t bbt_pattern[] = {'B', 'b', 't', '0' }; +static uint8_t mirror_pattern[] = {'1', 't', 'b', 'B' }; + +static struct nand_bbt_descr bbt_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 8, + .len = 4, + .veroffs = 12, + .maxblocks = 4, + .pattern = bbt_pattern, +}; + +static struct nand_bbt_descr bbt_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 8, + .len = 4, + .veroffs = 12, + .maxblocks = 4, + .pattern = mirror_pattern, +}; + +/* initialize driver data structures */ +static void denali_drv_init(struct denali_nand_info *denali) +{ + denali->idx = 0; + + /* setup interrupt handler */ + /* the completion object will be used to notify + * the callee that the interrupt is done */ + init_completion(&denali->complete); + + /* the spinlock will be used to synchronize the ISR + * with any element that might be access shared + * data (interrupt status) */ + spin_lock_init(&denali->irq_lock); + + /* indicate that MTD has not selected a valid bank yet */ + denali->flash_bank = CHIP_SELECT_INVALID; + + /* initialize our irq_status variable to indicate no interrupts */ + denali->irq_status = 0; +} + +int denali_init(struct denali_nand_info *denali) +{ + int ret; + + if (denali->platform == INTEL_CE4100) { + /* Due to a silicon limitation, we can only support + * ONFI timing mode 1 and below. + */ + if (onfi_timing_mode < -1 || onfi_timing_mode > 1) { + pr_err("Intel CE4100 only supports ONFI timing mode 1 or below\n"); + return -EINVAL; + } + } + + /* allocate a temporary buffer for nand_scan_ident() */ + denali->buf.buf = devm_kzalloc(denali->dev, PAGE_SIZE, + GFP_DMA | GFP_KERNEL); + if (!denali->buf.buf) + return -ENOMEM; + + denali->mtd.dev.parent = denali->dev; + denali_hw_init(denali); + denali_drv_init(denali); + + /* denali_isr register is done after all the hardware + * initilization is finished*/ + if (request_irq(denali->irq, denali_isr, IRQF_SHARED, + DENALI_NAND_NAME, denali)) { + pr_err("Spectra: Unable to allocate IRQ\n"); + return -ENODEV; + } + + /* now that our ISR is registered, we can enable interrupts */ + denali_set_intr_modes(denali, true); + denali->mtd.name = "denali-nand"; + denali->mtd.owner = THIS_MODULE; + denali->mtd.priv = &denali->nand; + + /* register the driver with the NAND core subsystem */ + denali->nand.select_chip = denali_select_chip; + denali->nand.cmdfunc = denali_cmdfunc; + denali->nand.read_byte = denali_read_byte; + denali->nand.waitfunc = denali_waitfunc; + + /* scan for NAND devices attached to the controller + * this is the first stage in a two step process to register + * with the nand subsystem */ + if (nand_scan_ident(&denali->mtd, denali->max_banks, NULL)) { + ret = -ENXIO; + goto failed_req_irq; + } + + /* allocate the right size buffer now */ + devm_kfree(denali->dev, denali->buf.buf); + denali->buf.buf = devm_kzalloc(denali->dev, + denali->mtd.writesize + denali->mtd.oobsize, + GFP_KERNEL); + if (!denali->buf.buf) { + ret = -ENOMEM; + goto failed_req_irq; + } + + /* Is 32-bit DMA supported? */ + ret = dma_set_mask(denali->dev, DMA_BIT_MASK(32)); + if (ret) { + pr_err("Spectra: no usable DMA configuration\n"); + goto failed_req_irq; + } + + denali->buf.dma_buf = dma_map_single(denali->dev, denali->buf.buf, + denali->mtd.writesize + denali->mtd.oobsize, + DMA_BIDIRECTIONAL); + if (dma_mapping_error(denali->dev, denali->buf.dma_buf)) { + dev_err(denali->dev, "Spectra: failed to map DMA buffer\n"); + ret = -EIO; + goto failed_req_irq; + } + + /* support for multi nand + * MTD known nothing about multi nand, + * so we should tell it the real pagesize + * and anything necessery + */ + denali->devnum = ioread32(denali->flash_reg + DEVICES_CONNECTED); + denali->nand.chipsize <<= (denali->devnum - 1); + denali->nand.page_shift += (denali->devnum - 1); + denali->nand.pagemask = (denali->nand.chipsize >> + denali->nand.page_shift) - 1; + denali->nand.bbt_erase_shift += (denali->devnum - 1); + denali->nand.phys_erase_shift = denali->nand.bbt_erase_shift; + denali->nand.chip_shift += (denali->devnum - 1); + denali->mtd.writesize <<= (denali->devnum - 1); + denali->mtd.oobsize <<= (denali->devnum - 1); + denali->mtd.erasesize <<= (denali->devnum - 1); + denali->mtd.size = denali->nand.numchips * denali->nand.chipsize; + denali->bbtskipbytes *= denali->devnum; + + /* second stage of the NAND scan + * this stage requires information regarding ECC and + * bad block management. */ + + /* Bad block management */ + denali->nand.bbt_td = &bbt_main_descr; + denali->nand.bbt_md = &bbt_mirror_descr; + + /* skip the scan for now until we have OOB read and write support */ + denali->nand.bbt_options |= NAND_BBT_USE_FLASH; + denali->nand.options |= NAND_SKIP_BBTSCAN; + denali->nand.ecc.mode = NAND_ECC_HW_SYNDROME; + + /* Denali Controller only support 15bit and 8bit ECC in MRST, + * so just let controller do 15bit ECC for MLC and 8bit ECC for + * SLC if possible. + * */ + if (!nand_is_slc(&denali->nand) && + (denali->mtd.oobsize > (denali->bbtskipbytes + + ECC_15BITS * (denali->mtd.writesize / + ECC_SECTOR_SIZE)))) { + /* if MLC OOB size is large enough, use 15bit ECC*/ + denali->nand.ecc.strength = 15; + denali->nand.ecc.layout = &nand_15bit_oob; + denali->nand.ecc.bytes = ECC_15BITS; + iowrite32(15, denali->flash_reg + ECC_CORRECTION); + } else if (denali->mtd.oobsize < (denali->bbtskipbytes + + ECC_8BITS * (denali->mtd.writesize / + ECC_SECTOR_SIZE))) { + pr_err("Your NAND chip OOB is not large enough to \ + contain 8bit ECC correction codes"); + goto failed_req_irq; + } else { + denali->nand.ecc.strength = 8; + denali->nand.ecc.layout = &nand_8bit_oob; + denali->nand.ecc.bytes = ECC_8BITS; + iowrite32(8, denali->flash_reg + ECC_CORRECTION); + } + + denali->nand.ecc.bytes *= denali->devnum; + denali->nand.ecc.strength *= denali->devnum; + denali->nand.ecc.layout->eccbytes *= + denali->mtd.writesize / ECC_SECTOR_SIZE; + denali->nand.ecc.layout->oobfree[0].offset = + denali->bbtskipbytes + denali->nand.ecc.layout->eccbytes; + denali->nand.ecc.layout->oobfree[0].length = + denali->mtd.oobsize - denali->nand.ecc.layout->eccbytes - + denali->bbtskipbytes; + + /* Let driver know the total blocks number and + * how many blocks contained by each nand chip. + * blksperchip will help driver to know how many + * blocks is taken by FW. + * */ + denali->totalblks = denali->mtd.size >> + denali->nand.phys_erase_shift; + denali->blksperchip = denali->totalblks / denali->nand.numchips; + + /* These functions are required by the NAND core framework, otherwise, + * the NAND core will assert. However, we don't need them, so we'll stub + * them out. */ + denali->nand.ecc.calculate = denali_ecc_calculate; + denali->nand.ecc.correct = denali_ecc_correct; + denali->nand.ecc.hwctl = denali_ecc_hwctl; + + /* override the default read operations */ + denali->nand.ecc.size = ECC_SECTOR_SIZE * denali->devnum; + denali->nand.ecc.read_page = denali_read_page; + denali->nand.ecc.read_page_raw = denali_read_page_raw; + denali->nand.ecc.write_page = denali_write_page; + denali->nand.ecc.write_page_raw = denali_write_page_raw; + denali->nand.ecc.read_oob = denali_read_oob; + denali->nand.ecc.write_oob = denali_write_oob; + denali->nand.erase = denali_erase; + + if (nand_scan_tail(&denali->mtd)) { + ret = -ENXIO; + goto failed_req_irq; + } + + ret = mtd_device_register(&denali->mtd, NULL, 0); + if (ret) { + dev_err(denali->dev, "Spectra: Failed to register MTD: %d\n", + ret); + goto failed_req_irq; + } + return 0; + +failed_req_irq: + denali_irq_cleanup(denali->irq, denali); + + return ret; +} +EXPORT_SYMBOL(denali_init); + +/* driver exit point */ +void denali_remove(struct denali_nand_info *denali) +{ + denali_irq_cleanup(denali->irq, denali); + dma_unmap_single(denali->dev, denali->buf.dma_buf, + denali->mtd.writesize + denali->mtd.oobsize, + DMA_BIDIRECTIONAL); +} +EXPORT_SYMBOL(denali_remove); diff --git a/drivers/mtd/nand/denali.h b/drivers/mtd/nand/denali.h new file mode 100644 index 00000000000..96681746242 --- /dev/null +++ b/drivers/mtd/nand/denali.h @@ -0,0 +1,502 @@ +/* + * NAND Flash Controller Device Driver + * Copyright (c) 2009 - 2010, Intel Corporation and its suppliers. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <linux/mtd/nand.h> + +#define DEVICE_RESET 0x0 +#define DEVICE_RESET__BANK0 0x0001 +#define DEVICE_RESET__BANK1 0x0002 +#define DEVICE_RESET__BANK2 0x0004 +#define DEVICE_RESET__BANK3 0x0008 + +#define TRANSFER_SPARE_REG 0x10 +#define TRANSFER_SPARE_REG__FLAG 0x0001 + +#define LOAD_WAIT_CNT 0x20 +#define LOAD_WAIT_CNT__VALUE 0xffff + +#define PROGRAM_WAIT_CNT 0x30 +#define PROGRAM_WAIT_CNT__VALUE 0xffff + +#define ERASE_WAIT_CNT 0x40 +#define ERASE_WAIT_CNT__VALUE 0xffff + +#define INT_MON_CYCCNT 0x50 +#define INT_MON_CYCCNT__VALUE 0xffff + +#define RB_PIN_ENABLED 0x60 +#define RB_PIN_ENABLED__BANK0 0x0001 +#define RB_PIN_ENABLED__BANK1 0x0002 +#define RB_PIN_ENABLED__BANK2 0x0004 +#define RB_PIN_ENABLED__BANK3 0x0008 + +#define MULTIPLANE_OPERATION 0x70 +#define MULTIPLANE_OPERATION__FLAG 0x0001 + +#define MULTIPLANE_READ_ENABLE 0x80 +#define MULTIPLANE_READ_ENABLE__FLAG 0x0001 + +#define COPYBACK_DISABLE 0x90 +#define COPYBACK_DISABLE__FLAG 0x0001 + +#define CACHE_WRITE_ENABLE 0xa0 +#define CACHE_WRITE_ENABLE__FLAG 0x0001 + +#define CACHE_READ_ENABLE 0xb0 +#define CACHE_READ_ENABLE__FLAG 0x0001 + +#define PREFETCH_MODE 0xc0 +#define PREFETCH_MODE__PREFETCH_EN 0x0001 +#define PREFETCH_MODE__PREFETCH_BURST_LENGTH 0xfff0 + +#define CHIP_ENABLE_DONT_CARE 0xd0 +#define CHIP_EN_DONT_CARE__FLAG 0x01 + +#define ECC_ENABLE 0xe0 +#define ECC_ENABLE__FLAG 0x0001 + +#define GLOBAL_INT_ENABLE 0xf0 +#define GLOBAL_INT_EN_FLAG 0x01 + +#define WE_2_RE 0x100 +#define WE_2_RE__VALUE 0x003f + +#define ADDR_2_DATA 0x110 +#define ADDR_2_DATA__VALUE 0x003f + +#define RE_2_WE 0x120 +#define RE_2_WE__VALUE 0x003f + +#define ACC_CLKS 0x130 +#define ACC_CLKS__VALUE 0x000f + +#define NUMBER_OF_PLANES 0x140 +#define NUMBER_OF_PLANES__VALUE 0x0007 + +#define PAGES_PER_BLOCK 0x150 +#define PAGES_PER_BLOCK__VALUE 0xffff + +#define DEVICE_WIDTH 0x160 +#define DEVICE_WIDTH__VALUE 0x0003 + +#define DEVICE_MAIN_AREA_SIZE 0x170 +#define DEVICE_MAIN_AREA_SIZE__VALUE 0xffff + +#define DEVICE_SPARE_AREA_SIZE 0x180 +#define DEVICE_SPARE_AREA_SIZE__VALUE 0xffff + +#define TWO_ROW_ADDR_CYCLES 0x190 +#define TWO_ROW_ADDR_CYCLES__FLAG 0x0001 + +#define MULTIPLANE_ADDR_RESTRICT 0x1a0 +#define MULTIPLANE_ADDR_RESTRICT__FLAG 0x0001 + +#define ECC_CORRECTION 0x1b0 +#define ECC_CORRECTION__VALUE 0x001f + +#define READ_MODE 0x1c0 +#define READ_MODE__VALUE 0x000f + +#define WRITE_MODE 0x1d0 +#define WRITE_MODE__VALUE 0x000f + +#define COPYBACK_MODE 0x1e0 +#define COPYBACK_MODE__VALUE 0x000f + +#define RDWR_EN_LO_CNT 0x1f0 +#define RDWR_EN_LO_CNT__VALUE 0x001f + +#define RDWR_EN_HI_CNT 0x200 +#define RDWR_EN_HI_CNT__VALUE 0x001f + +#define MAX_RD_DELAY 0x210 +#define MAX_RD_DELAY__VALUE 0x000f + +#define CS_SETUP_CNT 0x220 +#define CS_SETUP_CNT__VALUE 0x001f + +#define SPARE_AREA_SKIP_BYTES 0x230 +#define SPARE_AREA_SKIP_BYTES__VALUE 0x003f + +#define SPARE_AREA_MARKER 0x240 +#define SPARE_AREA_MARKER__VALUE 0xffff + +#define DEVICES_CONNECTED 0x250 +#define DEVICES_CONNECTED__VALUE 0x0007 + +#define DIE_MASK 0x260 +#define DIE_MASK__VALUE 0x00ff + +#define FIRST_BLOCK_OF_NEXT_PLANE 0x270 +#define FIRST_BLOCK_OF_NEXT_PLANE__VALUE 0xffff + +#define WRITE_PROTECT 0x280 +#define WRITE_PROTECT__FLAG 0x0001 + +#define RE_2_RE 0x290 +#define RE_2_RE__VALUE 0x003f + +#define MANUFACTURER_ID 0x300 +#define MANUFACTURER_ID__VALUE 0x00ff + +#define DEVICE_ID 0x310 +#define DEVICE_ID__VALUE 0x00ff + +#define DEVICE_PARAM_0 0x320 +#define DEVICE_PARAM_0__VALUE 0x00ff + +#define DEVICE_PARAM_1 0x330 +#define DEVICE_PARAM_1__VALUE 0x00ff + +#define DEVICE_PARAM_2 0x340 +#define DEVICE_PARAM_2__VALUE 0x00ff + +#define LOGICAL_PAGE_DATA_SIZE 0x350 +#define LOGICAL_PAGE_DATA_SIZE__VALUE 0xffff + +#define LOGICAL_PAGE_SPARE_SIZE 0x360 +#define LOGICAL_PAGE_SPARE_SIZE__VALUE 0xffff + +#define REVISION 0x370 +#define REVISION__VALUE 0xffff + +#define ONFI_DEVICE_FEATURES 0x380 +#define ONFI_DEVICE_FEATURES__VALUE 0x003f + +#define ONFI_OPTIONAL_COMMANDS 0x390 +#define ONFI_OPTIONAL_COMMANDS__VALUE 0x003f + +#define ONFI_TIMING_MODE 0x3a0 +#define ONFI_TIMING_MODE__VALUE 0x003f + +#define ONFI_PGM_CACHE_TIMING_MODE 0x3b0 +#define ONFI_PGM_CACHE_TIMING_MODE__VALUE 0x003f + +#define ONFI_DEVICE_NO_OF_LUNS 0x3c0 +#define ONFI_DEVICE_NO_OF_LUNS__NO_OF_LUNS 0x00ff +#define ONFI_DEVICE_NO_OF_LUNS__ONFI_DEVICE 0x0100 + +#define ONFI_DEVICE_NO_OF_BLOCKS_PER_LUN_L 0x3d0 +#define ONFI_DEVICE_NO_OF_BLOCKS_PER_LUN_L__VALUE 0xffff + +#define ONFI_DEVICE_NO_OF_BLOCKS_PER_LUN_U 0x3e0 +#define ONFI_DEVICE_NO_OF_BLOCKS_PER_LUN_U__VALUE 0xffff + +#define FEATURES 0x3f0 +#define FEATURES__N_BANKS 0x0003 +#define FEATURES__ECC_MAX_ERR 0x003c +#define FEATURES__DMA 0x0040 +#define FEATURES__CMD_DMA 0x0080 +#define FEATURES__PARTITION 0x0100 +#define FEATURES__XDMA_SIDEBAND 0x0200 +#define FEATURES__GPREG 0x0400 +#define FEATURES__INDEX_ADDR 0x0800 + +#define TRANSFER_MODE 0x400 +#define TRANSFER_MODE__VALUE 0x0003 + +#define INTR_STATUS(__bank) (0x410 + ((__bank) * 0x50)) +#define INTR_EN(__bank) (0x420 + ((__bank) * 0x50)) + +#define INTR_STATUS__ECC_TRANSACTION_DONE 0x0001 +#define INTR_STATUS__ECC_ERR 0x0002 +#define INTR_STATUS__DMA_CMD_COMP 0x0004 +#define INTR_STATUS__TIME_OUT 0x0008 +#define INTR_STATUS__PROGRAM_FAIL 0x0010 +#define INTR_STATUS__ERASE_FAIL 0x0020 +#define INTR_STATUS__LOAD_COMP 0x0040 +#define INTR_STATUS__PROGRAM_COMP 0x0080 +#define INTR_STATUS__ERASE_COMP 0x0100 +#define INTR_STATUS__PIPE_CPYBCK_CMD_COMP 0x0200 +#define INTR_STATUS__LOCKED_BLK 0x0400 +#define INTR_STATUS__UNSUP_CMD 0x0800 +#define INTR_STATUS__INT_ACT 0x1000 +#define INTR_STATUS__RST_COMP 0x2000 +#define INTR_STATUS__PIPE_CMD_ERR 0x4000 +#define INTR_STATUS__PAGE_XFER_INC 0x8000 + +#define INTR_EN__ECC_TRANSACTION_DONE 0x0001 +#define INTR_EN__ECC_ERR 0x0002 +#define INTR_EN__DMA_CMD_COMP 0x0004 +#define INTR_EN__TIME_OUT 0x0008 +#define INTR_EN__PROGRAM_FAIL 0x0010 +#define INTR_EN__ERASE_FAIL 0x0020 +#define INTR_EN__LOAD_COMP 0x0040 +#define INTR_EN__PROGRAM_COMP 0x0080 +#define INTR_EN__ERASE_COMP 0x0100 +#define INTR_EN__PIPE_CPYBCK_CMD_COMP 0x0200 +#define INTR_EN__LOCKED_BLK 0x0400 +#define INTR_EN__UNSUP_CMD 0x0800 +#define INTR_EN__INT_ACT 0x1000 +#define INTR_EN__RST_COMP 0x2000 +#define INTR_EN__PIPE_CMD_ERR 0x4000 +#define INTR_EN__PAGE_XFER_INC 0x8000 + +#define PAGE_CNT(__bank) (0x430 + ((__bank) * 0x50)) +#define ERR_PAGE_ADDR(__bank) (0x440 + ((__bank) * 0x50)) +#define ERR_BLOCK_ADDR(__bank) (0x450 + ((__bank) * 0x50)) + +#define DATA_INTR 0x550 +#define DATA_INTR__WRITE_SPACE_AV 0x0001 +#define DATA_INTR__READ_DATA_AV 0x0002 + +#define DATA_INTR_EN 0x560 +#define DATA_INTR_EN__WRITE_SPACE_AV 0x0001 +#define DATA_INTR_EN__READ_DATA_AV 0x0002 + +#define GPREG_0 0x570 +#define GPREG_0__VALUE 0xffff + +#define GPREG_1 0x580 +#define GPREG_1__VALUE 0xffff + +#define GPREG_2 0x590 +#define GPREG_2__VALUE 0xffff + +#define GPREG_3 0x5a0 +#define GPREG_3__VALUE 0xffff + +#define ECC_THRESHOLD 0x600 +#define ECC_THRESHOLD__VALUE 0x03ff + +#define ECC_ERROR_BLOCK_ADDRESS 0x610 +#define ECC_ERROR_BLOCK_ADDRESS__VALUE 0xffff + +#define ECC_ERROR_PAGE_ADDRESS 0x620 +#define ECC_ERROR_PAGE_ADDRESS__VALUE 0x0fff +#define ECC_ERROR_PAGE_ADDRESS__BANK 0xf000 + +#define ECC_ERROR_ADDRESS 0x630 +#define ECC_ERROR_ADDRESS__OFFSET 0x0fff +#define ECC_ERROR_ADDRESS__SECTOR_NR 0xf000 + +#define ERR_CORRECTION_INFO 0x640 +#define ERR_CORRECTION_INFO__BYTEMASK 0x00ff +#define ERR_CORRECTION_INFO__DEVICE_NR 0x0f00 +#define ERR_CORRECTION_INFO__ERROR_TYPE 0x4000 +#define ERR_CORRECTION_INFO__LAST_ERR_INFO 0x8000 + +#define DMA_ENABLE 0x700 +#define DMA_ENABLE__FLAG 0x0001 + +#define IGNORE_ECC_DONE 0x710 +#define IGNORE_ECC_DONE__FLAG 0x0001 + +#define DMA_INTR 0x720 +#define DMA_INTR__TARGET_ERROR 0x0001 +#define DMA_INTR__DESC_COMP_CHANNEL0 0x0002 +#define DMA_INTR__DESC_COMP_CHANNEL1 0x0004 +#define DMA_INTR__DESC_COMP_CHANNEL2 0x0008 +#define DMA_INTR__DESC_COMP_CHANNEL3 0x0010 +#define DMA_INTR__MEMCOPY_DESC_COMP 0x0020 + +#define DMA_INTR_EN 0x730 +#define DMA_INTR_EN__TARGET_ERROR 0x0001 +#define DMA_INTR_EN__DESC_COMP_CHANNEL0 0x0002 +#define DMA_INTR_EN__DESC_COMP_CHANNEL1 0x0004 +#define DMA_INTR_EN__DESC_COMP_CHANNEL2 0x0008 +#define DMA_INTR_EN__DESC_COMP_CHANNEL3 0x0010 +#define DMA_INTR_EN__MEMCOPY_DESC_COMP 0x0020 + +#define TARGET_ERR_ADDR_LO 0x740 +#define TARGET_ERR_ADDR_LO__VALUE 0xffff + +#define TARGET_ERR_ADDR_HI 0x750 +#define TARGET_ERR_ADDR_HI__VALUE 0xffff + +#define CHNL_ACTIVE 0x760 +#define CHNL_ACTIVE__CHANNEL0 0x0001 +#define CHNL_ACTIVE__CHANNEL1 0x0002 +#define CHNL_ACTIVE__CHANNEL2 0x0004 +#define CHNL_ACTIVE__CHANNEL3 0x0008 + +#define ACTIVE_SRC_ID 0x800 +#define ACTIVE_SRC_ID__VALUE 0x00ff + +#define PTN_INTR 0x810 +#define PTN_INTR__CONFIG_ERROR 0x0001 +#define PTN_INTR__ACCESS_ERROR_BANK0 0x0002 +#define PTN_INTR__ACCESS_ERROR_BANK1 0x0004 +#define PTN_INTR__ACCESS_ERROR_BANK2 0x0008 +#define PTN_INTR__ACCESS_ERROR_BANK3 0x0010 +#define PTN_INTR__REG_ACCESS_ERROR 0x0020 + +#define PTN_INTR_EN 0x820 +#define PTN_INTR_EN__CONFIG_ERROR 0x0001 +#define PTN_INTR_EN__ACCESS_ERROR_BANK0 0x0002 +#define PTN_INTR_EN__ACCESS_ERROR_BANK1 0x0004 +#define PTN_INTR_EN__ACCESS_ERROR_BANK2 0x0008 +#define PTN_INTR_EN__ACCESS_ERROR_BANK3 0x0010 +#define PTN_INTR_EN__REG_ACCESS_ERROR 0x0020 + +#define PERM_SRC_ID(__bank) (0x830 + ((__bank) * 0x40)) +#define PERM_SRC_ID__SRCID 0x00ff +#define PERM_SRC_ID__DIRECT_ACCESS_ACTIVE 0x0800 +#define PERM_SRC_ID__WRITE_ACTIVE 0x2000 +#define PERM_SRC_ID__READ_ACTIVE 0x4000 +#define PERM_SRC_ID__PARTITION_VALID 0x8000 + +#define MIN_BLK_ADDR(__bank) (0x840 + ((__bank) * 0x40)) +#define MIN_BLK_ADDR__VALUE 0xffff + +#define MAX_BLK_ADDR(__bank) (0x850 + ((__bank) * 0x40)) +#define MAX_BLK_ADDR__VALUE 0xffff + +#define MIN_MAX_BANK(__bank) (0x860 + ((__bank) * 0x40)) +#define MIN_MAX_BANK__MIN_VALUE 0x0003 +#define MIN_MAX_BANK__MAX_VALUE 0x000c + + +/* ffsdefs.h */ +#define CLEAR 0 /*use this to clear a field instead of "fail"*/ +#define SET 1 /*use this to set a field instead of "pass"*/ +#define FAIL 1 /*failed flag*/ +#define PASS 0 /*success flag*/ +#define ERR -1 /*error flag*/ + +/* lld.h */ +#define GOOD_BLOCK 0 +#define DEFECTIVE_BLOCK 1 +#define READ_ERROR 2 + +#define CLK_X 5 +#define CLK_MULTI 4 + +/* spectraswconfig.h */ +#define CMD_DMA 0 + +#define SPECTRA_PARTITION_ID 0 +/**** Block Table and Reserved Block Parameters *****/ +#define SPECTRA_START_BLOCK 3 +#define NUM_FREE_BLOCKS_GATE 30 + +/* KBV - Updated to LNW scratch register address */ +#define SCRATCH_REG_ADDR CONFIG_MTD_NAND_DENALI_SCRATCH_REG_ADDR +#define SCRATCH_REG_SIZE 64 + +#define GLOB_HWCTL_DEFAULT_BLKS 2048 + +#define SUPPORT_15BITECC 1 +#define SUPPORT_8BITECC 1 + +#define CUSTOM_CONF_PARAMS 0 + +#define ONFI_BLOOM_TIME 1 +#define MODE5_WORKAROUND 0 + +/* lld_nand.h */ +/* + * NAND Flash Controller Device Driver + * Copyright (c) 2009, Intel Corporation and its suppliers. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef _LLD_NAND_ +#define _LLD_NAND_ + +#define MODE_00 0x00000000 +#define MODE_01 0x04000000 +#define MODE_10 0x08000000 +#define MODE_11 0x0C000000 + + +#define DATA_TRANSFER_MODE 0 +#define PROTECTION_PER_BLOCK 1 +#define LOAD_WAIT_COUNT 2 +#define PROGRAM_WAIT_COUNT 3 +#define ERASE_WAIT_COUNT 4 +#define INT_MONITOR_CYCLE_COUNT 5 +#define READ_BUSY_PIN_ENABLED 6 +#define MULTIPLANE_OPERATION_SUPPORT 7 +#define PRE_FETCH_MODE 8 +#define CE_DONT_CARE_SUPPORT 9 +#define COPYBACK_SUPPORT 10 +#define CACHE_WRITE_SUPPORT 11 +#define CACHE_READ_SUPPORT 12 +#define NUM_PAGES_IN_BLOCK 13 +#define ECC_ENABLE_SELECT 14 +#define WRITE_ENABLE_2_READ_ENABLE 15 +#define ADDRESS_2_DATA 16 +#define READ_ENABLE_2_WRITE_ENABLE 17 +#define TWO_ROW_ADDRESS_CYCLES 18 +#define MULTIPLANE_ADDRESS_RESTRICT 19 +#define ACC_CLOCKS 20 +#define READ_WRITE_ENABLE_LOW_COUNT 21 +#define READ_WRITE_ENABLE_HIGH_COUNT 22 + +#define ECC_SECTOR_SIZE 512 + +struct nand_buf { + int head; + int tail; + uint8_t *buf; + dma_addr_t dma_buf; +}; + +#define INTEL_CE4100 1 +#define INTEL_MRST 2 +#define DT 3 + +struct denali_nand_info { + struct mtd_info mtd; + struct nand_chip nand; + int flash_bank; /* currently selected chip */ + int status; + int platform; + struct nand_buf buf; + struct device *dev; + int total_used_banks; + uint32_t block; /* stored for future use */ + uint16_t page; + void __iomem *flash_reg; /* Mapped io reg base address */ + void __iomem *flash_mem; /* Mapped io reg base address */ + + /* elements used by ISR */ + struct completion complete; + spinlock_t irq_lock; + uint32_t irq_status; + int irq_debug_array[32]; + int idx; + int irq; + + uint32_t devnum; /* represent how many nands connected */ + uint32_t fwblks; /* represent how many blocks FW used */ + uint32_t totalblks; + uint32_t blksperchip; + uint32_t bbtskipbytes; + uint32_t max_banks; +}; + +extern int denali_init(struct denali_nand_info *denali); +extern void denali_remove(struct denali_nand_info *denali); + +#endif /*_LLD_NAND_*/ diff --git a/drivers/mtd/nand/denali_dt.c b/drivers/mtd/nand/denali_dt.c new file mode 100644 index 00000000000..35cb17f5780 --- /dev/null +++ b/drivers/mtd/nand/denali_dt.c @@ -0,0 +1,132 @@ +/* + * NAND Flash Controller Device Driver for DT + * + * Copyright © 2011, Picochip. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/slab.h> + +#include "denali.h" + +struct denali_dt { + struct denali_nand_info denali; + struct clk *clk; +}; + +static const struct of_device_id denali_nand_dt_ids[] = { + { .compatible = "denali,denali-nand-dt" }, + { /* sentinel */ } + }; + +MODULE_DEVICE_TABLE(of, denali_nand_dt_ids); + +static u64 denali_dma_mask; + +static int denali_dt_probe(struct platform_device *ofdev) +{ + struct resource *denali_reg, *nand_data; + struct denali_dt *dt; + struct denali_nand_info *denali; + int ret; + const struct of_device_id *of_id; + + of_id = of_match_device(denali_nand_dt_ids, &ofdev->dev); + if (of_id) { + ofdev->id_entry = of_id->data; + } else { + pr_err("Failed to find the right device id.\n"); + return -ENOMEM; + } + + dt = devm_kzalloc(&ofdev->dev, sizeof(*dt), GFP_KERNEL); + if (!dt) + return -ENOMEM; + denali = &dt->denali; + + denali->platform = DT; + denali->dev = &ofdev->dev; + denali->irq = platform_get_irq(ofdev, 0); + if (denali->irq < 0) { + dev_err(&ofdev->dev, "no irq defined\n"); + return denali->irq; + } + + denali_reg = platform_get_resource_byname(ofdev, IORESOURCE_MEM, "denali_reg"); + denali->flash_reg = devm_ioremap_resource(&ofdev->dev, denali_reg); + if (IS_ERR(denali->flash_reg)) + return PTR_ERR(denali->flash_reg); + + nand_data = platform_get_resource_byname(ofdev, IORESOURCE_MEM, "nand_data"); + denali->flash_mem = devm_ioremap_resource(&ofdev->dev, nand_data); + if (IS_ERR(denali->flash_mem)) + return PTR_ERR(denali->flash_mem); + + if (!of_property_read_u32(ofdev->dev.of_node, + "dma-mask", (u32 *)&denali_dma_mask)) { + denali->dev->dma_mask = &denali_dma_mask; + } else { + denali->dev->dma_mask = NULL; + } + + dt->clk = devm_clk_get(&ofdev->dev, NULL); + if (IS_ERR(dt->clk)) { + dev_err(&ofdev->dev, "no clk available\n"); + return PTR_ERR(dt->clk); + } + clk_prepare_enable(dt->clk); + + ret = denali_init(denali); + if (ret) + goto out_disable_clk; + + platform_set_drvdata(ofdev, dt); + return 0; + +out_disable_clk: + clk_disable_unprepare(dt->clk); + + return ret; +} + +static int denali_dt_remove(struct platform_device *ofdev) +{ + struct denali_dt *dt = platform_get_drvdata(ofdev); + + denali_remove(&dt->denali); + clk_disable(dt->clk); + + return 0; +} + +static struct platform_driver denali_dt_driver = { + .probe = denali_dt_probe, + .remove = denali_dt_remove, + .driver = { + .name = "denali-nand-dt", + .owner = THIS_MODULE, + .of_match_table = denali_nand_dt_ids, + }, +}; + +module_platform_driver(denali_dt_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jamie Iles"); +MODULE_DESCRIPTION("DT driver for Denali NAND controller"); diff --git a/drivers/mtd/nand/denali_pci.c b/drivers/mtd/nand/denali_pci.c new file mode 100644 index 00000000000..6e2f387b823 --- /dev/null +++ b/drivers/mtd/nand/denali_pci.c @@ -0,0 +1,142 @@ +/* + * NAND Flash Controller Device Driver + * Copyright © 2009-2010, Intel Corporation and its suppliers. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/slab.h> + +#include "denali.h" + +#define DENALI_NAND_NAME "denali-nand-pci" + +/* List of platforms this NAND controller has be integrated into */ +static const struct pci_device_id denali_pci_ids[] = { + { PCI_VDEVICE(INTEL, 0x0701), INTEL_CE4100 }, + { PCI_VDEVICE(INTEL, 0x0809), INTEL_MRST }, + { /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE(pci, denali_pci_ids); + +static int denali_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + int ret = -ENODEV; + resource_size_t csr_base, mem_base; + unsigned long csr_len, mem_len; + struct denali_nand_info *denali; + + denali = kzalloc(sizeof(*denali), GFP_KERNEL); + if (!denali) + return -ENOMEM; + + ret = pci_enable_device(dev); + if (ret) { + pr_err("Spectra: pci_enable_device failed.\n"); + goto failed_alloc_memery; + } + + if (id->driver_data == INTEL_CE4100) { + denali->platform = INTEL_CE4100; + mem_base = pci_resource_start(dev, 0); + mem_len = pci_resource_len(dev, 1); + csr_base = pci_resource_start(dev, 1); + csr_len = pci_resource_len(dev, 1); + } else { + denali->platform = INTEL_MRST; + csr_base = pci_resource_start(dev, 0); + csr_len = pci_resource_len(dev, 0); + mem_base = pci_resource_start(dev, 1); + mem_len = pci_resource_len(dev, 1); + if (!mem_len) { + mem_base = csr_base + csr_len; + mem_len = csr_len; + } + } + + pci_set_master(dev); + denali->dev = &dev->dev; + denali->irq = dev->irq; + + ret = pci_request_regions(dev, DENALI_NAND_NAME); + if (ret) { + pr_err("Spectra: Unable to request memory regions\n"); + goto failed_enable_dev; + } + + denali->flash_reg = ioremap_nocache(csr_base, csr_len); + if (!denali->flash_reg) { + pr_err("Spectra: Unable to remap memory region\n"); + ret = -ENOMEM; + goto failed_req_regions; + } + + denali->flash_mem = ioremap_nocache(mem_base, mem_len); + if (!denali->flash_mem) { + pr_err("Spectra: ioremap_nocache failed!"); + ret = -ENOMEM; + goto failed_remap_reg; + } + + ret = denali_init(denali); + if (ret) + goto failed_remap_mem; + + pci_set_drvdata(dev, denali); + + return 0; + +failed_remap_mem: + iounmap(denali->flash_mem); +failed_remap_reg: + iounmap(denali->flash_reg); +failed_req_regions: + pci_release_regions(dev); +failed_enable_dev: + pci_disable_device(dev); +failed_alloc_memery: + kfree(denali); + + return ret; +} + +/* driver exit point */ +static void denali_pci_remove(struct pci_dev *dev) +{ + struct denali_nand_info *denali = pci_get_drvdata(dev); + + denali_remove(denali); + iounmap(denali->flash_reg); + iounmap(denali->flash_mem); + pci_release_regions(dev); + pci_disable_device(dev); + kfree(denali); +} + +static struct pci_driver denali_pci_driver = { + .name = DENALI_NAND_NAME, + .id_table = denali_pci_ids, + .probe = denali_pci_probe, + .remove = denali_pci_remove, +}; + +static int denali_init_pci(void) +{ + return pci_register_driver(&denali_pci_driver); +} +module_init(denali_init_pci); + +static void denali_exit_pci(void) +{ + pci_unregister_driver(&denali_pci_driver); +} +module_exit(denali_exit_pci); diff --git a/drivers/mtd/nand/diskonchip.c b/drivers/mtd/nand/diskonchip.c index 6107f532855..f68a7bccecd 100644 --- a/drivers/mtd/nand/diskonchip.c +++ b/drivers/mtd/nand/diskonchip.c @@ -15,8 +15,6 @@ * converted to the generic Reed-Solomon library by Thomas Gleixner <tglx@linutronix.de> * * Interface to generic NAND code for M-Systems DiskOnChip devices - * - * $Id: diskonchip.c,v 1.55 2005/11/07 11:14:30 gleixner Exp $ */ #include <linux/kernel.h> @@ -25,21 +23,22 @@ #include <linux/delay.h> #include <linux/rslib.h> #include <linux/moduleparam.h> +#include <linux/slab.h> #include <asm/io.h> #include <linux/mtd/mtd.h> #include <linux/mtd/nand.h> #include <linux/mtd/doc2000.h> -#include <linux/mtd/compatmac.h> #include <linux/mtd/partitions.h> #include <linux/mtd/inftl.h> +#include <linux/module.h> /* Where to look for the devices? */ #ifndef CONFIG_MTD_NAND_DISKONCHIP_PROBE_ADDRESS #define CONFIG_MTD_NAND_DISKONCHIP_PROBE_ADDRESS 0 #endif -static unsigned long __initdata doc_locations[] = { +static unsigned long doc_locations[] __initdata = { #if defined (__alpha__) || defined(__i386__) || defined(__x86_64__) #ifdef CONFIG_MTD_NAND_DISKONCHIP_PROBE_HIGH 0xfffc8000, 0xfffca000, 0xfffcc000, 0xfffce000, @@ -47,22 +46,13 @@ static unsigned long __initdata doc_locations[] = { 0xfffd8000, 0xfffda000, 0xfffdc000, 0xfffde000, 0xfffe0000, 0xfffe2000, 0xfffe4000, 0xfffe6000, 0xfffe8000, 0xfffea000, 0xfffec000, 0xfffee000, -#else /* CONFIG_MTD_DOCPROBE_HIGH */ +#else 0xc8000, 0xca000, 0xcc000, 0xce000, 0xd0000, 0xd2000, 0xd4000, 0xd6000, 0xd8000, 0xda000, 0xdc000, 0xde000, 0xe0000, 0xe2000, 0xe4000, 0xe6000, 0xe8000, 0xea000, 0xec000, 0xee000, -#endif /* CONFIG_MTD_DOCPROBE_HIGH */ -#elif defined(__PPC__) - 0xe4000000, -#elif defined(CONFIG_MOMENCO_OCELOT) - 0x2f000000, - 0xff000000, -#elif defined(CONFIG_MOMENCO_OCELOT_G) || defined (CONFIG_MOMENCO_OCELOT_C) - 0xff000000, -#else -#warning Unknown architecture for DiskOnChip. No default probe locations defined +#endif #endif 0xffffffff }; @@ -114,7 +104,7 @@ module_param(no_autopart, int, 0); static int show_firmware_partition = 0; module_param(show_firmware_partition, int, 0); -#ifdef MTD_NAND_DISKONCHIP_BBTWRITE +#ifdef CONFIG_MTD_NAND_DISKONCHIP_BBTWRITE static int inftl_bbt_write = 1; #else static int inftl_bbt_write = 0; @@ -141,12 +131,12 @@ static struct rs_control *rs_decoder; /* * The HW decoder in the DoC ASIC's provides us a error syndrome, - * which we must convert to a standard syndrom usable by the generic + * which we must convert to a standard syndrome usable by the generic * Reed-Solomon library code. * * Fabrice Bellard figured this out in the old docecc code. I added * some comments, improved a minor bit and converted it to make use - * of the generic Reed-Solomon libary. tglx + * of the generic Reed-Solomon library. tglx */ static int doc_ecc_decode(struct rs_control *rs, uint8_t *data, uint8_t *ecc) { @@ -154,6 +144,7 @@ static int doc_ecc_decode(struct rs_control *rs, uint8_t *data, uint8_t *ecc) uint8_t parity; uint16_t ds[4], s[5], tmp, errval[8], syn[4]; + memset(syn, 0, sizeof(syn)); /* Convert the ecc bytes into words */ ds[0] = ((ecc[4] & 0xff) >> 0) | ((ecc[5] & 0x03) << 8); ds[1] = ((ecc[5] & 0xfc) >> 2) | ((ecc[2] & 0x0f) << 6); @@ -161,7 +152,7 @@ static int doc_ecc_decode(struct rs_control *rs, uint8_t *data, uint8_t *ecc) ds[3] = ((ecc[3] & 0xc0) >> 6) | ((ecc[0] & 0xff) << 2); parity = ecc[1]; - /* Initialize the syndrom buffer */ + /* Initialize the syndrome buffer */ for (i = 0; i < NROOTS; i++) s[i] = ds[0]; /* @@ -177,9 +168,9 @@ static int doc_ecc_decode(struct rs_control *rs, uint8_t *data, uint8_t *ecc) s[i] ^= rs->alpha_to[rs_modnn(rs, tmp + (FCR + i) * j)]; } - /* Calc s[i] = s[i] / alpha^(v + i) */ + /* Calc syn[i] = s[i] / alpha^(v + i) */ for (i = 0; i < NROOTS; i++) { - if (syn[i]) + if (s[i]) syn[i] = rs_modnn(rs, rs->index_of[s[i]] + (NN - FCR - i)); } /* Call the decoder library */ @@ -225,7 +216,7 @@ static int doc_ecc_decode(struct rs_control *rs, uint8_t *data, uint8_t *ecc) } } /* If the parity is wrong, no rescue possible */ - return parity ? -1 : nerr; + return parity ? -EBADMSG : nerr; } static void DoC_Delay(struct doc_priv *doc, unsigned short cycles) @@ -383,19 +374,6 @@ static void doc2000_readbuf_dword(struct mtd_info *mtd, u_char *buf, int len) } } -static int doc2000_verifybuf(struct mtd_info *mtd, const u_char *buf, int len) -{ - struct nand_chip *this = mtd->priv; - struct doc_priv *doc = this->priv; - void __iomem *docptr = doc->virtadr; - int i; - - for (i = 0; i < len; i++) - if (buf[i] != ReadDOC(docptr, 2k_CDSN_IO)) - return -EFAULT; - return 0; -} - static uint16_t __init doc200x_ident_chip(struct mtd_info *mtd, int nr) { struct nand_chip *this = mtd->priv; @@ -408,7 +386,7 @@ static uint16_t __init doc200x_ident_chip(struct mtd_info *mtd, int nr) doc200x_hwcontrol(mtd, 0, NAND_CTRL_ALE | NAND_CTRL_CHANGE); doc200x_hwcontrol(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE); - /* We cant' use dev_ready here, but at least we wait for the + /* We can't use dev_ready here, but at least we wait for the * command to complete */ udelay(50); @@ -533,26 +511,6 @@ static void doc2001_readbuf(struct mtd_info *mtd, u_char *buf, int len) buf[i] = ReadDOC(docptr, LastDataRead); } -static int doc2001_verifybuf(struct mtd_info *mtd, const u_char *buf, int len) -{ - struct nand_chip *this = mtd->priv; - struct doc_priv *doc = this->priv; - void __iomem *docptr = doc->virtadr; - int i; - - /* Start read pipeline */ - ReadDOC(docptr, ReadPipeInit); - - for (i = 0; i < len - 1; i++) - if (buf[i] != ReadDOC(docptr, Mil_CDSN_IO)) { - ReadDOC(docptr, LastDataRead); - return i; - } - if (buf[i] != ReadDOC(docptr, LastDataRead)) - return i; - return 0; -} - static u_char doc2001plus_read_byte(struct mtd_info *mtd) { struct nand_chip *this = mtd->priv; @@ -617,33 +575,6 @@ static void doc2001plus_readbuf(struct mtd_info *mtd, u_char *buf, int len) printk("\n"); } -static int doc2001plus_verifybuf(struct mtd_info *mtd, const u_char *buf, int len) -{ - struct nand_chip *this = mtd->priv; - struct doc_priv *doc = this->priv; - void __iomem *docptr = doc->virtadr; - int i; - - if (debug) - printk("verifybuf of %d bytes: ", len); - - /* Start read pipeline */ - ReadDOC(docptr, Mplus_ReadPipeInit); - ReadDOC(docptr, Mplus_ReadPipeInit); - - for (i = 0; i < len - 2; i++) - if (buf[i] != ReadDOC(docptr, Mil_CDSN_IO)) { - ReadDOC(docptr, Mplus_LastDataRead); - ReadDOC(docptr, Mplus_LastDataRead); - return i; - } - if (buf[len - 2] != ReadDOC(docptr, Mplus_LastDataRead)) - return len - 2; - if (buf[len - 1] != ReadDOC(docptr, Mplus_LastDataRead)) - return len - 1; - return 0; -} - static void doc2001plus_select_chip(struct mtd_info *mtd, int chip) { struct nand_chip *this = mtd->priv; @@ -767,7 +698,8 @@ static void doc2001plus_command(struct mtd_info *mtd, unsigned command, int colu /* Serially input address */ if (column != -1) { /* Adjust columns for 16 bit buswidth */ - if (this->options & NAND_BUSWIDTH_16) + if (this->options & NAND_BUSWIDTH_16 && + !nand_opcode_8bits(command)) column >>= 1; WriteDOC(column, docptr, Mplus_FlashAddress); } @@ -994,7 +926,7 @@ static int doc200x_correct_data(struct mtd_info *mtd, u_char *dat, dummy = ReadDOC(docptr, ECCConf); } - /* Error occured ? */ + /* Error occurred ? */ if (dummy & 0x80) { for (i = 0; i < 6; i++) { if (DoC_is_MillenniumPlus(doc)) @@ -1039,7 +971,7 @@ static int doc200x_correct_data(struct mtd_info *mtd, u_char *dat, WriteDOC(DOC_ECC_DIS, docptr, Mplus_ECCConf); else WriteDOC(DOC_ECC_DIS, docptr, ECCConf); - if (no_ecc_failures && (ret == -1)) { + if (no_ecc_failures && mtd_is_eccerr(ret)) { printk(KERN_ERR "suppressing ECC failure\n"); ret = 0; } @@ -1065,7 +997,7 @@ static struct nand_ecclayout doc200x_oobinfo = { }; /* Find the (I)NFTL Media Header, and optionally also the mirror media header. - On sucessful return, buf will contain a copy of the media header for + On successful return, buf will contain a copy of the media header for further processing. id is the string to scan for, and will presumably be either "ANAND" or "BNAND". If findmirror=1, also look for the mirror media header. The page #s of the found media headers are placed in mh0_page and @@ -1079,7 +1011,7 @@ static int __init find_media_headers(struct mtd_info *mtd, u_char *buf, const ch size_t retlen; for (offs = 0; offs < mtd->size; offs += mtd->erasesize) { - ret = mtd->read(mtd, offs, mtd->writesize, &retlen, buf); + ret = mtd_read(mtd, offs, mtd->writesize, &retlen, buf); if (retlen != mtd->writesize) continue; if (ret) { @@ -1104,7 +1036,7 @@ static int __init find_media_headers(struct mtd_info *mtd, u_char *buf, const ch /* Only one mediaheader was found. We want buf to contain a mediaheader on return, so we'll have to re-read the one we found. */ offs = doc->mh0_page << this->page_shift; - ret = mtd->read(mtd, offs, mtd->writesize, &retlen, buf); + ret = mtd_read(mtd, offs, mtd->writesize, &retlen, buf); if (retlen != mtd->writesize) { /* Insanity. Give up. */ printk(KERN_ERR "Read DiskOnChip Media Header once, but can't reread it???\n"); @@ -1127,16 +1059,15 @@ static inline int __init nftl_partscan(struct mtd_info *mtd, struct mtd_partitio buf = kmalloc(mtd->writesize, GFP_KERNEL); if (!buf) { - printk(KERN_ERR "DiskOnChip mediaheader kmalloc failed!\n"); return 0; } if (!(numheaders = find_media_headers(mtd, buf, "ANAND", 1))) goto out; mh = (struct NFTLMediaHeader *)buf; - mh->NumEraseUnits = le16_to_cpu(mh->NumEraseUnits); - mh->FirstPhysicalEUN = le16_to_cpu(mh->FirstPhysicalEUN); - mh->FormattedSize = le32_to_cpu(mh->FormattedSize); + le16_to_cpus(&mh->NumEraseUnits); + le16_to_cpus(&mh->FirstPhysicalEUN); + le32_to_cpus(&mh->FormattedSize); printk(KERN_INFO " DataOrgID = %s\n" " NumEraseUnits = %d\n" @@ -1168,7 +1099,7 @@ static inline int __init nftl_partscan(struct mtd_info *mtd, struct mtd_partitio /* NOTE: The lines below modify internal variables of the NAND and MTD layers; variables with have already been configured by nand_scan. Unfortunately, we didn't know before this point what these values - should be. Thus, this code is somewhat dependant on the exact + should be. Thus, this code is somewhat dependent on the exact implementation of the NAND layer. */ if (mh->UnitSizeFactor != 0xff) { this->bbt_erase_shift += (0xff - mh->UnitSizeFactor); @@ -1235,7 +1166,6 @@ static inline int __init inftl_partscan(struct mtd_info *mtd, struct mtd_partiti buf = kmalloc(mtd->writesize, GFP_KERNEL); if (!buf) { - printk(KERN_ERR "DiskOnChip mediaheader kmalloc failed!\n"); return 0; } @@ -1244,12 +1174,12 @@ static inline int __init inftl_partscan(struct mtd_info *mtd, struct mtd_partiti doc->mh1_page = doc->mh0_page + (4096 >> this->page_shift); mh = (struct INFTLMediaHeader *)buf; - mh->NoOfBootImageBlocks = le32_to_cpu(mh->NoOfBootImageBlocks); - mh->NoOfBinaryPartitions = le32_to_cpu(mh->NoOfBinaryPartitions); - mh->NoOfBDTLPartitions = le32_to_cpu(mh->NoOfBDTLPartitions); - mh->BlockMultiplierBits = le32_to_cpu(mh->BlockMultiplierBits); - mh->FormatFlags = le32_to_cpu(mh->FormatFlags); - mh->PercentUsed = le32_to_cpu(mh->PercentUsed); + le32_to_cpus(&mh->NoOfBootImageBlocks); + le32_to_cpus(&mh->NoOfBinaryPartitions); + le32_to_cpus(&mh->NoOfBDTLPartitions); + le32_to_cpus(&mh->BlockMultiplierBits); + le32_to_cpus(&mh->FormatFlags); + le32_to_cpus(&mh->PercentUsed); printk(KERN_INFO " bootRecordID = %s\n" " NoOfBootImageBlocks = %d\n" @@ -1286,12 +1216,12 @@ static inline int __init inftl_partscan(struct mtd_info *mtd, struct mtd_partiti /* Scan the partitions */ for (i = 0; (i < 4); i++) { ip = &(mh->Partitions[i]); - ip->virtualUnits = le32_to_cpu(ip->virtualUnits); - ip->firstUnit = le32_to_cpu(ip->firstUnit); - ip->lastUnit = le32_to_cpu(ip->lastUnit); - ip->flags = le32_to_cpu(ip->flags); - ip->spareUnits = le32_to_cpu(ip->spareUnits); - ip->Reserved0 = le32_to_cpu(ip->Reserved0); + le32_to_cpus(&ip->virtualUnits); + le32_to_cpus(&ip->firstUnit); + le32_to_cpus(&ip->lastUnit); + le32_to_cpus(&ip->flags); + le32_to_cpus(&ip->spareUnits); + le32_to_cpus(&ip->Reserved0); printk(KERN_INFO " PARTITION[%d] ->\n" " virtualUnits = %d\n" @@ -1368,11 +1298,9 @@ static int __init nftl_scan_bbt(struct mtd_info *mtd) At least as nand_bbt.c is currently written. */ if ((ret = nand_scan_bbt(mtd, NULL))) return ret; - add_mtd_device(mtd); -#ifdef CONFIG_MTD_PARTITIONS + mtd_device_register(mtd, NULL, 0); if (!no_autopart) - add_mtd_partitions(mtd, parts, numparts); -#endif + mtd_device_register(mtd, parts, numparts); return 0; } @@ -1427,11 +1355,9 @@ static int __init inftl_scan_bbt(struct mtd_info *mtd) autopartitioning, but I want to give it more thought. */ if (!numparts) return -EIO; - add_mtd_device(mtd); -#ifdef CONFIG_MTD_PARTITIONS + mtd_device_register(mtd, NULL, 0); if (!no_autopart) - add_mtd_partitions(mtd, parts, numparts); -#endif + mtd_device_register(mtd, parts, numparts); return 0; } @@ -1443,7 +1369,6 @@ static inline int __init doc2000_init(struct mtd_info *mtd) this->read_byte = doc2000_read_byte; this->write_buf = doc2000_writebuf; this->read_buf = doc2000_readbuf; - this->verify_buf = doc2000_verifybuf; this->scan_bbt = nftl_scan_bbt; doc->CDSNControl = CDSN_CTRL_FLASH_IO | CDSN_CTRL_ECC_IO; @@ -1460,7 +1385,6 @@ static inline int __init doc2001_init(struct mtd_info *mtd) this->read_byte = doc2001_read_byte; this->write_buf = doc2001_writebuf; this->read_buf = doc2001_readbuf; - this->verify_buf = doc2001_verifybuf; ReadDOC(doc->virtadr, ChipID); ReadDOC(doc->virtadr, ChipID); @@ -1491,7 +1415,6 @@ static inline int __init doc2001plus_init(struct mtd_info *mtd) this->read_byte = doc2001plus_read_byte; this->write_buf = doc2001plus_writebuf; this->read_buf = doc2001plus_readbuf; - this->verify_buf = doc2001plus_verifybuf; this->scan_bbt = inftl_scan_bbt; this->cmd_ctrl = NULL; this->select_chip = doc2001plus_select_chip; @@ -1516,10 +1439,13 @@ static int __init doc_probe(unsigned long physadr) int reg, len, numchips; int ret = 0; + if (!request_mem_region(physadr, DOC_IOREMAP_LEN, "DiskOnChip")) + return -EBUSY; virtadr = ioremap(physadr, DOC_IOREMAP_LEN); if (!virtadr) { printk(KERN_ERR "Diskonchip ioremap failed: 0x%x bytes at 0x%lx\n", DOC_IOREMAP_LEN, physadr); - return -EIO; + ret = -EIO; + goto error_ioremap; } /* It's not possible to cleanly detect the DiskOnChip - the @@ -1635,13 +1561,11 @@ static int __init doc_probe(unsigned long physadr) len = sizeof(struct mtd_info) + sizeof(struct nand_chip) + sizeof(struct doc_priv) + (2 * sizeof(struct nand_bbt_descr)); - mtd = kmalloc(len, GFP_KERNEL); + mtd = kzalloc(len, GFP_KERNEL); if (!mtd) { - printk(KERN_ERR "DiskOnChip kmalloc (%d bytes) failed!\n", len); ret = -ENOMEM; goto fail; } - memset(mtd, 0, len); nand = (struct nand_chip *) (mtd + 1); doc = (struct doc_priv *) (nand + 1); @@ -1665,7 +1589,8 @@ static int __init doc_probe(unsigned long physadr) nand->ecc.mode = NAND_ECC_HW_SYNDROME; nand->ecc.size = 512; nand->ecc.bytes = 6; - nand->options = NAND_USE_FLASH_BBT; + nand->ecc.strength = 2; + nand->bbt_options = NAND_BBT_USE_FLASH; doc->physadr = physadr; doc->virtadr = virtadr; @@ -1687,9 +1612,9 @@ static int __init doc_probe(unsigned long physadr) /* DBB note: i believe nand_release is necessary here, as buffers may have been allocated in nand_base. Check with Thomas. FIX ME! */ - /* nand_release will call del_mtd_device, but we haven't yet - added it. This is handled without incident by - del_mtd_device, as far as I can tell. */ + /* nand_release will call mtd_device_unregister, but we + haven't yet added it. This is handled without incident by + mtd_device_unregister, as far as I can tell. */ nand_release(mtd); kfree(mtd); goto fail; @@ -1705,6 +1630,10 @@ static int __init doc_probe(unsigned long physadr) WriteDOC(save_control, virtadr, DOCControl); fail: iounmap(virtadr); + +error_ioremap: + release_mem_region(physadr, DOC_IOREMAP_LEN); + return ret; } @@ -1721,6 +1650,7 @@ static void release_nanddoc(void) nextmtd = doc->nextdoc; nand_release(mtd); iounmap(doc->virtadr); + release_mem_region(doc->physadr, DOC_IOREMAP_LEN); kfree(mtd); } } @@ -1783,4 +1713,4 @@ module_exit(cleanup_nanddoc); MODULE_LICENSE("GPL"); MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); -MODULE_DESCRIPTION("M-Systems DiskOnChip 2000, Millennium and Millennium Plus device driver\n"); +MODULE_DESCRIPTION("M-Systems DiskOnChip 2000, Millennium and Millennium Plus device driver"); diff --git a/drivers/mtd/nand/docg4.c b/drivers/mtd/nand/docg4.c new file mode 100644 index 00000000000..ce24637e14f --- /dev/null +++ b/drivers/mtd/nand/docg4.c @@ -0,0 +1,1394 @@ +/* + * Copyright © 2012 Mike Dunn <mikedunn@newsguy.com> + * + * mtd nand driver for M-Systems DiskOnChip G4 + * + * 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. + * + * Tested on the Palm Treo 680. The G4 is also present on Toshiba Portege, Asus + * P526, some HTC smartphones (Wizard, Prophet, ...), O2 XDA Zinc, maybe others. + * Should work on these as well. Let me know! + * + * TODO: + * + * Mechanism for management of password-protected areas + * + * Hamming ecc when reading oob only + * + * According to the M-Sys documentation, this device is also available in a + * "dual-die" configuration having a 256MB capacity, but no mechanism for + * detecting this variant is documented. Currently this driver assumes 128MB + * capacity. + * + * Support for multiple cascaded devices ("floors"). Not sure which gadgets + * contain multiple G4s in a cascaded configuration, if any. + * + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/string.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/export.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/bch.h> +#include <linux/bitrev.h> +#include <linux/jiffies.h> + +/* + * In "reliable mode" consecutive 2k pages are used in parallel (in some + * fashion) to store the same data. The data can be read back from the + * even-numbered pages in the normal manner; odd-numbered pages will appear to + * contain junk. Systems that boot from the docg4 typically write the secondary + * program loader (SPL) code in this mode. The SPL is loaded by the initial + * program loader (IPL, stored in the docg4's 2k NOR-like region that is mapped + * to the reset vector address). This module parameter enables you to use this + * driver to write the SPL. When in this mode, no more than 2k of data can be + * written at a time, because the addresses do not increment in the normal + * manner, and the starting offset must be within an even-numbered 2k region; + * i.e., invalid starting offsets are 0x800, 0xa00, 0xc00, 0xe00, 0x1800, + * 0x1a00, ... Reliable mode is a special case and should not be used unless + * you know what you're doing. + */ +static bool reliable_mode; +module_param(reliable_mode, bool, 0); +MODULE_PARM_DESC(reliable_mode, "pages are programmed in reliable mode"); + +/* + * You'll want to ignore badblocks if you're reading a partition that contains + * data written by the TrueFFS library (i.e., by PalmOS, Windows, etc), since + * it does not use mtd nand's method for marking bad blocks (using oob area). + * This will also skip the check of the "page written" flag. + */ +static bool ignore_badblocks; +module_param(ignore_badblocks, bool, 0); +MODULE_PARM_DESC(ignore_badblocks, "no badblock checking performed"); + +struct docg4_priv { + struct mtd_info *mtd; + struct device *dev; + void __iomem *virtadr; + int status; + struct { + unsigned int command; + int column; + int page; + } last_command; + uint8_t oob_buf[16]; + uint8_t ecc_buf[7]; + int oob_page; + struct bch_control *bch; +}; + +/* + * Defines prefixed with DOCG4 are unique to the diskonchip G4. All others are + * shared with other diskonchip devices (P3, G3 at least). + * + * Functions with names prefixed with docg4_ are mtd / nand interface functions + * (though they may also be called internally). All others are internal. + */ + +#define DOC_IOSPACE_DATA 0x0800 + +/* register offsets */ +#define DOC_CHIPID 0x1000 +#define DOC_DEVICESELECT 0x100a +#define DOC_ASICMODE 0x100c +#define DOC_DATAEND 0x101e +#define DOC_NOP 0x103e + +#define DOC_FLASHSEQUENCE 0x1032 +#define DOC_FLASHCOMMAND 0x1034 +#define DOC_FLASHADDRESS 0x1036 +#define DOC_FLASHCONTROL 0x1038 +#define DOC_ECCCONF0 0x1040 +#define DOC_ECCCONF1 0x1042 +#define DOC_HAMMINGPARITY 0x1046 +#define DOC_BCH_SYNDROM(idx) (0x1048 + idx) + +#define DOC_ASICMODECONFIRM 0x1072 +#define DOC_CHIPID_INV 0x1074 +#define DOC_POWERMODE 0x107c + +#define DOCG4_MYSTERY_REG 0x1050 + +/* apparently used only to write oob bytes 6 and 7 */ +#define DOCG4_OOB_6_7 0x1052 + +/* DOC_FLASHSEQUENCE register commands */ +#define DOC_SEQ_RESET 0x00 +#define DOCG4_SEQ_PAGE_READ 0x03 +#define DOCG4_SEQ_FLUSH 0x29 +#define DOCG4_SEQ_PAGEWRITE 0x16 +#define DOCG4_SEQ_PAGEPROG 0x1e +#define DOCG4_SEQ_BLOCKERASE 0x24 +#define DOCG4_SEQ_SETMODE 0x45 + +/* DOC_FLASHCOMMAND register commands */ +#define DOCG4_CMD_PAGE_READ 0x00 +#define DOC_CMD_ERASECYCLE2 0xd0 +#define DOCG4_CMD_FLUSH 0x70 +#define DOCG4_CMD_READ2 0x30 +#define DOC_CMD_PROG_BLOCK_ADDR 0x60 +#define DOCG4_CMD_PAGEWRITE 0x80 +#define DOC_CMD_PROG_CYCLE2 0x10 +#define DOCG4_CMD_FAST_MODE 0xa3 /* functionality guessed */ +#define DOC_CMD_RELIABLE_MODE 0x22 +#define DOC_CMD_RESET 0xff + +/* DOC_POWERMODE register bits */ +#define DOC_POWERDOWN_READY 0x80 + +/* DOC_FLASHCONTROL register bits */ +#define DOC_CTRL_CE 0x10 +#define DOC_CTRL_UNKNOWN 0x40 +#define DOC_CTRL_FLASHREADY 0x01 + +/* DOC_ECCCONF0 register bits */ +#define DOC_ECCCONF0_READ_MODE 0x8000 +#define DOC_ECCCONF0_UNKNOWN 0x2000 +#define DOC_ECCCONF0_ECC_ENABLE 0x1000 +#define DOC_ECCCONF0_DATA_BYTES_MASK 0x07ff + +/* DOC_ECCCONF1 register bits */ +#define DOC_ECCCONF1_BCH_SYNDROM_ERR 0x80 +#define DOC_ECCCONF1_ECC_ENABLE 0x07 +#define DOC_ECCCONF1_PAGE_IS_WRITTEN 0x20 + +/* DOC_ASICMODE register bits */ +#define DOC_ASICMODE_RESET 0x00 +#define DOC_ASICMODE_NORMAL 0x01 +#define DOC_ASICMODE_POWERDOWN 0x02 +#define DOC_ASICMODE_MDWREN 0x04 +#define DOC_ASICMODE_BDETCT_RESET 0x08 +#define DOC_ASICMODE_RSTIN_RESET 0x10 +#define DOC_ASICMODE_RAM_WE 0x20 + +/* good status values read after read/write/erase operations */ +#define DOCG4_PROGSTATUS_GOOD 0x51 +#define DOCG4_PROGSTATUS_GOOD_2 0xe0 + +/* + * On read operations (page and oob-only), the first byte read from I/O reg is a + * status. On error, it reads 0x73; otherwise, it reads either 0x71 (first read + * after reset only) or 0x51, so bit 1 is presumed to be an error indicator. + */ +#define DOCG4_READ_ERROR 0x02 /* bit 1 indicates read error */ + +/* anatomy of the device */ +#define DOCG4_CHIP_SIZE 0x8000000 +#define DOCG4_PAGE_SIZE 0x200 +#define DOCG4_PAGES_PER_BLOCK 0x200 +#define DOCG4_BLOCK_SIZE (DOCG4_PAGES_PER_BLOCK * DOCG4_PAGE_SIZE) +#define DOCG4_NUMBLOCKS (DOCG4_CHIP_SIZE / DOCG4_BLOCK_SIZE) +#define DOCG4_OOB_SIZE 0x10 +#define DOCG4_CHIP_SHIFT 27 /* log_2(DOCG4_CHIP_SIZE) */ +#define DOCG4_PAGE_SHIFT 9 /* log_2(DOCG4_PAGE_SIZE) */ +#define DOCG4_ERASE_SHIFT 18 /* log_2(DOCG4_BLOCK_SIZE) */ + +/* all but the last byte is included in ecc calculation */ +#define DOCG4_BCH_SIZE (DOCG4_PAGE_SIZE + DOCG4_OOB_SIZE - 1) + +#define DOCG4_USERDATA_LEN 520 /* 512 byte page plus 8 oob avail to user */ + +/* expected values from the ID registers */ +#define DOCG4_IDREG1_VALUE 0x0400 +#define DOCG4_IDREG2_VALUE 0xfbff + +/* primitive polynomial used to build the Galois field used by hw ecc gen */ +#define DOCG4_PRIMITIVE_POLY 0x4443 + +#define DOCG4_M 14 /* Galois field is of order 2^14 */ +#define DOCG4_T 4 /* BCH alg corrects up to 4 bit errors */ + +#define DOCG4_FACTORY_BBT_PAGE 16 /* page where read-only factory bbt lives */ +#define DOCG4_REDUNDANT_BBT_PAGE 24 /* page where redundant factory bbt lives */ + +/* + * Bytes 0, 1 are used as badblock marker. + * Bytes 2 - 6 are available to the user. + * Byte 7 is hamming ecc for first 7 oob bytes only. + * Bytes 8 - 14 are hw-generated ecc covering entire page + oob bytes 0 - 14. + * Byte 15 (the last) is used by the driver as a "page written" flag. + */ +static struct nand_ecclayout docg4_oobinfo = { + .eccbytes = 9, + .eccpos = {7, 8, 9, 10, 11, 12, 13, 14, 15}, + .oobavail = 5, + .oobfree = { {.offset = 2, .length = 5} } +}; + +/* + * The device has a nop register which M-Sys claims is for the purpose of + * inserting precise delays. But beware; at least some operations fail if the + * nop writes are replaced with a generic delay! + */ +static inline void write_nop(void __iomem *docptr) +{ + writew(0, docptr + DOC_NOP); +} + +static void docg4_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + int i; + struct nand_chip *nand = mtd->priv; + uint16_t *p = (uint16_t *) buf; + len >>= 1; + + for (i = 0; i < len; i++) + p[i] = readw(nand->IO_ADDR_R); +} + +static void docg4_write_buf16(struct mtd_info *mtd, const uint8_t *buf, int len) +{ + int i; + struct nand_chip *nand = mtd->priv; + uint16_t *p = (uint16_t *) buf; + len >>= 1; + + for (i = 0; i < len; i++) + writew(p[i], nand->IO_ADDR_W); +} + +static int poll_status(struct docg4_priv *doc) +{ + /* + * Busy-wait for the FLASHREADY bit to be set in the FLASHCONTROL + * register. Operations known to take a long time (e.g., block erase) + * should sleep for a while before calling this. + */ + + uint16_t flash_status; + unsigned long timeo; + void __iomem *docptr = doc->virtadr; + + dev_dbg(doc->dev, "%s...\n", __func__); + + /* hardware quirk requires reading twice initially */ + flash_status = readw(docptr + DOC_FLASHCONTROL); + + timeo = jiffies + msecs_to_jiffies(200); /* generous timeout */ + do { + cpu_relax(); + flash_status = readb(docptr + DOC_FLASHCONTROL); + } while (!(flash_status & DOC_CTRL_FLASHREADY) && + time_before(jiffies, timeo)); + + if (unlikely(!(flash_status & DOC_CTRL_FLASHREADY))) { + dev_err(doc->dev, "%s: timed out!\n", __func__); + return NAND_STATUS_FAIL; + } + + return 0; +} + + +static int docg4_wait(struct mtd_info *mtd, struct nand_chip *nand) +{ + + struct docg4_priv *doc = nand->priv; + int status = NAND_STATUS_WP; /* inverse logic?? */ + dev_dbg(doc->dev, "%s...\n", __func__); + + /* report any previously unreported error */ + if (doc->status) { + status |= doc->status; + doc->status = 0; + return status; + } + + status |= poll_status(doc); + return status; +} + +static void docg4_select_chip(struct mtd_info *mtd, int chip) +{ + /* + * Select among multiple cascaded chips ("floors"). Multiple floors are + * not yet supported, so the only valid non-negative value is 0. + */ + struct nand_chip *nand = mtd->priv; + struct docg4_priv *doc = nand->priv; + void __iomem *docptr = doc->virtadr; + + dev_dbg(doc->dev, "%s: chip %d\n", __func__, chip); + + if (chip < 0) + return; /* deselected */ + + if (chip > 0) + dev_warn(doc->dev, "multiple floors currently unsupported\n"); + + writew(0, docptr + DOC_DEVICESELECT); +} + +static void reset(struct mtd_info *mtd) +{ + /* full device reset */ + + struct nand_chip *nand = mtd->priv; + struct docg4_priv *doc = nand->priv; + void __iomem *docptr = doc->virtadr; + + writew(DOC_ASICMODE_RESET | DOC_ASICMODE_MDWREN, + docptr + DOC_ASICMODE); + writew(~(DOC_ASICMODE_RESET | DOC_ASICMODE_MDWREN), + docptr + DOC_ASICMODECONFIRM); + write_nop(docptr); + + writew(DOC_ASICMODE_NORMAL | DOC_ASICMODE_MDWREN, + docptr + DOC_ASICMODE); + writew(~(DOC_ASICMODE_NORMAL | DOC_ASICMODE_MDWREN), + docptr + DOC_ASICMODECONFIRM); + + writew(DOC_ECCCONF1_ECC_ENABLE, docptr + DOC_ECCCONF1); + + poll_status(doc); +} + +static void read_hw_ecc(void __iomem *docptr, uint8_t *ecc_buf) +{ + /* read the 7 hw-generated ecc bytes */ + + int i; + for (i = 0; i < 7; i++) { /* hw quirk; read twice */ + ecc_buf[i] = readb(docptr + DOC_BCH_SYNDROM(i)); + ecc_buf[i] = readb(docptr + DOC_BCH_SYNDROM(i)); + } +} + +static int correct_data(struct mtd_info *mtd, uint8_t *buf, int page) +{ + /* + * Called after a page read when hardware reports bitflips. + * Up to four bitflips can be corrected. + */ + + struct nand_chip *nand = mtd->priv; + struct docg4_priv *doc = nand->priv; + void __iomem *docptr = doc->virtadr; + int i, numerrs, errpos[4]; + const uint8_t blank_read_hwecc[8] = { + 0xcf, 0x72, 0xfc, 0x1b, 0xa9, 0xc7, 0xb9, 0 }; + + read_hw_ecc(docptr, doc->ecc_buf); /* read 7 hw-generated ecc bytes */ + + /* check if read error is due to a blank page */ + if (!memcmp(doc->ecc_buf, blank_read_hwecc, 7)) + return 0; /* yes */ + + /* skip additional check of "written flag" if ignore_badblocks */ + if (ignore_badblocks == false) { + + /* + * If the hw ecc bytes are not those of a blank page, there's + * still a chance that the page is blank, but was read with + * errors. Check the "written flag" in last oob byte, which + * is set to zero when a page is written. If more than half + * the bits are set, assume a blank page. Unfortunately, the + * bit flips(s) are not reported in stats. + */ + + if (nand->oob_poi[15]) { + int bit, numsetbits = 0; + unsigned long written_flag = nand->oob_poi[15]; + for_each_set_bit(bit, &written_flag, 8) + numsetbits++; + if (numsetbits > 4) { /* assume blank */ + dev_warn(doc->dev, + "error(s) in blank page " + "at offset %08x\n", + page * DOCG4_PAGE_SIZE); + return 0; + } + } + } + + /* + * The hardware ecc unit produces oob_ecc ^ calc_ecc. The kernel's bch + * algorithm is used to decode this. However the hw operates on page + * data in a bit order that is the reverse of that of the bch alg, + * requiring that the bits be reversed on the result. Thanks to Ivan + * Djelic for his analysis! + */ + for (i = 0; i < 7; i++) + doc->ecc_buf[i] = bitrev8(doc->ecc_buf[i]); + + numerrs = decode_bch(doc->bch, NULL, DOCG4_USERDATA_LEN, NULL, + doc->ecc_buf, NULL, errpos); + + if (numerrs == -EBADMSG) { + dev_warn(doc->dev, "uncorrectable errors at offset %08x\n", + page * DOCG4_PAGE_SIZE); + return -EBADMSG; + } + + BUG_ON(numerrs < 0); /* -EINVAL, or anything other than -EBADMSG */ + + /* undo last step in BCH alg (modulo mirroring not needed) */ + for (i = 0; i < numerrs; i++) + errpos[i] = (errpos[i] & ~7)|(7-(errpos[i] & 7)); + + /* fix the errors */ + for (i = 0; i < numerrs; i++) { + + /* ignore if error within oob ecc bytes */ + if (errpos[i] > DOCG4_USERDATA_LEN * 8) + continue; + + /* if error within oob area preceeding ecc bytes... */ + if (errpos[i] > DOCG4_PAGE_SIZE * 8) + change_bit(errpos[i] - DOCG4_PAGE_SIZE * 8, + (unsigned long *)nand->oob_poi); + + else /* error in page data */ + change_bit(errpos[i], (unsigned long *)buf); + } + + dev_notice(doc->dev, "%d error(s) corrected at offset %08x\n", + numerrs, page * DOCG4_PAGE_SIZE); + + return numerrs; +} + +static uint8_t docg4_read_byte(struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd->priv; + struct docg4_priv *doc = nand->priv; + + dev_dbg(doc->dev, "%s\n", __func__); + + if (doc->last_command.command == NAND_CMD_STATUS) { + int status; + + /* + * Previous nand command was status request, so nand + * infrastructure code expects to read the status here. If an + * error occurred in a previous operation, report it. + */ + doc->last_command.command = 0; + + if (doc->status) { + status = doc->status; + doc->status = 0; + } + + /* why is NAND_STATUS_WP inverse logic?? */ + else + status = NAND_STATUS_WP | NAND_STATUS_READY; + + return status; + } + + dev_warn(doc->dev, "unexpected call to read_byte()\n"); + + return 0; +} + +static void write_addr(struct docg4_priv *doc, uint32_t docg4_addr) +{ + /* write the four address bytes packed in docg4_addr to the device */ + + void __iomem *docptr = doc->virtadr; + writeb(docg4_addr & 0xff, docptr + DOC_FLASHADDRESS); + docg4_addr >>= 8; + writeb(docg4_addr & 0xff, docptr + DOC_FLASHADDRESS); + docg4_addr >>= 8; + writeb(docg4_addr & 0xff, docptr + DOC_FLASHADDRESS); + docg4_addr >>= 8; + writeb(docg4_addr & 0xff, docptr + DOC_FLASHADDRESS); +} + +static int read_progstatus(struct docg4_priv *doc) +{ + /* + * This apparently checks the status of programming. Done after an + * erasure, and after page data is written. On error, the status is + * saved, to be later retrieved by the nand infrastructure code. + */ + void __iomem *docptr = doc->virtadr; + + /* status is read from the I/O reg */ + uint16_t status1 = readw(docptr + DOC_IOSPACE_DATA); + uint16_t status2 = readw(docptr + DOC_IOSPACE_DATA); + uint16_t status3 = readw(docptr + DOCG4_MYSTERY_REG); + + dev_dbg(doc->dev, "docg4: %s: %02x %02x %02x\n", + __func__, status1, status2, status3); + + if (status1 != DOCG4_PROGSTATUS_GOOD + || status2 != DOCG4_PROGSTATUS_GOOD_2 + || status3 != DOCG4_PROGSTATUS_GOOD_2) { + doc->status = NAND_STATUS_FAIL; + dev_warn(doc->dev, "read_progstatus failed: " + "%02x, %02x, %02x\n", status1, status2, status3); + return -EIO; + } + return 0; +} + +static int pageprog(struct mtd_info *mtd) +{ + /* + * Final step in writing a page. Writes the contents of its + * internal buffer out to the flash array, or some such. + */ + + struct nand_chip *nand = mtd->priv; + struct docg4_priv *doc = nand->priv; + void __iomem *docptr = doc->virtadr; + int retval = 0; + + dev_dbg(doc->dev, "docg4: %s\n", __func__); + + writew(DOCG4_SEQ_PAGEPROG, docptr + DOC_FLASHSEQUENCE); + writew(DOC_CMD_PROG_CYCLE2, docptr + DOC_FLASHCOMMAND); + write_nop(docptr); + write_nop(docptr); + + /* Just busy-wait; usleep_range() slows things down noticeably. */ + poll_status(doc); + + writew(DOCG4_SEQ_FLUSH, docptr + DOC_FLASHSEQUENCE); + writew(DOCG4_CMD_FLUSH, docptr + DOC_FLASHCOMMAND); + writew(DOC_ECCCONF0_READ_MODE | 4, docptr + DOC_ECCCONF0); + write_nop(docptr); + write_nop(docptr); + write_nop(docptr); + write_nop(docptr); + write_nop(docptr); + + retval = read_progstatus(doc); + writew(0, docptr + DOC_DATAEND); + write_nop(docptr); + poll_status(doc); + write_nop(docptr); + + return retval; +} + +static void sequence_reset(struct mtd_info *mtd) +{ + /* common starting sequence for all operations */ + + struct nand_chip *nand = mtd->priv; + struct docg4_priv *doc = nand->priv; + void __iomem *docptr = doc->virtadr; + + writew(DOC_CTRL_UNKNOWN | DOC_CTRL_CE, docptr + DOC_FLASHCONTROL); + writew(DOC_SEQ_RESET, docptr + DOC_FLASHSEQUENCE); + writew(DOC_CMD_RESET, docptr + DOC_FLASHCOMMAND); + write_nop(docptr); + write_nop(docptr); + poll_status(doc); + write_nop(docptr); +} + +static void read_page_prologue(struct mtd_info *mtd, uint32_t docg4_addr) +{ + /* first step in reading a page */ + + struct nand_chip *nand = mtd->priv; + struct docg4_priv *doc = nand->priv; + void __iomem *docptr = doc->virtadr; + + dev_dbg(doc->dev, + "docg4: %s: g4 page %08x\n", __func__, docg4_addr); + + sequence_reset(mtd); + + writew(DOCG4_SEQ_PAGE_READ, docptr + DOC_FLASHSEQUENCE); + writew(DOCG4_CMD_PAGE_READ, docptr + DOC_FLASHCOMMAND); + write_nop(docptr); + + write_addr(doc, docg4_addr); + + write_nop(docptr); + writew(DOCG4_CMD_READ2, docptr + DOC_FLASHCOMMAND); + write_nop(docptr); + write_nop(docptr); + + poll_status(doc); +} + +static void write_page_prologue(struct mtd_info *mtd, uint32_t docg4_addr) +{ + /* first step in writing a page */ + + struct nand_chip *nand = mtd->priv; + struct docg4_priv *doc = nand->priv; + void __iomem *docptr = doc->virtadr; + + dev_dbg(doc->dev, + "docg4: %s: g4 addr: %x\n", __func__, docg4_addr); + sequence_reset(mtd); + + if (unlikely(reliable_mode)) { + writew(DOCG4_SEQ_SETMODE, docptr + DOC_FLASHSEQUENCE); + writew(DOCG4_CMD_FAST_MODE, docptr + DOC_FLASHCOMMAND); + writew(DOC_CMD_RELIABLE_MODE, docptr + DOC_FLASHCOMMAND); + write_nop(docptr); + } + + writew(DOCG4_SEQ_PAGEWRITE, docptr + DOC_FLASHSEQUENCE); + writew(DOCG4_CMD_PAGEWRITE, docptr + DOC_FLASHCOMMAND); + write_nop(docptr); + write_addr(doc, docg4_addr); + write_nop(docptr); + write_nop(docptr); + poll_status(doc); +} + +static uint32_t mtd_to_docg4_address(int page, int column) +{ + /* + * Convert mtd address to format used by the device, 32 bit packed. + * + * Some notes on G4 addressing... The M-Sys documentation on this device + * claims that pages are 2K in length, and indeed, the format of the + * address used by the device reflects that. But within each page are + * four 512 byte "sub-pages", each with its own oob data that is + * read/written immediately after the 512 bytes of page data. This oob + * data contains the ecc bytes for the preceeding 512 bytes. + * + * Rather than tell the mtd nand infrastructure that page size is 2k, + * with four sub-pages each, we engage in a little subterfuge and tell + * the infrastructure code that pages are 512 bytes in size. This is + * done because during the course of reverse-engineering the device, I + * never observed an instance where an entire 2K "page" was read or + * written as a unit. Each "sub-page" is always addressed individually, + * its data read/written, and ecc handled before the next "sub-page" is + * addressed. + * + * This requires us to convert addresses passed by the mtd nand + * infrastructure code to those used by the device. + * + * The address that is written to the device consists of four bytes: the + * first two are the 2k page number, and the second is the index into + * the page. The index is in terms of 16-bit half-words and includes + * the preceeding oob data, so e.g., the index into the second + * "sub-page" is 0x108, and the full device address of the start of mtd + * page 0x201 is 0x00800108. + */ + int g4_page = page / 4; /* device's 2K page */ + int g4_index = (page % 4) * 0x108 + column/2; /* offset into page */ + return (g4_page << 16) | g4_index; /* pack */ +} + +static void docg4_command(struct mtd_info *mtd, unsigned command, int column, + int page_addr) +{ + /* handle standard nand commands */ + + struct nand_chip *nand = mtd->priv; + struct docg4_priv *doc = nand->priv; + uint32_t g4_addr = mtd_to_docg4_address(page_addr, column); + + dev_dbg(doc->dev, "%s %x, page_addr=%x, column=%x\n", + __func__, command, page_addr, column); + + /* + * Save the command and its arguments. This enables emulation of + * standard flash devices, and also some optimizations. + */ + doc->last_command.command = command; + doc->last_command.column = column; + doc->last_command.page = page_addr; + + switch (command) { + + case NAND_CMD_RESET: + reset(mtd); + break; + + case NAND_CMD_READ0: + read_page_prologue(mtd, g4_addr); + break; + + case NAND_CMD_STATUS: + /* next call to read_byte() will expect a status */ + break; + + case NAND_CMD_SEQIN: + if (unlikely(reliable_mode)) { + uint16_t g4_page = g4_addr >> 16; + + /* writes to odd-numbered 2k pages are invalid */ + if (g4_page & 0x01) + dev_warn(doc->dev, + "invalid reliable mode address\n"); + } + + write_page_prologue(mtd, g4_addr); + + /* hack for deferred write of oob bytes */ + if (doc->oob_page == page_addr) + memcpy(nand->oob_poi, doc->oob_buf, 16); + break; + + case NAND_CMD_PAGEPROG: + pageprog(mtd); + break; + + /* we don't expect these, based on review of nand_base.c */ + case NAND_CMD_READOOB: + case NAND_CMD_READID: + case NAND_CMD_ERASE1: + case NAND_CMD_ERASE2: + dev_warn(doc->dev, "docg4_command: " + "unexpected nand command 0x%x\n", command); + break; + + } +} + +static int read_page(struct mtd_info *mtd, struct nand_chip *nand, + uint8_t *buf, int page, bool use_ecc) +{ + struct docg4_priv *doc = nand->priv; + void __iomem *docptr = doc->virtadr; + uint16_t status, edc_err, *buf16; + int bits_corrected = 0; + + dev_dbg(doc->dev, "%s: page %08x\n", __func__, page); + + writew(DOC_ECCCONF0_READ_MODE | + DOC_ECCCONF0_ECC_ENABLE | + DOC_ECCCONF0_UNKNOWN | + DOCG4_BCH_SIZE, + docptr + DOC_ECCCONF0); + write_nop(docptr); + write_nop(docptr); + write_nop(docptr); + write_nop(docptr); + write_nop(docptr); + + /* the 1st byte from the I/O reg is a status; the rest is page data */ + status = readw(docptr + DOC_IOSPACE_DATA); + if (status & DOCG4_READ_ERROR) { + dev_err(doc->dev, + "docg4_read_page: bad status: 0x%02x\n", status); + writew(0, docptr + DOC_DATAEND); + return -EIO; + } + + dev_dbg(doc->dev, "%s: status = 0x%x\n", __func__, status); + + docg4_read_buf(mtd, buf, DOCG4_PAGE_SIZE); /* read the page data */ + + /* this device always reads oob after page data */ + /* first 14 oob bytes read from I/O reg */ + docg4_read_buf(mtd, nand->oob_poi, 14); + + /* last 2 read from another reg */ + buf16 = (uint16_t *)(nand->oob_poi + 14); + *buf16 = readw(docptr + DOCG4_MYSTERY_REG); + + write_nop(docptr); + + if (likely(use_ecc == true)) { + + /* read the register that tells us if bitflip(s) detected */ + edc_err = readw(docptr + DOC_ECCCONF1); + edc_err = readw(docptr + DOC_ECCCONF1); + dev_dbg(doc->dev, "%s: edc_err = 0x%02x\n", __func__, edc_err); + + /* If bitflips are reported, attempt to correct with ecc */ + if (edc_err & DOC_ECCCONF1_BCH_SYNDROM_ERR) { + bits_corrected = correct_data(mtd, buf, page); + if (bits_corrected == -EBADMSG) + mtd->ecc_stats.failed++; + else + mtd->ecc_stats.corrected += bits_corrected; + } + } + + writew(0, docptr + DOC_DATAEND); + if (bits_corrected == -EBADMSG) /* uncorrectable errors */ + return 0; + return bits_corrected; +} + + +static int docg4_read_page_raw(struct mtd_info *mtd, struct nand_chip *nand, + uint8_t *buf, int oob_required, int page) +{ + return read_page(mtd, nand, buf, page, false); +} + +static int docg4_read_page(struct mtd_info *mtd, struct nand_chip *nand, + uint8_t *buf, int oob_required, int page) +{ + return read_page(mtd, nand, buf, page, true); +} + +static int docg4_read_oob(struct mtd_info *mtd, struct nand_chip *nand, + int page) +{ + struct docg4_priv *doc = nand->priv; + void __iomem *docptr = doc->virtadr; + uint16_t status; + + dev_dbg(doc->dev, "%s: page %x\n", __func__, page); + + docg4_command(mtd, NAND_CMD_READ0, nand->ecc.size, page); + + writew(DOC_ECCCONF0_READ_MODE | DOCG4_OOB_SIZE, docptr + DOC_ECCCONF0); + write_nop(docptr); + write_nop(docptr); + write_nop(docptr); + write_nop(docptr); + write_nop(docptr); + + /* the 1st byte from the I/O reg is a status; the rest is oob data */ + status = readw(docptr + DOC_IOSPACE_DATA); + if (status & DOCG4_READ_ERROR) { + dev_warn(doc->dev, + "docg4_read_oob failed: status = 0x%02x\n", status); + return -EIO; + } + + dev_dbg(doc->dev, "%s: status = 0x%x\n", __func__, status); + + docg4_read_buf(mtd, nand->oob_poi, 16); + + write_nop(docptr); + write_nop(docptr); + write_nop(docptr); + writew(0, docptr + DOC_DATAEND); + write_nop(docptr); + + return 0; +} + +static int docg4_erase_block(struct mtd_info *mtd, int page) +{ + struct nand_chip *nand = mtd->priv; + struct docg4_priv *doc = nand->priv; + void __iomem *docptr = doc->virtadr; + uint16_t g4_page; + + dev_dbg(doc->dev, "%s: page %04x\n", __func__, page); + + sequence_reset(mtd); + + writew(DOCG4_SEQ_BLOCKERASE, docptr + DOC_FLASHSEQUENCE); + writew(DOC_CMD_PROG_BLOCK_ADDR, docptr + DOC_FLASHCOMMAND); + write_nop(docptr); + + /* only 2 bytes of address are written to specify erase block */ + g4_page = (uint16_t)(page / 4); /* to g4's 2k page addressing */ + writeb(g4_page & 0xff, docptr + DOC_FLASHADDRESS); + g4_page >>= 8; + writeb(g4_page & 0xff, docptr + DOC_FLASHADDRESS); + write_nop(docptr); + + /* start the erasure */ + writew(DOC_CMD_ERASECYCLE2, docptr + DOC_FLASHCOMMAND); + write_nop(docptr); + write_nop(docptr); + + usleep_range(500, 1000); /* erasure is long; take a snooze */ + poll_status(doc); + writew(DOCG4_SEQ_FLUSH, docptr + DOC_FLASHSEQUENCE); + writew(DOCG4_CMD_FLUSH, docptr + DOC_FLASHCOMMAND); + writew(DOC_ECCCONF0_READ_MODE | 4, docptr + DOC_ECCCONF0); + write_nop(docptr); + write_nop(docptr); + write_nop(docptr); + write_nop(docptr); + write_nop(docptr); + + read_progstatus(doc); + + writew(0, docptr + DOC_DATAEND); + write_nop(docptr); + poll_status(doc); + write_nop(docptr); + + return nand->waitfunc(mtd, nand); +} + +static int write_page(struct mtd_info *mtd, struct nand_chip *nand, + const uint8_t *buf, bool use_ecc) +{ + struct docg4_priv *doc = nand->priv; + void __iomem *docptr = doc->virtadr; + uint8_t ecc_buf[8]; + + dev_dbg(doc->dev, "%s...\n", __func__); + + writew(DOC_ECCCONF0_ECC_ENABLE | + DOC_ECCCONF0_UNKNOWN | + DOCG4_BCH_SIZE, + docptr + DOC_ECCCONF0); + write_nop(docptr); + + /* write the page data */ + docg4_write_buf16(mtd, buf, DOCG4_PAGE_SIZE); + + /* oob bytes 0 through 5 are written to I/O reg */ + docg4_write_buf16(mtd, nand->oob_poi, 6); + + /* oob byte 6 written to a separate reg */ + writew(nand->oob_poi[6], docptr + DOCG4_OOB_6_7); + + write_nop(docptr); + write_nop(docptr); + + /* write hw-generated ecc bytes to oob */ + if (likely(use_ecc == true)) { + /* oob byte 7 is hamming code */ + uint8_t hamming = readb(docptr + DOC_HAMMINGPARITY); + hamming = readb(docptr + DOC_HAMMINGPARITY); /* 2nd read */ + writew(hamming, docptr + DOCG4_OOB_6_7); + write_nop(docptr); + + /* read the 7 bch bytes from ecc regs */ + read_hw_ecc(docptr, ecc_buf); + ecc_buf[7] = 0; /* clear the "page written" flag */ + } + + /* write user-supplied bytes to oob */ + else { + writew(nand->oob_poi[7], docptr + DOCG4_OOB_6_7); + write_nop(docptr); + memcpy(ecc_buf, &nand->oob_poi[8], 8); + } + + docg4_write_buf16(mtd, ecc_buf, 8); + write_nop(docptr); + write_nop(docptr); + writew(0, docptr + DOC_DATAEND); + write_nop(docptr); + + return 0; +} + +static int docg4_write_page_raw(struct mtd_info *mtd, struct nand_chip *nand, + const uint8_t *buf, int oob_required) +{ + return write_page(mtd, nand, buf, false); +} + +static int docg4_write_page(struct mtd_info *mtd, struct nand_chip *nand, + const uint8_t *buf, int oob_required) +{ + return write_page(mtd, nand, buf, true); +} + +static int docg4_write_oob(struct mtd_info *mtd, struct nand_chip *nand, + int page) +{ + /* + * Writing oob-only is not really supported, because MLC nand must write + * oob bytes at the same time as page data. Nonetheless, we save the + * oob buffer contents here, and then write it along with the page data + * if the same page is subsequently written. This allows user space + * utilities that write the oob data prior to the page data to work + * (e.g., nandwrite). The disdvantage is that, if the intention was to + * write oob only, the operation is quietly ignored. Also, oob can get + * corrupted if two concurrent processes are running nandwrite. + */ + + /* note that bytes 7..14 are hw generated hamming/ecc and overwritten */ + struct docg4_priv *doc = nand->priv; + doc->oob_page = page; + memcpy(doc->oob_buf, nand->oob_poi, 16); + return 0; +} + +static int __init read_factory_bbt(struct mtd_info *mtd) +{ + /* + * The device contains a read-only factory bad block table. Read it and + * update the memory-based bbt accordingly. + */ + + struct nand_chip *nand = mtd->priv; + struct docg4_priv *doc = nand->priv; + uint32_t g4_addr = mtd_to_docg4_address(DOCG4_FACTORY_BBT_PAGE, 0); + uint8_t *buf; + int i, block; + __u32 eccfailed_stats = mtd->ecc_stats.failed; + + buf = kzalloc(DOCG4_PAGE_SIZE, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + read_page_prologue(mtd, g4_addr); + docg4_read_page(mtd, nand, buf, 0, DOCG4_FACTORY_BBT_PAGE); + + /* + * If no memory-based bbt was created, exit. This will happen if module + * parameter ignore_badblocks is set. Then why even call this function? + * For an unknown reason, block erase always fails if it's the first + * operation after device power-up. The above read ensures it never is. + * Ugly, I know. + */ + if (nand->bbt == NULL) /* no memory-based bbt */ + goto exit; + + if (mtd->ecc_stats.failed > eccfailed_stats) { + /* + * Whoops, an ecc failure ocurred reading the factory bbt. + * It is stored redundantly, so we get another chance. + */ + eccfailed_stats = mtd->ecc_stats.failed; + docg4_read_page(mtd, nand, buf, 0, DOCG4_REDUNDANT_BBT_PAGE); + if (mtd->ecc_stats.failed > eccfailed_stats) { + dev_warn(doc->dev, + "The factory bbt could not be read!\n"); + goto exit; + } + } + + /* + * Parse factory bbt and update memory-based bbt. Factory bbt format is + * simple: one bit per block, block numbers increase left to right (msb + * to lsb). Bit clear means bad block. + */ + for (i = block = 0; block < DOCG4_NUMBLOCKS; block += 8, i++) { + int bitnum; + unsigned long bits = ~buf[i]; + for_each_set_bit(bitnum, &bits, 8) { + int badblock = block + 7 - bitnum; + nand->bbt[badblock / 4] |= + 0x03 << ((badblock % 4) * 2); + mtd->ecc_stats.badblocks++; + dev_notice(doc->dev, "factory-marked bad block: %d\n", + badblock); + } + } + exit: + kfree(buf); + return 0; +} + +static int docg4_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ + /* + * Mark a block as bad. Bad blocks are marked in the oob area of the + * first page of the block. The default scan_bbt() in the nand + * infrastructure code works fine for building the memory-based bbt + * during initialization, as does the nand infrastructure function that + * checks if a block is bad by reading the bbt. This function replaces + * the nand default because writes to oob-only are not supported. + */ + + int ret, i; + uint8_t *buf; + struct nand_chip *nand = mtd->priv; + struct docg4_priv *doc = nand->priv; + struct nand_bbt_descr *bbtd = nand->badblock_pattern; + int page = (int)(ofs >> nand->page_shift); + uint32_t g4_addr = mtd_to_docg4_address(page, 0); + + dev_dbg(doc->dev, "%s: %08llx\n", __func__, ofs); + + if (unlikely(ofs & (DOCG4_BLOCK_SIZE - 1))) + dev_warn(doc->dev, "%s: ofs %llx not start of block!\n", + __func__, ofs); + + /* allocate blank buffer for page data */ + buf = kzalloc(DOCG4_PAGE_SIZE, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* write bit-wise negation of pattern to oob buffer */ + memset(nand->oob_poi, 0xff, mtd->oobsize); + for (i = 0; i < bbtd->len; i++) + nand->oob_poi[bbtd->offs + i] = ~bbtd->pattern[i]; + + /* write first page of block */ + write_page_prologue(mtd, g4_addr); + docg4_write_page(mtd, nand, buf, 1); + ret = pageprog(mtd); + + kfree(buf); + + return ret; +} + +static int docg4_block_neverbad(struct mtd_info *mtd, loff_t ofs, int getchip) +{ + /* only called when module_param ignore_badblocks is set */ + return 0; +} + +static int docg4_suspend(struct platform_device *pdev, pm_message_t state) +{ + /* + * Put the device into "deep power-down" mode. Note that CE# must be + * deasserted for this to take effect. The xscale, e.g., can be + * configured to float this signal when the processor enters power-down, + * and a suitable pull-up ensures its deassertion. + */ + + int i; + uint8_t pwr_down; + struct docg4_priv *doc = platform_get_drvdata(pdev); + void __iomem *docptr = doc->virtadr; + + dev_dbg(doc->dev, "%s...\n", __func__); + + /* poll the register that tells us we're ready to go to sleep */ + for (i = 0; i < 10; i++) { + pwr_down = readb(docptr + DOC_POWERMODE); + if (pwr_down & DOC_POWERDOWN_READY) + break; + usleep_range(1000, 4000); + } + + if (pwr_down & DOC_POWERDOWN_READY) { + dev_err(doc->dev, "suspend failed; " + "timeout polling DOC_POWERDOWN_READY\n"); + return -EIO; + } + + writew(DOC_ASICMODE_POWERDOWN | DOC_ASICMODE_MDWREN, + docptr + DOC_ASICMODE); + writew(~(DOC_ASICMODE_POWERDOWN | DOC_ASICMODE_MDWREN), + docptr + DOC_ASICMODECONFIRM); + + write_nop(docptr); + + return 0; +} + +static int docg4_resume(struct platform_device *pdev) +{ + + /* + * Exit power-down. Twelve consecutive reads of the address below + * accomplishes this, assuming CE# has been asserted. + */ + + struct docg4_priv *doc = platform_get_drvdata(pdev); + void __iomem *docptr = doc->virtadr; + int i; + + dev_dbg(doc->dev, "%s...\n", __func__); + + for (i = 0; i < 12; i++) + readb(docptr + 0x1fff); + + return 0; +} + +static void __init init_mtd_structs(struct mtd_info *mtd) +{ + /* initialize mtd and nand data structures */ + + /* + * Note that some of the following initializations are not usually + * required within a nand driver because they are performed by the nand + * infrastructure code as part of nand_scan(). In this case they need + * to be initialized here because we skip call to nand_scan_ident() (the + * first half of nand_scan()). The call to nand_scan_ident() is skipped + * because for this device the chip id is not read in the manner of a + * standard nand device. Unfortunately, nand_scan_ident() does other + * things as well, such as call nand_set_defaults(). + */ + + struct nand_chip *nand = mtd->priv; + struct docg4_priv *doc = nand->priv; + + mtd->size = DOCG4_CHIP_SIZE; + mtd->name = "Msys_Diskonchip_G4"; + mtd->writesize = DOCG4_PAGE_SIZE; + mtd->erasesize = DOCG4_BLOCK_SIZE; + mtd->oobsize = DOCG4_OOB_SIZE; + nand->chipsize = DOCG4_CHIP_SIZE; + nand->chip_shift = DOCG4_CHIP_SHIFT; + nand->bbt_erase_shift = nand->phys_erase_shift = DOCG4_ERASE_SHIFT; + nand->chip_delay = 20; + nand->page_shift = DOCG4_PAGE_SHIFT; + nand->pagemask = 0x3ffff; + nand->badblockpos = NAND_LARGE_BADBLOCK_POS; + nand->badblockbits = 8; + nand->ecc.layout = &docg4_oobinfo; + nand->ecc.mode = NAND_ECC_HW_SYNDROME; + nand->ecc.size = DOCG4_PAGE_SIZE; + nand->ecc.prepad = 8; + nand->ecc.bytes = 8; + nand->ecc.strength = DOCG4_T; + nand->options = NAND_BUSWIDTH_16 | NAND_NO_SUBPAGE_WRITE; + nand->IO_ADDR_R = nand->IO_ADDR_W = doc->virtadr + DOC_IOSPACE_DATA; + nand->controller = &nand->hwcontrol; + spin_lock_init(&nand->controller->lock); + init_waitqueue_head(&nand->controller->wq); + + /* methods */ + nand->cmdfunc = docg4_command; + nand->waitfunc = docg4_wait; + nand->select_chip = docg4_select_chip; + nand->read_byte = docg4_read_byte; + nand->block_markbad = docg4_block_markbad; + nand->read_buf = docg4_read_buf; + nand->write_buf = docg4_write_buf16; + nand->erase = docg4_erase_block; + nand->ecc.read_page = docg4_read_page; + nand->ecc.write_page = docg4_write_page; + nand->ecc.read_page_raw = docg4_read_page_raw; + nand->ecc.write_page_raw = docg4_write_page_raw; + nand->ecc.read_oob = docg4_read_oob; + nand->ecc.write_oob = docg4_write_oob; + + /* + * The way the nand infrastructure code is written, a memory-based bbt + * is not created if NAND_SKIP_BBTSCAN is set. With no memory bbt, + * nand->block_bad() is used. So when ignoring bad blocks, we skip the + * scan and define a dummy block_bad() which always returns 0. + */ + if (ignore_badblocks) { + nand->options |= NAND_SKIP_BBTSCAN; + nand->block_bad = docg4_block_neverbad; + } + +} + +static int __init read_id_reg(struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd->priv; + struct docg4_priv *doc = nand->priv; + void __iomem *docptr = doc->virtadr; + uint16_t id1, id2; + + /* check for presence of g4 chip by reading id registers */ + id1 = readw(docptr + DOC_CHIPID); + id1 = readw(docptr + DOCG4_MYSTERY_REG); + id2 = readw(docptr + DOC_CHIPID_INV); + id2 = readw(docptr + DOCG4_MYSTERY_REG); + + if (id1 == DOCG4_IDREG1_VALUE && id2 == DOCG4_IDREG2_VALUE) { + dev_info(doc->dev, + "NAND device: 128MiB Diskonchip G4 detected\n"); + return 0; + } + + return -ENODEV; +} + +static char const *part_probes[] = { "cmdlinepart", "saftlpart", NULL }; + +static int __init probe_docg4(struct platform_device *pdev) +{ + struct mtd_info *mtd; + struct nand_chip *nand; + void __iomem *virtadr; + struct docg4_priv *doc; + int len, retval; + struct resource *r; + struct device *dev = &pdev->dev; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) { + dev_err(dev, "no io memory resource defined!\n"); + return -ENODEV; + } + + virtadr = ioremap(r->start, resource_size(r)); + if (!virtadr) { + dev_err(dev, "Diskonchip ioremap failed: %pR\n", r); + return -EIO; + } + + len = sizeof(struct mtd_info) + sizeof(struct nand_chip) + + sizeof(struct docg4_priv); + mtd = kzalloc(len, GFP_KERNEL); + if (mtd == NULL) { + retval = -ENOMEM; + goto fail; + } + nand = (struct nand_chip *) (mtd + 1); + doc = (struct docg4_priv *) (nand + 1); + mtd->priv = nand; + nand->priv = doc; + mtd->owner = THIS_MODULE; + doc->virtadr = virtadr; + doc->dev = dev; + + init_mtd_structs(mtd); + + /* initialize kernel bch algorithm */ + doc->bch = init_bch(DOCG4_M, DOCG4_T, DOCG4_PRIMITIVE_POLY); + if (doc->bch == NULL) { + retval = -EINVAL; + goto fail; + } + + platform_set_drvdata(pdev, doc); + + reset(mtd); + retval = read_id_reg(mtd); + if (retval == -ENODEV) { + dev_warn(dev, "No diskonchip G4 device found.\n"); + goto fail; + } + + retval = nand_scan_tail(mtd); + if (retval) + goto fail; + + retval = read_factory_bbt(mtd); + if (retval) + goto fail; + + retval = mtd_device_parse_register(mtd, part_probes, NULL, NULL, 0); + if (retval) + goto fail; + + doc->mtd = mtd; + return 0; + + fail: + iounmap(virtadr); + if (mtd) { + /* re-declarations avoid compiler warning */ + struct nand_chip *nand = mtd->priv; + struct docg4_priv *doc = nand->priv; + nand_release(mtd); /* deletes partitions and mtd devices */ + free_bch(doc->bch); + kfree(mtd); + } + + return retval; +} + +static int __exit cleanup_docg4(struct platform_device *pdev) +{ + struct docg4_priv *doc = platform_get_drvdata(pdev); + nand_release(doc->mtd); + free_bch(doc->bch); + kfree(doc->mtd); + iounmap(doc->virtadr); + return 0; +} + +static struct platform_driver docg4_driver = { + .driver = { + .name = "docg4", + .owner = THIS_MODULE, + }, + .suspend = docg4_suspend, + .resume = docg4_resume, + .remove = __exit_p(cleanup_docg4), +}; + +module_platform_driver_probe(docg4_driver, probe_docg4); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mike Dunn"); +MODULE_DESCRIPTION("M-Systems DiskOnChip G4 device driver"); diff --git a/drivers/mtd/nand/edb7312.c b/drivers/mtd/nand/edb7312.c deleted file mode 100644 index 12017f3c6bd..00000000000 --- a/drivers/mtd/nand/edb7312.c +++ /dev/null @@ -1,212 +0,0 @@ -/* - * drivers/mtd/nand/edb7312.c - * - * Copyright (C) 2002 Marius Gröger (mag@sysgo.de) - * - * Derived from drivers/mtd/nand/autcpu12.c - * Copyright (c) 2001 Thomas Gleixner (gleixner@autronix.de) - * - * $Id: edb7312.c,v 1.12 2005/11/07 11:14:30 gleixner Exp $ - * - * 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. - * - * Overview: - * This is a device driver for the NAND flash device found on the - * CLEP7312 board which utilizes the Toshiba TC58V64AFT part. This is - * a 64Mibit (8MiB x 8 bits) NAND flash device. - */ - -#include <linux/slab.h> -#include <linux/module.h> -#include <linux/init.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/nand.h> -#include <linux/mtd/partitions.h> -#include <asm/io.h> -#include <asm/arch/hardware.h> /* for CLPS7111_VIRT_BASE */ -#include <asm/sizes.h> -#include <asm/hardware/clps7111.h> - -/* - * MTD structure for EDB7312 board - */ -static struct mtd_info *ep7312_mtd = NULL; - -/* - * Values specific to the EDB7312 board (used with EP7312 processor) - */ -#define EP7312_FIO_PBASE 0x10000000 /* Phys address of flash */ -#define EP7312_PXDR 0x0001 /* - * IO offset to Port B data register - * where the CLE, ALE and NCE pins - * are wired to. - */ -#define EP7312_PXDDR 0x0041 /* - * IO offset to Port B data direction - * register so we can control the IO - * lines. - */ - -/* - * Module stuff - */ - -static unsigned long ep7312_fio_pbase = EP7312_FIO_PBASE; -static void __iomem *ep7312_pxdr = (void __iomem *)EP7312_PXDR; -static void __iomem *ep7312_pxddr = (void __iomem *)EP7312_PXDDR; - -#ifdef CONFIG_MTD_PARTITIONS -/* - * Define static partitions for flash device - */ -static struct mtd_partition partition_info[] = { - {.name = "EP7312 Nand Flash", - .offset = 0, - .size = 8 * 1024 * 1024} -}; - -#define NUM_PARTITIONS 1 - -#endif - -/* - * hardware specific access to control-lines - * - * NAND_NCE: bit 0 -> bit 7 - * NAND_CLE: bit 1 -> bit 4 - * NAND_ALE: bit 2 -> bit 5 - */ -static void ep7312_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl) -{ - struct nand_chip *chip = mtd->priv; - - if (ctrl & NAND_CTRL_CHANGE) { - unsigned char bits; - - bits = (ctrl & (NAND_CLE | NAND_ALE)) << 3; - bits = (ctrl & NAND_NCE) << 7; - - clps_writeb((clps_readb(ep7312_pxdr) & 0xB0) | 0x10, - ep7312_pxdr); - } - if (cmd != NAND_CMD_NONE) - writeb(cmd, chip->IO_ADDR_W); -} - -/* - * read device ready pin - */ -static int ep7312_device_ready(struct mtd_info *mtd) -{ - return 1; -} - -#ifdef CONFIG_MTD_PARTITIONS -const char *part_probes[] = { "cmdlinepart", NULL }; -#endif - -/* - * Main initialization routine - */ -static int __init ep7312_init(void) -{ - struct nand_chip *this; - const char *part_type = 0; - int mtd_parts_nb = 0; - struct mtd_partition *mtd_parts = 0; - void __iomem *ep7312_fio_base; - - /* Allocate memory for MTD device structure and private data */ - ep7312_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL); - if (!ep7312_mtd) { - printk("Unable to allocate EDB7312 NAND MTD device structure.\n"); - return -ENOMEM; - } - - /* map physical adress */ - ep7312_fio_base = ioremap(ep7312_fio_pbase, SZ_1K); - if (!ep7312_fio_base) { - printk("ioremap EDB7312 NAND flash failed\n"); - kfree(ep7312_mtd); - return -EIO; - } - - /* Get pointer to private data */ - this = (struct nand_chip *)(&ep7312_mtd[1]); - - /* Initialize structures */ - memset(ep7312_mtd, 0, sizeof(struct mtd_info)); - memset(this, 0, sizeof(struct nand_chip)); - - /* Link the private data with the MTD structure */ - ep7312_mtd->priv = this; - ep7312_mtd->owner = THIS_MODULE; - - /* - * Set GPIO Port B control register so that the pins are configured - * to be outputs for controlling the NAND flash. - */ - clps_writeb(0xf0, ep7312_pxddr); - - /* insert callbacks */ - this->IO_ADDR_R = ep7312_fio_base; - this->IO_ADDR_W = ep7312_fio_base; - this->cmd_ctrl = ep7312_hwcontrol; - this->dev_ready = ep7312_device_ready; - /* 15 us command delay time */ - this->chip_delay = 15; - - /* Scan to find existence of the device */ - if (nand_scan(ep7312_mtd, 1)) { - iounmap((void *)ep7312_fio_base); - kfree(ep7312_mtd); - return -ENXIO; - } -#ifdef CONFIG_MTD_PARTITIONS - ep7312_mtd->name = "edb7312-nand"; - mtd_parts_nb = parse_mtd_partitions(ep7312_mtd, part_probes, &mtd_parts, 0); - if (mtd_parts_nb > 0) - part_type = "command line"; - else - mtd_parts_nb = 0; -#endif - if (mtd_parts_nb == 0) { - mtd_parts = partition_info; - mtd_parts_nb = NUM_PARTITIONS; - part_type = "static"; - } - - /* Register the partitions */ - printk(KERN_NOTICE "Using %s partition definition\n", part_type); - add_mtd_partitions(ep7312_mtd, mtd_parts, mtd_parts_nb); - - /* Return happy */ - return 0; -} - -module_init(ep7312_init); - -/* - * Clean up routine - */ -static void __exit ep7312_cleanup(void) -{ - struct nand_chip *this = (struct nand_chip *)&ep7312_mtd[1]; - - /* Release resources, unregister device */ - nand_release(ap7312_mtd); - - /* Release io resource */ - iounmap((void *)this->IO_ADDR_R); - - /* Free the MTD device structure */ - kfree(ep7312_mtd); -} - -module_exit(ep7312_cleanup); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Marius Groeger <mag@sysgo.de>"); -MODULE_DESCRIPTION("MTD map driver for Cogent EDB7312 board"); diff --git a/drivers/mtd/nand/fsl_elbc_nand.c b/drivers/mtd/nand/fsl_elbc_nand.c new file mode 100644 index 00000000000..545a5c002f0 --- /dev/null +++ b/drivers/mtd/nand/fsl_elbc_nand.c @@ -0,0 +1,964 @@ +/* Freescale Enhanced Local Bus Controller NAND driver + * + * Copyright © 2006-2007, 2010 Freescale Semiconductor + * + * Authors: Nick Spence <nick.spence@freescale.com>, + * Scott Wood <scottwood@freescale.com> + * Jack Lan <jack.lan@freescale.com> + * Roy Zang <tie-fei.zang@freescale.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/interrupt.h> + +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/nand_ecc.h> +#include <linux/mtd/partitions.h> + +#include <asm/io.h> +#include <asm/fsl_lbc.h> + +#define MAX_BANKS 8 +#define ERR_BYTE 0xFF /* Value returned for read bytes when read failed */ +#define FCM_TIMEOUT_MSECS 500 /* Maximum number of mSecs to wait for FCM */ + +/* mtd information per set */ + +struct fsl_elbc_mtd { + struct mtd_info mtd; + struct nand_chip chip; + struct fsl_lbc_ctrl *ctrl; + + struct device *dev; + int bank; /* Chip select bank number */ + u8 __iomem *vbase; /* Chip select base virtual address */ + int page_size; /* NAND page size (0=512, 1=2048) */ + unsigned int fmr; /* FCM Flash Mode Register value */ +}; + +/* Freescale eLBC FCM controller information */ + +struct fsl_elbc_fcm_ctrl { + struct nand_hw_control controller; + struct fsl_elbc_mtd *chips[MAX_BANKS]; + + u8 __iomem *addr; /* Address of assigned FCM buffer */ + unsigned int page; /* Last page written to / read from */ + unsigned int read_bytes; /* Number of bytes read during command */ + unsigned int column; /* Saved column from SEQIN */ + unsigned int index; /* Pointer to next byte to 'read' */ + unsigned int status; /* status read from LTESR after last op */ + unsigned int mdr; /* UPM/FCM Data Register value */ + unsigned int use_mdr; /* Non zero if the MDR is to be set */ + unsigned int oob; /* Non zero if operating on OOB data */ + unsigned int counter; /* counter for the initializations */ + unsigned int max_bitflips; /* Saved during READ0 cmd */ +}; + +/* These map to the positions used by the FCM hardware ECC generator */ + +/* Small Page FLASH with FMR[ECCM] = 0 */ +static struct nand_ecclayout fsl_elbc_oob_sp_eccm0 = { + .eccbytes = 3, + .eccpos = {6, 7, 8}, + .oobfree = { {0, 5}, {9, 7} }, +}; + +/* Small Page FLASH with FMR[ECCM] = 1 */ +static struct nand_ecclayout fsl_elbc_oob_sp_eccm1 = { + .eccbytes = 3, + .eccpos = {8, 9, 10}, + .oobfree = { {0, 5}, {6, 2}, {11, 5} }, +}; + +/* Large Page FLASH with FMR[ECCM] = 0 */ +static struct nand_ecclayout fsl_elbc_oob_lp_eccm0 = { + .eccbytes = 12, + .eccpos = {6, 7, 8, 22, 23, 24, 38, 39, 40, 54, 55, 56}, + .oobfree = { {1, 5}, {9, 13}, {25, 13}, {41, 13}, {57, 7} }, +}; + +/* Large Page FLASH with FMR[ECCM] = 1 */ +static struct nand_ecclayout fsl_elbc_oob_lp_eccm1 = { + .eccbytes = 12, + .eccpos = {8, 9, 10, 24, 25, 26, 40, 41, 42, 56, 57, 58}, + .oobfree = { {1, 7}, {11, 13}, {27, 13}, {43, 13}, {59, 5} }, +}; + +/* + * ELBC may use HW ECC, so that OOB offsets, that NAND core uses for bbt, + * interfere with ECC positions, that's why we implement our own descriptors. + * OOB {11, 5}, works for both SP and LP chips, with ECCM = 1 and ECCM = 0. + */ +static u8 bbt_pattern[] = {'B', 'b', 't', '0' }; +static u8 mirror_pattern[] = {'1', 't', 'b', 'B' }; + +static struct nand_bbt_descr bbt_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | + NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 11, + .len = 4, + .veroffs = 15, + .maxblocks = 4, + .pattern = bbt_pattern, +}; + +static struct nand_bbt_descr bbt_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | + NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 11, + .len = 4, + .veroffs = 15, + .maxblocks = 4, + .pattern = mirror_pattern, +}; + +/*=================================*/ + +/* + * Set up the FCM hardware block and page address fields, and the fcm + * structure addr field to point to the correct FCM buffer in memory + */ +static void set_addr(struct mtd_info *mtd, int column, int page_addr, int oob) +{ + struct nand_chip *chip = mtd->priv; + struct fsl_elbc_mtd *priv = chip->priv; + struct fsl_lbc_ctrl *ctrl = priv->ctrl; + struct fsl_lbc_regs __iomem *lbc = ctrl->regs; + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = ctrl->nand; + int buf_num; + + elbc_fcm_ctrl->page = page_addr; + + if (priv->page_size) { + /* + * large page size chip : FPAR[PI] save the lowest 6 bits, + * FBAR[BLK] save the other bits. + */ + out_be32(&lbc->fbar, page_addr >> 6); + out_be32(&lbc->fpar, + ((page_addr << FPAR_LP_PI_SHIFT) & FPAR_LP_PI) | + (oob ? FPAR_LP_MS : 0) | column); + buf_num = (page_addr & 1) << 2; + } else { + /* + * small page size chip : FPAR[PI] save the lowest 5 bits, + * FBAR[BLK] save the other bits. + */ + out_be32(&lbc->fbar, page_addr >> 5); + out_be32(&lbc->fpar, + ((page_addr << FPAR_SP_PI_SHIFT) & FPAR_SP_PI) | + (oob ? FPAR_SP_MS : 0) | column); + buf_num = page_addr & 7; + } + + elbc_fcm_ctrl->addr = priv->vbase + buf_num * 1024; + elbc_fcm_ctrl->index = column; + + /* for OOB data point to the second half of the buffer */ + if (oob) + elbc_fcm_ctrl->index += priv->page_size ? 2048 : 512; + + dev_vdbg(priv->dev, "set_addr: bank=%d, " + "elbc_fcm_ctrl->addr=0x%p (0x%p), " + "index %x, pes %d ps %d\n", + buf_num, elbc_fcm_ctrl->addr, priv->vbase, + elbc_fcm_ctrl->index, + chip->phys_erase_shift, chip->page_shift); +} + +/* + * execute FCM command and wait for it to complete + */ +static int fsl_elbc_run_command(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct fsl_elbc_mtd *priv = chip->priv; + struct fsl_lbc_ctrl *ctrl = priv->ctrl; + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = ctrl->nand; + struct fsl_lbc_regs __iomem *lbc = ctrl->regs; + + /* Setup the FMR[OP] to execute without write protection */ + out_be32(&lbc->fmr, priv->fmr | 3); + if (elbc_fcm_ctrl->use_mdr) + out_be32(&lbc->mdr, elbc_fcm_ctrl->mdr); + + dev_vdbg(priv->dev, + "fsl_elbc_run_command: fmr=%08x fir=%08x fcr=%08x\n", + in_be32(&lbc->fmr), in_be32(&lbc->fir), in_be32(&lbc->fcr)); + dev_vdbg(priv->dev, + "fsl_elbc_run_command: fbar=%08x fpar=%08x " + "fbcr=%08x bank=%d\n", + in_be32(&lbc->fbar), in_be32(&lbc->fpar), + in_be32(&lbc->fbcr), priv->bank); + + ctrl->irq_status = 0; + /* execute special operation */ + out_be32(&lbc->lsor, priv->bank); + + /* wait for FCM complete flag or timeout */ + wait_event_timeout(ctrl->irq_wait, ctrl->irq_status, + FCM_TIMEOUT_MSECS * HZ/1000); + elbc_fcm_ctrl->status = ctrl->irq_status; + /* store mdr value in case it was needed */ + if (elbc_fcm_ctrl->use_mdr) + elbc_fcm_ctrl->mdr = in_be32(&lbc->mdr); + + elbc_fcm_ctrl->use_mdr = 0; + + if (elbc_fcm_ctrl->status != LTESR_CC) { + dev_info(priv->dev, + "command failed: fir %x fcr %x status %x mdr %x\n", + in_be32(&lbc->fir), in_be32(&lbc->fcr), + elbc_fcm_ctrl->status, elbc_fcm_ctrl->mdr); + return -EIO; + } + + if (chip->ecc.mode != NAND_ECC_HW) + return 0; + + elbc_fcm_ctrl->max_bitflips = 0; + + if (elbc_fcm_ctrl->read_bytes == mtd->writesize + mtd->oobsize) { + uint32_t lteccr = in_be32(&lbc->lteccr); + /* + * if command was a full page read and the ELBC + * has the LTECCR register, then bits 12-15 (ppc order) of + * LTECCR indicates which 512 byte sub-pages had fixed errors. + * bits 28-31 are uncorrectable errors, marked elsewhere. + * for small page nand only 1 bit is used. + * if the ELBC doesn't have the lteccr register it reads 0 + * FIXME: 4 bits can be corrected on NANDs with 2k pages, so + * count the number of sub-pages with bitflips and update + * ecc_stats.corrected accordingly. + */ + if (lteccr & 0x000F000F) + out_be32(&lbc->lteccr, 0x000F000F); /* clear lteccr */ + if (lteccr & 0x000F0000) { + mtd->ecc_stats.corrected++; + elbc_fcm_ctrl->max_bitflips = 1; + } + } + + return 0; +} + +static void fsl_elbc_do_read(struct nand_chip *chip, int oob) +{ + struct fsl_elbc_mtd *priv = chip->priv; + struct fsl_lbc_ctrl *ctrl = priv->ctrl; + struct fsl_lbc_regs __iomem *lbc = ctrl->regs; + + if (priv->page_size) { + out_be32(&lbc->fir, + (FIR_OP_CM0 << FIR_OP0_SHIFT) | + (FIR_OP_CA << FIR_OP1_SHIFT) | + (FIR_OP_PA << FIR_OP2_SHIFT) | + (FIR_OP_CM1 << FIR_OP3_SHIFT) | + (FIR_OP_RBW << FIR_OP4_SHIFT)); + + out_be32(&lbc->fcr, (NAND_CMD_READ0 << FCR_CMD0_SHIFT) | + (NAND_CMD_READSTART << FCR_CMD1_SHIFT)); + } else { + out_be32(&lbc->fir, + (FIR_OP_CM0 << FIR_OP0_SHIFT) | + (FIR_OP_CA << FIR_OP1_SHIFT) | + (FIR_OP_PA << FIR_OP2_SHIFT) | + (FIR_OP_RBW << FIR_OP3_SHIFT)); + + if (oob) + out_be32(&lbc->fcr, NAND_CMD_READOOB << FCR_CMD0_SHIFT); + else + out_be32(&lbc->fcr, NAND_CMD_READ0 << FCR_CMD0_SHIFT); + } +} + +/* cmdfunc send commands to the FCM */ +static void fsl_elbc_cmdfunc(struct mtd_info *mtd, unsigned int command, + int column, int page_addr) +{ + struct nand_chip *chip = mtd->priv; + struct fsl_elbc_mtd *priv = chip->priv; + struct fsl_lbc_ctrl *ctrl = priv->ctrl; + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = ctrl->nand; + struct fsl_lbc_regs __iomem *lbc = ctrl->regs; + + elbc_fcm_ctrl->use_mdr = 0; + + /* clear the read buffer */ + elbc_fcm_ctrl->read_bytes = 0; + if (command != NAND_CMD_PAGEPROG) + elbc_fcm_ctrl->index = 0; + + switch (command) { + /* READ0 and READ1 read the entire buffer to use hardware ECC. */ + case NAND_CMD_READ1: + column += 256; + + /* fall-through */ + case NAND_CMD_READ0: + dev_dbg(priv->dev, + "fsl_elbc_cmdfunc: NAND_CMD_READ0, page_addr:" + " 0x%x, column: 0x%x.\n", page_addr, column); + + + out_be32(&lbc->fbcr, 0); /* read entire page to enable ECC */ + set_addr(mtd, 0, page_addr, 0); + + elbc_fcm_ctrl->read_bytes = mtd->writesize + mtd->oobsize; + elbc_fcm_ctrl->index += column; + + fsl_elbc_do_read(chip, 0); + fsl_elbc_run_command(mtd); + return; + + /* READOOB reads only the OOB because no ECC is performed. */ + case NAND_CMD_READOOB: + dev_vdbg(priv->dev, + "fsl_elbc_cmdfunc: NAND_CMD_READOOB, page_addr:" + " 0x%x, column: 0x%x.\n", page_addr, column); + + out_be32(&lbc->fbcr, mtd->oobsize - column); + set_addr(mtd, column, page_addr, 1); + + elbc_fcm_ctrl->read_bytes = mtd->writesize + mtd->oobsize; + + fsl_elbc_do_read(chip, 1); + fsl_elbc_run_command(mtd); + return; + + case NAND_CMD_READID: + case NAND_CMD_PARAM: + dev_vdbg(priv->dev, "fsl_elbc_cmdfunc: NAND_CMD %x\n", command); + + out_be32(&lbc->fir, (FIR_OP_CM0 << FIR_OP0_SHIFT) | + (FIR_OP_UA << FIR_OP1_SHIFT) | + (FIR_OP_RBW << FIR_OP2_SHIFT)); + out_be32(&lbc->fcr, command << FCR_CMD0_SHIFT); + /* + * although currently it's 8 bytes for READID, we always read + * the maximum 256 bytes(for PARAM) + */ + out_be32(&lbc->fbcr, 256); + elbc_fcm_ctrl->read_bytes = 256; + elbc_fcm_ctrl->use_mdr = 1; + elbc_fcm_ctrl->mdr = column; + set_addr(mtd, 0, 0, 0); + fsl_elbc_run_command(mtd); + return; + + /* ERASE1 stores the block and page address */ + case NAND_CMD_ERASE1: + dev_vdbg(priv->dev, + "fsl_elbc_cmdfunc: NAND_CMD_ERASE1, " + "page_addr: 0x%x.\n", page_addr); + set_addr(mtd, 0, page_addr, 0); + return; + + /* ERASE2 uses the block and page address from ERASE1 */ + case NAND_CMD_ERASE2: + dev_vdbg(priv->dev, "fsl_elbc_cmdfunc: NAND_CMD_ERASE2.\n"); + + out_be32(&lbc->fir, + (FIR_OP_CM0 << FIR_OP0_SHIFT) | + (FIR_OP_PA << FIR_OP1_SHIFT) | + (FIR_OP_CM2 << FIR_OP2_SHIFT) | + (FIR_OP_CW1 << FIR_OP3_SHIFT) | + (FIR_OP_RS << FIR_OP4_SHIFT)); + + out_be32(&lbc->fcr, + (NAND_CMD_ERASE1 << FCR_CMD0_SHIFT) | + (NAND_CMD_STATUS << FCR_CMD1_SHIFT) | + (NAND_CMD_ERASE2 << FCR_CMD2_SHIFT)); + + out_be32(&lbc->fbcr, 0); + elbc_fcm_ctrl->read_bytes = 0; + elbc_fcm_ctrl->use_mdr = 1; + + fsl_elbc_run_command(mtd); + return; + + /* SEQIN sets up the addr buffer and all registers except the length */ + case NAND_CMD_SEQIN: { + __be32 fcr; + dev_vdbg(priv->dev, + "fsl_elbc_cmdfunc: NAND_CMD_SEQIN/PAGE_PROG, " + "page_addr: 0x%x, column: 0x%x.\n", + page_addr, column); + + elbc_fcm_ctrl->column = column; + elbc_fcm_ctrl->use_mdr = 1; + + if (column >= mtd->writesize) { + /* OOB area */ + column -= mtd->writesize; + elbc_fcm_ctrl->oob = 1; + } else { + WARN_ON(column != 0); + elbc_fcm_ctrl->oob = 0; + } + + fcr = (NAND_CMD_STATUS << FCR_CMD1_SHIFT) | + (NAND_CMD_SEQIN << FCR_CMD2_SHIFT) | + (NAND_CMD_PAGEPROG << FCR_CMD3_SHIFT); + + if (priv->page_size) { + out_be32(&lbc->fir, + (FIR_OP_CM2 << FIR_OP0_SHIFT) | + (FIR_OP_CA << FIR_OP1_SHIFT) | + (FIR_OP_PA << FIR_OP2_SHIFT) | + (FIR_OP_WB << FIR_OP3_SHIFT) | + (FIR_OP_CM3 << FIR_OP4_SHIFT) | + (FIR_OP_CW1 << FIR_OP5_SHIFT) | + (FIR_OP_RS << FIR_OP6_SHIFT)); + } else { + out_be32(&lbc->fir, + (FIR_OP_CM0 << FIR_OP0_SHIFT) | + (FIR_OP_CM2 << FIR_OP1_SHIFT) | + (FIR_OP_CA << FIR_OP2_SHIFT) | + (FIR_OP_PA << FIR_OP3_SHIFT) | + (FIR_OP_WB << FIR_OP4_SHIFT) | + (FIR_OP_CM3 << FIR_OP5_SHIFT) | + (FIR_OP_CW1 << FIR_OP6_SHIFT) | + (FIR_OP_RS << FIR_OP7_SHIFT)); + + if (elbc_fcm_ctrl->oob) + /* OOB area --> READOOB */ + fcr |= NAND_CMD_READOOB << FCR_CMD0_SHIFT; + else + /* First 256 bytes --> READ0 */ + fcr |= NAND_CMD_READ0 << FCR_CMD0_SHIFT; + } + + out_be32(&lbc->fcr, fcr); + set_addr(mtd, column, page_addr, elbc_fcm_ctrl->oob); + return; + } + + /* PAGEPROG reuses all of the setup from SEQIN and adds the length */ + case NAND_CMD_PAGEPROG: { + dev_vdbg(priv->dev, + "fsl_elbc_cmdfunc: NAND_CMD_PAGEPROG " + "writing %d bytes.\n", elbc_fcm_ctrl->index); + + /* if the write did not start at 0 or is not a full page + * then set the exact length, otherwise use a full page + * write so the HW generates the ECC. + */ + if (elbc_fcm_ctrl->oob || elbc_fcm_ctrl->column != 0 || + elbc_fcm_ctrl->index != mtd->writesize + mtd->oobsize) + out_be32(&lbc->fbcr, + elbc_fcm_ctrl->index - elbc_fcm_ctrl->column); + else + out_be32(&lbc->fbcr, 0); + + fsl_elbc_run_command(mtd); + return; + } + + /* CMD_STATUS must read the status byte while CEB is active */ + /* Note - it does not wait for the ready line */ + case NAND_CMD_STATUS: + out_be32(&lbc->fir, + (FIR_OP_CM0 << FIR_OP0_SHIFT) | + (FIR_OP_RBW << FIR_OP1_SHIFT)); + out_be32(&lbc->fcr, NAND_CMD_STATUS << FCR_CMD0_SHIFT); + out_be32(&lbc->fbcr, 1); + set_addr(mtd, 0, 0, 0); + elbc_fcm_ctrl->read_bytes = 1; + + fsl_elbc_run_command(mtd); + + /* The chip always seems to report that it is + * write-protected, even when it is not. + */ + setbits8(elbc_fcm_ctrl->addr, NAND_STATUS_WP); + return; + + /* RESET without waiting for the ready line */ + case NAND_CMD_RESET: + dev_dbg(priv->dev, "fsl_elbc_cmdfunc: NAND_CMD_RESET.\n"); + out_be32(&lbc->fir, FIR_OP_CM0 << FIR_OP0_SHIFT); + out_be32(&lbc->fcr, NAND_CMD_RESET << FCR_CMD0_SHIFT); + fsl_elbc_run_command(mtd); + return; + + default: + dev_err(priv->dev, + "fsl_elbc_cmdfunc: error, unsupported command 0x%x.\n", + command); + } +} + +static void fsl_elbc_select_chip(struct mtd_info *mtd, int chip) +{ + /* The hardware does not seem to support multiple + * chips per bank. + */ +} + +/* + * Write buf to the FCM Controller Data Buffer + */ +static void fsl_elbc_write_buf(struct mtd_info *mtd, const u8 *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct fsl_elbc_mtd *priv = chip->priv; + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = priv->ctrl->nand; + unsigned int bufsize = mtd->writesize + mtd->oobsize; + + if (len <= 0) { + dev_err(priv->dev, "write_buf of %d bytes", len); + elbc_fcm_ctrl->status = 0; + return; + } + + if ((unsigned int)len > bufsize - elbc_fcm_ctrl->index) { + dev_err(priv->dev, + "write_buf beyond end of buffer " + "(%d requested, %u available)\n", + len, bufsize - elbc_fcm_ctrl->index); + len = bufsize - elbc_fcm_ctrl->index; + } + + memcpy_toio(&elbc_fcm_ctrl->addr[elbc_fcm_ctrl->index], buf, len); + /* + * This is workaround for the weird elbc hangs during nand write, + * Scott Wood says: "...perhaps difference in how long it takes a + * write to make it through the localbus compared to a write to IMMR + * is causing problems, and sync isn't helping for some reason." + * Reading back the last byte helps though. + */ + in_8(&elbc_fcm_ctrl->addr[elbc_fcm_ctrl->index] + len - 1); + + elbc_fcm_ctrl->index += len; +} + +/* + * read a byte from either the FCM hardware buffer if it has any data left + * otherwise issue a command to read a single byte. + */ +static u8 fsl_elbc_read_byte(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct fsl_elbc_mtd *priv = chip->priv; + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = priv->ctrl->nand; + + /* If there are still bytes in the FCM, then use the next byte. */ + if (elbc_fcm_ctrl->index < elbc_fcm_ctrl->read_bytes) + return in_8(&elbc_fcm_ctrl->addr[elbc_fcm_ctrl->index++]); + + dev_err(priv->dev, "read_byte beyond end of buffer\n"); + return ERR_BYTE; +} + +/* + * Read from the FCM Controller Data Buffer + */ +static void fsl_elbc_read_buf(struct mtd_info *mtd, u8 *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct fsl_elbc_mtd *priv = chip->priv; + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = priv->ctrl->nand; + int avail; + + if (len < 0) + return; + + avail = min((unsigned int)len, + elbc_fcm_ctrl->read_bytes - elbc_fcm_ctrl->index); + memcpy_fromio(buf, &elbc_fcm_ctrl->addr[elbc_fcm_ctrl->index], avail); + elbc_fcm_ctrl->index += avail; + + if (len > avail) + dev_err(priv->dev, + "read_buf beyond end of buffer " + "(%d requested, %d available)\n", + len, avail); +} + +/* This function is called after Program and Erase Operations to + * check for success or failure. + */ +static int fsl_elbc_wait(struct mtd_info *mtd, struct nand_chip *chip) +{ + struct fsl_elbc_mtd *priv = chip->priv; + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = priv->ctrl->nand; + + if (elbc_fcm_ctrl->status != LTESR_CC) + return NAND_STATUS_FAIL; + + /* The chip always seems to report that it is + * write-protected, even when it is not. + */ + return (elbc_fcm_ctrl->mdr & 0xff) | NAND_STATUS_WP; +} + +static int fsl_elbc_chip_init_tail(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct fsl_elbc_mtd *priv = chip->priv; + struct fsl_lbc_ctrl *ctrl = priv->ctrl; + struct fsl_lbc_regs __iomem *lbc = ctrl->regs; + unsigned int al; + + /* calculate FMR Address Length field */ + al = 0; + if (chip->pagemask & 0xffff0000) + al++; + if (chip->pagemask & 0xff000000) + al++; + + priv->fmr |= al << FMR_AL_SHIFT; + + dev_dbg(priv->dev, "fsl_elbc_init: nand->numchips = %d\n", + chip->numchips); + dev_dbg(priv->dev, "fsl_elbc_init: nand->chipsize = %lld\n", + chip->chipsize); + dev_dbg(priv->dev, "fsl_elbc_init: nand->pagemask = %8x\n", + chip->pagemask); + dev_dbg(priv->dev, "fsl_elbc_init: nand->chip_delay = %d\n", + chip->chip_delay); + dev_dbg(priv->dev, "fsl_elbc_init: nand->badblockpos = %d\n", + chip->badblockpos); + dev_dbg(priv->dev, "fsl_elbc_init: nand->chip_shift = %d\n", + chip->chip_shift); + dev_dbg(priv->dev, "fsl_elbc_init: nand->page_shift = %d\n", + chip->page_shift); + dev_dbg(priv->dev, "fsl_elbc_init: nand->phys_erase_shift = %d\n", + chip->phys_erase_shift); + dev_dbg(priv->dev, "fsl_elbc_init: nand->ecc.mode = %d\n", + chip->ecc.mode); + dev_dbg(priv->dev, "fsl_elbc_init: nand->ecc.steps = %d\n", + chip->ecc.steps); + dev_dbg(priv->dev, "fsl_elbc_init: nand->ecc.bytes = %d\n", + chip->ecc.bytes); + dev_dbg(priv->dev, "fsl_elbc_init: nand->ecc.total = %d\n", + chip->ecc.total); + dev_dbg(priv->dev, "fsl_elbc_init: nand->ecc.layout = %p\n", + chip->ecc.layout); + dev_dbg(priv->dev, "fsl_elbc_init: mtd->flags = %08x\n", mtd->flags); + dev_dbg(priv->dev, "fsl_elbc_init: mtd->size = %lld\n", mtd->size); + dev_dbg(priv->dev, "fsl_elbc_init: mtd->erasesize = %d\n", + mtd->erasesize); + dev_dbg(priv->dev, "fsl_elbc_init: mtd->writesize = %d\n", + mtd->writesize); + dev_dbg(priv->dev, "fsl_elbc_init: mtd->oobsize = %d\n", + mtd->oobsize); + + /* adjust Option Register and ECC to match Flash page size */ + if (mtd->writesize == 512) { + priv->page_size = 0; + clrbits32(&lbc->bank[priv->bank].or, OR_FCM_PGS); + } else if (mtd->writesize == 2048) { + priv->page_size = 1; + setbits32(&lbc->bank[priv->bank].or, OR_FCM_PGS); + /* adjust ecc setup if needed */ + if ((in_be32(&lbc->bank[priv->bank].br) & BR_DECC) == + BR_DECC_CHK_GEN) { + chip->ecc.size = 512; + chip->ecc.layout = (priv->fmr & FMR_ECCM) ? + &fsl_elbc_oob_lp_eccm1 : + &fsl_elbc_oob_lp_eccm0; + } + } else { + dev_err(priv->dev, + "fsl_elbc_init: page size %d is not supported\n", + mtd->writesize); + return -1; + } + + return 0; +} + +static int fsl_elbc_read_page(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf, int oob_required, int page) +{ + struct fsl_elbc_mtd *priv = chip->priv; + struct fsl_lbc_ctrl *ctrl = priv->ctrl; + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = ctrl->nand; + + fsl_elbc_read_buf(mtd, buf, mtd->writesize); + if (oob_required) + fsl_elbc_read_buf(mtd, chip->oob_poi, mtd->oobsize); + + if (fsl_elbc_wait(mtd, chip) & NAND_STATUS_FAIL) + mtd->ecc_stats.failed++; + + return elbc_fcm_ctrl->max_bitflips; +} + +/* ECC will be calculated automatically, and errors will be detected in + * waitfunc. + */ +static int fsl_elbc_write_page(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf, int oob_required) +{ + fsl_elbc_write_buf(mtd, buf, mtd->writesize); + fsl_elbc_write_buf(mtd, chip->oob_poi, mtd->oobsize); + + return 0; +} + +/* ECC will be calculated automatically, and errors will be detected in + * waitfunc. + */ +static int fsl_elbc_write_subpage(struct mtd_info *mtd, struct nand_chip *chip, + uint32_t offset, uint32_t data_len, + const uint8_t *buf, int oob_required) +{ + fsl_elbc_write_buf(mtd, buf, mtd->writesize); + fsl_elbc_write_buf(mtd, chip->oob_poi, mtd->oobsize); + + return 0; +} + +static int fsl_elbc_chip_init(struct fsl_elbc_mtd *priv) +{ + struct fsl_lbc_ctrl *ctrl = priv->ctrl; + struct fsl_lbc_regs __iomem *lbc = ctrl->regs; + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = ctrl->nand; + struct nand_chip *chip = &priv->chip; + + dev_dbg(priv->dev, "eLBC Set Information for bank %d\n", priv->bank); + + /* Fill in fsl_elbc_mtd structure */ + priv->mtd.priv = chip; + priv->mtd.owner = THIS_MODULE; + + /* set timeout to maximum */ + priv->fmr = 15 << FMR_CWTO_SHIFT; + if (in_be32(&lbc->bank[priv->bank].or) & OR_FCM_PGS) + priv->fmr |= FMR_ECCM; + + /* fill in nand_chip structure */ + /* set up function call table */ + chip->read_byte = fsl_elbc_read_byte; + chip->write_buf = fsl_elbc_write_buf; + chip->read_buf = fsl_elbc_read_buf; + chip->select_chip = fsl_elbc_select_chip; + chip->cmdfunc = fsl_elbc_cmdfunc; + chip->waitfunc = fsl_elbc_wait; + + chip->bbt_td = &bbt_main_descr; + chip->bbt_md = &bbt_mirror_descr; + + /* set up nand options */ + chip->bbt_options = NAND_BBT_USE_FLASH; + + chip->controller = &elbc_fcm_ctrl->controller; + chip->priv = priv; + + chip->ecc.read_page = fsl_elbc_read_page; + chip->ecc.write_page = fsl_elbc_write_page; + chip->ecc.write_subpage = fsl_elbc_write_subpage; + + /* If CS Base Register selects full hardware ECC then use it */ + if ((in_be32(&lbc->bank[priv->bank].br) & BR_DECC) == + BR_DECC_CHK_GEN) { + chip->ecc.mode = NAND_ECC_HW; + /* put in small page settings and adjust later if needed */ + chip->ecc.layout = (priv->fmr & FMR_ECCM) ? + &fsl_elbc_oob_sp_eccm1 : &fsl_elbc_oob_sp_eccm0; + chip->ecc.size = 512; + chip->ecc.bytes = 3; + chip->ecc.strength = 1; + } else { + /* otherwise fall back to default software ECC */ + chip->ecc.mode = NAND_ECC_SOFT; + } + + return 0; +} + +static int fsl_elbc_chip_remove(struct fsl_elbc_mtd *priv) +{ + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = priv->ctrl->nand; + nand_release(&priv->mtd); + + kfree(priv->mtd.name); + + if (priv->vbase) + iounmap(priv->vbase); + + elbc_fcm_ctrl->chips[priv->bank] = NULL; + kfree(priv); + return 0; +} + +static DEFINE_MUTEX(fsl_elbc_nand_mutex); + +static int fsl_elbc_nand_probe(struct platform_device *pdev) +{ + struct fsl_lbc_regs __iomem *lbc; + struct fsl_elbc_mtd *priv; + struct resource res; + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl; + static const char *part_probe_types[] + = { "cmdlinepart", "RedBoot", "ofpart", NULL }; + int ret; + int bank; + struct device *dev; + struct device_node *node = pdev->dev.of_node; + struct mtd_part_parser_data ppdata; + + ppdata.of_node = pdev->dev.of_node; + if (!fsl_lbc_ctrl_dev || !fsl_lbc_ctrl_dev->regs) + return -ENODEV; + lbc = fsl_lbc_ctrl_dev->regs; + dev = fsl_lbc_ctrl_dev->dev; + + /* get, allocate and map the memory resource */ + ret = of_address_to_resource(node, 0, &res); + if (ret) { + dev_err(dev, "failed to get resource\n"); + return ret; + } + + /* find which chip select it is connected to */ + for (bank = 0; bank < MAX_BANKS; bank++) + if ((in_be32(&lbc->bank[bank].br) & BR_V) && + (in_be32(&lbc->bank[bank].br) & BR_MSEL) == BR_MS_FCM && + (in_be32(&lbc->bank[bank].br) & + in_be32(&lbc->bank[bank].or) & BR_BA) + == fsl_lbc_addr(res.start)) + break; + + if (bank >= MAX_BANKS) { + dev_err(dev, "address did not match any chip selects\n"); + return -ENODEV; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_lock(&fsl_elbc_nand_mutex); + if (!fsl_lbc_ctrl_dev->nand) { + elbc_fcm_ctrl = kzalloc(sizeof(*elbc_fcm_ctrl), GFP_KERNEL); + if (!elbc_fcm_ctrl) { + mutex_unlock(&fsl_elbc_nand_mutex); + ret = -ENOMEM; + goto err; + } + elbc_fcm_ctrl->counter++; + + spin_lock_init(&elbc_fcm_ctrl->controller.lock); + init_waitqueue_head(&elbc_fcm_ctrl->controller.wq); + fsl_lbc_ctrl_dev->nand = elbc_fcm_ctrl; + } else { + elbc_fcm_ctrl = fsl_lbc_ctrl_dev->nand; + } + mutex_unlock(&fsl_elbc_nand_mutex); + + elbc_fcm_ctrl->chips[bank] = priv; + priv->bank = bank; + priv->ctrl = fsl_lbc_ctrl_dev; + priv->dev = &pdev->dev; + dev_set_drvdata(priv->dev, priv); + + priv->vbase = ioremap(res.start, resource_size(&res)); + if (!priv->vbase) { + dev_err(dev, "failed to map chip region\n"); + ret = -ENOMEM; + goto err; + } + + priv->mtd.name = kasprintf(GFP_KERNEL, "%llx.flash", (u64)res.start); + if (!priv->mtd.name) { + ret = -ENOMEM; + goto err; + } + + ret = fsl_elbc_chip_init(priv); + if (ret) + goto err; + + ret = nand_scan_ident(&priv->mtd, 1, NULL); + if (ret) + goto err; + + ret = fsl_elbc_chip_init_tail(&priv->mtd); + if (ret) + goto err; + + ret = nand_scan_tail(&priv->mtd); + if (ret) + goto err; + + /* First look for RedBoot table or partitions on the command + * line, these take precedence over device tree information */ + mtd_device_parse_register(&priv->mtd, part_probe_types, &ppdata, + NULL, 0); + + printk(KERN_INFO "eLBC NAND device at 0x%llx, bank %d\n", + (unsigned long long)res.start, priv->bank); + return 0; + +err: + fsl_elbc_chip_remove(priv); + return ret; +} + +static int fsl_elbc_nand_remove(struct platform_device *pdev) +{ + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = fsl_lbc_ctrl_dev->nand; + struct fsl_elbc_mtd *priv = dev_get_drvdata(&pdev->dev); + + fsl_elbc_chip_remove(priv); + + mutex_lock(&fsl_elbc_nand_mutex); + elbc_fcm_ctrl->counter--; + if (!elbc_fcm_ctrl->counter) { + fsl_lbc_ctrl_dev->nand = NULL; + kfree(elbc_fcm_ctrl); + } + mutex_unlock(&fsl_elbc_nand_mutex); + + return 0; + +} + +static const struct of_device_id fsl_elbc_nand_match[] = { + { .compatible = "fsl,elbc-fcm-nand", }, + {} +}; + +static struct platform_driver fsl_elbc_nand_driver = { + .driver = { + .name = "fsl,elbc-fcm-nand", + .owner = THIS_MODULE, + .of_match_table = fsl_elbc_nand_match, + }, + .probe = fsl_elbc_nand_probe, + .remove = fsl_elbc_nand_remove, +}; + +module_platform_driver(fsl_elbc_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale"); +MODULE_DESCRIPTION("Freescale Enhanced Local Bus Controller MTD NAND driver"); diff --git a/drivers/mtd/nand/fsl_ifc_nand.c b/drivers/mtd/nand/fsl_ifc_nand.c new file mode 100644 index 00000000000..2338124dd05 --- /dev/null +++ b/drivers/mtd/nand/fsl_ifc_nand.c @@ -0,0 +1,1181 @@ +/* + * Freescale Integrated Flash Controller NAND driver + * + * Copyright 2011-2012 Freescale Semiconductor, Inc + * + * Author: Dipen Dudhat <Dipen.Dudhat@freescale.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/of_address.h> +#include <linux/slab.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/nand_ecc.h> +#include <linux/fsl_ifc.h> + +#define FSL_IFC_V1_1_0 0x01010000 +#define ERR_BYTE 0xFF /* Value returned for read + bytes when read failed */ +#define IFC_TIMEOUT_MSECS 500 /* Maximum number of mSecs to wait + for IFC NAND Machine */ + +struct fsl_ifc_ctrl; + +/* mtd information per set */ +struct fsl_ifc_mtd { + struct mtd_info mtd; + struct nand_chip chip; + struct fsl_ifc_ctrl *ctrl; + + struct device *dev; + int bank; /* Chip select bank number */ + unsigned int bufnum_mask; /* bufnum = page & bufnum_mask */ + u8 __iomem *vbase; /* Chip select base virtual address */ +}; + +/* overview of the fsl ifc controller */ +struct fsl_ifc_nand_ctrl { + struct nand_hw_control controller; + struct fsl_ifc_mtd *chips[FSL_IFC_BANK_COUNT]; + + void __iomem *addr; /* Address of assigned IFC buffer */ + unsigned int page; /* Last page written to / read from */ + unsigned int read_bytes;/* Number of bytes read during command */ + unsigned int column; /* Saved column from SEQIN */ + unsigned int index; /* Pointer to next byte to 'read' */ + unsigned int oob; /* Non zero if operating on OOB data */ + unsigned int eccread; /* Non zero for a full-page ECC read */ + unsigned int counter; /* counter for the initializations */ + unsigned int max_bitflips; /* Saved during READ0 cmd */ +}; + +static struct fsl_ifc_nand_ctrl *ifc_nand_ctrl; + +/* 512-byte page with 4-bit ECC, 8-bit */ +static struct nand_ecclayout oob_512_8bit_ecc4 = { + .eccbytes = 8, + .eccpos = {8, 9, 10, 11, 12, 13, 14, 15}, + .oobfree = { {0, 5}, {6, 2} }, +}; + +/* 512-byte page with 4-bit ECC, 16-bit */ +static struct nand_ecclayout oob_512_16bit_ecc4 = { + .eccbytes = 8, + .eccpos = {8, 9, 10, 11, 12, 13, 14, 15}, + .oobfree = { {2, 6}, }, +}; + +/* 2048-byte page size with 4-bit ECC */ +static struct nand_ecclayout oob_2048_ecc4 = { + .eccbytes = 32, + .eccpos = { + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + }, + .oobfree = { {2, 6}, {40, 24} }, +}; + +/* 4096-byte page size with 4-bit ECC */ +static struct nand_ecclayout oob_4096_ecc4 = { + .eccbytes = 64, + .eccpos = { + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, + }, + .oobfree = { {2, 6}, {72, 56} }, +}; + +/* 4096-byte page size with 8-bit ECC -- requires 218-byte OOB */ +static struct nand_ecclayout oob_4096_ecc8 = { + .eccbytes = 128, + .eccpos = { + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, + 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, + 128, 129, 130, 131, 132, 133, 134, 135, + }, + .oobfree = { {2, 6}, {136, 82} }, +}; + +/* 8192-byte page size with 4-bit ECC */ +static struct nand_ecclayout oob_8192_ecc4 = { + .eccbytes = 128, + .eccpos = { + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, + 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, + 128, 129, 130, 131, 132, 133, 134, 135, + }, + .oobfree = { {2, 6}, {136, 208} }, +}; + +/* 8192-byte page size with 8-bit ECC -- requires 218-byte OOB */ +static struct nand_ecclayout oob_8192_ecc8 = { + .eccbytes = 256, + .eccpos = { + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, + 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, + 128, 129, 130, 131, 132, 133, 134, 135, + 136, 137, 138, 139, 140, 141, 142, 143, + 144, 145, 146, 147, 148, 149, 150, 151, + 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, + 168, 169, 170, 171, 172, 173, 174, 175, + 176, 177, 178, 179, 180, 181, 182, 183, + 184, 185, 186, 187, 188, 189, 190, 191, + 192, 193, 194, 195, 196, 197, 198, 199, + 200, 201, 202, 203, 204, 205, 206, 207, + 208, 209, 210, 211, 212, 213, 214, 215, + 216, 217, 218, 219, 220, 221, 222, 223, + 224, 225, 226, 227, 228, 229, 230, 231, + 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, + 248, 249, 250, 251, 252, 253, 254, 255, + 256, 257, 258, 259, 260, 261, 262, 263, + }, + .oobfree = { {2, 6}, {264, 80} }, +}; + +/* + * Generic flash bbt descriptors + */ +static u8 bbt_pattern[] = {'B', 'b', 't', '0' }; +static u8 mirror_pattern[] = {'1', 't', 'b', 'B' }; + +static struct nand_bbt_descr bbt_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | + NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 2, /* 0 on 8-bit small page */ + .len = 4, + .veroffs = 6, + .maxblocks = 4, + .pattern = bbt_pattern, +}; + +static struct nand_bbt_descr bbt_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | + NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 2, /* 0 on 8-bit small page */ + .len = 4, + .veroffs = 6, + .maxblocks = 4, + .pattern = mirror_pattern, +}; + +/* + * Set up the IFC hardware block and page address fields, and the ifc nand + * structure addr field to point to the correct IFC buffer in memory + */ +static void set_addr(struct mtd_info *mtd, int column, int page_addr, int oob) +{ + struct nand_chip *chip = mtd->priv; + struct fsl_ifc_mtd *priv = chip->priv; + struct fsl_ifc_ctrl *ctrl = priv->ctrl; + struct fsl_ifc_regs __iomem *ifc = ctrl->regs; + int buf_num; + + ifc_nand_ctrl->page = page_addr; + /* Program ROW0/COL0 */ + iowrite32be(page_addr, &ifc->ifc_nand.row0); + iowrite32be((oob ? IFC_NAND_COL_MS : 0) | column, &ifc->ifc_nand.col0); + + buf_num = page_addr & priv->bufnum_mask; + + ifc_nand_ctrl->addr = priv->vbase + buf_num * (mtd->writesize * 2); + ifc_nand_ctrl->index = column; + + /* for OOB data point to the second half of the buffer */ + if (oob) + ifc_nand_ctrl->index += mtd->writesize; +} + +static int is_blank(struct mtd_info *mtd, unsigned int bufnum) +{ + struct nand_chip *chip = mtd->priv; + struct fsl_ifc_mtd *priv = chip->priv; + u8 __iomem *addr = priv->vbase + bufnum * (mtd->writesize * 2); + u32 __iomem *mainarea = (u32 __iomem *)addr; + u8 __iomem *oob = addr + mtd->writesize; + int i; + + for (i = 0; i < mtd->writesize / 4; i++) { + if (__raw_readl(&mainarea[i]) != 0xffffffff) + return 0; + } + + for (i = 0; i < chip->ecc.layout->eccbytes; i++) { + int pos = chip->ecc.layout->eccpos[i]; + + if (__raw_readb(&oob[pos]) != 0xff) + return 0; + } + + return 1; +} + +/* returns nonzero if entire page is blank */ +static int check_read_ecc(struct mtd_info *mtd, struct fsl_ifc_ctrl *ctrl, + u32 *eccstat, unsigned int bufnum) +{ + u32 reg = eccstat[bufnum / 4]; + int errors; + + errors = (reg >> ((3 - bufnum % 4) * 8)) & 15; + + return errors; +} + +/* + * execute IFC NAND command and wait for it to complete + */ +static void fsl_ifc_run_command(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct fsl_ifc_mtd *priv = chip->priv; + struct fsl_ifc_ctrl *ctrl = priv->ctrl; + struct fsl_ifc_nand_ctrl *nctrl = ifc_nand_ctrl; + struct fsl_ifc_regs __iomem *ifc = ctrl->regs; + u32 eccstat[4]; + int i; + + /* set the chip select for NAND Transaction */ + iowrite32be(priv->bank << IFC_NAND_CSEL_SHIFT, + &ifc->ifc_nand.nand_csel); + + dev_vdbg(priv->dev, + "%s: fir0=%08x fcr0=%08x\n", + __func__, + ioread32be(&ifc->ifc_nand.nand_fir0), + ioread32be(&ifc->ifc_nand.nand_fcr0)); + + ctrl->nand_stat = 0; + + /* start read/write seq */ + iowrite32be(IFC_NAND_SEQ_STRT_FIR_STRT, &ifc->ifc_nand.nandseq_strt); + + /* wait for command complete flag or timeout */ + wait_event_timeout(ctrl->nand_wait, ctrl->nand_stat, + IFC_TIMEOUT_MSECS * HZ/1000); + + /* ctrl->nand_stat will be updated from IRQ context */ + if (!ctrl->nand_stat) + dev_err(priv->dev, "Controller is not responding\n"); + if (ctrl->nand_stat & IFC_NAND_EVTER_STAT_FTOER) + dev_err(priv->dev, "NAND Flash Timeout Error\n"); + if (ctrl->nand_stat & IFC_NAND_EVTER_STAT_WPER) + dev_err(priv->dev, "NAND Flash Write Protect Error\n"); + + nctrl->max_bitflips = 0; + + if (nctrl->eccread) { + int errors; + int bufnum = nctrl->page & priv->bufnum_mask; + int sector = bufnum * chip->ecc.steps; + int sector_end = sector + chip->ecc.steps - 1; + + for (i = sector / 4; i <= sector_end / 4; i++) + eccstat[i] = ioread32be(&ifc->ifc_nand.nand_eccstat[i]); + + for (i = sector; i <= sector_end; i++) { + errors = check_read_ecc(mtd, ctrl, eccstat, i); + + if (errors == 15) { + /* + * Uncorrectable error. + * OK only if the whole page is blank. + * + * We disable ECCER reporting due to... + * erratum IFC-A002770 -- so report it now if we + * see an uncorrectable error in ECCSTAT. + */ + if (!is_blank(mtd, bufnum)) + ctrl->nand_stat |= + IFC_NAND_EVTER_STAT_ECCER; + break; + } + + mtd->ecc_stats.corrected += errors; + nctrl->max_bitflips = max_t(unsigned int, + nctrl->max_bitflips, + errors); + } + + nctrl->eccread = 0; + } +} + +static void fsl_ifc_do_read(struct nand_chip *chip, + int oob, + struct mtd_info *mtd) +{ + struct fsl_ifc_mtd *priv = chip->priv; + struct fsl_ifc_ctrl *ctrl = priv->ctrl; + struct fsl_ifc_regs __iomem *ifc = ctrl->regs; + + /* Program FIR/IFC_NAND_FCR0 for Small/Large page */ + if (mtd->writesize > 512) { + iowrite32be((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | + (IFC_FIR_OP_CA0 << IFC_NAND_FIR0_OP1_SHIFT) | + (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP2_SHIFT) | + (IFC_FIR_OP_CMD1 << IFC_NAND_FIR0_OP3_SHIFT) | + (IFC_FIR_OP_RBCD << IFC_NAND_FIR0_OP4_SHIFT), + &ifc->ifc_nand.nand_fir0); + iowrite32be(0x0, &ifc->ifc_nand.nand_fir1); + + iowrite32be((NAND_CMD_READ0 << IFC_NAND_FCR0_CMD0_SHIFT) | + (NAND_CMD_READSTART << IFC_NAND_FCR0_CMD1_SHIFT), + &ifc->ifc_nand.nand_fcr0); + } else { + iowrite32be((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | + (IFC_FIR_OP_CA0 << IFC_NAND_FIR0_OP1_SHIFT) | + (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP2_SHIFT) | + (IFC_FIR_OP_RBCD << IFC_NAND_FIR0_OP3_SHIFT), + &ifc->ifc_nand.nand_fir0); + iowrite32be(0x0, &ifc->ifc_nand.nand_fir1); + + if (oob) + iowrite32be(NAND_CMD_READOOB << + IFC_NAND_FCR0_CMD0_SHIFT, + &ifc->ifc_nand.nand_fcr0); + else + iowrite32be(NAND_CMD_READ0 << + IFC_NAND_FCR0_CMD0_SHIFT, + &ifc->ifc_nand.nand_fcr0); + } +} + +/* cmdfunc send commands to the IFC NAND Machine */ +static void fsl_ifc_cmdfunc(struct mtd_info *mtd, unsigned int command, + int column, int page_addr) { + struct nand_chip *chip = mtd->priv; + struct fsl_ifc_mtd *priv = chip->priv; + struct fsl_ifc_ctrl *ctrl = priv->ctrl; + struct fsl_ifc_regs __iomem *ifc = ctrl->regs; + + /* clear the read buffer */ + ifc_nand_ctrl->read_bytes = 0; + if (command != NAND_CMD_PAGEPROG) + ifc_nand_ctrl->index = 0; + + switch (command) { + /* READ0 read the entire buffer to use hardware ECC. */ + case NAND_CMD_READ0: + iowrite32be(0, &ifc->ifc_nand.nand_fbcr); + set_addr(mtd, 0, page_addr, 0); + + ifc_nand_ctrl->read_bytes = mtd->writesize + mtd->oobsize; + ifc_nand_ctrl->index += column; + + if (chip->ecc.mode == NAND_ECC_HW) + ifc_nand_ctrl->eccread = 1; + + fsl_ifc_do_read(chip, 0, mtd); + fsl_ifc_run_command(mtd); + return; + + /* READOOB reads only the OOB because no ECC is performed. */ + case NAND_CMD_READOOB: + iowrite32be(mtd->oobsize - column, &ifc->ifc_nand.nand_fbcr); + set_addr(mtd, column, page_addr, 1); + + ifc_nand_ctrl->read_bytes = mtd->writesize + mtd->oobsize; + + fsl_ifc_do_read(chip, 1, mtd); + fsl_ifc_run_command(mtd); + + return; + + case NAND_CMD_READID: + case NAND_CMD_PARAM: { + int timing = IFC_FIR_OP_RB; + if (command == NAND_CMD_PARAM) + timing = IFC_FIR_OP_RBCD; + + iowrite32be((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | + (IFC_FIR_OP_UA << IFC_NAND_FIR0_OP1_SHIFT) | + (timing << IFC_NAND_FIR0_OP2_SHIFT), + &ifc->ifc_nand.nand_fir0); + iowrite32be(command << IFC_NAND_FCR0_CMD0_SHIFT, + &ifc->ifc_nand.nand_fcr0); + iowrite32be(column, &ifc->ifc_nand.row3); + + /* + * although currently it's 8 bytes for READID, we always read + * the maximum 256 bytes(for PARAM) + */ + iowrite32be(256, &ifc->ifc_nand.nand_fbcr); + ifc_nand_ctrl->read_bytes = 256; + + set_addr(mtd, 0, 0, 0); + fsl_ifc_run_command(mtd); + return; + } + + /* ERASE1 stores the block and page address */ + case NAND_CMD_ERASE1: + set_addr(mtd, 0, page_addr, 0); + return; + + /* ERASE2 uses the block and page address from ERASE1 */ + case NAND_CMD_ERASE2: + iowrite32be((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | + (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP1_SHIFT) | + (IFC_FIR_OP_CMD1 << IFC_NAND_FIR0_OP2_SHIFT), + &ifc->ifc_nand.nand_fir0); + + iowrite32be((NAND_CMD_ERASE1 << IFC_NAND_FCR0_CMD0_SHIFT) | + (NAND_CMD_ERASE2 << IFC_NAND_FCR0_CMD1_SHIFT), + &ifc->ifc_nand.nand_fcr0); + + iowrite32be(0, &ifc->ifc_nand.nand_fbcr); + ifc_nand_ctrl->read_bytes = 0; + fsl_ifc_run_command(mtd); + return; + + /* SEQIN sets up the addr buffer and all registers except the length */ + case NAND_CMD_SEQIN: { + u32 nand_fcr0; + ifc_nand_ctrl->column = column; + ifc_nand_ctrl->oob = 0; + + if (mtd->writesize > 512) { + nand_fcr0 = + (NAND_CMD_SEQIN << IFC_NAND_FCR0_CMD0_SHIFT) | + (NAND_CMD_STATUS << IFC_NAND_FCR0_CMD1_SHIFT) | + (NAND_CMD_PAGEPROG << IFC_NAND_FCR0_CMD2_SHIFT); + + iowrite32be( + (IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | + (IFC_FIR_OP_CA0 << IFC_NAND_FIR0_OP1_SHIFT) | + (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP2_SHIFT) | + (IFC_FIR_OP_WBCD << IFC_NAND_FIR0_OP3_SHIFT) | + (IFC_FIR_OP_CMD2 << IFC_NAND_FIR0_OP4_SHIFT), + &ifc->ifc_nand.nand_fir0); + iowrite32be( + (IFC_FIR_OP_CW1 << IFC_NAND_FIR1_OP5_SHIFT) | + (IFC_FIR_OP_RDSTAT << + IFC_NAND_FIR1_OP6_SHIFT) | + (IFC_FIR_OP_NOP << IFC_NAND_FIR1_OP7_SHIFT), + &ifc->ifc_nand.nand_fir1); + } else { + nand_fcr0 = ((NAND_CMD_PAGEPROG << + IFC_NAND_FCR0_CMD1_SHIFT) | + (NAND_CMD_SEQIN << + IFC_NAND_FCR0_CMD2_SHIFT) | + (NAND_CMD_STATUS << + IFC_NAND_FCR0_CMD3_SHIFT)); + + iowrite32be( + (IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | + (IFC_FIR_OP_CMD2 << IFC_NAND_FIR0_OP1_SHIFT) | + (IFC_FIR_OP_CA0 << IFC_NAND_FIR0_OP2_SHIFT) | + (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP3_SHIFT) | + (IFC_FIR_OP_WBCD << IFC_NAND_FIR0_OP4_SHIFT), + &ifc->ifc_nand.nand_fir0); + iowrite32be( + (IFC_FIR_OP_CMD1 << IFC_NAND_FIR1_OP5_SHIFT) | + (IFC_FIR_OP_CW3 << IFC_NAND_FIR1_OP6_SHIFT) | + (IFC_FIR_OP_RDSTAT << + IFC_NAND_FIR1_OP7_SHIFT) | + (IFC_FIR_OP_NOP << IFC_NAND_FIR1_OP8_SHIFT), + &ifc->ifc_nand.nand_fir1); + + if (column >= mtd->writesize) + nand_fcr0 |= + NAND_CMD_READOOB << IFC_NAND_FCR0_CMD0_SHIFT; + else + nand_fcr0 |= + NAND_CMD_READ0 << IFC_NAND_FCR0_CMD0_SHIFT; + } + + if (column >= mtd->writesize) { + /* OOB area --> READOOB */ + column -= mtd->writesize; + ifc_nand_ctrl->oob = 1; + } + iowrite32be(nand_fcr0, &ifc->ifc_nand.nand_fcr0); + set_addr(mtd, column, page_addr, ifc_nand_ctrl->oob); + return; + } + + /* PAGEPROG reuses all of the setup from SEQIN and adds the length */ + case NAND_CMD_PAGEPROG: { + if (ifc_nand_ctrl->oob) { + iowrite32be(ifc_nand_ctrl->index - + ifc_nand_ctrl->column, + &ifc->ifc_nand.nand_fbcr); + } else { + iowrite32be(0, &ifc->ifc_nand.nand_fbcr); + } + + fsl_ifc_run_command(mtd); + return; + } + + case NAND_CMD_STATUS: + iowrite32be((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | + (IFC_FIR_OP_RB << IFC_NAND_FIR0_OP1_SHIFT), + &ifc->ifc_nand.nand_fir0); + iowrite32be(NAND_CMD_STATUS << IFC_NAND_FCR0_CMD0_SHIFT, + &ifc->ifc_nand.nand_fcr0); + iowrite32be(1, &ifc->ifc_nand.nand_fbcr); + set_addr(mtd, 0, 0, 0); + ifc_nand_ctrl->read_bytes = 1; + + fsl_ifc_run_command(mtd); + + /* + * The chip always seems to report that it is + * write-protected, even when it is not. + */ + if (chip->options & NAND_BUSWIDTH_16) + setbits16(ifc_nand_ctrl->addr, NAND_STATUS_WP); + else + setbits8(ifc_nand_ctrl->addr, NAND_STATUS_WP); + return; + + case NAND_CMD_RESET: + iowrite32be(IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT, + &ifc->ifc_nand.nand_fir0); + iowrite32be(NAND_CMD_RESET << IFC_NAND_FCR0_CMD0_SHIFT, + &ifc->ifc_nand.nand_fcr0); + fsl_ifc_run_command(mtd); + return; + + default: + dev_err(priv->dev, "%s: error, unsupported command 0x%x.\n", + __func__, command); + } +} + +static void fsl_ifc_select_chip(struct mtd_info *mtd, int chip) +{ + /* The hardware does not seem to support multiple + * chips per bank. + */ +} + +/* + * Write buf to the IFC NAND Controller Data Buffer + */ +static void fsl_ifc_write_buf(struct mtd_info *mtd, const u8 *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct fsl_ifc_mtd *priv = chip->priv; + unsigned int bufsize = mtd->writesize + mtd->oobsize; + + if (len <= 0) { + dev_err(priv->dev, "%s: len %d bytes", __func__, len); + return; + } + + if ((unsigned int)len > bufsize - ifc_nand_ctrl->index) { + dev_err(priv->dev, + "%s: beyond end of buffer (%d requested, %u available)\n", + __func__, len, bufsize - ifc_nand_ctrl->index); + len = bufsize - ifc_nand_ctrl->index; + } + + memcpy_toio(ifc_nand_ctrl->addr + ifc_nand_ctrl->index, buf, len); + ifc_nand_ctrl->index += len; +} + +/* + * Read a byte from either the IFC hardware buffer + * read function for 8-bit buswidth + */ +static uint8_t fsl_ifc_read_byte(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct fsl_ifc_mtd *priv = chip->priv; + unsigned int offset; + + /* + * If there are still bytes in the IFC buffer, then use the + * next byte. + */ + if (ifc_nand_ctrl->index < ifc_nand_ctrl->read_bytes) { + offset = ifc_nand_ctrl->index++; + return in_8(ifc_nand_ctrl->addr + offset); + } + + dev_err(priv->dev, "%s: beyond end of buffer\n", __func__); + return ERR_BYTE; +} + +/* + * Read two bytes from the IFC hardware buffer + * read function for 16-bit buswith + */ +static uint8_t fsl_ifc_read_byte16(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct fsl_ifc_mtd *priv = chip->priv; + uint16_t data; + + /* + * If there are still bytes in the IFC buffer, then use the + * next byte. + */ + if (ifc_nand_ctrl->index < ifc_nand_ctrl->read_bytes) { + data = in_be16(ifc_nand_ctrl->addr + ifc_nand_ctrl->index); + ifc_nand_ctrl->index += 2; + return (uint8_t) data; + } + + dev_err(priv->dev, "%s: beyond end of buffer\n", __func__); + return ERR_BYTE; +} + +/* + * Read from the IFC Controller Data Buffer + */ +static void fsl_ifc_read_buf(struct mtd_info *mtd, u8 *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct fsl_ifc_mtd *priv = chip->priv; + int avail; + + if (len < 0) { + dev_err(priv->dev, "%s: len %d bytes", __func__, len); + return; + } + + avail = min((unsigned int)len, + ifc_nand_ctrl->read_bytes - ifc_nand_ctrl->index); + memcpy_fromio(buf, ifc_nand_ctrl->addr + ifc_nand_ctrl->index, avail); + ifc_nand_ctrl->index += avail; + + if (len > avail) + dev_err(priv->dev, + "%s: beyond end of buffer (%d requested, %d available)\n", + __func__, len, avail); +} + +/* + * This function is called after Program and Erase Operations to + * check for success or failure. + */ +static int fsl_ifc_wait(struct mtd_info *mtd, struct nand_chip *chip) +{ + struct fsl_ifc_mtd *priv = chip->priv; + struct fsl_ifc_ctrl *ctrl = priv->ctrl; + struct fsl_ifc_regs __iomem *ifc = ctrl->regs; + u32 nand_fsr; + + /* Use READ_STATUS command, but wait for the device to be ready */ + iowrite32be((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | + (IFC_FIR_OP_RDSTAT << IFC_NAND_FIR0_OP1_SHIFT), + &ifc->ifc_nand.nand_fir0); + iowrite32be(NAND_CMD_STATUS << IFC_NAND_FCR0_CMD0_SHIFT, + &ifc->ifc_nand.nand_fcr0); + iowrite32be(1, &ifc->ifc_nand.nand_fbcr); + set_addr(mtd, 0, 0, 0); + ifc_nand_ctrl->read_bytes = 1; + + fsl_ifc_run_command(mtd); + + nand_fsr = ioread32be(&ifc->ifc_nand.nand_fsr); + + /* + * The chip always seems to report that it is + * write-protected, even when it is not. + */ + return nand_fsr | NAND_STATUS_WP; +} + +static int fsl_ifc_read_page(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf, int oob_required, int page) +{ + struct fsl_ifc_mtd *priv = chip->priv; + struct fsl_ifc_ctrl *ctrl = priv->ctrl; + struct fsl_ifc_nand_ctrl *nctrl = ifc_nand_ctrl; + + fsl_ifc_read_buf(mtd, buf, mtd->writesize); + if (oob_required) + fsl_ifc_read_buf(mtd, chip->oob_poi, mtd->oobsize); + + if (ctrl->nand_stat & IFC_NAND_EVTER_STAT_ECCER) + dev_err(priv->dev, "NAND Flash ECC Uncorrectable Error\n"); + + if (ctrl->nand_stat != IFC_NAND_EVTER_STAT_OPC) + mtd->ecc_stats.failed++; + + return nctrl->max_bitflips; +} + +/* ECC will be calculated automatically, and errors will be detected in + * waitfunc. + */ +static int fsl_ifc_write_page(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf, int oob_required) +{ + fsl_ifc_write_buf(mtd, buf, mtd->writesize); + fsl_ifc_write_buf(mtd, chip->oob_poi, mtd->oobsize); + + return 0; +} + +static int fsl_ifc_chip_init_tail(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct fsl_ifc_mtd *priv = chip->priv; + + dev_dbg(priv->dev, "%s: nand->numchips = %d\n", __func__, + chip->numchips); + dev_dbg(priv->dev, "%s: nand->chipsize = %lld\n", __func__, + chip->chipsize); + dev_dbg(priv->dev, "%s: nand->pagemask = %8x\n", __func__, + chip->pagemask); + dev_dbg(priv->dev, "%s: nand->chip_delay = %d\n", __func__, + chip->chip_delay); + dev_dbg(priv->dev, "%s: nand->badblockpos = %d\n", __func__, + chip->badblockpos); + dev_dbg(priv->dev, "%s: nand->chip_shift = %d\n", __func__, + chip->chip_shift); + dev_dbg(priv->dev, "%s: nand->page_shift = %d\n", __func__, + chip->page_shift); + dev_dbg(priv->dev, "%s: nand->phys_erase_shift = %d\n", __func__, + chip->phys_erase_shift); + dev_dbg(priv->dev, "%s: nand->ecc.mode = %d\n", __func__, + chip->ecc.mode); + dev_dbg(priv->dev, "%s: nand->ecc.steps = %d\n", __func__, + chip->ecc.steps); + dev_dbg(priv->dev, "%s: nand->ecc.bytes = %d\n", __func__, + chip->ecc.bytes); + dev_dbg(priv->dev, "%s: nand->ecc.total = %d\n", __func__, + chip->ecc.total); + dev_dbg(priv->dev, "%s: nand->ecc.layout = %p\n", __func__, + chip->ecc.layout); + dev_dbg(priv->dev, "%s: mtd->flags = %08x\n", __func__, mtd->flags); + dev_dbg(priv->dev, "%s: mtd->size = %lld\n", __func__, mtd->size); + dev_dbg(priv->dev, "%s: mtd->erasesize = %d\n", __func__, + mtd->erasesize); + dev_dbg(priv->dev, "%s: mtd->writesize = %d\n", __func__, + mtd->writesize); + dev_dbg(priv->dev, "%s: mtd->oobsize = %d\n", __func__, + mtd->oobsize); + + return 0; +} + +static void fsl_ifc_sram_init(struct fsl_ifc_mtd *priv) +{ + struct fsl_ifc_ctrl *ctrl = priv->ctrl; + struct fsl_ifc_regs __iomem *ifc = ctrl->regs; + uint32_t csor = 0, csor_8k = 0, csor_ext = 0; + uint32_t cs = priv->bank; + + /* Save CSOR and CSOR_ext */ + csor = ioread32be(&ifc->csor_cs[cs].csor); + csor_ext = ioread32be(&ifc->csor_cs[cs].csor_ext); + + /* chage PageSize 8K and SpareSize 1K*/ + csor_8k = (csor & ~(CSOR_NAND_PGS_MASK)) | 0x0018C000; + iowrite32be(csor_8k, &ifc->csor_cs[cs].csor); + iowrite32be(0x0000400, &ifc->csor_cs[cs].csor_ext); + + /* READID */ + iowrite32be((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | + (IFC_FIR_OP_UA << IFC_NAND_FIR0_OP1_SHIFT) | + (IFC_FIR_OP_RB << IFC_NAND_FIR0_OP2_SHIFT), + &ifc->ifc_nand.nand_fir0); + iowrite32be(NAND_CMD_READID << IFC_NAND_FCR0_CMD0_SHIFT, + &ifc->ifc_nand.nand_fcr0); + iowrite32be(0x0, &ifc->ifc_nand.row3); + + iowrite32be(0x0, &ifc->ifc_nand.nand_fbcr); + + /* Program ROW0/COL0 */ + iowrite32be(0x0, &ifc->ifc_nand.row0); + iowrite32be(0x0, &ifc->ifc_nand.col0); + + /* set the chip select for NAND Transaction */ + iowrite32be(cs << IFC_NAND_CSEL_SHIFT, &ifc->ifc_nand.nand_csel); + + /* start read seq */ + iowrite32be(IFC_NAND_SEQ_STRT_FIR_STRT, &ifc->ifc_nand.nandseq_strt); + + /* wait for command complete flag or timeout */ + wait_event_timeout(ctrl->nand_wait, ctrl->nand_stat, + IFC_TIMEOUT_MSECS * HZ/1000); + + if (ctrl->nand_stat != IFC_NAND_EVTER_STAT_OPC) + printk(KERN_ERR "fsl-ifc: Failed to Initialise SRAM\n"); + + /* Restore CSOR and CSOR_ext */ + iowrite32be(csor, &ifc->csor_cs[cs].csor); + iowrite32be(csor_ext, &ifc->csor_cs[cs].csor_ext); +} + +static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv) +{ + struct fsl_ifc_ctrl *ctrl = priv->ctrl; + struct fsl_ifc_regs __iomem *ifc = ctrl->regs; + struct nand_chip *chip = &priv->chip; + struct nand_ecclayout *layout; + u32 csor, ver; + + /* Fill in fsl_ifc_mtd structure */ + priv->mtd.priv = chip; + priv->mtd.owner = THIS_MODULE; + + /* fill in nand_chip structure */ + /* set up function call table */ + if ((ioread32be(&ifc->cspr_cs[priv->bank].cspr)) & CSPR_PORT_SIZE_16) + chip->read_byte = fsl_ifc_read_byte16; + else + chip->read_byte = fsl_ifc_read_byte; + + chip->write_buf = fsl_ifc_write_buf; + chip->read_buf = fsl_ifc_read_buf; + chip->select_chip = fsl_ifc_select_chip; + chip->cmdfunc = fsl_ifc_cmdfunc; + chip->waitfunc = fsl_ifc_wait; + + chip->bbt_td = &bbt_main_descr; + chip->bbt_md = &bbt_mirror_descr; + + iowrite32be(0x0, &ifc->ifc_nand.ncfgr); + + /* set up nand options */ + chip->bbt_options = NAND_BBT_USE_FLASH; + chip->options = NAND_NO_SUBPAGE_WRITE; + + if (ioread32be(&ifc->cspr_cs[priv->bank].cspr) & CSPR_PORT_SIZE_16) { + chip->read_byte = fsl_ifc_read_byte16; + chip->options |= NAND_BUSWIDTH_16; + } else { + chip->read_byte = fsl_ifc_read_byte; + } + + chip->controller = &ifc_nand_ctrl->controller; + chip->priv = priv; + + chip->ecc.read_page = fsl_ifc_read_page; + chip->ecc.write_page = fsl_ifc_write_page; + + csor = ioread32be(&ifc->csor_cs[priv->bank].csor); + + /* Hardware generates ECC per 512 Bytes */ + chip->ecc.size = 512; + chip->ecc.bytes = 8; + chip->ecc.strength = 4; + + switch (csor & CSOR_NAND_PGS_MASK) { + case CSOR_NAND_PGS_512: + if (chip->options & NAND_BUSWIDTH_16) { + layout = &oob_512_16bit_ecc4; + } else { + layout = &oob_512_8bit_ecc4; + + /* Avoid conflict with bad block marker */ + bbt_main_descr.offs = 0; + bbt_mirror_descr.offs = 0; + } + + priv->bufnum_mask = 15; + break; + + case CSOR_NAND_PGS_2K: + layout = &oob_2048_ecc4; + priv->bufnum_mask = 3; + break; + + case CSOR_NAND_PGS_4K: + if ((csor & CSOR_NAND_ECC_MODE_MASK) == + CSOR_NAND_ECC_MODE_4) { + layout = &oob_4096_ecc4; + } else { + layout = &oob_4096_ecc8; + chip->ecc.bytes = 16; + chip->ecc.strength = 8; + } + + priv->bufnum_mask = 1; + break; + + case CSOR_NAND_PGS_8K: + if ((csor & CSOR_NAND_ECC_MODE_MASK) == + CSOR_NAND_ECC_MODE_4) { + layout = &oob_8192_ecc4; + } else { + layout = &oob_8192_ecc8; + chip->ecc.bytes = 16; + chip->ecc.strength = 8; + } + + priv->bufnum_mask = 0; + break; + + default: + dev_err(priv->dev, "bad csor %#x: bad page size\n", csor); + return -ENODEV; + } + + /* Must also set CSOR_NAND_ECC_ENC_EN if DEC_EN set */ + if (csor & CSOR_NAND_ECC_DEC_EN) { + chip->ecc.mode = NAND_ECC_HW; + chip->ecc.layout = layout; + } else { + chip->ecc.mode = NAND_ECC_SOFT; + } + + ver = ioread32be(&ifc->ifc_rev); + if (ver == FSL_IFC_V1_1_0) + fsl_ifc_sram_init(priv); + + return 0; +} + +static int fsl_ifc_chip_remove(struct fsl_ifc_mtd *priv) +{ + nand_release(&priv->mtd); + + kfree(priv->mtd.name); + + if (priv->vbase) + iounmap(priv->vbase); + + ifc_nand_ctrl->chips[priv->bank] = NULL; + + return 0; +} + +static int match_bank(struct fsl_ifc_regs __iomem *ifc, int bank, + phys_addr_t addr) +{ + u32 cspr = ioread32be(&ifc->cspr_cs[bank].cspr); + + if (!(cspr & CSPR_V)) + return 0; + if ((cspr & CSPR_MSEL) != CSPR_MSEL_NAND) + return 0; + + return (cspr & CSPR_BA) == convert_ifc_address(addr); +} + +static DEFINE_MUTEX(fsl_ifc_nand_mutex); + +static int fsl_ifc_nand_probe(struct platform_device *dev) +{ + struct fsl_ifc_regs __iomem *ifc; + struct fsl_ifc_mtd *priv; + struct resource res; + static const char *part_probe_types[] + = { "cmdlinepart", "RedBoot", "ofpart", NULL }; + int ret; + int bank; + struct device_node *node = dev->dev.of_node; + struct mtd_part_parser_data ppdata; + + ppdata.of_node = dev->dev.of_node; + if (!fsl_ifc_ctrl_dev || !fsl_ifc_ctrl_dev->regs) + return -ENODEV; + ifc = fsl_ifc_ctrl_dev->regs; + + /* get, allocate and map the memory resource */ + ret = of_address_to_resource(node, 0, &res); + if (ret) { + dev_err(&dev->dev, "%s: failed to get resource\n", __func__); + return ret; + } + + /* find which chip select it is connected to */ + for (bank = 0; bank < FSL_IFC_BANK_COUNT; bank++) { + if (match_bank(ifc, bank, res.start)) + break; + } + + if (bank >= FSL_IFC_BANK_COUNT) { + dev_err(&dev->dev, "%s: address did not match any chip selects\n", + __func__); + return -ENODEV; + } + + priv = devm_kzalloc(&dev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_lock(&fsl_ifc_nand_mutex); + if (!fsl_ifc_ctrl_dev->nand) { + ifc_nand_ctrl = kzalloc(sizeof(*ifc_nand_ctrl), GFP_KERNEL); + if (!ifc_nand_ctrl) { + mutex_unlock(&fsl_ifc_nand_mutex); + return -ENOMEM; + } + + ifc_nand_ctrl->read_bytes = 0; + ifc_nand_ctrl->index = 0; + ifc_nand_ctrl->addr = NULL; + fsl_ifc_ctrl_dev->nand = ifc_nand_ctrl; + + spin_lock_init(&ifc_nand_ctrl->controller.lock); + init_waitqueue_head(&ifc_nand_ctrl->controller.wq); + } else { + ifc_nand_ctrl = fsl_ifc_ctrl_dev->nand; + } + mutex_unlock(&fsl_ifc_nand_mutex); + + ifc_nand_ctrl->chips[bank] = priv; + priv->bank = bank; + priv->ctrl = fsl_ifc_ctrl_dev; + priv->dev = &dev->dev; + + priv->vbase = ioremap(res.start, resource_size(&res)); + if (!priv->vbase) { + dev_err(priv->dev, "%s: failed to map chip region\n", __func__); + ret = -ENOMEM; + goto err; + } + + dev_set_drvdata(priv->dev, priv); + + iowrite32be(IFC_NAND_EVTER_EN_OPC_EN | + IFC_NAND_EVTER_EN_FTOER_EN | + IFC_NAND_EVTER_EN_WPER_EN, + &ifc->ifc_nand.nand_evter_en); + + /* enable NAND Machine Interrupts */ + iowrite32be(IFC_NAND_EVTER_INTR_OPCIR_EN | + IFC_NAND_EVTER_INTR_FTOERIR_EN | + IFC_NAND_EVTER_INTR_WPERIR_EN, + &ifc->ifc_nand.nand_evter_intr_en); + priv->mtd.name = kasprintf(GFP_KERNEL, "%llx.flash", (u64)res.start); + if (!priv->mtd.name) { + ret = -ENOMEM; + goto err; + } + + ret = fsl_ifc_chip_init(priv); + if (ret) + goto err; + + ret = nand_scan_ident(&priv->mtd, 1, NULL); + if (ret) + goto err; + + ret = fsl_ifc_chip_init_tail(&priv->mtd); + if (ret) + goto err; + + ret = nand_scan_tail(&priv->mtd); + if (ret) + goto err; + + /* First look for RedBoot table or partitions on the command + * line, these take precedence over device tree information */ + mtd_device_parse_register(&priv->mtd, part_probe_types, &ppdata, + NULL, 0); + + dev_info(priv->dev, "IFC NAND device at 0x%llx, bank %d\n", + (unsigned long long)res.start, priv->bank); + return 0; + +err: + fsl_ifc_chip_remove(priv); + return ret; +} + +static int fsl_ifc_nand_remove(struct platform_device *dev) +{ + struct fsl_ifc_mtd *priv = dev_get_drvdata(&dev->dev); + + fsl_ifc_chip_remove(priv); + + mutex_lock(&fsl_ifc_nand_mutex); + ifc_nand_ctrl->counter--; + if (!ifc_nand_ctrl->counter) { + fsl_ifc_ctrl_dev->nand = NULL; + kfree(ifc_nand_ctrl); + } + mutex_unlock(&fsl_ifc_nand_mutex); + + return 0; +} + +static const struct of_device_id fsl_ifc_nand_match[] = { + { + .compatible = "fsl,ifc-nand", + }, + {} +}; + +static struct platform_driver fsl_ifc_nand_driver = { + .driver = { + .name = "fsl,ifc-nand", + .owner = THIS_MODULE, + .of_match_table = fsl_ifc_nand_match, + }, + .probe = fsl_ifc_nand_probe, + .remove = fsl_ifc_nand_remove, +}; + +module_platform_driver(fsl_ifc_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale"); +MODULE_DESCRIPTION("Freescale Integrated Flash Controller MTD NAND driver"); diff --git a/drivers/mtd/nand/fsl_upm.c b/drivers/mtd/nand/fsl_upm.c new file mode 100644 index 00000000000..4d203e84e8c --- /dev/null +++ b/drivers/mtd/nand/fsl_upm.c @@ -0,0 +1,362 @@ +/* + * Freescale UPM NAND driver. + * + * Copyright © 2007-2008 MontaVista Software, Inc. + * + * Author: Anton Vorontsov <avorontsov@ru.mvista.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/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/nand_ecc.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/mtd.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/of_gpio.h> +#include <linux/io.h> +#include <linux/slab.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; + struct nand_chip chip; + int last_ctrl; + struct mtd_partition *parts; + struct fsl_upm upm; + uint8_t upm_addr_offset; + uint8_t upm_cmd_offset; + void __iomem *io_base; + 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; +}; + +static inline struct fsl_upm_nand *to_fsl_upm_nand(struct mtd_info *mtdinfo) +{ + return container_of(mtdinfo, struct fsl_upm_nand, mtd); +} + +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[fun->mchip_number])) + return 1; + + dev_vdbg(fun->dev, "busy\n"); + return 0; +} + +static void fun_wait_rnb(struct fsl_upm_nand *fun) +{ + if (fun->rnb_gpio[fun->mchip_number] >= 0) { + int cnt = 1000000; + + while (--cnt && !fun_chip_ready(&fun->mtd)) + cpu_relax(); + if (!cnt) + dev_err(fun->dev, "tired waiting for RNB\n"); + } else { + ndelay(100); + } +} + +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); + + if (cmd == NAND_CMD_NONE) + return; + + fun->last_ctrl = ctrl & (NAND_ALE | NAND_CLE); + } + + if (ctrl & NAND_CTRL_CHANGE) { + if (ctrl & NAND_ALE) + fsl_upm_start_pattern(&fun->upm, fun->upm_addr_offset); + else if (ctrl & NAND_CLE) + fsl_upm_start_pattern(&fun->upm, fun->upm_cmd_offset); + } + + 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); + + if (mchip_nr == -1) { + chip->cmd_ctrl(mtd, NAND_CMD_NONE, 0 | NAND_CTRL_CHANGE); + } else if (mchip_nr >= 0 && mchip_nr < NAND_MAX_CHIPS) { + 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) +{ + struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd); + + return in_8(fun->chip.IO_ADDR_R); +} + +static void fun_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd); + int i; + + for (i = 0; i < len; i++) + buf[i] = in_8(fun->chip.IO_ADDR_R); +} + +static void fun_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) +{ + struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd); + int i; + + for (i = 0; i < len; i++) { + out_8(fun->chip.IO_ADDR_W, buf[i]); + 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 fun_chip_init(struct fsl_upm_nand *fun, + const struct device_node *upm_np, + const struct resource *io_res) +{ + int ret; + struct device_node *flash_np; + struct mtd_part_parser_data ppdata; + + fun->chip.IO_ADDR_R = fun->io_base; + fun->chip.IO_ADDR_W = fun->io_base; + fun->chip.cmd_ctrl = fun_cmd_ctrl; + fun->chip.chip_delay = fun->chip_delay; + fun->chip.read_byte = fun_read_byte; + 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] >= 0) + fun->chip.dev_ready = fun_chip_ready; + + fun->mtd.priv = &fun->chip; + fun->mtd.owner = THIS_MODULE; + + flash_np = of_get_next_child(upm_np, NULL); + if (!flash_np) + return -ENODEV; + + fun->mtd.name = kasprintf(GFP_KERNEL, "0x%llx.%s", (u64)io_res->start, + flash_np->name); + if (!fun->mtd.name) { + ret = -ENOMEM; + goto err; + } + + ret = nand_scan(&fun->mtd, fun->mchip_count); + if (ret) + goto err; + + ppdata.of_node = flash_np; + ret = mtd_device_parse_register(&fun->mtd, NULL, &ppdata, NULL, 0); +err: + of_node_put(flash_np); + if (ret) + kfree(fun->mtd.name); + return ret; +} + +static int fun_probe(struct platform_device *ofdev) +{ + struct fsl_upm_nand *fun; + struct resource io_res; + const __be32 *prop; + int rnb_gpio; + int ret; + int size; + int i; + + fun = kzalloc(sizeof(*fun), GFP_KERNEL); + if (!fun) + return -ENOMEM; + + ret = of_address_to_resource(ofdev->dev.of_node, 0, &io_res); + if (ret) { + dev_err(&ofdev->dev, "can't get IO base\n"); + goto err1; + } + + ret = fsl_upm_find(io_res.start, &fun->upm); + if (ret) { + dev_err(&ofdev->dev, "can't find UPM\n"); + goto err1; + } + + prop = of_get_property(ofdev->dev.of_node, "fsl,upm-addr-offset", + &size); + if (!prop || size != sizeof(uint32_t)) { + dev_err(&ofdev->dev, "can't get UPM address offset\n"); + ret = -EINVAL; + goto err1; + } + fun->upm_addr_offset = *prop; + + prop = of_get_property(ofdev->dev.of_node, "fsl,upm-cmd-offset", &size); + if (!prop || size != sizeof(uint32_t)) { + dev_err(&ofdev->dev, "can't get UPM command offset\n"); + ret = -EINVAL; + goto err1; + } + fun->upm_cmd_offset = *prop; + + prop = of_get_property(ofdev->dev.of_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] = be32_to_cpu(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->dev.of_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; + } + } + + prop = of_get_property(ofdev->dev.of_node, "chip-delay", NULL); + if (prop) + fun->chip_delay = be32_to_cpup(prop); + else + fun->chip_delay = 50; + + prop = of_get_property(ofdev->dev.of_node, "fsl,upm-wait-flags", &size); + if (prop && size == sizeof(uint32_t)) + fun->wait_flags = be32_to_cpup(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, + resource_size(&io_res)); + if (!fun->io_base) { + ret = -ENOMEM; + goto err2; + } + + fun->dev = &ofdev->dev; + fun->last_ctrl = NAND_CLE; + + ret = fun_chip_init(fun, ofdev->dev.of_node, &io_res); + if (ret) + goto err2; + + dev_set_drvdata(&ofdev->dev, fun); + + return 0; +err2: + for (i = 0; i < fun->mchip_count; i++) { + if (fun->rnb_gpio[i] < 0) + break; + gpio_free(fun->rnb_gpio[i]); + } +err1: + kfree(fun); + + return ret; +} + +static int fun_remove(struct platform_device *ofdev) +{ + struct fsl_upm_nand *fun = dev_get_drvdata(&ofdev->dev); + int i; + + nand_release(&fun->mtd); + kfree(fun->mtd.name); + + for (i = 0; i < fun->mchip_count; i++) { + if (fun->rnb_gpio[i] < 0) + break; + gpio_free(fun->rnb_gpio[i]); + } + + kfree(fun); + + return 0; +} + +static const struct of_device_id of_fun_match[] = { + { .compatible = "fsl,upm-nand" }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_fun_match); + +static struct platform_driver of_fun_driver = { + .driver = { + .name = "fsl,upm-nand", + .owner = THIS_MODULE, + .of_match_table = of_fun_match, + }, + .probe = fun_probe, + .remove = fun_remove, +}; + +module_platform_driver(of_fun_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Anton Vorontsov <avorontsov@ru.mvista.com>"); +MODULE_DESCRIPTION("Driver for NAND chips working through Freescale " + "LocalBus User-Programmable Machine"); diff --git a/drivers/mtd/nand/fsmc_nand.c b/drivers/mtd/nand/fsmc_nand.c new file mode 100644 index 00000000000..1550692973d --- /dev/null +++ b/drivers/mtd/nand/fsmc_nand.c @@ -0,0 +1,1238 @@ +/* + * drivers/mtd/nand/fsmc_nand.c + * + * ST Microelectronics + * Flexible Static Memory Controller (FSMC) + * Driver for NAND portions + * + * Copyright © 2010 ST Microelectronics + * Vipin Kumar <vipin.kumar@st.com> + * Ashish Priyadarshi + * + * Based on drivers/mtd/nand/nomadik_nand.c + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/dmaengine.h> +#include <linux/dma-direction.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/resource.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/nand_ecc.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/mtd/partitions.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/mtd/fsmc.h> +#include <linux/amba/bus.h> +#include <mtd/mtd-abi.h> + +static struct nand_ecclayout fsmc_ecc1_128_layout = { + .eccbytes = 24, + .eccpos = {2, 3, 4, 18, 19, 20, 34, 35, 36, 50, 51, 52, + 66, 67, 68, 82, 83, 84, 98, 99, 100, 114, 115, 116}, + .oobfree = { + {.offset = 8, .length = 8}, + {.offset = 24, .length = 8}, + {.offset = 40, .length = 8}, + {.offset = 56, .length = 8}, + {.offset = 72, .length = 8}, + {.offset = 88, .length = 8}, + {.offset = 104, .length = 8}, + {.offset = 120, .length = 8} + } +}; + +static struct nand_ecclayout fsmc_ecc1_64_layout = { + .eccbytes = 12, + .eccpos = {2, 3, 4, 18, 19, 20, 34, 35, 36, 50, 51, 52}, + .oobfree = { + {.offset = 8, .length = 8}, + {.offset = 24, .length = 8}, + {.offset = 40, .length = 8}, + {.offset = 56, .length = 8}, + } +}; + +static struct nand_ecclayout fsmc_ecc1_16_layout = { + .eccbytes = 3, + .eccpos = {2, 3, 4}, + .oobfree = { + {.offset = 8, .length = 8}, + } +}; + +/* + * ECC4 layout for NAND of pagesize 8192 bytes & OOBsize 256 bytes. 13*16 bytes + * of OB size is reserved for ECC, Byte no. 0 & 1 reserved for bad block and 46 + * bytes are free for use. + */ +static struct nand_ecclayout fsmc_ecc4_256_layout = { + .eccbytes = 208, + .eccpos = { 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, + 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, + 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, + 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, + 66, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 76, 77, 78, + 82, 83, 84, 85, 86, 87, 88, + 89, 90, 91, 92, 93, 94, + 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, + 114, 115, 116, 117, 118, 119, 120, + 121, 122, 123, 124, 125, 126, + 130, 131, 132, 133, 134, 135, 136, + 137, 138, 139, 140, 141, 142, + 146, 147, 148, 149, 150, 151, 152, + 153, 154, 155, 156, 157, 158, + 162, 163, 164, 165, 166, 167, 168, + 169, 170, 171, 172, 173, 174, + 178, 179, 180, 181, 182, 183, 184, + 185, 186, 187, 188, 189, 190, + 194, 195, 196, 197, 198, 199, 200, + 201, 202, 203, 204, 205, 206, + 210, 211, 212, 213, 214, 215, 216, + 217, 218, 219, 220, 221, 222, + 226, 227, 228, 229, 230, 231, 232, + 233, 234, 235, 236, 237, 238, + 242, 243, 244, 245, 246, 247, 248, + 249, 250, 251, 252, 253, 254 + }, + .oobfree = { + {.offset = 15, .length = 3}, + {.offset = 31, .length = 3}, + {.offset = 47, .length = 3}, + {.offset = 63, .length = 3}, + {.offset = 79, .length = 3}, + {.offset = 95, .length = 3}, + {.offset = 111, .length = 3}, + {.offset = 127, .length = 3}, + {.offset = 143, .length = 3}, + {.offset = 159, .length = 3}, + {.offset = 175, .length = 3}, + {.offset = 191, .length = 3}, + {.offset = 207, .length = 3}, + {.offset = 223, .length = 3}, + {.offset = 239, .length = 3}, + {.offset = 255, .length = 1} + } +}; + +/* + * ECC4 layout for NAND of pagesize 4096 bytes & OOBsize 224 bytes. 13*8 bytes + * of OOB size is reserved for ECC, Byte no. 0 & 1 reserved for bad block & 118 + * bytes are free for use. + */ +static struct nand_ecclayout fsmc_ecc4_224_layout = { + .eccbytes = 104, + .eccpos = { 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, + 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, + 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, + 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, + 66, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 76, 77, 78, + 82, 83, 84, 85, 86, 87, 88, + 89, 90, 91, 92, 93, 94, + 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, + 114, 115, 116, 117, 118, 119, 120, + 121, 122, 123, 124, 125, 126 + }, + .oobfree = { + {.offset = 15, .length = 3}, + {.offset = 31, .length = 3}, + {.offset = 47, .length = 3}, + {.offset = 63, .length = 3}, + {.offset = 79, .length = 3}, + {.offset = 95, .length = 3}, + {.offset = 111, .length = 3}, + {.offset = 127, .length = 97} + } +}; + +/* + * ECC4 layout for NAND of pagesize 4096 bytes & OOBsize 128 bytes. 13*8 bytes + * of OOB size is reserved for ECC, Byte no. 0 & 1 reserved for bad block & 22 + * bytes are free for use. + */ +static struct nand_ecclayout fsmc_ecc4_128_layout = { + .eccbytes = 104, + .eccpos = { 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, + 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, + 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, + 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, + 66, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 76, 77, 78, + 82, 83, 84, 85, 86, 87, 88, + 89, 90, 91, 92, 93, 94, + 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, + 114, 115, 116, 117, 118, 119, 120, + 121, 122, 123, 124, 125, 126 + }, + .oobfree = { + {.offset = 15, .length = 3}, + {.offset = 31, .length = 3}, + {.offset = 47, .length = 3}, + {.offset = 63, .length = 3}, + {.offset = 79, .length = 3}, + {.offset = 95, .length = 3}, + {.offset = 111, .length = 3}, + {.offset = 127, .length = 1} + } +}; + +/* + * ECC4 layout for NAND of pagesize 2048 bytes & OOBsize 64 bytes. 13*4 bytes of + * OOB size is reserved for ECC, Byte no. 0 & 1 reserved for bad block and 10 + * bytes are free for use. + */ +static struct nand_ecclayout fsmc_ecc4_64_layout = { + .eccbytes = 52, + .eccpos = { 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, + 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, + 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, + 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, + }, + .oobfree = { + {.offset = 15, .length = 3}, + {.offset = 31, .length = 3}, + {.offset = 47, .length = 3}, + {.offset = 63, .length = 1}, + } +}; + +/* + * ECC4 layout for NAND of pagesize 512 bytes & OOBsize 16 bytes. 13 bytes of + * OOB size is reserved for ECC, Byte no. 4 & 5 reserved for bad block and One + * byte is free for use. + */ +static struct nand_ecclayout fsmc_ecc4_16_layout = { + .eccbytes = 13, + .eccpos = { 0, 1, 2, 3, 6, 7, 8, + 9, 10, 11, 12, 13, 14 + }, + .oobfree = { + {.offset = 15, .length = 1}, + } +}; + +/* + * ECC placement definitions in oobfree type format. + * There are 13 bytes of ecc for every 512 byte block and it has to be read + * consecutively and immediately after the 512 byte data block for hardware to + * generate the error bit offsets in 512 byte data. + * Managing the ecc bytes in the following way makes it easier for software to + * read ecc bytes consecutive to data bytes. This way is similar to + * oobfree structure maintained already in generic nand driver + */ +static struct fsmc_eccplace fsmc_ecc4_lp_place = { + .eccplace = { + {.offset = 2, .length = 13}, + {.offset = 18, .length = 13}, + {.offset = 34, .length = 13}, + {.offset = 50, .length = 13}, + {.offset = 66, .length = 13}, + {.offset = 82, .length = 13}, + {.offset = 98, .length = 13}, + {.offset = 114, .length = 13} + } +}; + +static struct fsmc_eccplace fsmc_ecc4_sp_place = { + .eccplace = { + {.offset = 0, .length = 4}, + {.offset = 6, .length = 9} + } +}; + +/** + * struct fsmc_nand_data - structure for FSMC NAND device state + * + * @pid: Part ID on the AMBA PrimeCell format + * @mtd: MTD info for a NAND flash. + * @nand: Chip related info for a NAND flash. + * @partitions: Partition info for a NAND Flash. + * @nr_partitions: Total number of partition of a NAND flash. + * + * @ecc_place: ECC placing locations in oobfree type format. + * @bank: Bank number for probed device. + * @clk: Clock structure for FSMC. + * + * @read_dma_chan: DMA channel for read access + * @write_dma_chan: DMA channel for write access to NAND + * @dma_access_complete: Completion structure + * + * @data_pa: NAND Physical port for Data. + * @data_va: NAND port for Data. + * @cmd_va: NAND port for Command. + * @addr_va: NAND port for Address. + * @regs_va: FSMC regs base address. + */ +struct fsmc_nand_data { + u32 pid; + struct mtd_info mtd; + struct nand_chip nand; + struct mtd_partition *partitions; + unsigned int nr_partitions; + + struct fsmc_eccplace *ecc_place; + unsigned int bank; + struct device *dev; + enum access_mode mode; + struct clk *clk; + + /* DMA related objects */ + struct dma_chan *read_dma_chan; + struct dma_chan *write_dma_chan; + struct completion dma_access_complete; + + struct fsmc_nand_timings *dev_timings; + + dma_addr_t data_pa; + void __iomem *data_va; + void __iomem *cmd_va; + void __iomem *addr_va; + void __iomem *regs_va; + + void (*select_chip)(uint32_t bank, uint32_t busw); +}; + +/* Assert CS signal based on chipnr */ +static void fsmc_select_chip(struct mtd_info *mtd, int chipnr) +{ + struct nand_chip *chip = mtd->priv; + struct fsmc_nand_data *host; + + host = container_of(mtd, struct fsmc_nand_data, mtd); + + switch (chipnr) { + case -1: + chip->cmd_ctrl(mtd, NAND_CMD_NONE, 0 | NAND_CTRL_CHANGE); + break; + case 0: + case 1: + case 2: + case 3: + if (host->select_chip) + host->select_chip(chipnr, + chip->options & NAND_BUSWIDTH_16); + break; + + default: + BUG(); + } +} + +/* + * fsmc_cmd_ctrl - For facilitaing Hardware access + * This routine allows hardware specific access to control-lines(ALE,CLE) + */ +static void fsmc_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl) +{ + struct nand_chip *this = mtd->priv; + struct fsmc_nand_data *host = container_of(mtd, + struct fsmc_nand_data, mtd); + void __iomem *regs = host->regs_va; + unsigned int bank = host->bank; + + if (ctrl & NAND_CTRL_CHANGE) { + u32 pc; + + if (ctrl & NAND_CLE) { + this->IO_ADDR_R = host->cmd_va; + this->IO_ADDR_W = host->cmd_va; + } else if (ctrl & NAND_ALE) { + this->IO_ADDR_R = host->addr_va; + this->IO_ADDR_W = host->addr_va; + } else { + this->IO_ADDR_R = host->data_va; + this->IO_ADDR_W = host->data_va; + } + + pc = readl(FSMC_NAND_REG(regs, bank, PC)); + if (ctrl & NAND_NCE) + pc |= FSMC_ENABLE; + else + pc &= ~FSMC_ENABLE; + writel_relaxed(pc, FSMC_NAND_REG(regs, bank, PC)); + } + + mb(); + + if (cmd != NAND_CMD_NONE) + writeb_relaxed(cmd, this->IO_ADDR_W); +} + +/* + * fsmc_nand_setup - FSMC (Flexible Static Memory Controller) init routine + * + * This routine initializes timing parameters related to NAND memory access in + * FSMC registers + */ +static void fsmc_nand_setup(void __iomem *regs, uint32_t bank, + uint32_t busw, struct fsmc_nand_timings *timings) +{ + uint32_t value = FSMC_DEVTYPE_NAND | FSMC_ENABLE | FSMC_WAITON; + uint32_t tclr, tar, thiz, thold, twait, tset; + struct fsmc_nand_timings *tims; + struct fsmc_nand_timings default_timings = { + .tclr = FSMC_TCLR_1, + .tar = FSMC_TAR_1, + .thiz = FSMC_THIZ_1, + .thold = FSMC_THOLD_4, + .twait = FSMC_TWAIT_6, + .tset = FSMC_TSET_0, + }; + + if (timings) + tims = timings; + else + tims = &default_timings; + + tclr = (tims->tclr & FSMC_TCLR_MASK) << FSMC_TCLR_SHIFT; + tar = (tims->tar & FSMC_TAR_MASK) << FSMC_TAR_SHIFT; + thiz = (tims->thiz & FSMC_THIZ_MASK) << FSMC_THIZ_SHIFT; + thold = (tims->thold & FSMC_THOLD_MASK) << FSMC_THOLD_SHIFT; + twait = (tims->twait & FSMC_TWAIT_MASK) << FSMC_TWAIT_SHIFT; + tset = (tims->tset & FSMC_TSET_MASK) << FSMC_TSET_SHIFT; + + if (busw) + writel_relaxed(value | FSMC_DEVWID_16, + FSMC_NAND_REG(regs, bank, PC)); + else + writel_relaxed(value | FSMC_DEVWID_8, + FSMC_NAND_REG(regs, bank, PC)); + + writel_relaxed(readl(FSMC_NAND_REG(regs, bank, PC)) | tclr | tar, + FSMC_NAND_REG(regs, bank, PC)); + writel_relaxed(thiz | thold | twait | tset, + FSMC_NAND_REG(regs, bank, COMM)); + writel_relaxed(thiz | thold | twait | tset, + FSMC_NAND_REG(regs, bank, ATTRIB)); +} + +/* + * fsmc_enable_hwecc - Enables Hardware ECC through FSMC registers + */ +static void fsmc_enable_hwecc(struct mtd_info *mtd, int mode) +{ + struct fsmc_nand_data *host = container_of(mtd, + struct fsmc_nand_data, mtd); + void __iomem *regs = host->regs_va; + uint32_t bank = host->bank; + + writel_relaxed(readl(FSMC_NAND_REG(regs, bank, PC)) & ~FSMC_ECCPLEN_256, + FSMC_NAND_REG(regs, bank, PC)); + writel_relaxed(readl(FSMC_NAND_REG(regs, bank, PC)) & ~FSMC_ECCEN, + FSMC_NAND_REG(regs, bank, PC)); + writel_relaxed(readl(FSMC_NAND_REG(regs, bank, PC)) | FSMC_ECCEN, + FSMC_NAND_REG(regs, bank, PC)); +} + +/* + * fsmc_read_hwecc_ecc4 - Hardware ECC calculator for ecc4 option supported by + * FSMC. ECC is 13 bytes for 512 bytes of data (supports error correction up to + * max of 8-bits) + */ +static int fsmc_read_hwecc_ecc4(struct mtd_info *mtd, const uint8_t *data, + uint8_t *ecc) +{ + struct fsmc_nand_data *host = container_of(mtd, + struct fsmc_nand_data, mtd); + void __iomem *regs = host->regs_va; + uint32_t bank = host->bank; + uint32_t ecc_tmp; + unsigned long deadline = jiffies + FSMC_BUSY_WAIT_TIMEOUT; + + do { + if (readl_relaxed(FSMC_NAND_REG(regs, bank, STS)) & FSMC_CODE_RDY) + break; + else + cond_resched(); + } while (!time_after_eq(jiffies, deadline)); + + if (time_after_eq(jiffies, deadline)) { + dev_err(host->dev, "calculate ecc timed out\n"); + return -ETIMEDOUT; + } + + ecc_tmp = readl_relaxed(FSMC_NAND_REG(regs, bank, ECC1)); + ecc[0] = (uint8_t) (ecc_tmp >> 0); + ecc[1] = (uint8_t) (ecc_tmp >> 8); + ecc[2] = (uint8_t) (ecc_tmp >> 16); + ecc[3] = (uint8_t) (ecc_tmp >> 24); + + ecc_tmp = readl_relaxed(FSMC_NAND_REG(regs, bank, ECC2)); + ecc[4] = (uint8_t) (ecc_tmp >> 0); + ecc[5] = (uint8_t) (ecc_tmp >> 8); + ecc[6] = (uint8_t) (ecc_tmp >> 16); + ecc[7] = (uint8_t) (ecc_tmp >> 24); + + ecc_tmp = readl_relaxed(FSMC_NAND_REG(regs, bank, ECC3)); + ecc[8] = (uint8_t) (ecc_tmp >> 0); + ecc[9] = (uint8_t) (ecc_tmp >> 8); + ecc[10] = (uint8_t) (ecc_tmp >> 16); + ecc[11] = (uint8_t) (ecc_tmp >> 24); + + ecc_tmp = readl_relaxed(FSMC_NAND_REG(regs, bank, STS)); + ecc[12] = (uint8_t) (ecc_tmp >> 16); + + return 0; +} + +/* + * fsmc_read_hwecc_ecc1 - Hardware ECC calculator for ecc1 option supported by + * FSMC. ECC is 3 bytes for 512 bytes of data (supports error correction up to + * max of 1-bit) + */ +static int fsmc_read_hwecc_ecc1(struct mtd_info *mtd, const uint8_t *data, + uint8_t *ecc) +{ + struct fsmc_nand_data *host = container_of(mtd, + struct fsmc_nand_data, mtd); + void __iomem *regs = host->regs_va; + uint32_t bank = host->bank; + uint32_t ecc_tmp; + + ecc_tmp = readl_relaxed(FSMC_NAND_REG(regs, bank, ECC1)); + ecc[0] = (uint8_t) (ecc_tmp >> 0); + ecc[1] = (uint8_t) (ecc_tmp >> 8); + ecc[2] = (uint8_t) (ecc_tmp >> 16); + + return 0; +} + +/* Count the number of 0's in buff upto a max of max_bits */ +static int count_written_bits(uint8_t *buff, int size, int max_bits) +{ + int k, written_bits = 0; + + for (k = 0; k < size; k++) { + written_bits += hweight8(~buff[k]); + if (written_bits > max_bits) + break; + } + + return written_bits; +} + +static void dma_complete(void *param) +{ + struct fsmc_nand_data *host = param; + + complete(&host->dma_access_complete); +} + +static int dma_xfer(struct fsmc_nand_data *host, void *buffer, int len, + enum dma_data_direction direction) +{ + struct dma_chan *chan; + struct dma_device *dma_dev; + struct dma_async_tx_descriptor *tx; + dma_addr_t dma_dst, dma_src, dma_addr; + dma_cookie_t cookie; + unsigned long flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT; + int ret; + + if (direction == DMA_TO_DEVICE) + chan = host->write_dma_chan; + else if (direction == DMA_FROM_DEVICE) + chan = host->read_dma_chan; + else + return -EINVAL; + + dma_dev = chan->device; + dma_addr = dma_map_single(dma_dev->dev, buffer, len, direction); + + if (direction == DMA_TO_DEVICE) { + dma_src = dma_addr; + dma_dst = host->data_pa; + } else { + dma_src = host->data_pa; + dma_dst = dma_addr; + } + + tx = dma_dev->device_prep_dma_memcpy(chan, dma_dst, dma_src, + len, flags); + if (!tx) { + dev_err(host->dev, "device_prep_dma_memcpy error\n"); + ret = -EIO; + goto unmap_dma; + } + + tx->callback = dma_complete; + tx->callback_param = host; + cookie = tx->tx_submit(tx); + + ret = dma_submit_error(cookie); + if (ret) { + dev_err(host->dev, "dma_submit_error %d\n", cookie); + goto unmap_dma; + } + + dma_async_issue_pending(chan); + + ret = + wait_for_completion_timeout(&host->dma_access_complete, + msecs_to_jiffies(3000)); + if (ret <= 0) { + chan->device->device_control(chan, DMA_TERMINATE_ALL, 0); + dev_err(host->dev, "wait_for_completion_timeout\n"); + if (!ret) + ret = -ETIMEDOUT; + goto unmap_dma; + } + + ret = 0; + +unmap_dma: + dma_unmap_single(dma_dev->dev, dma_addr, len, direction); + + return ret; +} + +/* + * fsmc_write_buf - write buffer to chip + * @mtd: MTD device structure + * @buf: data buffer + * @len: number of bytes to write + */ +static void fsmc_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) +{ + int i; + struct nand_chip *chip = mtd->priv; + + if (IS_ALIGNED((uint32_t)buf, sizeof(uint32_t)) && + IS_ALIGNED(len, sizeof(uint32_t))) { + uint32_t *p = (uint32_t *)buf; + len = len >> 2; + for (i = 0; i < len; i++) + writel_relaxed(p[i], chip->IO_ADDR_W); + } else { + for (i = 0; i < len; i++) + writeb_relaxed(buf[i], chip->IO_ADDR_W); + } +} + +/* + * fsmc_read_buf - read chip data into buffer + * @mtd: MTD device structure + * @buf: buffer to store date + * @len: number of bytes to read + */ +static void fsmc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + int i; + struct nand_chip *chip = mtd->priv; + + if (IS_ALIGNED((uint32_t)buf, sizeof(uint32_t)) && + IS_ALIGNED(len, sizeof(uint32_t))) { + uint32_t *p = (uint32_t *)buf; + len = len >> 2; + for (i = 0; i < len; i++) + p[i] = readl_relaxed(chip->IO_ADDR_R); + } else { + for (i = 0; i < len; i++) + buf[i] = readb_relaxed(chip->IO_ADDR_R); + } +} + +/* + * fsmc_read_buf_dma - read chip data into buffer + * @mtd: MTD device structure + * @buf: buffer to store date + * @len: number of bytes to read + */ +static void fsmc_read_buf_dma(struct mtd_info *mtd, uint8_t *buf, int len) +{ + struct fsmc_nand_data *host; + + host = container_of(mtd, struct fsmc_nand_data, mtd); + dma_xfer(host, buf, len, DMA_FROM_DEVICE); +} + +/* + * fsmc_write_buf_dma - write buffer to chip + * @mtd: MTD device structure + * @buf: data buffer + * @len: number of bytes to write + */ +static void fsmc_write_buf_dma(struct mtd_info *mtd, const uint8_t *buf, + int len) +{ + struct fsmc_nand_data *host; + + host = container_of(mtd, struct fsmc_nand_data, mtd); + dma_xfer(host, (void *)buf, len, DMA_TO_DEVICE); +} + +/* + * fsmc_read_page_hwecc + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: buffer to store read data + * @oob_required: caller expects OOB data read to chip->oob_poi + * @page: page number to read + * + * This routine is needed for fsmc version 8 as reading from NAND chip has to be + * performed in a strict sequence as follows: + * data(512 byte) -> ecc(13 byte) + * After this read, fsmc hardware generates and reports error data bits(up to a + * max of 8 bits) + */ +static int fsmc_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf, int oob_required, int page) +{ + struct fsmc_nand_data *host = container_of(mtd, + struct fsmc_nand_data, mtd); + struct fsmc_eccplace *ecc_place = host->ecc_place; + int i, j, s, stat, eccsize = chip->ecc.size; + int eccbytes = chip->ecc.bytes; + int eccsteps = chip->ecc.steps; + uint8_t *p = buf; + uint8_t *ecc_calc = chip->buffers->ecccalc; + uint8_t *ecc_code = chip->buffers->ecccode; + int off, len, group = 0; + /* + * ecc_oob is intentionally taken as uint16_t. In 16bit devices, we + * end up reading 14 bytes (7 words) from oob. The local array is + * to maintain word alignment + */ + uint16_t ecc_oob[7]; + uint8_t *oob = (uint8_t *)&ecc_oob[0]; + unsigned int max_bitflips = 0; + + for (i = 0, s = 0; s < eccsteps; s++, i += eccbytes, p += eccsize) { + chip->cmdfunc(mtd, NAND_CMD_READ0, s * eccsize, page); + chip->ecc.hwctl(mtd, NAND_ECC_READ); + chip->read_buf(mtd, p, eccsize); + + for (j = 0; j < eccbytes;) { + off = ecc_place->eccplace[group].offset; + len = ecc_place->eccplace[group].length; + group++; + + /* + * length is intentionally kept a higher multiple of 2 + * to read at least 13 bytes even in case of 16 bit NAND + * devices + */ + if (chip->options & NAND_BUSWIDTH_16) + len = roundup(len, 2); + + chip->cmdfunc(mtd, NAND_CMD_READOOB, off, page); + chip->read_buf(mtd, oob + j, len); + j += len; + } + + memcpy(&ecc_code[i], oob, chip->ecc.bytes); + chip->ecc.calculate(mtd, p, &ecc_calc[i]); + + stat = chip->ecc.correct(mtd, p, &ecc_code[i], &ecc_calc[i]); + if (stat < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } + } + + return max_bitflips; +} + +/* + * fsmc_bch8_correct_data + * @mtd: mtd info structure + * @dat: buffer of read data + * @read_ecc: ecc read from device spare area + * @calc_ecc: ecc calculated from read data + * + * calc_ecc is a 104 bit information containing maximum of 8 error + * offset informations of 13 bits each in 512 bytes of read data. + */ +static int fsmc_bch8_correct_data(struct mtd_info *mtd, uint8_t *dat, + uint8_t *read_ecc, uint8_t *calc_ecc) +{ + struct fsmc_nand_data *host = container_of(mtd, + struct fsmc_nand_data, mtd); + struct nand_chip *chip = mtd->priv; + void __iomem *regs = host->regs_va; + unsigned int bank = host->bank; + uint32_t err_idx[8]; + uint32_t num_err, i; + uint32_t ecc1, ecc2, ecc3, ecc4; + + num_err = (readl_relaxed(FSMC_NAND_REG(regs, bank, STS)) >> 10) & 0xF; + + /* no bit flipping */ + if (likely(num_err == 0)) + return 0; + + /* too many errors */ + if (unlikely(num_err > 8)) { + /* + * This is a temporary erase check. A newly erased page read + * would result in an ecc error because the oob data is also + * erased to FF and the calculated ecc for an FF data is not + * FF..FF. + * This is a workaround to skip performing correction in case + * data is FF..FF + * + * Logic: + * For every page, each bit written as 0 is counted until these + * number of bits are greater than 8 (the maximum correction + * capability of FSMC for each 512 + 13 bytes) + */ + + int bits_ecc = count_written_bits(read_ecc, chip->ecc.bytes, 8); + int bits_data = count_written_bits(dat, chip->ecc.size, 8); + + if ((bits_ecc + bits_data) <= 8) { + if (bits_data) + memset(dat, 0xff, chip->ecc.size); + return bits_data; + } + + return -EBADMSG; + } + + /* + * ------------------- calc_ecc[] bit wise -----------|--13 bits--| + * |---idx[7]--|--.....-----|---idx[2]--||---idx[1]--||---idx[0]--| + * + * calc_ecc is a 104 bit information containing maximum of 8 error + * offset informations of 13 bits each. calc_ecc is copied into a + * uint64_t array and error offset indexes are populated in err_idx + * array + */ + ecc1 = readl_relaxed(FSMC_NAND_REG(regs, bank, ECC1)); + ecc2 = readl_relaxed(FSMC_NAND_REG(regs, bank, ECC2)); + ecc3 = readl_relaxed(FSMC_NAND_REG(regs, bank, ECC3)); + ecc4 = readl_relaxed(FSMC_NAND_REG(regs, bank, STS)); + + err_idx[0] = (ecc1 >> 0) & 0x1FFF; + err_idx[1] = (ecc1 >> 13) & 0x1FFF; + err_idx[2] = (((ecc2 >> 0) & 0x7F) << 6) | ((ecc1 >> 26) & 0x3F); + err_idx[3] = (ecc2 >> 7) & 0x1FFF; + err_idx[4] = (((ecc3 >> 0) & 0x1) << 12) | ((ecc2 >> 20) & 0xFFF); + err_idx[5] = (ecc3 >> 1) & 0x1FFF; + err_idx[6] = (ecc3 >> 14) & 0x1FFF; + err_idx[7] = (((ecc4 >> 16) & 0xFF) << 5) | ((ecc3 >> 27) & 0x1F); + + i = 0; + while (num_err--) { + change_bit(0, (unsigned long *)&err_idx[i]); + change_bit(1, (unsigned long *)&err_idx[i]); + + if (err_idx[i] < chip->ecc.size * 8) { + change_bit(err_idx[i], (unsigned long *)dat); + i++; + } + } + return i; +} + +static bool filter(struct dma_chan *chan, void *slave) +{ + chan->private = slave; + return true; +} + +#ifdef CONFIG_OF +static int fsmc_nand_probe_config_dt(struct platform_device *pdev, + struct device_node *np) +{ + struct fsmc_nand_platform_data *pdata = dev_get_platdata(&pdev->dev); + u32 val; + + /* Set default NAND width to 8 bits */ + pdata->width = 8; + if (!of_property_read_u32(np, "bank-width", &val)) { + if (val == 2) { + pdata->width = 16; + } else if (val != 1) { + dev_err(&pdev->dev, "invalid bank-width %u\n", val); + return -EINVAL; + } + } + if (of_get_property(np, "nand-skip-bbtscan", NULL)) + pdata->options = NAND_SKIP_BBTSCAN; + + pdata->nand_timings = devm_kzalloc(&pdev->dev, + sizeof(*pdata->nand_timings), GFP_KERNEL); + if (!pdata->nand_timings) + return -ENOMEM; + of_property_read_u8_array(np, "timings", (u8 *)pdata->nand_timings, + sizeof(*pdata->nand_timings)); + + /* Set default NAND bank to 0 */ + pdata->bank = 0; + if (!of_property_read_u32(np, "bank", &val)) { + if (val > 3) { + dev_err(&pdev->dev, "invalid bank %u\n", val); + return -EINVAL; + } + pdata->bank = val; + } + return 0; +} +#else +static int fsmc_nand_probe_config_dt(struct platform_device *pdev, + struct device_node *np) +{ + return -ENOSYS; +} +#endif + +/* + * fsmc_nand_probe - Probe function + * @pdev: platform device structure + */ +static int __init fsmc_nand_probe(struct platform_device *pdev) +{ + struct fsmc_nand_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct device_node __maybe_unused *np = pdev->dev.of_node; + struct mtd_part_parser_data ppdata = {}; + struct fsmc_nand_data *host; + struct mtd_info *mtd; + struct nand_chip *nand; + struct resource *res; + dma_cap_mask_t mask; + int ret = 0; + u32 pid; + int i; + + if (np) { + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + pdev->dev.platform_data = pdata; + ret = fsmc_nand_probe_config_dt(pdev, np); + if (ret) { + dev_err(&pdev->dev, "no platform data\n"); + return -ENODEV; + } + } + + if (!pdata) { + dev_err(&pdev->dev, "platform data is NULL\n"); + return -EINVAL; + } + + /* Allocate memory for the device structure (and zero it) */ + host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand_data"); + host->data_va = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(host->data_va)) + return PTR_ERR(host->data_va); + + host->data_pa = (dma_addr_t)res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand_addr"); + host->addr_va = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(host->addr_va)) + return PTR_ERR(host->addr_va); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand_cmd"); + host->cmd_va = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(host->cmd_va)) + return PTR_ERR(host->cmd_va); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "fsmc_regs"); + host->regs_va = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(host->regs_va)) + return PTR_ERR(host->regs_va); + + host->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(host->clk)) { + dev_err(&pdev->dev, "failed to fetch block clock\n"); + return PTR_ERR(host->clk); + } + + ret = clk_prepare_enable(host->clk); + if (ret) + goto err_clk_prepare_enable; + + /* + * This device ID is actually a common AMBA ID as used on the + * AMBA PrimeCell bus. However it is not a PrimeCell. + */ + for (pid = 0, i = 0; i < 4; i++) + pid |= (readl(host->regs_va + resource_size(res) - 0x20 + 4 * i) & 255) << (i * 8); + host->pid = pid; + dev_info(&pdev->dev, "FSMC device partno %03x, manufacturer %02x, " + "revision %02x, config %02x\n", + AMBA_PART_BITS(pid), AMBA_MANF_BITS(pid), + AMBA_REV_BITS(pid), AMBA_CONFIG_BITS(pid)); + + host->bank = pdata->bank; + host->select_chip = pdata->select_bank; + host->partitions = pdata->partitions; + host->nr_partitions = pdata->nr_partitions; + host->dev = &pdev->dev; + host->dev_timings = pdata->nand_timings; + host->mode = pdata->mode; + + if (host->mode == USE_DMA_ACCESS) + init_completion(&host->dma_access_complete); + + /* Link all private pointers */ + mtd = &host->mtd; + nand = &host->nand; + mtd->priv = nand; + nand->priv = host; + + host->mtd.owner = THIS_MODULE; + nand->IO_ADDR_R = host->data_va; + nand->IO_ADDR_W = host->data_va; + nand->cmd_ctrl = fsmc_cmd_ctrl; + nand->chip_delay = 30; + + nand->ecc.mode = NAND_ECC_HW; + nand->ecc.hwctl = fsmc_enable_hwecc; + nand->ecc.size = 512; + nand->options = pdata->options; + nand->select_chip = fsmc_select_chip; + nand->badblockbits = 7; + + if (pdata->width == FSMC_NAND_BW16) + nand->options |= NAND_BUSWIDTH_16; + + switch (host->mode) { + case USE_DMA_ACCESS: + dma_cap_zero(mask); + dma_cap_set(DMA_MEMCPY, mask); + host->read_dma_chan = dma_request_channel(mask, filter, + pdata->read_dma_priv); + if (!host->read_dma_chan) { + dev_err(&pdev->dev, "Unable to get read dma channel\n"); + goto err_req_read_chnl; + } + host->write_dma_chan = dma_request_channel(mask, filter, + pdata->write_dma_priv); + if (!host->write_dma_chan) { + dev_err(&pdev->dev, "Unable to get write dma channel\n"); + goto err_req_write_chnl; + } + nand->read_buf = fsmc_read_buf_dma; + nand->write_buf = fsmc_write_buf_dma; + break; + + default: + case USE_WORD_ACCESS: + nand->read_buf = fsmc_read_buf; + nand->write_buf = fsmc_write_buf; + break; + } + + fsmc_nand_setup(host->regs_va, host->bank, + nand->options & NAND_BUSWIDTH_16, + host->dev_timings); + + if (AMBA_REV_BITS(host->pid) >= 8) { + nand->ecc.read_page = fsmc_read_page_hwecc; + nand->ecc.calculate = fsmc_read_hwecc_ecc4; + nand->ecc.correct = fsmc_bch8_correct_data; + nand->ecc.bytes = 13; + nand->ecc.strength = 8; + } else { + nand->ecc.calculate = fsmc_read_hwecc_ecc1; + nand->ecc.correct = nand_correct_data; + nand->ecc.bytes = 3; + nand->ecc.strength = 1; + } + + /* + * Scan to find existence of the device + */ + if (nand_scan_ident(&host->mtd, 1, NULL)) { + ret = -ENXIO; + dev_err(&pdev->dev, "No NAND Device found!\n"); + goto err_scan_ident; + } + + if (AMBA_REV_BITS(host->pid) >= 8) { + switch (host->mtd.oobsize) { + case 16: + nand->ecc.layout = &fsmc_ecc4_16_layout; + host->ecc_place = &fsmc_ecc4_sp_place; + break; + case 64: + nand->ecc.layout = &fsmc_ecc4_64_layout; + host->ecc_place = &fsmc_ecc4_lp_place; + break; + case 128: + nand->ecc.layout = &fsmc_ecc4_128_layout; + host->ecc_place = &fsmc_ecc4_lp_place; + break; + case 224: + nand->ecc.layout = &fsmc_ecc4_224_layout; + host->ecc_place = &fsmc_ecc4_lp_place; + break; + case 256: + nand->ecc.layout = &fsmc_ecc4_256_layout; + host->ecc_place = &fsmc_ecc4_lp_place; + break; + default: + dev_warn(&pdev->dev, "No oob scheme defined for oobsize %d\n", + mtd->oobsize); + BUG(); + } + } else { + switch (host->mtd.oobsize) { + case 16: + nand->ecc.layout = &fsmc_ecc1_16_layout; + break; + case 64: + nand->ecc.layout = &fsmc_ecc1_64_layout; + break; + case 128: + nand->ecc.layout = &fsmc_ecc1_128_layout; + break; + default: + dev_warn(&pdev->dev, "No oob scheme defined for oobsize %d\n", + mtd->oobsize); + BUG(); + } + } + + /* Second stage of scan to fill MTD data-structures */ + if (nand_scan_tail(&host->mtd)) { + ret = -ENXIO; + goto err_probe; + } + + /* + * The partition information can is accessed by (in the same precedence) + * + * command line through Bootloader, + * platform data, + * default partition information present in driver. + */ + /* + * Check for partition info passed + */ + host->mtd.name = "nand"; + ppdata.of_node = np; + ret = mtd_device_parse_register(&host->mtd, NULL, &ppdata, + host->partitions, host->nr_partitions); + if (ret) + goto err_probe; + + platform_set_drvdata(pdev, host); + dev_info(&pdev->dev, "FSMC NAND driver registration successful\n"); + return 0; + +err_probe: +err_scan_ident: + if (host->mode == USE_DMA_ACCESS) + dma_release_channel(host->write_dma_chan); +err_req_write_chnl: + if (host->mode == USE_DMA_ACCESS) + dma_release_channel(host->read_dma_chan); +err_req_read_chnl: + clk_disable_unprepare(host->clk); +err_clk_prepare_enable: + clk_put(host->clk); + return ret; +} + +/* + * Clean up routine + */ +static int fsmc_nand_remove(struct platform_device *pdev) +{ + struct fsmc_nand_data *host = platform_get_drvdata(pdev); + + if (host) { + nand_release(&host->mtd); + + if (host->mode == USE_DMA_ACCESS) { + dma_release_channel(host->write_dma_chan); + dma_release_channel(host->read_dma_chan); + } + clk_disable_unprepare(host->clk); + clk_put(host->clk); + } + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int fsmc_nand_suspend(struct device *dev) +{ + struct fsmc_nand_data *host = dev_get_drvdata(dev); + if (host) + clk_disable_unprepare(host->clk); + return 0; +} + +static int fsmc_nand_resume(struct device *dev) +{ + struct fsmc_nand_data *host = dev_get_drvdata(dev); + if (host) { + clk_prepare_enable(host->clk); + fsmc_nand_setup(host->regs_va, host->bank, + host->nand.options & NAND_BUSWIDTH_16, + host->dev_timings); + } + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(fsmc_nand_pm_ops, fsmc_nand_suspend, fsmc_nand_resume); + +#ifdef CONFIG_OF +static const struct of_device_id fsmc_nand_id_table[] = { + { .compatible = "st,spear600-fsmc-nand" }, + { .compatible = "stericsson,fsmc-nand" }, + {} +}; +MODULE_DEVICE_TABLE(of, fsmc_nand_id_table); +#endif + +static struct platform_driver fsmc_nand_driver = { + .remove = fsmc_nand_remove, + .driver = { + .owner = THIS_MODULE, + .name = "fsmc-nand", + .of_match_table = of_match_ptr(fsmc_nand_id_table), + .pm = &fsmc_nand_pm_ops, + }, +}; + +module_platform_driver_probe(fsmc_nand_driver, fsmc_nand_probe); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Vipin Kumar <vipin.kumar@st.com>, Ashish Priyadarshi"); +MODULE_DESCRIPTION("NAND driver for SPEAr Platforms"); diff --git a/drivers/mtd/nand/gpio.c b/drivers/mtd/nand/gpio.c new file mode 100644 index 00000000000..117ce333fdd --- /dev/null +++ b/drivers/mtd/nand/gpio.c @@ -0,0 +1,320 @@ +/* + * drivers/mtd/nand/gpio.c + * + * Updated, and converted to generic GPIO based driver by Russell King. + * + * Written by Ben Dooks <ben@simtec.co.uk> + * Based on 2.4 version by Mark Whittaker + * + * © 2004 Simtec Electronics + * + * Device driver for NAND connected via GPIO + * + * 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/kernel.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/io.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/nand-gpio.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_gpio.h> + +struct gpiomtd { + void __iomem *io_sync; + struct mtd_info mtd_info; + struct nand_chip nand_chip; + struct gpio_nand_platdata plat; +}; + +#define gpio_nand_getpriv(x) container_of(x, struct gpiomtd, mtd_info) + + +#ifdef CONFIG_ARM +/* gpio_nand_dosync() + * + * Make sure the GPIO state changes occur in-order with writes to NAND + * memory region. + * Needed on PXA due to bus-reordering within the SoC itself (see section on + * I/O ordering in PXA manual (section 2.3, p35) + */ +static void gpio_nand_dosync(struct gpiomtd *gpiomtd) +{ + unsigned long tmp; + + if (gpiomtd->io_sync) { + /* + * Linux memory barriers don't cater for what's required here. + * What's required is what's here - a read from a separate + * region with a dependency on that read. + */ + tmp = readl(gpiomtd->io_sync); + asm volatile("mov %1, %0\n" : "=r" (tmp) : "r" (tmp)); + } +} +#else +static inline void gpio_nand_dosync(struct gpiomtd *gpiomtd) {} +#endif + +static void gpio_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl) +{ + struct gpiomtd *gpiomtd = gpio_nand_getpriv(mtd); + + gpio_nand_dosync(gpiomtd); + + if (ctrl & NAND_CTRL_CHANGE) { + gpio_set_value(gpiomtd->plat.gpio_nce, !(ctrl & NAND_NCE)); + gpio_set_value(gpiomtd->plat.gpio_cle, !!(ctrl & NAND_CLE)); + gpio_set_value(gpiomtd->plat.gpio_ale, !!(ctrl & NAND_ALE)); + gpio_nand_dosync(gpiomtd); + } + if (cmd == NAND_CMD_NONE) + return; + + writeb(cmd, gpiomtd->nand_chip.IO_ADDR_W); + gpio_nand_dosync(gpiomtd); +} + +static int gpio_nand_devready(struct mtd_info *mtd) +{ + struct gpiomtd *gpiomtd = gpio_nand_getpriv(mtd); + + return gpio_get_value(gpiomtd->plat.gpio_rdy); +} + +#ifdef CONFIG_OF +static const struct of_device_id gpio_nand_id_table[] = { + { .compatible = "gpio-control-nand" }, + {} +}; +MODULE_DEVICE_TABLE(of, gpio_nand_id_table); + +static int gpio_nand_get_config_of(const struct device *dev, + struct gpio_nand_platdata *plat) +{ + u32 val; + + if (!dev->of_node) + return -ENODEV; + + if (!of_property_read_u32(dev->of_node, "bank-width", &val)) { + if (val == 2) { + plat->options |= NAND_BUSWIDTH_16; + } else if (val != 1) { + dev_err(dev, "invalid bank-width %u\n", val); + return -EINVAL; + } + } + + plat->gpio_rdy = of_get_gpio(dev->of_node, 0); + plat->gpio_nce = of_get_gpio(dev->of_node, 1); + plat->gpio_ale = of_get_gpio(dev->of_node, 2); + plat->gpio_cle = of_get_gpio(dev->of_node, 3); + plat->gpio_nwp = of_get_gpio(dev->of_node, 4); + + if (!of_property_read_u32(dev->of_node, "chip-delay", &val)) + plat->chip_delay = val; + + return 0; +} + +static struct resource *gpio_nand_get_io_sync_of(struct platform_device *pdev) +{ + struct resource *r; + u64 addr; + + if (of_property_read_u64(pdev->dev.of_node, + "gpio-control-nand,io-sync-reg", &addr)) + return NULL; + + r = devm_kzalloc(&pdev->dev, sizeof(*r), GFP_KERNEL); + if (!r) + return NULL; + + r->start = addr; + r->end = r->start + 0x3; + r->flags = IORESOURCE_MEM; + + return r; +} +#else /* CONFIG_OF */ +static inline int gpio_nand_get_config_of(const struct device *dev, + struct gpio_nand_platdata *plat) +{ + return -ENOSYS; +} + +static inline struct resource * +gpio_nand_get_io_sync_of(struct platform_device *pdev) +{ + return NULL; +} +#endif /* CONFIG_OF */ + +static inline int gpio_nand_get_config(const struct device *dev, + struct gpio_nand_platdata *plat) +{ + int ret = gpio_nand_get_config_of(dev, plat); + + if (!ret) + return ret; + + if (dev_get_platdata(dev)) { + memcpy(plat, dev_get_platdata(dev), sizeof(*plat)); + return 0; + } + + return -EINVAL; +} + +static inline struct resource * +gpio_nand_get_io_sync(struct platform_device *pdev) +{ + struct resource *r = gpio_nand_get_io_sync_of(pdev); + + if (r) + return r; + + return platform_get_resource(pdev, IORESOURCE_MEM, 1); +} + +static int gpio_nand_remove(struct platform_device *pdev) +{ + struct gpiomtd *gpiomtd = platform_get_drvdata(pdev); + + nand_release(&gpiomtd->mtd_info); + + if (gpio_is_valid(gpiomtd->plat.gpio_nwp)) + gpio_set_value(gpiomtd->plat.gpio_nwp, 0); + gpio_set_value(gpiomtd->plat.gpio_nce, 1); + + return 0; +} + +static int gpio_nand_probe(struct platform_device *pdev) +{ + struct gpiomtd *gpiomtd; + struct nand_chip *chip; + struct resource *res; + struct mtd_part_parser_data ppdata = {}; + int ret = 0; + + if (!pdev->dev.of_node && !dev_get_platdata(&pdev->dev)) + return -EINVAL; + + gpiomtd = devm_kzalloc(&pdev->dev, sizeof(*gpiomtd), GFP_KERNEL); + if (!gpiomtd) + return -ENOMEM; + + chip = &gpiomtd->nand_chip; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + chip->IO_ADDR_R = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(chip->IO_ADDR_R)) + return PTR_ERR(chip->IO_ADDR_R); + + res = gpio_nand_get_io_sync(pdev); + if (res) { + gpiomtd->io_sync = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(gpiomtd->io_sync)) + return PTR_ERR(gpiomtd->io_sync); + } + + ret = gpio_nand_get_config(&pdev->dev, &gpiomtd->plat); + if (ret) + return ret; + + ret = devm_gpio_request(&pdev->dev, gpiomtd->plat.gpio_nce, "NAND NCE"); + if (ret) + return ret; + gpio_direction_output(gpiomtd->plat.gpio_nce, 1); + + if (gpio_is_valid(gpiomtd->plat.gpio_nwp)) { + ret = devm_gpio_request(&pdev->dev, gpiomtd->plat.gpio_nwp, + "NAND NWP"); + if (ret) + return ret; + } + + ret = devm_gpio_request(&pdev->dev, gpiomtd->plat.gpio_ale, "NAND ALE"); + if (ret) + return ret; + gpio_direction_output(gpiomtd->plat.gpio_ale, 0); + + ret = devm_gpio_request(&pdev->dev, gpiomtd->plat.gpio_cle, "NAND CLE"); + if (ret) + return ret; + gpio_direction_output(gpiomtd->plat.gpio_cle, 0); + + if (gpio_is_valid(gpiomtd->plat.gpio_rdy)) { + ret = devm_gpio_request(&pdev->dev, gpiomtd->plat.gpio_rdy, + "NAND RDY"); + if (ret) + return ret; + gpio_direction_input(gpiomtd->plat.gpio_rdy); + chip->dev_ready = gpio_nand_devready; + } + + chip->IO_ADDR_W = chip->IO_ADDR_R; + chip->ecc.mode = NAND_ECC_SOFT; + chip->options = gpiomtd->plat.options; + chip->chip_delay = gpiomtd->plat.chip_delay; + chip->cmd_ctrl = gpio_nand_cmd_ctrl; + + gpiomtd->mtd_info.priv = chip; + gpiomtd->mtd_info.owner = THIS_MODULE; + + platform_set_drvdata(pdev, gpiomtd); + + if (gpio_is_valid(gpiomtd->plat.gpio_nwp)) + gpio_direction_output(gpiomtd->plat.gpio_nwp, 1); + + if (nand_scan(&gpiomtd->mtd_info, 1)) { + ret = -ENXIO; + goto err_wp; + } + + if (gpiomtd->plat.adjust_parts) + gpiomtd->plat.adjust_parts(&gpiomtd->plat, + gpiomtd->mtd_info.size); + + ppdata.of_node = pdev->dev.of_node; + ret = mtd_device_parse_register(&gpiomtd->mtd_info, NULL, &ppdata, + gpiomtd->plat.parts, + gpiomtd->plat.num_parts); + if (!ret) + return 0; + +err_wp: + if (gpio_is_valid(gpiomtd->plat.gpio_nwp)) + gpio_set_value(gpiomtd->plat.gpio_nwp, 0); + + return ret; +} + +static struct platform_driver gpio_nand_driver = { + .probe = gpio_nand_probe, + .remove = gpio_nand_remove, + .driver = { + .name = "gpio-nand", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(gpio_nand_id_table), + }, +}; + +module_platform_driver(gpio_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); +MODULE_DESCRIPTION("GPIO NAND Driver"); diff --git a/drivers/mtd/nand/gpmi-nand/Makefile b/drivers/mtd/nand/gpmi-nand/Makefile new file mode 100644 index 00000000000..3a462487c35 --- /dev/null +++ b/drivers/mtd/nand/gpmi-nand/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_MTD_NAND_GPMI_NAND) += gpmi_nand.o +gpmi_nand-objs += gpmi-nand.o +gpmi_nand-objs += gpmi-lib.o diff --git a/drivers/mtd/nand/gpmi-nand/bch-regs.h b/drivers/mtd/nand/gpmi-nand/bch-regs.h new file mode 100644 index 00000000000..05bb91f2f4c --- /dev/null +++ b/drivers/mtd/nand/gpmi-nand/bch-regs.h @@ -0,0 +1,128 @@ +/* + * Freescale GPMI NAND Flash Driver + * + * Copyright 2008-2011 Freescale Semiconductor, Inc. + * Copyright 2008 Embedded Alley Solutions, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#ifndef __GPMI_NAND_BCH_REGS_H +#define __GPMI_NAND_BCH_REGS_H + +#define HW_BCH_CTRL 0x00000000 +#define HW_BCH_CTRL_SET 0x00000004 +#define HW_BCH_CTRL_CLR 0x00000008 +#define HW_BCH_CTRL_TOG 0x0000000c + +#define BM_BCH_CTRL_COMPLETE_IRQ_EN (1 << 8) +#define BM_BCH_CTRL_COMPLETE_IRQ (1 << 0) + +#define HW_BCH_STATUS0 0x00000010 +#define HW_BCH_MODE 0x00000020 +#define HW_BCH_ENCODEPTR 0x00000030 +#define HW_BCH_DATAPTR 0x00000040 +#define HW_BCH_METAPTR 0x00000050 +#define HW_BCH_LAYOUTSELECT 0x00000070 + +#define HW_BCH_FLASH0LAYOUT0 0x00000080 + +#define BP_BCH_FLASH0LAYOUT0_NBLOCKS 24 +#define BM_BCH_FLASH0LAYOUT0_NBLOCKS (0xff << BP_BCH_FLASH0LAYOUT0_NBLOCKS) +#define BF_BCH_FLASH0LAYOUT0_NBLOCKS(v) \ + (((v) << BP_BCH_FLASH0LAYOUT0_NBLOCKS) & BM_BCH_FLASH0LAYOUT0_NBLOCKS) + +#define BP_BCH_FLASH0LAYOUT0_META_SIZE 16 +#define BM_BCH_FLASH0LAYOUT0_META_SIZE (0xff << BP_BCH_FLASH0LAYOUT0_META_SIZE) +#define BF_BCH_FLASH0LAYOUT0_META_SIZE(v) \ + (((v) << BP_BCH_FLASH0LAYOUT0_META_SIZE)\ + & BM_BCH_FLASH0LAYOUT0_META_SIZE) + +#define BP_BCH_FLASH0LAYOUT0_ECC0 12 +#define BM_BCH_FLASH0LAYOUT0_ECC0 (0xf << BP_BCH_FLASH0LAYOUT0_ECC0) +#define MX6Q_BP_BCH_FLASH0LAYOUT0_ECC0 11 +#define MX6Q_BM_BCH_FLASH0LAYOUT0_ECC0 (0x1f << MX6Q_BP_BCH_FLASH0LAYOUT0_ECC0) +#define BF_BCH_FLASH0LAYOUT0_ECC0(v, x) \ + (GPMI_IS_MX6(x) \ + ? (((v) << MX6Q_BP_BCH_FLASH0LAYOUT0_ECC0) \ + & MX6Q_BM_BCH_FLASH0LAYOUT0_ECC0) \ + : (((v) << BP_BCH_FLASH0LAYOUT0_ECC0) \ + & BM_BCH_FLASH0LAYOUT0_ECC0) \ + ) + +#define MX6Q_BP_BCH_FLASH0LAYOUT0_GF_13_14 10 +#define MX6Q_BM_BCH_FLASH0LAYOUT0_GF_13_14 \ + (0x1 << MX6Q_BP_BCH_FLASH0LAYOUT0_GF_13_14) +#define BF_BCH_FLASH0LAYOUT0_GF(v, x) \ + ((GPMI_IS_MX6(x) && ((v) == 14)) \ + ? (((1) << MX6Q_BP_BCH_FLASH0LAYOUT0_GF_13_14) \ + & MX6Q_BM_BCH_FLASH0LAYOUT0_GF_13_14) \ + : 0 \ + ) + +#define BP_BCH_FLASH0LAYOUT0_DATA0_SIZE 0 +#define BM_BCH_FLASH0LAYOUT0_DATA0_SIZE \ + (0xfff << BP_BCH_FLASH0LAYOUT0_DATA0_SIZE) +#define MX6Q_BM_BCH_FLASH0LAYOUT0_DATA0_SIZE \ + (0x3ff << BP_BCH_FLASH0LAYOUT0_DATA0_SIZE) +#define BF_BCH_FLASH0LAYOUT0_DATA0_SIZE(v, x) \ + (GPMI_IS_MX6(x) \ + ? (((v) >> 2) & MX6Q_BM_BCH_FLASH0LAYOUT0_DATA0_SIZE) \ + : ((v) & BM_BCH_FLASH0LAYOUT0_DATA0_SIZE) \ + ) + +#define HW_BCH_FLASH0LAYOUT1 0x00000090 + +#define BP_BCH_FLASH0LAYOUT1_PAGE_SIZE 16 +#define BM_BCH_FLASH0LAYOUT1_PAGE_SIZE \ + (0xffff << BP_BCH_FLASH0LAYOUT1_PAGE_SIZE) +#define BF_BCH_FLASH0LAYOUT1_PAGE_SIZE(v) \ + (((v) << BP_BCH_FLASH0LAYOUT1_PAGE_SIZE) \ + & BM_BCH_FLASH0LAYOUT1_PAGE_SIZE) + +#define BP_BCH_FLASH0LAYOUT1_ECCN 12 +#define BM_BCH_FLASH0LAYOUT1_ECCN (0xf << BP_BCH_FLASH0LAYOUT1_ECCN) +#define MX6Q_BP_BCH_FLASH0LAYOUT1_ECCN 11 +#define MX6Q_BM_BCH_FLASH0LAYOUT1_ECCN (0x1f << MX6Q_BP_BCH_FLASH0LAYOUT1_ECCN) +#define BF_BCH_FLASH0LAYOUT1_ECCN(v, x) \ + (GPMI_IS_MX6(x) \ + ? (((v) << MX6Q_BP_BCH_FLASH0LAYOUT1_ECCN) \ + & MX6Q_BM_BCH_FLASH0LAYOUT1_ECCN) \ + : (((v) << BP_BCH_FLASH0LAYOUT1_ECCN) \ + & BM_BCH_FLASH0LAYOUT1_ECCN) \ + ) + +#define MX6Q_BP_BCH_FLASH0LAYOUT1_GF_13_14 10 +#define MX6Q_BM_BCH_FLASH0LAYOUT1_GF_13_14 \ + (0x1 << MX6Q_BP_BCH_FLASH0LAYOUT1_GF_13_14) +#define BF_BCH_FLASH0LAYOUT1_GF(v, x) \ + ((GPMI_IS_MX6(x) && ((v) == 14)) \ + ? (((1) << MX6Q_BP_BCH_FLASH0LAYOUT1_GF_13_14) \ + & MX6Q_BM_BCH_FLASH0LAYOUT1_GF_13_14) \ + : 0 \ + ) + +#define BP_BCH_FLASH0LAYOUT1_DATAN_SIZE 0 +#define BM_BCH_FLASH0LAYOUT1_DATAN_SIZE \ + (0xfff << BP_BCH_FLASH0LAYOUT1_DATAN_SIZE) +#define MX6Q_BM_BCH_FLASH0LAYOUT1_DATAN_SIZE \ + (0x3ff << BP_BCH_FLASH0LAYOUT1_DATAN_SIZE) +#define BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(v, x) \ + (GPMI_IS_MX6(x) \ + ? (((v) >> 2) & MX6Q_BM_BCH_FLASH0LAYOUT1_DATAN_SIZE) \ + : ((v) & BM_BCH_FLASH0LAYOUT1_DATAN_SIZE) \ + ) + +#define HW_BCH_VERSION 0x00000160 +#endif diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-lib.c b/drivers/mtd/nand/gpmi-nand/gpmi-lib.c new file mode 100644 index 00000000000..87e658ce23e --- /dev/null +++ b/drivers/mtd/nand/gpmi-nand/gpmi-lib.c @@ -0,0 +1,1355 @@ +/* + * Freescale GPMI NAND Flash Driver + * + * Copyright (C) 2008-2011 Freescale Semiconductor, Inc. + * Copyright (C) 2008 Embedded Alley Solutions, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/slab.h> + +#include "gpmi-nand.h" +#include "gpmi-regs.h" +#include "bch-regs.h" + +static struct timing_threshod timing_default_threshold = { + .max_data_setup_cycles = (BM_GPMI_TIMING0_DATA_SETUP >> + BP_GPMI_TIMING0_DATA_SETUP), + .internal_data_setup_in_ns = 0, + .max_sample_delay_factor = (BM_GPMI_CTRL1_RDN_DELAY >> + BP_GPMI_CTRL1_RDN_DELAY), + .max_dll_clock_period_in_ns = 32, + .max_dll_delay_in_ns = 16, +}; + +#define MXS_SET_ADDR 0x4 +#define MXS_CLR_ADDR 0x8 +/* + * Clear the bit and poll it cleared. This is usually called with + * a reset address and mask being either SFTRST(bit 31) or CLKGATE + * (bit 30). + */ +static int clear_poll_bit(void __iomem *addr, u32 mask) +{ + int timeout = 0x400; + + /* clear the bit */ + writel(mask, addr + MXS_CLR_ADDR); + + /* + * SFTRST needs 3 GPMI clocks to settle, the reference manual + * recommends to wait 1us. + */ + udelay(1); + + /* poll the bit becoming clear */ + while ((readl(addr) & mask) && --timeout) + /* nothing */; + + return !timeout; +} + +#define MODULE_CLKGATE (1 << 30) +#define MODULE_SFTRST (1 << 31) +/* + * The current mxs_reset_block() will do two things: + * [1] enable the module. + * [2] reset the module. + * + * In most of the cases, it's ok. + * But in MX23, there is a hardware bug in the BCH block (see erratum #2847). + * If you try to soft reset the BCH block, it becomes unusable until + * the next hard reset. This case occurs in the NAND boot mode. When the board + * boots by NAND, the ROM of the chip will initialize the BCH blocks itself. + * So If the driver tries to reset the BCH again, the BCH will not work anymore. + * You will see a DMA timeout in this case. The bug has been fixed + * in the following chips, such as MX28. + * + * To avoid this bug, just add a new parameter `just_enable` for + * the mxs_reset_block(), and rewrite it here. + */ +static int gpmi_reset_block(void __iomem *reset_addr, bool just_enable) +{ + int ret; + int timeout = 0x400; + + /* clear and poll SFTRST */ + ret = clear_poll_bit(reset_addr, MODULE_SFTRST); + if (unlikely(ret)) + goto error; + + /* clear CLKGATE */ + writel(MODULE_CLKGATE, reset_addr + MXS_CLR_ADDR); + + if (!just_enable) { + /* set SFTRST to reset the block */ + writel(MODULE_SFTRST, reset_addr + MXS_SET_ADDR); + udelay(1); + + /* poll CLKGATE becoming set */ + while ((!(readl(reset_addr) & MODULE_CLKGATE)) && --timeout) + /* nothing */; + if (unlikely(!timeout)) + goto error; + } + + /* clear and poll SFTRST */ + ret = clear_poll_bit(reset_addr, MODULE_SFTRST); + if (unlikely(ret)) + goto error; + + /* clear and poll CLKGATE */ + ret = clear_poll_bit(reset_addr, MODULE_CLKGATE); + if (unlikely(ret)) + goto error; + + return 0; + +error: + pr_err("%s(%p): module reset timeout\n", __func__, reset_addr); + return -ETIMEDOUT; +} + +static int __gpmi_enable_clk(struct gpmi_nand_data *this, bool v) +{ + struct clk *clk; + int ret; + int i; + + for (i = 0; i < GPMI_CLK_MAX; i++) { + clk = this->resources.clock[i]; + if (!clk) + break; + + if (v) { + ret = clk_prepare_enable(clk); + if (ret) + goto err_clk; + } else { + clk_disable_unprepare(clk); + } + } + return 0; + +err_clk: + for (; i > 0; i--) + clk_disable_unprepare(this->resources.clock[i - 1]); + return ret; +} + +#define gpmi_enable_clk(x) __gpmi_enable_clk(x, true) +#define gpmi_disable_clk(x) __gpmi_enable_clk(x, false) + +int gpmi_init(struct gpmi_nand_data *this) +{ + struct resources *r = &this->resources; + int ret; + + ret = gpmi_enable_clk(this); + if (ret) + goto err_out; + ret = gpmi_reset_block(r->gpmi_regs, false); + if (ret) + goto err_out; + + /* + * Reset BCH here, too. We got failures otherwise :( + * See later BCH reset for explanation of MX23 handling + */ + ret = gpmi_reset_block(r->bch_regs, GPMI_IS_MX23(this)); + if (ret) + goto err_out; + + + /* Choose NAND mode. */ + writel(BM_GPMI_CTRL1_GPMI_MODE, r->gpmi_regs + HW_GPMI_CTRL1_CLR); + + /* Set the IRQ polarity. */ + writel(BM_GPMI_CTRL1_ATA_IRQRDY_POLARITY, + r->gpmi_regs + HW_GPMI_CTRL1_SET); + + /* Disable Write-Protection. */ + writel(BM_GPMI_CTRL1_DEV_RESET, r->gpmi_regs + HW_GPMI_CTRL1_SET); + + /* Select BCH ECC. */ + writel(BM_GPMI_CTRL1_BCH_MODE, r->gpmi_regs + HW_GPMI_CTRL1_SET); + + /* + * Decouple the chip select from dma channel. We use dma0 for all + * the chips. + */ + writel(BM_GPMI_CTRL1_DECOUPLE_CS, r->gpmi_regs + HW_GPMI_CTRL1_SET); + + gpmi_disable_clk(this); + return 0; +err_out: + return ret; +} + +/* This function is very useful. It is called only when the bug occur. */ +void gpmi_dump_info(struct gpmi_nand_data *this) +{ + struct resources *r = &this->resources; + struct bch_geometry *geo = &this->bch_geometry; + u32 reg; + int i; + + dev_err(this->dev, "Show GPMI registers :\n"); + for (i = 0; i <= HW_GPMI_DEBUG / 0x10 + 1; i++) { + reg = readl(r->gpmi_regs + i * 0x10); + dev_err(this->dev, "offset 0x%.3x : 0x%.8x\n", i * 0x10, reg); + } + + /* start to print out the BCH info */ + dev_err(this->dev, "Show BCH registers :\n"); + for (i = 0; i <= HW_BCH_VERSION / 0x10 + 1; i++) { + reg = readl(r->bch_regs + i * 0x10); + dev_err(this->dev, "offset 0x%.3x : 0x%.8x\n", i * 0x10, reg); + } + dev_err(this->dev, "BCH Geometry :\n" + "GF length : %u\n" + "ECC Strength : %u\n" + "Page Size in Bytes : %u\n" + "Metadata Size in Bytes : %u\n" + "ECC Chunk Size in Bytes: %u\n" + "ECC Chunk Count : %u\n" + "Payload Size in Bytes : %u\n" + "Auxiliary Size in Bytes: %u\n" + "Auxiliary Status Offset: %u\n" + "Block Mark Byte Offset : %u\n" + "Block Mark Bit Offset : %u\n", + geo->gf_len, + geo->ecc_strength, + geo->page_size, + geo->metadata_size, + geo->ecc_chunk_size, + geo->ecc_chunk_count, + geo->payload_size, + geo->auxiliary_size, + geo->auxiliary_status_offset, + geo->block_mark_byte_offset, + geo->block_mark_bit_offset); +} + +/* Configures the geometry for BCH. */ +int bch_set_geometry(struct gpmi_nand_data *this) +{ + struct resources *r = &this->resources; + struct bch_geometry *bch_geo = &this->bch_geometry; + unsigned int block_count; + unsigned int block_size; + unsigned int metadata_size; + unsigned int ecc_strength; + unsigned int page_size; + unsigned int gf_len; + int ret; + + if (common_nfc_set_geometry(this)) + return !0; + + block_count = bch_geo->ecc_chunk_count - 1; + block_size = bch_geo->ecc_chunk_size; + metadata_size = bch_geo->metadata_size; + ecc_strength = bch_geo->ecc_strength >> 1; + page_size = bch_geo->page_size; + gf_len = bch_geo->gf_len; + + ret = gpmi_enable_clk(this); + if (ret) + goto err_out; + + /* + * Due to erratum #2847 of the MX23, the BCH cannot be soft reset on this + * chip, otherwise it will lock up. So we skip resetting BCH on the MX23. + * On the other hand, the MX28 needs the reset, because one case has been + * seen where the BCH produced ECC errors constantly after 10000 + * consecutive reboots. The latter case has not been seen on the MX23 + * yet, still we don't know if it could happen there as well. + */ + ret = gpmi_reset_block(r->bch_regs, GPMI_IS_MX23(this)); + if (ret) + goto err_out; + + /* Configure layout 0. */ + writel(BF_BCH_FLASH0LAYOUT0_NBLOCKS(block_count) + | BF_BCH_FLASH0LAYOUT0_META_SIZE(metadata_size) + | BF_BCH_FLASH0LAYOUT0_ECC0(ecc_strength, this) + | BF_BCH_FLASH0LAYOUT0_GF(gf_len, this) + | BF_BCH_FLASH0LAYOUT0_DATA0_SIZE(block_size, this), + r->bch_regs + HW_BCH_FLASH0LAYOUT0); + + writel(BF_BCH_FLASH0LAYOUT1_PAGE_SIZE(page_size) + | BF_BCH_FLASH0LAYOUT1_ECCN(ecc_strength, this) + | BF_BCH_FLASH0LAYOUT1_GF(gf_len, this) + | BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(block_size, this), + r->bch_regs + HW_BCH_FLASH0LAYOUT1); + + /* Set *all* chip selects to use layout 0. */ + writel(0, r->bch_regs + HW_BCH_LAYOUTSELECT); + + /* Enable interrupts. */ + writel(BM_BCH_CTRL_COMPLETE_IRQ_EN, + r->bch_regs + HW_BCH_CTRL_SET); + + gpmi_disable_clk(this); + return 0; +err_out: + return ret; +} + +/* Converts time in nanoseconds to cycles. */ +static unsigned int ns_to_cycles(unsigned int time, + unsigned int period, unsigned int min) +{ + unsigned int k; + + k = (time + period - 1) / period; + return max(k, min); +} + +#define DEF_MIN_PROP_DELAY 5 +#define DEF_MAX_PROP_DELAY 9 +/* Apply timing to current hardware conditions. */ +static int gpmi_nfc_compute_hardware_timing(struct gpmi_nand_data *this, + struct gpmi_nfc_hardware_timing *hw) +{ + struct timing_threshod *nfc = &timing_default_threshold; + struct resources *r = &this->resources; + struct nand_chip *nand = &this->nand; + struct nand_timing target = this->timing; + bool improved_timing_is_available; + unsigned long clock_frequency_in_hz; + unsigned int clock_period_in_ns; + bool dll_use_half_periods; + unsigned int dll_delay_shift; + unsigned int max_sample_delay_in_ns; + unsigned int address_setup_in_cycles; + unsigned int data_setup_in_ns; + unsigned int data_setup_in_cycles; + unsigned int data_hold_in_cycles; + int ideal_sample_delay_in_ns; + unsigned int sample_delay_factor; + int tEYE; + unsigned int min_prop_delay_in_ns = DEF_MIN_PROP_DELAY; + unsigned int max_prop_delay_in_ns = DEF_MAX_PROP_DELAY; + + /* + * If there are multiple chips, we need to relax the timings to allow + * for signal distortion due to higher capacitance. + */ + if (nand->numchips > 2) { + target.data_setup_in_ns += 10; + target.data_hold_in_ns += 10; + target.address_setup_in_ns += 10; + } else if (nand->numchips > 1) { + target.data_setup_in_ns += 5; + target.data_hold_in_ns += 5; + target.address_setup_in_ns += 5; + } + + /* Check if improved timing information is available. */ + improved_timing_is_available = + (target.tREA_in_ns >= 0) && + (target.tRLOH_in_ns >= 0) && + (target.tRHOH_in_ns >= 0); + + /* Inspect the clock. */ + nfc->clock_frequency_in_hz = clk_get_rate(r->clock[0]); + clock_frequency_in_hz = nfc->clock_frequency_in_hz; + clock_period_in_ns = NSEC_PER_SEC / clock_frequency_in_hz; + + /* + * The NFC quantizes setup and hold parameters in terms of clock cycles. + * Here, we quantize the setup and hold timing parameters to the + * next-highest clock period to make sure we apply at least the + * specified times. + * + * For data setup and data hold, the hardware interprets a value of zero + * as the largest possible delay. This is not what's intended by a zero + * in the input parameter, so we impose a minimum of one cycle. + */ + data_setup_in_cycles = ns_to_cycles(target.data_setup_in_ns, + clock_period_in_ns, 1); + data_hold_in_cycles = ns_to_cycles(target.data_hold_in_ns, + clock_period_in_ns, 1); + address_setup_in_cycles = ns_to_cycles(target.address_setup_in_ns, + clock_period_in_ns, 0); + + /* + * The clock's period affects the sample delay in a number of ways: + * + * (1) The NFC HAL tells us the maximum clock period the sample delay + * DLL can tolerate. If the clock period is greater than half that + * maximum, we must configure the DLL to be driven by half periods. + * + * (2) We need to convert from an ideal sample delay, in ns, to a + * "sample delay factor," which the NFC uses. This factor depends on + * whether we're driving the DLL with full or half periods. + * Paraphrasing the reference manual: + * + * AD = SDF x 0.125 x RP + * + * where: + * + * AD is the applied delay, in ns. + * SDF is the sample delay factor, which is dimensionless. + * RP is the reference period, in ns, which is a full clock period + * if the DLL is being driven by full periods, or half that if + * the DLL is being driven by half periods. + * + * Let's re-arrange this in a way that's more useful to us: + * + * 8 + * SDF = AD x ---- + * RP + * + * The reference period is either the clock period or half that, so this + * is: + * + * 8 AD x DDF + * SDF = AD x ----- = -------- + * f x P P + * + * where: + * + * f is 1 or 1/2, depending on how we're driving the DLL. + * P is the clock period. + * DDF is the DLL Delay Factor, a dimensionless value that + * incorporates all the constants in the conversion. + * + * DDF will be either 8 or 16, both of which are powers of two. We can + * reduce the cost of this conversion by using bit shifts instead of + * multiplication or division. Thus: + * + * AD << DDS + * SDF = --------- + * P + * + * or + * + * AD = (SDF >> DDS) x P + * + * where: + * + * DDS is the DLL Delay Shift, the logarithm to base 2 of the DDF. + */ + if (clock_period_in_ns > (nfc->max_dll_clock_period_in_ns >> 1)) { + dll_use_half_periods = true; + dll_delay_shift = 3 + 1; + } else { + dll_use_half_periods = false; + dll_delay_shift = 3; + } + + /* + * Compute the maximum sample delay the NFC allows, under current + * conditions. If the clock is running too slowly, no sample delay is + * possible. + */ + if (clock_period_in_ns > nfc->max_dll_clock_period_in_ns) + max_sample_delay_in_ns = 0; + else { + /* + * Compute the delay implied by the largest sample delay factor + * the NFC allows. + */ + max_sample_delay_in_ns = + (nfc->max_sample_delay_factor * clock_period_in_ns) >> + dll_delay_shift; + + /* + * Check if the implied sample delay larger than the NFC + * actually allows. + */ + if (max_sample_delay_in_ns > nfc->max_dll_delay_in_ns) + max_sample_delay_in_ns = nfc->max_dll_delay_in_ns; + } + + /* + * Check if improved timing information is available. If not, we have to + * use a less-sophisticated algorithm. + */ + if (!improved_timing_is_available) { + /* + * Fold the read setup time required by the NFC into the ideal + * sample delay. + */ + ideal_sample_delay_in_ns = target.gpmi_sample_delay_in_ns + + nfc->internal_data_setup_in_ns; + + /* + * The ideal sample delay may be greater than the maximum + * allowed by the NFC. If so, we can trade off sample delay time + * for more data setup time. + * + * In each iteration of the following loop, we add a cycle to + * the data setup time and subtract a corresponding amount from + * the sample delay until we've satisified the constraints or + * can't do any better. + */ + while ((ideal_sample_delay_in_ns > max_sample_delay_in_ns) && + (data_setup_in_cycles < nfc->max_data_setup_cycles)) { + + data_setup_in_cycles++; + ideal_sample_delay_in_ns -= clock_period_in_ns; + + if (ideal_sample_delay_in_ns < 0) + ideal_sample_delay_in_ns = 0; + + } + + /* + * Compute the sample delay factor that corresponds most closely + * to the ideal sample delay. If the result is too large for the + * NFC, use the maximum value. + * + * Notice that we use the ns_to_cycles function to compute the + * sample delay factor. We do this because the form of the + * computation is the same as that for calculating cycles. + */ + sample_delay_factor = + ns_to_cycles( + ideal_sample_delay_in_ns << dll_delay_shift, + clock_period_in_ns, 0); + + if (sample_delay_factor > nfc->max_sample_delay_factor) + sample_delay_factor = nfc->max_sample_delay_factor; + + /* Skip to the part where we return our results. */ + goto return_results; + } + + /* + * If control arrives here, we have more detailed timing information, + * so we can use a better algorithm. + */ + + /* + * Fold the read setup time required by the NFC into the maximum + * propagation delay. + */ + max_prop_delay_in_ns += nfc->internal_data_setup_in_ns; + + /* + * Earlier, we computed the number of clock cycles required to satisfy + * the data setup time. Now, we need to know the actual nanoseconds. + */ + data_setup_in_ns = clock_period_in_ns * data_setup_in_cycles; + + /* + * Compute tEYE, the width of the data eye when reading from the NAND + * Flash. The eye width is fundamentally determined by the data setup + * time, perturbed by propagation delays and some characteristics of the + * NAND Flash device. + * + * start of the eye = max_prop_delay + tREA + * end of the eye = min_prop_delay + tRHOH + data_setup + */ + tEYE = (int)min_prop_delay_in_ns + (int)target.tRHOH_in_ns + + (int)data_setup_in_ns; + + tEYE -= (int)max_prop_delay_in_ns + (int)target.tREA_in_ns; + + /* + * The eye must be open. If it's not, we can try to open it by + * increasing its main forcer, the data setup time. + * + * In each iteration of the following loop, we increase the data setup + * time by a single clock cycle. We do this until either the eye is + * open or we run into NFC limits. + */ + while ((tEYE <= 0) && + (data_setup_in_cycles < nfc->max_data_setup_cycles)) { + /* Give a cycle to data setup. */ + data_setup_in_cycles++; + /* Synchronize the data setup time with the cycles. */ + data_setup_in_ns += clock_period_in_ns; + /* Adjust tEYE accordingly. */ + tEYE += clock_period_in_ns; + } + + /* + * When control arrives here, the eye is open. The ideal time to sample + * the data is in the center of the eye: + * + * end of the eye + start of the eye + * --------------------------------- - data_setup + * 2 + * + * After some algebra, this simplifies to the code immediately below. + */ + ideal_sample_delay_in_ns = + ((int)max_prop_delay_in_ns + + (int)target.tREA_in_ns + + (int)min_prop_delay_in_ns + + (int)target.tRHOH_in_ns - + (int)data_setup_in_ns) >> 1; + + /* + * The following figure illustrates some aspects of a NAND Flash read: + * + * + * __ _____________________________________ + * RDN \_________________/ + * + * <---- tEYE -----> + * /-----------------\ + * Read Data ----------------------------< >--------- + * \-----------------/ + * ^ ^ ^ ^ + * | | | | + * |<--Data Setup -->|<--Delay Time -->| | + * | | | | + * | | | + * | |<-- Quantized Delay Time -->| + * | | | + * + * + * We have some issues we must now address: + * + * (1) The *ideal* sample delay time must not be negative. If it is, we + * jam it to zero. + * + * (2) The *ideal* sample delay time must not be greater than that + * allowed by the NFC. If it is, we can increase the data setup + * time, which will reduce the delay between the end of the data + * setup and the center of the eye. It will also make the eye + * larger, which might help with the next issue... + * + * (3) The *quantized* sample delay time must not fall either before the + * eye opens or after it closes (the latter is the problem + * illustrated in the above figure). + */ + + /* Jam a negative ideal sample delay to zero. */ + if (ideal_sample_delay_in_ns < 0) + ideal_sample_delay_in_ns = 0; + + /* + * Extend the data setup as needed to reduce the ideal sample delay + * below the maximum permitted by the NFC. + */ + while ((ideal_sample_delay_in_ns > max_sample_delay_in_ns) && + (data_setup_in_cycles < nfc->max_data_setup_cycles)) { + + /* Give a cycle to data setup. */ + data_setup_in_cycles++; + /* Synchronize the data setup time with the cycles. */ + data_setup_in_ns += clock_period_in_ns; + /* Adjust tEYE accordingly. */ + tEYE += clock_period_in_ns; + + /* + * Decrease the ideal sample delay by one half cycle, to keep it + * in the middle of the eye. + */ + ideal_sample_delay_in_ns -= (clock_period_in_ns >> 1); + + /* Jam a negative ideal sample delay to zero. */ + if (ideal_sample_delay_in_ns < 0) + ideal_sample_delay_in_ns = 0; + } + + /* + * Compute the sample delay factor that corresponds to the ideal sample + * delay. If the result is too large, then use the maximum allowed + * value. + * + * Notice that we use the ns_to_cycles function to compute the sample + * delay factor. We do this because the form of the computation is the + * same as that for calculating cycles. + */ + sample_delay_factor = + ns_to_cycles(ideal_sample_delay_in_ns << dll_delay_shift, + clock_period_in_ns, 0); + + if (sample_delay_factor > nfc->max_sample_delay_factor) + sample_delay_factor = nfc->max_sample_delay_factor; + + /* + * These macros conveniently encapsulate a computation we'll use to + * continuously evaluate whether or not the data sample delay is inside + * the eye. + */ + #define IDEAL_DELAY ((int) ideal_sample_delay_in_ns) + + #define QUANTIZED_DELAY \ + ((int) ((sample_delay_factor * clock_period_in_ns) >> \ + dll_delay_shift)) + + #define DELAY_ERROR (abs(QUANTIZED_DELAY - IDEAL_DELAY)) + + #define SAMPLE_IS_NOT_WITHIN_THE_EYE (DELAY_ERROR > (tEYE >> 1)) + + /* + * While the quantized sample time falls outside the eye, reduce the + * sample delay or extend the data setup to move the sampling point back + * toward the eye. Do not allow the number of data setup cycles to + * exceed the maximum allowed by the NFC. + */ + while (SAMPLE_IS_NOT_WITHIN_THE_EYE && + (data_setup_in_cycles < nfc->max_data_setup_cycles)) { + /* + * If control arrives here, the quantized sample delay falls + * outside the eye. Check if it's before the eye opens, or after + * the eye closes. + */ + if (QUANTIZED_DELAY > IDEAL_DELAY) { + /* + * If control arrives here, the quantized sample delay + * falls after the eye closes. Decrease the quantized + * delay time and then go back to re-evaluate. + */ + if (sample_delay_factor != 0) + sample_delay_factor--; + continue; + } + + /* + * If control arrives here, the quantized sample delay falls + * before the eye opens. Shift the sample point by increasing + * data setup time. This will also make the eye larger. + */ + + /* Give a cycle to data setup. */ + data_setup_in_cycles++; + /* Synchronize the data setup time with the cycles. */ + data_setup_in_ns += clock_period_in_ns; + /* Adjust tEYE accordingly. */ + tEYE += clock_period_in_ns; + + /* + * Decrease the ideal sample delay by one half cycle, to keep it + * in the middle of the eye. + */ + ideal_sample_delay_in_ns -= (clock_period_in_ns >> 1); + + /* ...and one less period for the delay time. */ + ideal_sample_delay_in_ns -= clock_period_in_ns; + + /* Jam a negative ideal sample delay to zero. */ + if (ideal_sample_delay_in_ns < 0) + ideal_sample_delay_in_ns = 0; + + /* + * We have a new ideal sample delay, so re-compute the quantized + * delay. + */ + sample_delay_factor = + ns_to_cycles( + ideal_sample_delay_in_ns << dll_delay_shift, + clock_period_in_ns, 0); + + if (sample_delay_factor > nfc->max_sample_delay_factor) + sample_delay_factor = nfc->max_sample_delay_factor; + } + + /* Control arrives here when we're ready to return our results. */ +return_results: + hw->data_setup_in_cycles = data_setup_in_cycles; + hw->data_hold_in_cycles = data_hold_in_cycles; + hw->address_setup_in_cycles = address_setup_in_cycles; + hw->use_half_periods = dll_use_half_periods; + hw->sample_delay_factor = sample_delay_factor; + hw->device_busy_timeout = GPMI_DEFAULT_BUSY_TIMEOUT; + hw->wrn_dly_sel = BV_GPMI_CTRL1_WRN_DLY_SEL_4_TO_8NS; + + /* Return success. */ + return 0; +} + +/* + * <1> Firstly, we should know what's the GPMI-clock means. + * The GPMI-clock is the internal clock in the gpmi nand controller. + * If you set 100MHz to gpmi nand controller, the GPMI-clock's period + * is 10ns. Mark the GPMI-clock's period as GPMI-clock-period. + * + * <2> Secondly, we should know what's the frequency on the nand chip pins. + * The frequency on the nand chip pins is derived from the GPMI-clock. + * We can get it from the following equation: + * + * F = G / (DS + DH) + * + * F : the frequency on the nand chip pins. + * G : the GPMI clock, such as 100MHz. + * DS : GPMI_HW_GPMI_TIMING0:DATA_SETUP + * DH : GPMI_HW_GPMI_TIMING0:DATA_HOLD + * + * <3> Thirdly, when the frequency on the nand chip pins is above 33MHz, + * the nand EDO(extended Data Out) timing could be applied. + * The GPMI implements a feedback read strobe to sample the read data. + * The feedback read strobe can be delayed to support the nand EDO timing + * where the read strobe may deasserts before the read data is valid, and + * read data is valid for some time after read strobe. + * + * The following figure illustrates some aspects of a NAND Flash read: + * + * |<---tREA---->| + * | | + * | | | + * |<--tRP-->| | + * | | | + * __ ___|__________________________________ + * RDN \________/ | + * | + * /---------\ + * Read Data --------------< >--------- + * \---------/ + * | | + * |<-D->| + * FeedbackRDN ________ ____________ + * \___________/ + * + * D stands for delay, set in the HW_GPMI_CTRL1:RDN_DELAY. + * + * + * <4> Now, we begin to describe how to compute the right RDN_DELAY. + * + * 4.1) From the aspect of the nand chip pins: + * Delay = (tREA + C - tRP) {1} + * + * tREA : the maximum read access time. From the ONFI nand standards, + * we know that tREA is 16ns in mode 5, tREA is 20ns is mode 4. + * Please check it in : www.onfi.org + * C : a constant for adjust the delay. default is 4. + * tRP : the read pulse width. + * Specified by the HW_GPMI_TIMING0:DATA_SETUP: + * tRP = (GPMI-clock-period) * DATA_SETUP + * + * 4.2) From the aspect of the GPMI nand controller: + * Delay = RDN_DELAY * 0.125 * RP {2} + * + * RP : the DLL reference period. + * if (GPMI-clock-period > DLL_THRETHOLD) + * RP = GPMI-clock-period / 2; + * else + * RP = GPMI-clock-period; + * + * Set the HW_GPMI_CTRL1:HALF_PERIOD if GPMI-clock-period + * is greater DLL_THRETHOLD. In other SOCs, the DLL_THRETHOLD + * is 16ns, but in mx6q, we use 12ns. + * + * 4.3) since {1} equals {2}, we get: + * + * (tREA + 4 - tRP) * 8 + * RDN_DELAY = --------------------- {3} + * RP + * + * 4.4) We only support the fastest asynchronous mode of ONFI nand. + * For some ONFI nand, the mode 4 is the fastest mode; + * while for some ONFI nand, the mode 5 is the fastest mode. + * So we only support the mode 4 and mode 5. It is no need to + * support other modes. + */ +static void gpmi_compute_edo_timing(struct gpmi_nand_data *this, + struct gpmi_nfc_hardware_timing *hw) +{ + struct resources *r = &this->resources; + unsigned long rate = clk_get_rate(r->clock[0]); + int mode = this->timing_mode; + int dll_threshold = this->devdata->max_chain_delay; + unsigned long delay; + unsigned long clk_period; + int t_rea; + int c = 4; + int t_rp; + int rp; + + /* + * [1] for GPMI_HW_GPMI_TIMING0: + * The async mode requires 40MHz for mode 4, 50MHz for mode 5. + * The GPMI can support 100MHz at most. So if we want to + * get the 40MHz or 50MHz, we have to set DS=1, DH=1. + * Set the ADDRESS_SETUP to 0 in mode 4. + */ + hw->data_setup_in_cycles = 1; + hw->data_hold_in_cycles = 1; + hw->address_setup_in_cycles = ((mode == 5) ? 1 : 0); + + /* [2] for GPMI_HW_GPMI_TIMING1 */ + hw->device_busy_timeout = 0x9000; + + /* [3] for GPMI_HW_GPMI_CTRL1 */ + hw->wrn_dly_sel = BV_GPMI_CTRL1_WRN_DLY_SEL_NO_DELAY; + + /* + * Enlarge 10 times for the numerator and denominator in {3}. + * This make us to get more accurate result. + */ + clk_period = NSEC_PER_SEC / (rate / 10); + dll_threshold *= 10; + t_rea = ((mode == 5) ? 16 : 20) * 10; + c *= 10; + + t_rp = clk_period * 1; /* DATA_SETUP is 1 */ + + if (clk_period > dll_threshold) { + hw->use_half_periods = 1; + rp = clk_period / 2; + } else { + hw->use_half_periods = 0; + rp = clk_period; + } + + /* + * Multiply the numerator with 10, we could do a round off: + * 7.8 round up to 8; 7.4 round down to 7. + */ + delay = (((t_rea + c - t_rp) * 8) * 10) / rp; + delay = (delay + 5) / 10; + + hw->sample_delay_factor = delay; +} + +static int enable_edo_mode(struct gpmi_nand_data *this, int mode) +{ + struct resources *r = &this->resources; + struct nand_chip *nand = &this->nand; + struct mtd_info *mtd = &this->mtd; + uint8_t *feature; + unsigned long rate; + int ret; + + feature = kzalloc(ONFI_SUBFEATURE_PARAM_LEN, GFP_KERNEL); + if (!feature) + return -ENOMEM; + + nand->select_chip(mtd, 0); + + /* [1] send SET FEATURE commond to NAND */ + feature[0] = mode; + ret = nand->onfi_set_features(mtd, nand, + ONFI_FEATURE_ADDR_TIMING_MODE, feature); + if (ret) + goto err_out; + + /* [2] send GET FEATURE command to double-check the timing mode */ + memset(feature, 0, ONFI_SUBFEATURE_PARAM_LEN); + ret = nand->onfi_get_features(mtd, nand, + ONFI_FEATURE_ADDR_TIMING_MODE, feature); + if (ret || feature[0] != mode) + goto err_out; + + nand->select_chip(mtd, -1); + + /* [3] set the main IO clock, 100MHz for mode 5, 80MHz for mode 4. */ + rate = (mode == 5) ? 100000000 : 80000000; + clk_set_rate(r->clock[0], rate); + + /* Let the gpmi_begin() re-compute the timing again. */ + this->flags &= ~GPMI_TIMING_INIT_OK; + + this->flags |= GPMI_ASYNC_EDO_ENABLED; + this->timing_mode = mode; + kfree(feature); + dev_info(this->dev, "enable the asynchronous EDO mode %d\n", mode); + return 0; + +err_out: + nand->select_chip(mtd, -1); + kfree(feature); + dev_err(this->dev, "mode:%d ,failed in set feature.\n", mode); + return -EINVAL; +} + +int gpmi_extra_init(struct gpmi_nand_data *this) +{ + struct nand_chip *chip = &this->nand; + + /* Enable the asynchronous EDO feature. */ + if (GPMI_IS_MX6(this) && chip->onfi_version) { + int mode = onfi_get_async_timing_mode(chip); + + /* We only support the timing mode 4 and mode 5. */ + if (mode & ONFI_TIMING_MODE_5) + mode = 5; + else if (mode & ONFI_TIMING_MODE_4) + mode = 4; + else + return 0; + + return enable_edo_mode(this, mode); + } + return 0; +} + +/* Begin the I/O */ +void gpmi_begin(struct gpmi_nand_data *this) +{ + struct resources *r = &this->resources; + void __iomem *gpmi_regs = r->gpmi_regs; + unsigned int clock_period_in_ns; + uint32_t reg; + unsigned int dll_wait_time_in_us; + struct gpmi_nfc_hardware_timing hw; + int ret; + + /* Enable the clock. */ + ret = gpmi_enable_clk(this); + if (ret) { + dev_err(this->dev, "We failed in enable the clk\n"); + goto err_out; + } + + /* Only initialize the timing once */ + if (this->flags & GPMI_TIMING_INIT_OK) + return; + this->flags |= GPMI_TIMING_INIT_OK; + + if (this->flags & GPMI_ASYNC_EDO_ENABLED) + gpmi_compute_edo_timing(this, &hw); + else + gpmi_nfc_compute_hardware_timing(this, &hw); + + /* [1] Set HW_GPMI_TIMING0 */ + reg = BF_GPMI_TIMING0_ADDRESS_SETUP(hw.address_setup_in_cycles) | + BF_GPMI_TIMING0_DATA_HOLD(hw.data_hold_in_cycles) | + BF_GPMI_TIMING0_DATA_SETUP(hw.data_setup_in_cycles); + + writel(reg, gpmi_regs + HW_GPMI_TIMING0); + + /* [2] Set HW_GPMI_TIMING1 */ + writel(BF_GPMI_TIMING1_BUSY_TIMEOUT(hw.device_busy_timeout), + gpmi_regs + HW_GPMI_TIMING1); + + /* [3] The following code is to set the HW_GPMI_CTRL1. */ + + /* Set the WRN_DLY_SEL */ + writel(BM_GPMI_CTRL1_WRN_DLY_SEL, gpmi_regs + HW_GPMI_CTRL1_CLR); + writel(BF_GPMI_CTRL1_WRN_DLY_SEL(hw.wrn_dly_sel), + gpmi_regs + HW_GPMI_CTRL1_SET); + + /* DLL_ENABLE must be set to 0 when setting RDN_DELAY or HALF_PERIOD. */ + writel(BM_GPMI_CTRL1_DLL_ENABLE, gpmi_regs + HW_GPMI_CTRL1_CLR); + + /* Clear out the DLL control fields. */ + reg = BM_GPMI_CTRL1_RDN_DELAY | BM_GPMI_CTRL1_HALF_PERIOD; + writel(reg, gpmi_regs + HW_GPMI_CTRL1_CLR); + + /* If no sample delay is called for, return immediately. */ + if (!hw.sample_delay_factor) + return; + + /* Set RDN_DELAY or HALF_PERIOD. */ + reg = ((hw.use_half_periods) ? BM_GPMI_CTRL1_HALF_PERIOD : 0) + | BF_GPMI_CTRL1_RDN_DELAY(hw.sample_delay_factor); + + writel(reg, gpmi_regs + HW_GPMI_CTRL1_SET); + + /* At last, we enable the DLL. */ + writel(BM_GPMI_CTRL1_DLL_ENABLE, gpmi_regs + HW_GPMI_CTRL1_SET); + + /* + * After we enable the GPMI DLL, we have to wait 64 clock cycles before + * we can use the GPMI. Calculate the amount of time we need to wait, + * in microseconds. + */ + clock_period_in_ns = NSEC_PER_SEC / clk_get_rate(r->clock[0]); + dll_wait_time_in_us = (clock_period_in_ns * 64) / 1000; + + if (!dll_wait_time_in_us) + dll_wait_time_in_us = 1; + + /* Wait for the DLL to settle. */ + udelay(dll_wait_time_in_us); + +err_out: + return; +} + +void gpmi_end(struct gpmi_nand_data *this) +{ + gpmi_disable_clk(this); +} + +/* Clears a BCH interrupt. */ +void gpmi_clear_bch(struct gpmi_nand_data *this) +{ + struct resources *r = &this->resources; + writel(BM_BCH_CTRL_COMPLETE_IRQ, r->bch_regs + HW_BCH_CTRL_CLR); +} + +/* Returns the Ready/Busy status of the given chip. */ +int gpmi_is_ready(struct gpmi_nand_data *this, unsigned chip) +{ + struct resources *r = &this->resources; + uint32_t mask = 0; + uint32_t reg = 0; + + if (GPMI_IS_MX23(this)) { + mask = MX23_BM_GPMI_DEBUG_READY0 << chip; + reg = readl(r->gpmi_regs + HW_GPMI_DEBUG); + } else if (GPMI_IS_MX28(this) || GPMI_IS_MX6(this)) { + /* + * In the imx6, all the ready/busy pins are bound + * together. So we only need to check chip 0. + */ + if (GPMI_IS_MX6(this)) + chip = 0; + + /* MX28 shares the same R/B register as MX6Q. */ + mask = MX28_BF_GPMI_STAT_READY_BUSY(1 << chip); + reg = readl(r->gpmi_regs + HW_GPMI_STAT); + } else + dev_err(this->dev, "unknow arch.\n"); + return reg & mask; +} + +static inline void set_dma_type(struct gpmi_nand_data *this, + enum dma_ops_type type) +{ + this->last_dma_type = this->dma_type; + this->dma_type = type; +} + +int gpmi_send_command(struct gpmi_nand_data *this) +{ + struct dma_chan *channel = get_dma_chan(this); + struct dma_async_tx_descriptor *desc; + struct scatterlist *sgl; + int chip = this->current_chip; + u32 pio[3]; + + /* [1] send out the PIO words */ + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(BV_GPMI_CTRL0_COMMAND_MODE__WRITE) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(chip, this) + | BF_GPMI_CTRL0_LOCK_CS(LOCK_CS_ENABLE, this) + | BF_GPMI_CTRL0_ADDRESS(BV_GPMI_CTRL0_ADDRESS__NAND_CLE) + | BM_GPMI_CTRL0_ADDRESS_INCREMENT + | BF_GPMI_CTRL0_XFER_COUNT(this->command_length); + pio[1] = pio[2] = 0; + desc = dmaengine_prep_slave_sg(channel, + (struct scatterlist *)pio, + ARRAY_SIZE(pio), DMA_TRANS_NONE, 0); + if (!desc) + return -EINVAL; + + /* [2] send out the COMMAND + ADDRESS string stored in @buffer */ + sgl = &this->cmd_sgl; + + sg_init_one(sgl, this->cmd_buffer, this->command_length); + dma_map_sg(this->dev, sgl, 1, DMA_TO_DEVICE); + desc = dmaengine_prep_slave_sg(channel, + sgl, 1, DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) + return -EINVAL; + + /* [3] submit the DMA */ + set_dma_type(this, DMA_FOR_COMMAND); + return start_dma_without_bch_irq(this, desc); +} + +int gpmi_send_data(struct gpmi_nand_data *this) +{ + struct dma_async_tx_descriptor *desc; + struct dma_chan *channel = get_dma_chan(this); + int chip = this->current_chip; + uint32_t command_mode; + uint32_t address; + u32 pio[2]; + + /* [1] PIO */ + command_mode = BV_GPMI_CTRL0_COMMAND_MODE__WRITE; + address = BV_GPMI_CTRL0_ADDRESS__NAND_DATA; + + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(command_mode) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(chip, this) + | BF_GPMI_CTRL0_LOCK_CS(LOCK_CS_ENABLE, this) + | BF_GPMI_CTRL0_ADDRESS(address) + | BF_GPMI_CTRL0_XFER_COUNT(this->upper_len); + pio[1] = 0; + desc = dmaengine_prep_slave_sg(channel, (struct scatterlist *)pio, + ARRAY_SIZE(pio), DMA_TRANS_NONE, 0); + if (!desc) + return -EINVAL; + + /* [2] send DMA request */ + prepare_data_dma(this, DMA_TO_DEVICE); + desc = dmaengine_prep_slave_sg(channel, &this->data_sgl, + 1, DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) + return -EINVAL; + + /* [3] submit the DMA */ + set_dma_type(this, DMA_FOR_WRITE_DATA); + return start_dma_without_bch_irq(this, desc); +} + +int gpmi_read_data(struct gpmi_nand_data *this) +{ + struct dma_async_tx_descriptor *desc; + struct dma_chan *channel = get_dma_chan(this); + int chip = this->current_chip; + u32 pio[2]; + + /* [1] : send PIO */ + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(BV_GPMI_CTRL0_COMMAND_MODE__READ) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(chip, this) + | BF_GPMI_CTRL0_LOCK_CS(LOCK_CS_ENABLE, this) + | BF_GPMI_CTRL0_ADDRESS(BV_GPMI_CTRL0_ADDRESS__NAND_DATA) + | BF_GPMI_CTRL0_XFER_COUNT(this->upper_len); + pio[1] = 0; + desc = dmaengine_prep_slave_sg(channel, + (struct scatterlist *)pio, + ARRAY_SIZE(pio), DMA_TRANS_NONE, 0); + if (!desc) + return -EINVAL; + + /* [2] : send DMA request */ + prepare_data_dma(this, DMA_FROM_DEVICE); + desc = dmaengine_prep_slave_sg(channel, &this->data_sgl, + 1, DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) + return -EINVAL; + + /* [3] : submit the DMA */ + set_dma_type(this, DMA_FOR_READ_DATA); + return start_dma_without_bch_irq(this, desc); +} + +int gpmi_send_page(struct gpmi_nand_data *this, + dma_addr_t payload, dma_addr_t auxiliary) +{ + struct bch_geometry *geo = &this->bch_geometry; + uint32_t command_mode; + uint32_t address; + uint32_t ecc_command; + uint32_t buffer_mask; + struct dma_async_tx_descriptor *desc; + struct dma_chan *channel = get_dma_chan(this); + int chip = this->current_chip; + u32 pio[6]; + + /* A DMA descriptor that does an ECC page read. */ + command_mode = BV_GPMI_CTRL0_COMMAND_MODE__WRITE; + address = BV_GPMI_CTRL0_ADDRESS__NAND_DATA; + ecc_command = BV_GPMI_ECCCTRL_ECC_CMD__BCH_ENCODE; + buffer_mask = BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_PAGE | + BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY; + + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(command_mode) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(chip, this) + | BF_GPMI_CTRL0_LOCK_CS(LOCK_CS_ENABLE, this) + | BF_GPMI_CTRL0_ADDRESS(address) + | BF_GPMI_CTRL0_XFER_COUNT(0); + pio[1] = 0; + pio[2] = BM_GPMI_ECCCTRL_ENABLE_ECC + | BF_GPMI_ECCCTRL_ECC_CMD(ecc_command) + | BF_GPMI_ECCCTRL_BUFFER_MASK(buffer_mask); + pio[3] = geo->page_size; + pio[4] = payload; + pio[5] = auxiliary; + + desc = dmaengine_prep_slave_sg(channel, + (struct scatterlist *)pio, + ARRAY_SIZE(pio), DMA_TRANS_NONE, + DMA_CTRL_ACK); + if (!desc) + return -EINVAL; + + set_dma_type(this, DMA_FOR_WRITE_ECC_PAGE); + return start_dma_with_bch_irq(this, desc); +} + +int gpmi_read_page(struct gpmi_nand_data *this, + dma_addr_t payload, dma_addr_t auxiliary) +{ + struct bch_geometry *geo = &this->bch_geometry; + uint32_t command_mode; + uint32_t address; + uint32_t ecc_command; + uint32_t buffer_mask; + struct dma_async_tx_descriptor *desc; + struct dma_chan *channel = get_dma_chan(this); + int chip = this->current_chip; + u32 pio[6]; + + /* [1] Wait for the chip to report ready. */ + command_mode = BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY; + address = BV_GPMI_CTRL0_ADDRESS__NAND_DATA; + + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(command_mode) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(chip, this) + | BF_GPMI_CTRL0_LOCK_CS(LOCK_CS_ENABLE, this) + | BF_GPMI_CTRL0_ADDRESS(address) + | BF_GPMI_CTRL0_XFER_COUNT(0); + pio[1] = 0; + desc = dmaengine_prep_slave_sg(channel, + (struct scatterlist *)pio, 2, + DMA_TRANS_NONE, 0); + if (!desc) + return -EINVAL; + + /* [2] Enable the BCH block and read. */ + command_mode = BV_GPMI_CTRL0_COMMAND_MODE__READ; + address = BV_GPMI_CTRL0_ADDRESS__NAND_DATA; + ecc_command = BV_GPMI_ECCCTRL_ECC_CMD__BCH_DECODE; + buffer_mask = BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_PAGE + | BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY; + + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(command_mode) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(chip, this) + | BF_GPMI_CTRL0_LOCK_CS(LOCK_CS_ENABLE, this) + | BF_GPMI_CTRL0_ADDRESS(address) + | BF_GPMI_CTRL0_XFER_COUNT(geo->page_size); + + pio[1] = 0; + pio[2] = BM_GPMI_ECCCTRL_ENABLE_ECC + | BF_GPMI_ECCCTRL_ECC_CMD(ecc_command) + | BF_GPMI_ECCCTRL_BUFFER_MASK(buffer_mask); + pio[3] = geo->page_size; + pio[4] = payload; + pio[5] = auxiliary; + desc = dmaengine_prep_slave_sg(channel, + (struct scatterlist *)pio, + ARRAY_SIZE(pio), DMA_TRANS_NONE, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) + return -EINVAL; + + /* [3] Disable the BCH block */ + command_mode = BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY; + address = BV_GPMI_CTRL0_ADDRESS__NAND_DATA; + + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(command_mode) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(chip, this) + | BF_GPMI_CTRL0_LOCK_CS(LOCK_CS_ENABLE, this) + | BF_GPMI_CTRL0_ADDRESS(address) + | BF_GPMI_CTRL0_XFER_COUNT(geo->page_size); + pio[1] = 0; + pio[2] = 0; /* clear GPMI_HW_GPMI_ECCCTRL, disable the BCH. */ + desc = dmaengine_prep_slave_sg(channel, + (struct scatterlist *)pio, 3, + DMA_TRANS_NONE, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) + return -EINVAL; + + /* [4] submit the DMA */ + set_dma_type(this, DMA_FOR_READ_ECC_PAGE); + return start_dma_with_bch_irq(this, desc); +} diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c new file mode 100644 index 00000000000..f638cd8077c --- /dev/null +++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c @@ -0,0 +1,1844 @@ +/* + * Freescale GPMI NAND Flash Driver + * + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. + * Copyright (C) 2008 Embedded Alley Solutions, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/mtd/partitions.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_mtd.h> +#include "gpmi-nand.h" +#include "bch-regs.h" + +/* Resource names for the GPMI NAND driver. */ +#define GPMI_NAND_GPMI_REGS_ADDR_RES_NAME "gpmi-nand" +#define GPMI_NAND_BCH_REGS_ADDR_RES_NAME "bch" +#define GPMI_NAND_BCH_INTERRUPT_RES_NAME "bch" + +/* add our owner bbt descriptor */ +static uint8_t scan_ff_pattern[] = { 0xff }; +static struct nand_bbt_descr gpmi_bbt_descr = { + .options = 0, + .offs = 0, + .len = 1, + .pattern = scan_ff_pattern +}; + +/* + * We may change the layout if we can get the ECC info from the datasheet, + * else we will use all the (page + OOB). + */ +static struct nand_ecclayout gpmi_hw_ecclayout = { + .eccbytes = 0, + .eccpos = { 0, }, + .oobfree = { {.offset = 0, .length = 0} } +}; + +static const struct gpmi_devdata gpmi_devdata_imx23 = { + .type = IS_MX23, + .bch_max_ecc_strength = 20, + .max_chain_delay = 16, +}; + +static const struct gpmi_devdata gpmi_devdata_imx28 = { + .type = IS_MX28, + .bch_max_ecc_strength = 20, + .max_chain_delay = 16, +}; + +static const struct gpmi_devdata gpmi_devdata_imx6q = { + .type = IS_MX6Q, + .bch_max_ecc_strength = 40, + .max_chain_delay = 12, +}; + +static const struct gpmi_devdata gpmi_devdata_imx6sx = { + .type = IS_MX6SX, + .bch_max_ecc_strength = 62, + .max_chain_delay = 12, +}; + +static irqreturn_t bch_irq(int irq, void *cookie) +{ + struct gpmi_nand_data *this = cookie; + + gpmi_clear_bch(this); + complete(&this->bch_done); + return IRQ_HANDLED; +} + +/* + * Calculate the ECC strength by hand: + * E : The ECC strength. + * G : the length of Galois Field. + * N : The chunk count of per page. + * O : the oobsize of the NAND chip. + * M : the metasize of per page. + * + * The formula is : + * E * G * N + * ------------ <= (O - M) + * 8 + * + * So, we get E by: + * (O - M) * 8 + * E <= ------------- + * G * N + */ +static inline int get_ecc_strength(struct gpmi_nand_data *this) +{ + struct bch_geometry *geo = &this->bch_geometry; + struct mtd_info *mtd = &this->mtd; + int ecc_strength; + + ecc_strength = ((mtd->oobsize - geo->metadata_size) * 8) + / (geo->gf_len * geo->ecc_chunk_count); + + /* We need the minor even number. */ + return round_down(ecc_strength, 2); +} + +static inline bool gpmi_check_ecc(struct gpmi_nand_data *this) +{ + struct bch_geometry *geo = &this->bch_geometry; + + /* Do the sanity check. */ + if (GPMI_IS_MX23(this) || GPMI_IS_MX28(this)) { + /* The mx23/mx28 only support the GF13. */ + if (geo->gf_len == 14) + return false; + } + return geo->ecc_strength <= this->devdata->bch_max_ecc_strength; +} + +/* + * If we can get the ECC information from the nand chip, we do not + * need to calculate them ourselves. + * + * We may have available oob space in this case. + */ +static bool set_geometry_by_ecc_info(struct gpmi_nand_data *this) +{ + struct bch_geometry *geo = &this->bch_geometry; + struct mtd_info *mtd = &this->mtd; + struct nand_chip *chip = mtd->priv; + struct nand_oobfree *of = gpmi_hw_ecclayout.oobfree; + unsigned int block_mark_bit_offset; + + if (!(chip->ecc_strength_ds > 0 && chip->ecc_step_ds > 0)) + return false; + + switch (chip->ecc_step_ds) { + case SZ_512: + geo->gf_len = 13; + break; + case SZ_1K: + geo->gf_len = 14; + break; + default: + dev_err(this->dev, + "unsupported nand chip. ecc bits : %d, ecc size : %d\n", + chip->ecc_strength_ds, chip->ecc_step_ds); + return false; + } + geo->ecc_chunk_size = chip->ecc_step_ds; + geo->ecc_strength = round_up(chip->ecc_strength_ds, 2); + if (!gpmi_check_ecc(this)) + return false; + + /* Keep the C >= O */ + if (geo->ecc_chunk_size < mtd->oobsize) { + dev_err(this->dev, + "unsupported nand chip. ecc size: %d, oob size : %d\n", + chip->ecc_step_ds, mtd->oobsize); + return false; + } + + /* The default value, see comment in the legacy_set_geometry(). */ + geo->metadata_size = 10; + + geo->ecc_chunk_count = mtd->writesize / geo->ecc_chunk_size; + + /* + * Now, the NAND chip with 2K page(data chunk is 512byte) shows below: + * + * | P | + * |<----------------------------------------------------->| + * | | + * | (Block Mark) | + * | P' | | | | + * |<-------------------------------------------->| D | | O' | + * | |<---->| |<--->| + * V V V V V + * +---+----------+-+----------+-+----------+-+----------+-+-----+ + * | M | data |E| data |E| data |E| data |E| | + * +---+----------+-+----------+-+----------+-+----------+-+-----+ + * ^ ^ + * | O | + * |<------------>| + * | | + * + * P : the page size for BCH module. + * E : The ECC strength. + * G : the length of Galois Field. + * N : The chunk count of per page. + * M : the metasize of per page. + * C : the ecc chunk size, aka the "data" above. + * P': the nand chip's page size. + * O : the nand chip's oob size. + * O': the free oob. + * + * The formula for P is : + * + * E * G * N + * P = ------------ + P' + M + * 8 + * + * The position of block mark moves forward in the ECC-based view + * of page, and the delta is: + * + * E * G * (N - 1) + * D = (---------------- + M) + * 8 + * + * Please see the comment in legacy_set_geometry(). + * With the condition C >= O , we still can get same result. + * So the bit position of the physical block mark within the ECC-based + * view of the page is : + * (P' - D) * 8 + */ + geo->page_size = mtd->writesize + geo->metadata_size + + (geo->gf_len * geo->ecc_strength * geo->ecc_chunk_count) / 8; + + /* The available oob size we have. */ + if (geo->page_size < mtd->writesize + mtd->oobsize) { + of->offset = geo->page_size - mtd->writesize; + of->length = mtd->oobsize - of->offset; + } + + geo->payload_size = mtd->writesize; + + geo->auxiliary_status_offset = ALIGN(geo->metadata_size, 4); + geo->auxiliary_size = ALIGN(geo->metadata_size, 4) + + ALIGN(geo->ecc_chunk_count, 4); + + if (!this->swap_block_mark) + return true; + + /* For bit swap. */ + block_mark_bit_offset = mtd->writesize * 8 - + (geo->ecc_strength * geo->gf_len * (geo->ecc_chunk_count - 1) + + geo->metadata_size * 8); + + geo->block_mark_byte_offset = block_mark_bit_offset / 8; + geo->block_mark_bit_offset = block_mark_bit_offset % 8; + return true; +} + +static int legacy_set_geometry(struct gpmi_nand_data *this) +{ + struct bch_geometry *geo = &this->bch_geometry; + struct mtd_info *mtd = &this->mtd; + unsigned int metadata_size; + unsigned int status_size; + unsigned int block_mark_bit_offset; + + /* + * The size of the metadata can be changed, though we set it to 10 + * bytes now. But it can't be too large, because we have to save + * enough space for BCH. + */ + geo->metadata_size = 10; + + /* The default for the length of Galois Field. */ + geo->gf_len = 13; + + /* The default for chunk size. */ + geo->ecc_chunk_size = 512; + while (geo->ecc_chunk_size < mtd->oobsize) { + geo->ecc_chunk_size *= 2; /* keep C >= O */ + geo->gf_len = 14; + } + + geo->ecc_chunk_count = mtd->writesize / geo->ecc_chunk_size; + + /* We use the same ECC strength for all chunks. */ + geo->ecc_strength = get_ecc_strength(this); + if (!gpmi_check_ecc(this)) { + dev_err(this->dev, + "We can not support this nand chip." + " Its required ecc strength(%d) is beyond our" + " capability(%d).\n", geo->ecc_strength, + this->devdata->bch_max_ecc_strength); + return -EINVAL; + } + + geo->page_size = mtd->writesize + mtd->oobsize; + geo->payload_size = mtd->writesize; + + /* + * The auxiliary buffer contains the metadata and the ECC status. The + * metadata is padded to the nearest 32-bit boundary. The ECC status + * contains one byte for every ECC chunk, and is also padded to the + * nearest 32-bit boundary. + */ + metadata_size = ALIGN(geo->metadata_size, 4); + status_size = ALIGN(geo->ecc_chunk_count, 4); + + geo->auxiliary_size = metadata_size + status_size; + geo->auxiliary_status_offset = metadata_size; + + if (!this->swap_block_mark) + return 0; + + /* + * We need to compute the byte and bit offsets of + * the physical block mark within the ECC-based view of the page. + * + * NAND chip with 2K page shows below: + * (Block Mark) + * | | + * | D | + * |<---->| + * V V + * +---+----------+-+----------+-+----------+-+----------+-+ + * | M | data |E| data |E| data |E| data |E| + * +---+----------+-+----------+-+----------+-+----------+-+ + * + * The position of block mark moves forward in the ECC-based view + * of page, and the delta is: + * + * E * G * (N - 1) + * D = (---------------- + M) + * 8 + * + * With the formula to compute the ECC strength, and the condition + * : C >= O (C is the ecc chunk size) + * + * It's easy to deduce to the following result: + * + * E * G (O - M) C - M C - M + * ----------- <= ------- <= -------- < --------- + * 8 N N (N - 1) + * + * So, we get: + * + * E * G * (N - 1) + * D = (---------------- + M) < C + * 8 + * + * The above inequality means the position of block mark + * within the ECC-based view of the page is still in the data chunk, + * and it's NOT in the ECC bits of the chunk. + * + * Use the following to compute the bit position of the + * physical block mark within the ECC-based view of the page: + * (page_size - D) * 8 + * + * --Huang Shijie + */ + block_mark_bit_offset = mtd->writesize * 8 - + (geo->ecc_strength * geo->gf_len * (geo->ecc_chunk_count - 1) + + geo->metadata_size * 8); + + geo->block_mark_byte_offset = block_mark_bit_offset / 8; + geo->block_mark_bit_offset = block_mark_bit_offset % 8; + return 0; +} + +int common_nfc_set_geometry(struct gpmi_nand_data *this) +{ + if (of_property_read_bool(this->dev->of_node, "fsl,use-minimum-ecc") + && set_geometry_by_ecc_info(this)) + return 0; + return legacy_set_geometry(this); +} + +struct dma_chan *get_dma_chan(struct gpmi_nand_data *this) +{ + /* We use the DMA channel 0 to access all the nand chips. */ + return this->dma_chans[0]; +} + +/* Can we use the upper's buffer directly for DMA? */ +void prepare_data_dma(struct gpmi_nand_data *this, enum dma_data_direction dr) +{ + struct scatterlist *sgl = &this->data_sgl; + int ret; + + /* first try to map the upper buffer directly */ + if (virt_addr_valid(this->upper_buf) && + !object_is_on_stack(this->upper_buf)) { + sg_init_one(sgl, this->upper_buf, this->upper_len); + ret = dma_map_sg(this->dev, sgl, 1, dr); + if (ret == 0) + goto map_fail; + + this->direct_dma_map_ok = true; + return; + } + +map_fail: + /* We have to use our own DMA buffer. */ + sg_init_one(sgl, this->data_buffer_dma, this->upper_len); + + if (dr == DMA_TO_DEVICE) + memcpy(this->data_buffer_dma, this->upper_buf, this->upper_len); + + dma_map_sg(this->dev, sgl, 1, dr); + + this->direct_dma_map_ok = false; +} + +/* This will be called after the DMA operation is finished. */ +static void dma_irq_callback(void *param) +{ + struct gpmi_nand_data *this = param; + struct completion *dma_c = &this->dma_done; + + switch (this->dma_type) { + case DMA_FOR_COMMAND: + dma_unmap_sg(this->dev, &this->cmd_sgl, 1, DMA_TO_DEVICE); + break; + + case DMA_FOR_READ_DATA: + dma_unmap_sg(this->dev, &this->data_sgl, 1, DMA_FROM_DEVICE); + if (this->direct_dma_map_ok == false) + memcpy(this->upper_buf, this->data_buffer_dma, + this->upper_len); + break; + + case DMA_FOR_WRITE_DATA: + dma_unmap_sg(this->dev, &this->data_sgl, 1, DMA_TO_DEVICE); + break; + + case DMA_FOR_READ_ECC_PAGE: + case DMA_FOR_WRITE_ECC_PAGE: + /* We have to wait the BCH interrupt to finish. */ + break; + + default: + dev_err(this->dev, "in wrong DMA operation.\n"); + } + + complete(dma_c); +} + +int start_dma_without_bch_irq(struct gpmi_nand_data *this, + struct dma_async_tx_descriptor *desc) +{ + struct completion *dma_c = &this->dma_done; + int err; + + init_completion(dma_c); + + desc->callback = dma_irq_callback; + desc->callback_param = this; + dmaengine_submit(desc); + dma_async_issue_pending(get_dma_chan(this)); + + /* Wait for the interrupt from the DMA block. */ + err = wait_for_completion_timeout(dma_c, msecs_to_jiffies(1000)); + if (!err) { + dev_err(this->dev, "DMA timeout, last DMA :%d\n", + this->last_dma_type); + gpmi_dump_info(this); + return -ETIMEDOUT; + } + return 0; +} + +/* + * This function is used in BCH reading or BCH writing pages. + * It will wait for the BCH interrupt as long as ONE second. + * Actually, we must wait for two interrupts : + * [1] firstly the DMA interrupt and + * [2] secondly the BCH interrupt. + */ +int start_dma_with_bch_irq(struct gpmi_nand_data *this, + struct dma_async_tx_descriptor *desc) +{ + struct completion *bch_c = &this->bch_done; + int err; + + /* Prepare to receive an interrupt from the BCH block. */ + init_completion(bch_c); + + /* start the DMA */ + start_dma_without_bch_irq(this, desc); + + /* Wait for the interrupt from the BCH block. */ + err = wait_for_completion_timeout(bch_c, msecs_to_jiffies(1000)); + if (!err) { + dev_err(this->dev, "BCH timeout, last DMA :%d\n", + this->last_dma_type); + gpmi_dump_info(this); + return -ETIMEDOUT; + } + return 0; +} + +static int acquire_register_block(struct gpmi_nand_data *this, + const char *res_name) +{ + struct platform_device *pdev = this->pdev; + struct resources *res = &this->resources; + struct resource *r; + void __iomem *p; + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, res_name); + p = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(p)) + return PTR_ERR(p); + + if (!strcmp(res_name, GPMI_NAND_GPMI_REGS_ADDR_RES_NAME)) + res->gpmi_regs = p; + else if (!strcmp(res_name, GPMI_NAND_BCH_REGS_ADDR_RES_NAME)) + res->bch_regs = p; + else + dev_err(this->dev, "unknown resource name : %s\n", res_name); + + return 0; +} + +static int acquire_bch_irq(struct gpmi_nand_data *this, irq_handler_t irq_h) +{ + struct platform_device *pdev = this->pdev; + const char *res_name = GPMI_NAND_BCH_INTERRUPT_RES_NAME; + struct resource *r; + int err; + + r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, res_name); + if (!r) { + dev_err(this->dev, "Can't get resource for %s\n", res_name); + return -ENODEV; + } + + err = devm_request_irq(this->dev, r->start, irq_h, 0, res_name, this); + if (err) + dev_err(this->dev, "error requesting BCH IRQ\n"); + + return err; +} + +static void release_dma_channels(struct gpmi_nand_data *this) +{ + unsigned int i; + for (i = 0; i < DMA_CHANS; i++) + if (this->dma_chans[i]) { + dma_release_channel(this->dma_chans[i]); + this->dma_chans[i] = NULL; + } +} + +static int acquire_dma_channels(struct gpmi_nand_data *this) +{ + struct platform_device *pdev = this->pdev; + struct dma_chan *dma_chan; + + /* request dma channel */ + dma_chan = dma_request_slave_channel(&pdev->dev, "rx-tx"); + if (!dma_chan) { + dev_err(this->dev, "Failed to request DMA channel.\n"); + goto acquire_err; + } + + this->dma_chans[0] = dma_chan; + return 0; + +acquire_err: + release_dma_channels(this); + return -EINVAL; +} + +static char *extra_clks_for_mx6q[GPMI_CLK_MAX] = { + "gpmi_apb", "gpmi_bch", "gpmi_bch_apb", "per1_bch", +}; + +static int gpmi_get_clks(struct gpmi_nand_data *this) +{ + struct resources *r = &this->resources; + char **extra_clks = NULL; + struct clk *clk; + int err, i; + + /* The main clock is stored in the first. */ + r->clock[0] = devm_clk_get(this->dev, "gpmi_io"); + if (IS_ERR(r->clock[0])) { + err = PTR_ERR(r->clock[0]); + goto err_clock; + } + + /* Get extra clocks */ + if (GPMI_IS_MX6(this)) + extra_clks = extra_clks_for_mx6q; + if (!extra_clks) + return 0; + + for (i = 1; i < GPMI_CLK_MAX; i++) { + if (extra_clks[i - 1] == NULL) + break; + + clk = devm_clk_get(this->dev, extra_clks[i - 1]); + if (IS_ERR(clk)) { + err = PTR_ERR(clk); + goto err_clock; + } + + r->clock[i] = clk; + } + + if (GPMI_IS_MX6(this)) + /* + * Set the default value for the gpmi clock. + * + * If you want to use the ONFI nand which is in the + * Synchronous Mode, you should change the clock as you need. + */ + clk_set_rate(r->clock[0], 22000000); + + return 0; + +err_clock: + dev_dbg(this->dev, "failed in finding the clocks.\n"); + return err; +} + +static int acquire_resources(struct gpmi_nand_data *this) +{ + int ret; + + ret = acquire_register_block(this, GPMI_NAND_GPMI_REGS_ADDR_RES_NAME); + if (ret) + goto exit_regs; + + ret = acquire_register_block(this, GPMI_NAND_BCH_REGS_ADDR_RES_NAME); + if (ret) + goto exit_regs; + + ret = acquire_bch_irq(this, bch_irq); + if (ret) + goto exit_regs; + + ret = acquire_dma_channels(this); + if (ret) + goto exit_regs; + + ret = gpmi_get_clks(this); + if (ret) + goto exit_clock; + return 0; + +exit_clock: + release_dma_channels(this); +exit_regs: + return ret; +} + +static void release_resources(struct gpmi_nand_data *this) +{ + release_dma_channels(this); +} + +static int init_hardware(struct gpmi_nand_data *this) +{ + int ret; + + /* + * This structure contains the "safe" GPMI timing that should succeed + * with any NAND Flash device + * (although, with less-than-optimal performance). + */ + struct nand_timing safe_timing = { + .data_setup_in_ns = 80, + .data_hold_in_ns = 60, + .address_setup_in_ns = 25, + .gpmi_sample_delay_in_ns = 6, + .tREA_in_ns = -1, + .tRLOH_in_ns = -1, + .tRHOH_in_ns = -1, + }; + + /* Initialize the hardwares. */ + ret = gpmi_init(this); + if (ret) + return ret; + + this->timing = safe_timing; + return 0; +} + +static int read_page_prepare(struct gpmi_nand_data *this, + void *destination, unsigned length, + void *alt_virt, dma_addr_t alt_phys, unsigned alt_size, + void **use_virt, dma_addr_t *use_phys) +{ + struct device *dev = this->dev; + + if (virt_addr_valid(destination)) { + dma_addr_t dest_phys; + + dest_phys = dma_map_single(dev, destination, + length, DMA_FROM_DEVICE); + if (dma_mapping_error(dev, dest_phys)) { + if (alt_size < length) { + dev_err(dev, "Alternate buffer is too small\n"); + return -ENOMEM; + } + goto map_failed; + } + *use_virt = destination; + *use_phys = dest_phys; + this->direct_dma_map_ok = true; + return 0; + } + +map_failed: + *use_virt = alt_virt; + *use_phys = alt_phys; + this->direct_dma_map_ok = false; + return 0; +} + +static inline void read_page_end(struct gpmi_nand_data *this, + void *destination, unsigned length, + void *alt_virt, dma_addr_t alt_phys, unsigned alt_size, + void *used_virt, dma_addr_t used_phys) +{ + if (this->direct_dma_map_ok) + dma_unmap_single(this->dev, used_phys, length, DMA_FROM_DEVICE); +} + +static inline void read_page_swap_end(struct gpmi_nand_data *this, + void *destination, unsigned length, + void *alt_virt, dma_addr_t alt_phys, unsigned alt_size, + void *used_virt, dma_addr_t used_phys) +{ + if (!this->direct_dma_map_ok) + memcpy(destination, alt_virt, length); +} + +static int send_page_prepare(struct gpmi_nand_data *this, + const void *source, unsigned length, + void *alt_virt, dma_addr_t alt_phys, unsigned alt_size, + const void **use_virt, dma_addr_t *use_phys) +{ + struct device *dev = this->dev; + + if (virt_addr_valid(source)) { + dma_addr_t source_phys; + + source_phys = dma_map_single(dev, (void *)source, length, + DMA_TO_DEVICE); + if (dma_mapping_error(dev, source_phys)) { + if (alt_size < length) { + dev_err(dev, "Alternate buffer is too small\n"); + return -ENOMEM; + } + goto map_failed; + } + *use_virt = source; + *use_phys = source_phys; + return 0; + } +map_failed: + /* + * Copy the content of the source buffer into the alternate + * buffer and set up the return values accordingly. + */ + memcpy(alt_virt, source, length); + + *use_virt = alt_virt; + *use_phys = alt_phys; + return 0; +} + +static void send_page_end(struct gpmi_nand_data *this, + const void *source, unsigned length, + void *alt_virt, dma_addr_t alt_phys, unsigned alt_size, + const void *used_virt, dma_addr_t used_phys) +{ + struct device *dev = this->dev; + if (used_virt == source) + dma_unmap_single(dev, used_phys, length, DMA_TO_DEVICE); +} + +static void gpmi_free_dma_buffer(struct gpmi_nand_data *this) +{ + struct device *dev = this->dev; + + if (this->page_buffer_virt && virt_addr_valid(this->page_buffer_virt)) + dma_free_coherent(dev, this->page_buffer_size, + this->page_buffer_virt, + this->page_buffer_phys); + kfree(this->cmd_buffer); + kfree(this->data_buffer_dma); + + this->cmd_buffer = NULL; + this->data_buffer_dma = NULL; + this->page_buffer_virt = NULL; + this->page_buffer_size = 0; +} + +/* Allocate the DMA buffers */ +static int gpmi_alloc_dma_buffer(struct gpmi_nand_data *this) +{ + struct bch_geometry *geo = &this->bch_geometry; + struct device *dev = this->dev; + struct mtd_info *mtd = &this->mtd; + + /* [1] Allocate a command buffer. PAGE_SIZE is enough. */ + this->cmd_buffer = kzalloc(PAGE_SIZE, GFP_DMA | GFP_KERNEL); + if (this->cmd_buffer == NULL) + goto error_alloc; + + /* + * [2] Allocate a read/write data buffer. + * The gpmi_alloc_dma_buffer can be called twice. + * We allocate a PAGE_SIZE length buffer if gpmi_alloc_dma_buffer + * is called before the nand_scan_ident; and we allocate a buffer + * of the real NAND page size when the gpmi_alloc_dma_buffer is + * called after the nand_scan_ident. + */ + this->data_buffer_dma = kzalloc(mtd->writesize ?: PAGE_SIZE, + GFP_DMA | GFP_KERNEL); + if (this->data_buffer_dma == NULL) + goto error_alloc; + + /* + * [3] Allocate the page buffer. + * + * Both the payload buffer and the auxiliary buffer must appear on + * 32-bit boundaries. We presume the size of the payload buffer is a + * power of two and is much larger than four, which guarantees the + * auxiliary buffer will appear on a 32-bit boundary. + */ + this->page_buffer_size = geo->payload_size + geo->auxiliary_size; + this->page_buffer_virt = dma_alloc_coherent(dev, this->page_buffer_size, + &this->page_buffer_phys, GFP_DMA); + if (!this->page_buffer_virt) + goto error_alloc; + + + /* Slice up the page buffer. */ + this->payload_virt = this->page_buffer_virt; + this->payload_phys = this->page_buffer_phys; + this->auxiliary_virt = this->payload_virt + geo->payload_size; + this->auxiliary_phys = this->payload_phys + geo->payload_size; + return 0; + +error_alloc: + gpmi_free_dma_buffer(this); + return -ENOMEM; +} + +static void gpmi_cmd_ctrl(struct mtd_info *mtd, int data, unsigned int ctrl) +{ + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *this = chip->priv; + int ret; + + /* + * Every operation begins with a command byte and a series of zero or + * more address bytes. These are distinguished by either the Address + * Latch Enable (ALE) or Command Latch Enable (CLE) signals being + * asserted. When MTD is ready to execute the command, it will deassert + * both latch enables. + * + * Rather than run a separate DMA operation for every single byte, we + * queue them up and run a single DMA operation for the entire series + * of command and data bytes. NAND_CMD_NONE means the END of the queue. + */ + if ((ctrl & (NAND_ALE | NAND_CLE))) { + if (data != NAND_CMD_NONE) + this->cmd_buffer[this->command_length++] = data; + return; + } + + if (!this->command_length) + return; + + ret = gpmi_send_command(this); + if (ret) + dev_err(this->dev, "Chip: %u, Error %d\n", + this->current_chip, ret); + + this->command_length = 0; +} + +static int gpmi_dev_ready(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *this = chip->priv; + + return gpmi_is_ready(this, this->current_chip); +} + +static void gpmi_select_chip(struct mtd_info *mtd, int chipnr) +{ + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *this = chip->priv; + + if ((this->current_chip < 0) && (chipnr >= 0)) + gpmi_begin(this); + else if ((this->current_chip >= 0) && (chipnr < 0)) + gpmi_end(this); + + this->current_chip = chipnr; +} + +static void gpmi_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *this = chip->priv; + + dev_dbg(this->dev, "len is %d\n", len); + this->upper_buf = buf; + this->upper_len = len; + + gpmi_read_data(this); +} + +static void gpmi_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *this = chip->priv; + + dev_dbg(this->dev, "len is %d\n", len); + this->upper_buf = (uint8_t *)buf; + this->upper_len = len; + + gpmi_send_data(this); +} + +static uint8_t gpmi_read_byte(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *this = chip->priv; + uint8_t *buf = this->data_buffer_dma; + + gpmi_read_buf(mtd, buf, 1); + return buf[0]; +} + +/* + * Handles block mark swapping. + * It can be called in swapping the block mark, or swapping it back, + * because the the operations are the same. + */ +static void block_mark_swapping(struct gpmi_nand_data *this, + void *payload, void *auxiliary) +{ + struct bch_geometry *nfc_geo = &this->bch_geometry; + unsigned char *p; + unsigned char *a; + unsigned int bit; + unsigned char mask; + unsigned char from_data; + unsigned char from_oob; + + if (!this->swap_block_mark) + return; + + /* + * If control arrives here, we're swapping. Make some convenience + * variables. + */ + bit = nfc_geo->block_mark_bit_offset; + p = payload + nfc_geo->block_mark_byte_offset; + a = auxiliary; + + /* + * Get the byte from the data area that overlays the block mark. Since + * the ECC engine applies its own view to the bits in the page, the + * physical block mark won't (in general) appear on a byte boundary in + * the data. + */ + from_data = (p[0] >> bit) | (p[1] << (8 - bit)); + + /* Get the byte from the OOB. */ + from_oob = a[0]; + + /* Swap them. */ + a[0] = from_data; + + mask = (0x1 << bit) - 1; + p[0] = (p[0] & mask) | (from_oob << bit); + + mask = ~0 << bit; + p[1] = (p[1] & mask) | (from_oob >> (8 - bit)); +} + +static int gpmi_ecc_read_page(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf, int oob_required, int page) +{ + struct gpmi_nand_data *this = chip->priv; + struct bch_geometry *nfc_geo = &this->bch_geometry; + void *payload_virt; + dma_addr_t payload_phys; + void *auxiliary_virt; + dma_addr_t auxiliary_phys; + unsigned int i; + unsigned char *status; + unsigned int max_bitflips = 0; + int ret; + + dev_dbg(this->dev, "page number is : %d\n", page); + ret = read_page_prepare(this, buf, nfc_geo->payload_size, + this->payload_virt, this->payload_phys, + nfc_geo->payload_size, + &payload_virt, &payload_phys); + if (ret) { + dev_err(this->dev, "Inadequate DMA buffer\n"); + ret = -ENOMEM; + return ret; + } + auxiliary_virt = this->auxiliary_virt; + auxiliary_phys = this->auxiliary_phys; + + /* go! */ + ret = gpmi_read_page(this, payload_phys, auxiliary_phys); + read_page_end(this, buf, nfc_geo->payload_size, + this->payload_virt, this->payload_phys, + nfc_geo->payload_size, + payload_virt, payload_phys); + if (ret) { + dev_err(this->dev, "Error in ECC-based read: %d\n", ret); + return ret; + } + + /* handle the block mark swapping */ + block_mark_swapping(this, payload_virt, auxiliary_virt); + + /* Loop over status bytes, accumulating ECC status. */ + status = auxiliary_virt + nfc_geo->auxiliary_status_offset; + + for (i = 0; i < nfc_geo->ecc_chunk_count; i++, status++) { + if ((*status == STATUS_GOOD) || (*status == STATUS_ERASED)) + continue; + + if (*status == STATUS_UNCORRECTABLE) { + mtd->ecc_stats.failed++; + continue; + } + mtd->ecc_stats.corrected += *status; + max_bitflips = max_t(unsigned int, max_bitflips, *status); + } + + if (oob_required) { + /* + * It's time to deliver the OOB bytes. See gpmi_ecc_read_oob() + * for details about our policy for delivering the OOB. + * + * We fill the caller's buffer with set bits, and then copy the + * block mark to th caller's buffer. Note that, if block mark + * swapping was necessary, it has already been done, so we can + * rely on the first byte of the auxiliary buffer to contain + * the block mark. + */ + memset(chip->oob_poi, ~0, mtd->oobsize); + chip->oob_poi[0] = ((uint8_t *) auxiliary_virt)[0]; + } + + read_page_swap_end(this, buf, nfc_geo->payload_size, + this->payload_virt, this->payload_phys, + nfc_geo->payload_size, + payload_virt, payload_phys); + + return max_bitflips; +} + +/* Fake a virtual small page for the subpage read */ +static int gpmi_ecc_read_subpage(struct mtd_info *mtd, struct nand_chip *chip, + uint32_t offs, uint32_t len, uint8_t *buf, int page) +{ + struct gpmi_nand_data *this = chip->priv; + void __iomem *bch_regs = this->resources.bch_regs; + struct bch_geometry old_geo = this->bch_geometry; + struct bch_geometry *geo = &this->bch_geometry; + int size = chip->ecc.size; /* ECC chunk size */ + int meta, n, page_size; + u32 r1_old, r2_old, r1_new, r2_new; + unsigned int max_bitflips; + int first, last, marker_pos; + int ecc_parity_size; + int col = 0; + + /* The size of ECC parity */ + ecc_parity_size = geo->gf_len * geo->ecc_strength / 8; + + /* Align it with the chunk size */ + first = offs / size; + last = (offs + len - 1) / size; + + /* + * Find the chunk which contains the Block Marker. If this chunk is + * in the range of [first, last], we have to read out the whole page. + * Why? since we had swapped the data at the position of Block Marker + * to the metadata which is bound with the chunk 0. + */ + marker_pos = geo->block_mark_byte_offset / size; + if (last >= marker_pos && first <= marker_pos) { + dev_dbg(this->dev, "page:%d, first:%d, last:%d, marker at:%d\n", + page, first, last, marker_pos); + return gpmi_ecc_read_page(mtd, chip, buf, 0, page); + } + + meta = geo->metadata_size; + if (first) { + col = meta + (size + ecc_parity_size) * first; + chip->cmdfunc(mtd, NAND_CMD_RNDOUT, col, -1); + + meta = 0; + buf = buf + first * size; + } + + /* Save the old environment */ + r1_old = r1_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT0); + r2_old = r2_new = readl(bch_regs + HW_BCH_FLASH0LAYOUT1); + + /* change the BCH registers and bch_geometry{} */ + n = last - first + 1; + page_size = meta + (size + ecc_parity_size) * n; + + r1_new &= ~(BM_BCH_FLASH0LAYOUT0_NBLOCKS | + BM_BCH_FLASH0LAYOUT0_META_SIZE); + r1_new |= BF_BCH_FLASH0LAYOUT0_NBLOCKS(n - 1) + | BF_BCH_FLASH0LAYOUT0_META_SIZE(meta); + writel(r1_new, bch_regs + HW_BCH_FLASH0LAYOUT0); + + r2_new &= ~BM_BCH_FLASH0LAYOUT1_PAGE_SIZE; + r2_new |= BF_BCH_FLASH0LAYOUT1_PAGE_SIZE(page_size); + writel(r2_new, bch_regs + HW_BCH_FLASH0LAYOUT1); + + geo->ecc_chunk_count = n; + geo->payload_size = n * size; + geo->page_size = page_size; + geo->auxiliary_status_offset = ALIGN(meta, 4); + + dev_dbg(this->dev, "page:%d(%d:%d)%d, chunk:(%d:%d), BCH PG size:%d\n", + page, offs, len, col, first, n, page_size); + + /* Read the subpage now */ + this->swap_block_mark = false; + max_bitflips = gpmi_ecc_read_page(mtd, chip, buf, 0, page); + + /* Restore */ + writel(r1_old, bch_regs + HW_BCH_FLASH0LAYOUT0); + writel(r2_old, bch_regs + HW_BCH_FLASH0LAYOUT1); + this->bch_geometry = old_geo; + this->swap_block_mark = true; + + return max_bitflips; +} + +static int gpmi_ecc_write_page(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf, int oob_required) +{ + struct gpmi_nand_data *this = chip->priv; + struct bch_geometry *nfc_geo = &this->bch_geometry; + const void *payload_virt; + dma_addr_t payload_phys; + const void *auxiliary_virt; + dma_addr_t auxiliary_phys; + int ret; + + dev_dbg(this->dev, "ecc write page.\n"); + if (this->swap_block_mark) { + /* + * If control arrives here, we're doing block mark swapping. + * Since we can't modify the caller's buffers, we must copy them + * into our own. + */ + memcpy(this->payload_virt, buf, mtd->writesize); + payload_virt = this->payload_virt; + payload_phys = this->payload_phys; + + memcpy(this->auxiliary_virt, chip->oob_poi, + nfc_geo->auxiliary_size); + auxiliary_virt = this->auxiliary_virt; + auxiliary_phys = this->auxiliary_phys; + + /* Handle block mark swapping. */ + block_mark_swapping(this, + (void *) payload_virt, (void *) auxiliary_virt); + } else { + /* + * If control arrives here, we're not doing block mark swapping, + * so we can to try and use the caller's buffers. + */ + ret = send_page_prepare(this, + buf, mtd->writesize, + this->payload_virt, this->payload_phys, + nfc_geo->payload_size, + &payload_virt, &payload_phys); + if (ret) { + dev_err(this->dev, "Inadequate payload DMA buffer\n"); + return 0; + } + + ret = send_page_prepare(this, + chip->oob_poi, mtd->oobsize, + this->auxiliary_virt, this->auxiliary_phys, + nfc_geo->auxiliary_size, + &auxiliary_virt, &auxiliary_phys); + if (ret) { + dev_err(this->dev, "Inadequate auxiliary DMA buffer\n"); + goto exit_auxiliary; + } + } + + /* Ask the NFC. */ + ret = gpmi_send_page(this, payload_phys, auxiliary_phys); + if (ret) + dev_err(this->dev, "Error in ECC-based write: %d\n", ret); + + if (!this->swap_block_mark) { + send_page_end(this, chip->oob_poi, mtd->oobsize, + this->auxiliary_virt, this->auxiliary_phys, + nfc_geo->auxiliary_size, + auxiliary_virt, auxiliary_phys); +exit_auxiliary: + send_page_end(this, buf, mtd->writesize, + this->payload_virt, this->payload_phys, + nfc_geo->payload_size, + payload_virt, payload_phys); + } + + return 0; +} + +/* + * There are several places in this driver where we have to handle the OOB and + * block marks. This is the function where things are the most complicated, so + * this is where we try to explain it all. All the other places refer back to + * here. + * + * These are the rules, in order of decreasing importance: + * + * 1) Nothing the caller does can be allowed to imperil the block mark. + * + * 2) In read operations, the first byte of the OOB we return must reflect the + * true state of the block mark, no matter where that block mark appears in + * the physical page. + * + * 3) ECC-based read operations return an OOB full of set bits (since we never + * allow ECC-based writes to the OOB, it doesn't matter what ECC-based reads + * return). + * + * 4) "Raw" read operations return a direct view of the physical bytes in the + * page, using the conventional definition of which bytes are data and which + * are OOB. This gives the caller a way to see the actual, physical bytes + * in the page, without the distortions applied by our ECC engine. + * + * + * What we do for this specific read operation depends on two questions: + * + * 1) Are we doing a "raw" read, or an ECC-based read? + * + * 2) Are we using block mark swapping or transcription? + * + * There are four cases, illustrated by the following Karnaugh map: + * + * | Raw | ECC-based | + * -------------+-------------------------+-------------------------+ + * | Read the conventional | | + * | OOB at the end of the | | + * Swapping | page and return it. It | | + * | contains exactly what | | + * | we want. | Read the block mark and | + * -------------+-------------------------+ return it in a buffer | + * | Read the conventional | full of set bits. | + * | OOB at the end of the | | + * | page and also the block | | + * Transcribing | mark in the metadata. | | + * | Copy the block mark | | + * | into the first byte of | | + * | the OOB. | | + * -------------+-------------------------+-------------------------+ + * + * Note that we break rule #4 in the Transcribing/Raw case because we're not + * giving an accurate view of the actual, physical bytes in the page (we're + * overwriting the block mark). That's OK because it's more important to follow + * rule #2. + * + * It turns out that knowing whether we want an "ECC-based" or "raw" read is not + * easy. When reading a page, for example, the NAND Flash MTD code calls our + * ecc.read_page or ecc.read_page_raw function. Thus, the fact that MTD wants an + * ECC-based or raw view of the page is implicit in which function it calls + * (there is a similar pair of ECC-based/raw functions for writing). + * + * FIXME: The following paragraph is incorrect, now that there exist + * ecc.read_oob_raw and ecc.write_oob_raw functions. + * + * Since MTD assumes the OOB is not covered by ECC, there is no pair of + * ECC-based/raw functions for reading or or writing the OOB. The fact that the + * caller wants an ECC-based or raw view of the page is not propagated down to + * this driver. + */ +static int gpmi_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *chip, + int page) +{ + struct gpmi_nand_data *this = chip->priv; + + dev_dbg(this->dev, "page number is %d\n", page); + /* clear the OOB buffer */ + memset(chip->oob_poi, ~0, mtd->oobsize); + + /* Read out the conventional OOB. */ + chip->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page); + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + + /* + * Now, we want to make sure the block mark is correct. In the + * Swapping/Raw case, we already have it. Otherwise, we need to + * explicitly read it. + */ + if (!this->swap_block_mark) { + /* Read the block mark into the first byte of the OOB buffer. */ + chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page); + chip->oob_poi[0] = chip->read_byte(mtd); + } + + return 0; +} + +static int +gpmi_ecc_write_oob(struct mtd_info *mtd, struct nand_chip *chip, int page) +{ + struct nand_oobfree *of = mtd->ecclayout->oobfree; + int status = 0; + + /* Do we have available oob area? */ + if (!of->length) + return -EPERM; + + if (!nand_is_slc(chip)) + return -EPERM; + + chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize + of->offset, page); + chip->write_buf(mtd, chip->oob_poi + of->offset, of->length); + chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + + status = chip->waitfunc(mtd, chip); + return status & NAND_STATUS_FAIL ? -EIO : 0; +} + +static int gpmi_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *this = chip->priv; + int ret = 0; + uint8_t *block_mark; + int column, page, status, chipnr; + + chipnr = (int)(ofs >> chip->chip_shift); + chip->select_chip(mtd, chipnr); + + column = this->swap_block_mark ? mtd->writesize : 0; + + /* Write the block mark. */ + block_mark = this->data_buffer_dma; + block_mark[0] = 0; /* bad block marker */ + + /* Shift to get page */ + page = (int)(ofs >> chip->page_shift); + + chip->cmdfunc(mtd, NAND_CMD_SEQIN, column, page); + chip->write_buf(mtd, block_mark, 1); + chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + + status = chip->waitfunc(mtd, chip); + if (status & NAND_STATUS_FAIL) + ret = -EIO; + + chip->select_chip(mtd, -1); + + return ret; +} + +static int nand_boot_set_geometry(struct gpmi_nand_data *this) +{ + struct boot_rom_geometry *geometry = &this->rom_geometry; + + /* + * Set the boot block stride size. + * + * In principle, we should be reading this from the OTP bits, since + * that's where the ROM is going to get it. In fact, we don't have any + * way to read the OTP bits, so we go with the default and hope for the + * best. + */ + geometry->stride_size_in_pages = 64; + + /* + * Set the search area stride exponent. + * + * In principle, we should be reading this from the OTP bits, since + * that's where the ROM is going to get it. In fact, we don't have any + * way to read the OTP bits, so we go with the default and hope for the + * best. + */ + geometry->search_area_stride_exponent = 2; + return 0; +} + +static const char *fingerprint = "STMP"; +static int mx23_check_transcription_stamp(struct gpmi_nand_data *this) +{ + struct boot_rom_geometry *rom_geo = &this->rom_geometry; + struct device *dev = this->dev; + struct mtd_info *mtd = &this->mtd; + struct nand_chip *chip = &this->nand; + unsigned int search_area_size_in_strides; + unsigned int stride; + unsigned int page; + uint8_t *buffer = chip->buffers->databuf; + int saved_chip_number; + int found_an_ncb_fingerprint = false; + + /* Compute the number of strides in a search area. */ + search_area_size_in_strides = 1 << rom_geo->search_area_stride_exponent; + + saved_chip_number = this->current_chip; + chip->select_chip(mtd, 0); + + /* + * Loop through the first search area, looking for the NCB fingerprint. + */ + dev_dbg(dev, "Scanning for an NCB fingerprint...\n"); + + for (stride = 0; stride < search_area_size_in_strides; stride++) { + /* Compute the page addresses. */ + page = stride * rom_geo->stride_size_in_pages; + + dev_dbg(dev, "Looking for a fingerprint in page 0x%x\n", page); + + /* + * Read the NCB fingerprint. The fingerprint is four bytes long + * and starts in the 12th byte of the page. + */ + chip->cmdfunc(mtd, NAND_CMD_READ0, 12, page); + chip->read_buf(mtd, buffer, strlen(fingerprint)); + + /* Look for the fingerprint. */ + if (!memcmp(buffer, fingerprint, strlen(fingerprint))) { + found_an_ncb_fingerprint = true; + break; + } + + } + + chip->select_chip(mtd, saved_chip_number); + + if (found_an_ncb_fingerprint) + dev_dbg(dev, "\tFound a fingerprint\n"); + else + dev_dbg(dev, "\tNo fingerprint found\n"); + return found_an_ncb_fingerprint; +} + +/* Writes a transcription stamp. */ +static int mx23_write_transcription_stamp(struct gpmi_nand_data *this) +{ + struct device *dev = this->dev; + struct boot_rom_geometry *rom_geo = &this->rom_geometry; + struct mtd_info *mtd = &this->mtd; + struct nand_chip *chip = &this->nand; + unsigned int block_size_in_pages; + unsigned int search_area_size_in_strides; + unsigned int search_area_size_in_pages; + unsigned int search_area_size_in_blocks; + unsigned int block; + unsigned int stride; + unsigned int page; + uint8_t *buffer = chip->buffers->databuf; + int saved_chip_number; + int status; + + /* Compute the search area geometry. */ + block_size_in_pages = mtd->erasesize / mtd->writesize; + search_area_size_in_strides = 1 << rom_geo->search_area_stride_exponent; + search_area_size_in_pages = search_area_size_in_strides * + rom_geo->stride_size_in_pages; + search_area_size_in_blocks = + (search_area_size_in_pages + (block_size_in_pages - 1)) / + block_size_in_pages; + + dev_dbg(dev, "Search Area Geometry :\n"); + dev_dbg(dev, "\tin Blocks : %u\n", search_area_size_in_blocks); + dev_dbg(dev, "\tin Strides: %u\n", search_area_size_in_strides); + dev_dbg(dev, "\tin Pages : %u\n", search_area_size_in_pages); + + /* Select chip 0. */ + saved_chip_number = this->current_chip; + chip->select_chip(mtd, 0); + + /* Loop over blocks in the first search area, erasing them. */ + dev_dbg(dev, "Erasing the search area...\n"); + + for (block = 0; block < search_area_size_in_blocks; block++) { + /* Compute the page address. */ + page = block * block_size_in_pages; + + /* Erase this block. */ + dev_dbg(dev, "\tErasing block 0x%x\n", block); + chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page); + chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1); + + /* Wait for the erase to finish. */ + status = chip->waitfunc(mtd, chip); + if (status & NAND_STATUS_FAIL) + dev_err(dev, "[%s] Erase failed.\n", __func__); + } + + /* Write the NCB fingerprint into the page buffer. */ + memset(buffer, ~0, mtd->writesize); + memcpy(buffer + 12, fingerprint, strlen(fingerprint)); + + /* Loop through the first search area, writing NCB fingerprints. */ + dev_dbg(dev, "Writing NCB fingerprints...\n"); + for (stride = 0; stride < search_area_size_in_strides; stride++) { + /* Compute the page addresses. */ + page = stride * rom_geo->stride_size_in_pages; + + /* Write the first page of the current stride. */ + dev_dbg(dev, "Writing an NCB fingerprint in page 0x%x\n", page); + chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page); + chip->ecc.write_page_raw(mtd, chip, buffer, 0); + chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + + /* Wait for the write to finish. */ + status = chip->waitfunc(mtd, chip); + if (status & NAND_STATUS_FAIL) + dev_err(dev, "[%s] Write failed.\n", __func__); + } + + /* Deselect chip 0. */ + chip->select_chip(mtd, saved_chip_number); + return 0; +} + +static int mx23_boot_init(struct gpmi_nand_data *this) +{ + struct device *dev = this->dev; + struct nand_chip *chip = &this->nand; + struct mtd_info *mtd = &this->mtd; + unsigned int block_count; + unsigned int block; + int chipnr; + int page; + loff_t byte; + uint8_t block_mark; + int ret = 0; + + /* + * If control arrives here, we can't use block mark swapping, which + * means we're forced to use transcription. First, scan for the + * transcription stamp. If we find it, then we don't have to do + * anything -- the block marks are already transcribed. + */ + if (mx23_check_transcription_stamp(this)) + return 0; + + /* + * If control arrives here, we couldn't find a transcription stamp, so + * so we presume the block marks are in the conventional location. + */ + dev_dbg(dev, "Transcribing bad block marks...\n"); + + /* Compute the number of blocks in the entire medium. */ + block_count = chip->chipsize >> chip->phys_erase_shift; + + /* + * Loop over all the blocks in the medium, transcribing block marks as + * we go. + */ + for (block = 0; block < block_count; block++) { + /* + * Compute the chip, page and byte addresses for this block's + * conventional mark. + */ + chipnr = block >> (chip->chip_shift - chip->phys_erase_shift); + page = block << (chip->phys_erase_shift - chip->page_shift); + byte = block << chip->phys_erase_shift; + + /* Send the command to read the conventional block mark. */ + chip->select_chip(mtd, chipnr); + chip->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page); + block_mark = chip->read_byte(mtd); + chip->select_chip(mtd, -1); + + /* + * Check if the block is marked bad. If so, we need to mark it + * again, but this time the result will be a mark in the + * location where we transcribe block marks. + */ + if (block_mark != 0xff) { + dev_dbg(dev, "Transcribing mark in block %u\n", block); + ret = chip->block_markbad(mtd, byte); + if (ret) + dev_err(dev, "Failed to mark block bad with " + "ret %d\n", ret); + } + } + + /* Write the stamp that indicates we've transcribed the block marks. */ + mx23_write_transcription_stamp(this); + return 0; +} + +static int nand_boot_init(struct gpmi_nand_data *this) +{ + nand_boot_set_geometry(this); + + /* This is ROM arch-specific initilization before the BBT scanning. */ + if (GPMI_IS_MX23(this)) + return mx23_boot_init(this); + return 0; +} + +static int gpmi_set_geometry(struct gpmi_nand_data *this) +{ + int ret; + + /* Free the temporary DMA memory for reading ID. */ + gpmi_free_dma_buffer(this); + + /* Set up the NFC geometry which is used by BCH. */ + ret = bch_set_geometry(this); + if (ret) { + dev_err(this->dev, "Error setting BCH geometry : %d\n", ret); + return ret; + } + + /* Alloc the new DMA buffers according to the pagesize and oobsize */ + return gpmi_alloc_dma_buffer(this); +} + +static void gpmi_nand_exit(struct gpmi_nand_data *this) +{ + nand_release(&this->mtd); + gpmi_free_dma_buffer(this); +} + +static int gpmi_init_last(struct gpmi_nand_data *this) +{ + struct mtd_info *mtd = &this->mtd; + struct nand_chip *chip = mtd->priv; + struct nand_ecc_ctrl *ecc = &chip->ecc; + struct bch_geometry *bch_geo = &this->bch_geometry; + int ret; + + /* Set up swap_block_mark, must be set before the gpmi_set_geometry() */ + this->swap_block_mark = !GPMI_IS_MX23(this); + + /* Set up the medium geometry */ + ret = gpmi_set_geometry(this); + if (ret) + return ret; + + /* Init the nand_ecc_ctrl{} */ + ecc->read_page = gpmi_ecc_read_page; + ecc->write_page = gpmi_ecc_write_page; + ecc->read_oob = gpmi_ecc_read_oob; + ecc->write_oob = gpmi_ecc_write_oob; + ecc->mode = NAND_ECC_HW; + ecc->size = bch_geo->ecc_chunk_size; + ecc->strength = bch_geo->ecc_strength; + ecc->layout = &gpmi_hw_ecclayout; + + /* + * We only enable the subpage read when: + * (1) the chip is imx6, and + * (2) the size of the ECC parity is byte aligned. + */ + if (GPMI_IS_MX6(this) && + ((bch_geo->gf_len * bch_geo->ecc_strength) % 8) == 0) { + ecc->read_subpage = gpmi_ecc_read_subpage; + chip->options |= NAND_SUBPAGE_READ; + } + + /* + * Can we enable the extra features? such as EDO or Sync mode. + * + * We do not check the return value now. That's means if we fail in + * enable the extra features, we still can run in the normal way. + */ + gpmi_extra_init(this); + + return 0; +} + +static int gpmi_nand_init(struct gpmi_nand_data *this) +{ + struct mtd_info *mtd = &this->mtd; + struct nand_chip *chip = &this->nand; + struct mtd_part_parser_data ppdata = {}; + int ret; + + /* init current chip */ + this->current_chip = -1; + + /* init the MTD data structures */ + mtd->priv = chip; + mtd->name = "gpmi-nand"; + mtd->owner = THIS_MODULE; + + /* init the nand_chip{}, we don't support a 16-bit NAND Flash bus. */ + chip->priv = this; + chip->select_chip = gpmi_select_chip; + chip->cmd_ctrl = gpmi_cmd_ctrl; + chip->dev_ready = gpmi_dev_ready; + chip->read_byte = gpmi_read_byte; + chip->read_buf = gpmi_read_buf; + chip->write_buf = gpmi_write_buf; + chip->badblock_pattern = &gpmi_bbt_descr; + chip->block_markbad = gpmi_block_markbad; + chip->options |= NAND_NO_SUBPAGE_WRITE; + if (of_get_nand_on_flash_bbt(this->dev->of_node)) + chip->bbt_options |= NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB; + + /* + * Allocate a temporary DMA buffer for reading ID in the + * nand_scan_ident(). + */ + this->bch_geometry.payload_size = 1024; + this->bch_geometry.auxiliary_size = 128; + ret = gpmi_alloc_dma_buffer(this); + if (ret) + goto err_out; + + ret = nand_scan_ident(mtd, GPMI_IS_MX6(this) ? 2 : 1, NULL); + if (ret) + goto err_out; + + ret = gpmi_init_last(this); + if (ret) + goto err_out; + + chip->options |= NAND_SKIP_BBTSCAN; + ret = nand_scan_tail(mtd); + if (ret) + goto err_out; + + ret = nand_boot_init(this); + if (ret) + goto err_out; + chip->scan_bbt(mtd); + + ppdata.of_node = this->pdev->dev.of_node; + ret = mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0); + if (ret) + goto err_out; + return 0; + +err_out: + gpmi_nand_exit(this); + return ret; +} + +static const struct of_device_id gpmi_nand_id_table[] = { + { + .compatible = "fsl,imx23-gpmi-nand", + .data = (void *)&gpmi_devdata_imx23, + }, { + .compatible = "fsl,imx28-gpmi-nand", + .data = (void *)&gpmi_devdata_imx28, + }, { + .compatible = "fsl,imx6q-gpmi-nand", + .data = (void *)&gpmi_devdata_imx6q, + }, { + .compatible = "fsl,imx6sx-gpmi-nand", + .data = (void *)&gpmi_devdata_imx6sx, + }, {} +}; +MODULE_DEVICE_TABLE(of, gpmi_nand_id_table); + +static int gpmi_nand_probe(struct platform_device *pdev) +{ + struct gpmi_nand_data *this; + const struct of_device_id *of_id; + int ret; + + this = devm_kzalloc(&pdev->dev, sizeof(*this), GFP_KERNEL); + if (!this) + return -ENOMEM; + + of_id = of_match_device(gpmi_nand_id_table, &pdev->dev); + if (of_id) { + this->devdata = of_id->data; + } else { + dev_err(&pdev->dev, "Failed to find the right device id.\n"); + return -ENODEV; + } + + platform_set_drvdata(pdev, this); + this->pdev = pdev; + this->dev = &pdev->dev; + + ret = acquire_resources(this); + if (ret) + goto exit_acquire_resources; + + ret = init_hardware(this); + if (ret) + goto exit_nfc_init; + + ret = gpmi_nand_init(this); + if (ret) + goto exit_nfc_init; + + dev_info(this->dev, "driver registered.\n"); + + return 0; + +exit_nfc_init: + release_resources(this); +exit_acquire_resources: + dev_err(this->dev, "driver registration failed: %d\n", ret); + + return ret; +} + +static int gpmi_nand_remove(struct platform_device *pdev) +{ + struct gpmi_nand_data *this = platform_get_drvdata(pdev); + + gpmi_nand_exit(this); + release_resources(this); + return 0; +} + +static struct platform_driver gpmi_nand_driver = { + .driver = { + .name = "gpmi-nand", + .of_match_table = gpmi_nand_id_table, + }, + .probe = gpmi_nand_probe, + .remove = gpmi_nand_remove, +}; +module_platform_driver(gpmi_nand_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX GPMI NAND Flash Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.h b/drivers/mtd/nand/gpmi-nand/gpmi-nand.h new file mode 100644 index 00000000000..32c6ba49f98 --- /dev/null +++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.h @@ -0,0 +1,305 @@ +/* + * Freescale GPMI NAND Flash Driver + * + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. + * Copyright (C) 2008 Embedded Alley Solutions, Inc. + * + * 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. + */ +#ifndef __DRIVERS_MTD_NAND_GPMI_NAND_H +#define __DRIVERS_MTD_NAND_GPMI_NAND_H + +#include <linux/mtd/nand.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> + +#define GPMI_CLK_MAX 5 /* MX6Q needs five clocks */ +struct resources { + void __iomem *gpmi_regs; + void __iomem *bch_regs; + unsigned int dma_low_channel; + unsigned int dma_high_channel; + struct clk *clock[GPMI_CLK_MAX]; +}; + +/** + * struct bch_geometry - BCH geometry description. + * @gf_len: The length of Galois Field. (e.g., 13 or 14) + * @ecc_strength: A number that describes the strength of the ECC + * algorithm. + * @page_size: The size, in bytes, of a physical page, including + * both data and OOB. + * @metadata_size: The size, in bytes, of the metadata. + * @ecc_chunk_size: The size, in bytes, of a single ECC chunk. Note + * the first chunk in the page includes both data and + * metadata, so it's a bit larger than this value. + * @ecc_chunk_count: The number of ECC chunks in the page, + * @payload_size: The size, in bytes, of the payload buffer. + * @auxiliary_size: The size, in bytes, of the auxiliary buffer. + * @auxiliary_status_offset: The offset into the auxiliary buffer at which + * the ECC status appears. + * @block_mark_byte_offset: The byte offset in the ECC-based page view at + * which the underlying physical block mark appears. + * @block_mark_bit_offset: The bit offset into the ECC-based page view at + * which the underlying physical block mark appears. + */ +struct bch_geometry { + unsigned int gf_len; + unsigned int ecc_strength; + unsigned int page_size; + unsigned int metadata_size; + unsigned int ecc_chunk_size; + unsigned int ecc_chunk_count; + unsigned int payload_size; + unsigned int auxiliary_size; + unsigned int auxiliary_status_offset; + unsigned int block_mark_byte_offset; + unsigned int block_mark_bit_offset; +}; + +/** + * struct boot_rom_geometry - Boot ROM geometry description. + * @stride_size_in_pages: The size of a boot block stride, in pages. + * @search_area_stride_exponent: The logarithm to base 2 of the size of a + * search area in boot block strides. + */ +struct boot_rom_geometry { + unsigned int stride_size_in_pages; + unsigned int search_area_stride_exponent; +}; + +/* DMA operations types */ +enum dma_ops_type { + DMA_FOR_COMMAND = 1, + DMA_FOR_READ_DATA, + DMA_FOR_WRITE_DATA, + DMA_FOR_READ_ECC_PAGE, + DMA_FOR_WRITE_ECC_PAGE +}; + +/** + * struct nand_timing - Fundamental timing attributes for NAND. + * @data_setup_in_ns: The data setup time, in nanoseconds. Usually the + * maximum of tDS and tWP. A negative value + * indicates this characteristic isn't known. + * @data_hold_in_ns: The data hold time, in nanoseconds. Usually the + * maximum of tDH, tWH and tREH. A negative value + * indicates this characteristic isn't known. + * @address_setup_in_ns: The address setup time, in nanoseconds. Usually + * the maximum of tCLS, tCS and tALS. A negative + * value indicates this characteristic isn't known. + * @gpmi_sample_delay_in_ns: A GPMI-specific timing parameter. A negative value + * indicates this characteristic isn't known. + * @tREA_in_ns: tREA, in nanoseconds, from the data sheet. A + * negative value indicates this characteristic isn't + * known. + * @tRLOH_in_ns: tRLOH, in nanoseconds, from the data sheet. A + * negative value indicates this characteristic isn't + * known. + * @tRHOH_in_ns: tRHOH, in nanoseconds, from the data sheet. A + * negative value indicates this characteristic isn't + * known. + */ +struct nand_timing { + int8_t data_setup_in_ns; + int8_t data_hold_in_ns; + int8_t address_setup_in_ns; + int8_t gpmi_sample_delay_in_ns; + int8_t tREA_in_ns; + int8_t tRLOH_in_ns; + int8_t tRHOH_in_ns; +}; + +enum gpmi_type { + IS_MX23, + IS_MX28, + IS_MX6Q, + IS_MX6SX +}; + +struct gpmi_devdata { + enum gpmi_type type; + int bch_max_ecc_strength; + int max_chain_delay; /* See the async EDO mode */ +}; + +struct gpmi_nand_data { + /* flags */ +#define GPMI_ASYNC_EDO_ENABLED (1 << 0) +#define GPMI_TIMING_INIT_OK (1 << 1) + int flags; + const struct gpmi_devdata *devdata; + + /* System Interface */ + struct device *dev; + struct platform_device *pdev; + + /* Resources */ + struct resources resources; + + /* Flash Hardware */ + struct nand_timing timing; + int timing_mode; + + /* BCH */ + struct bch_geometry bch_geometry; + struct completion bch_done; + + /* NAND Boot issue */ + bool swap_block_mark; + struct boot_rom_geometry rom_geometry; + + /* MTD / NAND */ + struct nand_chip nand; + struct mtd_info mtd; + + /* General-use Variables */ + int current_chip; + unsigned int command_length; + + /* passed from upper layer */ + uint8_t *upper_buf; + int upper_len; + + /* for DMA operations */ + bool direct_dma_map_ok; + + struct scatterlist cmd_sgl; + char *cmd_buffer; + + struct scatterlist data_sgl; + char *data_buffer_dma; + + void *page_buffer_virt; + dma_addr_t page_buffer_phys; + unsigned int page_buffer_size; + + void *payload_virt; + dma_addr_t payload_phys; + + void *auxiliary_virt; + dma_addr_t auxiliary_phys; + + /* DMA channels */ +#define DMA_CHANS 8 + struct dma_chan *dma_chans[DMA_CHANS]; + enum dma_ops_type last_dma_type; + enum dma_ops_type dma_type; + struct completion dma_done; + + /* private */ + void *private; +}; + +/** + * struct gpmi_nfc_hardware_timing - GPMI hardware timing parameters. + * @data_setup_in_cycles: The data setup time, in cycles. + * @data_hold_in_cycles: The data hold time, in cycles. + * @address_setup_in_cycles: The address setup time, in cycles. + * @device_busy_timeout: The timeout waiting for NAND Ready/Busy, + * this value is the number of cycles multiplied + * by 4096. + * @use_half_periods: Indicates the clock is running slowly, so the + * NFC DLL should use half-periods. + * @sample_delay_factor: The sample delay factor. + * @wrn_dly_sel: The delay on the GPMI write strobe. + */ +struct gpmi_nfc_hardware_timing { + /* for HW_GPMI_TIMING0 */ + uint8_t data_setup_in_cycles; + uint8_t data_hold_in_cycles; + uint8_t address_setup_in_cycles; + + /* for HW_GPMI_TIMING1 */ + uint16_t device_busy_timeout; +#define GPMI_DEFAULT_BUSY_TIMEOUT 0x500 /* default busy timeout value.*/ + + /* for HW_GPMI_CTRL1 */ + bool use_half_periods; + uint8_t sample_delay_factor; + uint8_t wrn_dly_sel; +}; + +/** + * struct timing_threshod - Timing threshold + * @max_data_setup_cycles: The maximum number of data setup cycles that + * can be expressed in the hardware. + * @internal_data_setup_in_ns: The time, in ns, that the NFC hardware requires + * for data read internal setup. In the Reference + * Manual, see the chapter "High-Speed NAND + * Timing" for more details. + * @max_sample_delay_factor: The maximum sample delay factor that can be + * expressed in the hardware. + * @max_dll_clock_period_in_ns: The maximum period of the GPMI clock that the + * sample delay DLL hardware can possibly work + * with (the DLL is unusable with longer periods). + * If the full-cycle period is greater than HALF + * this value, the DLL must be configured to use + * half-periods. + * @max_dll_delay_in_ns: The maximum amount of delay, in ns, that the + * DLL can implement. + * @clock_frequency_in_hz: The clock frequency, in Hz, during the current + * I/O transaction. If no I/O transaction is in + * progress, this is the clock frequency during + * the most recent I/O transaction. + */ +struct timing_threshod { + const unsigned int max_chip_count; + const unsigned int max_data_setup_cycles; + const unsigned int internal_data_setup_in_ns; + const unsigned int max_sample_delay_factor; + const unsigned int max_dll_clock_period_in_ns; + const unsigned int max_dll_delay_in_ns; + unsigned long clock_frequency_in_hz; + +}; + +/* Common Services */ +extern int common_nfc_set_geometry(struct gpmi_nand_data *); +extern struct dma_chan *get_dma_chan(struct gpmi_nand_data *); +extern void prepare_data_dma(struct gpmi_nand_data *, + enum dma_data_direction dr); +extern int start_dma_without_bch_irq(struct gpmi_nand_data *, + struct dma_async_tx_descriptor *); +extern int start_dma_with_bch_irq(struct gpmi_nand_data *, + struct dma_async_tx_descriptor *); + +/* GPMI-NAND helper function library */ +extern int gpmi_init(struct gpmi_nand_data *); +extern int gpmi_extra_init(struct gpmi_nand_data *); +extern void gpmi_clear_bch(struct gpmi_nand_data *); +extern void gpmi_dump_info(struct gpmi_nand_data *); +extern int bch_set_geometry(struct gpmi_nand_data *); +extern int gpmi_is_ready(struct gpmi_nand_data *, unsigned chip); +extern int gpmi_send_command(struct gpmi_nand_data *); +extern void gpmi_begin(struct gpmi_nand_data *); +extern void gpmi_end(struct gpmi_nand_data *); +extern int gpmi_read_data(struct gpmi_nand_data *); +extern int gpmi_send_data(struct gpmi_nand_data *); +extern int gpmi_send_page(struct gpmi_nand_data *, + dma_addr_t payload, dma_addr_t auxiliary); +extern int gpmi_read_page(struct gpmi_nand_data *, + dma_addr_t payload, dma_addr_t auxiliary); + +/* BCH : Status Block Completion Codes */ +#define STATUS_GOOD 0x00 +#define STATUS_ERASED 0xff +#define STATUS_UNCORRECTABLE 0xfe + +/* Use the devdata to distinguish different Archs. */ +#define GPMI_IS_MX23(x) ((x)->devdata->type == IS_MX23) +#define GPMI_IS_MX28(x) ((x)->devdata->type == IS_MX28) +#define GPMI_IS_MX6Q(x) ((x)->devdata->type == IS_MX6Q) +#define GPMI_IS_MX6SX(x) ((x)->devdata->type == IS_MX6SX) + +#define GPMI_IS_MX6(x) (GPMI_IS_MX6Q(x) || GPMI_IS_MX6SX(x)) +#endif diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-regs.h b/drivers/mtd/nand/gpmi-nand/gpmi-regs.h new file mode 100644 index 00000000000..82114cdc833 --- /dev/null +++ b/drivers/mtd/nand/gpmi-nand/gpmi-regs.h @@ -0,0 +1,187 @@ +/* + * Freescale GPMI NAND Flash Driver + * + * Copyright 2008-2011 Freescale Semiconductor, Inc. + * Copyright 2008 Embedded Alley Solutions, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#ifndef __GPMI_NAND_GPMI_REGS_H +#define __GPMI_NAND_GPMI_REGS_H + +#define HW_GPMI_CTRL0 0x00000000 +#define HW_GPMI_CTRL0_SET 0x00000004 +#define HW_GPMI_CTRL0_CLR 0x00000008 +#define HW_GPMI_CTRL0_TOG 0x0000000c + +#define BP_GPMI_CTRL0_COMMAND_MODE 24 +#define BM_GPMI_CTRL0_COMMAND_MODE (3 << BP_GPMI_CTRL0_COMMAND_MODE) +#define BF_GPMI_CTRL0_COMMAND_MODE(v) \ + (((v) << BP_GPMI_CTRL0_COMMAND_MODE) & BM_GPMI_CTRL0_COMMAND_MODE) +#define BV_GPMI_CTRL0_COMMAND_MODE__WRITE 0x0 +#define BV_GPMI_CTRL0_COMMAND_MODE__READ 0x1 +#define BV_GPMI_CTRL0_COMMAND_MODE__READ_AND_COMPARE 0x2 +#define BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY 0x3 + +#define BM_GPMI_CTRL0_WORD_LENGTH (1 << 23) +#define BV_GPMI_CTRL0_WORD_LENGTH__16_BIT 0x0 +#define BV_GPMI_CTRL0_WORD_LENGTH__8_BIT 0x1 + +/* + * Difference in LOCK_CS between imx23 and imx28 : + * This bit may impact the _POWER_ consumption. So some chips + * do not set it. + */ +#define MX23_BP_GPMI_CTRL0_LOCK_CS 22 +#define MX28_BP_GPMI_CTRL0_LOCK_CS 27 +#define LOCK_CS_ENABLE 0x1 +#define BF_GPMI_CTRL0_LOCK_CS(v, x) 0x0 + +/* Difference in CS between imx23 and imx28 */ +#define BP_GPMI_CTRL0_CS 20 +#define MX23_BM_GPMI_CTRL0_CS (3 << BP_GPMI_CTRL0_CS) +#define MX28_BM_GPMI_CTRL0_CS (7 << BP_GPMI_CTRL0_CS) +#define BF_GPMI_CTRL0_CS(v, x) (((v) << BP_GPMI_CTRL0_CS) & \ + (GPMI_IS_MX23((x)) \ + ? MX23_BM_GPMI_CTRL0_CS \ + : MX28_BM_GPMI_CTRL0_CS)) + +#define BP_GPMI_CTRL0_ADDRESS 17 +#define BM_GPMI_CTRL0_ADDRESS (3 << BP_GPMI_CTRL0_ADDRESS) +#define BF_GPMI_CTRL0_ADDRESS(v) \ + (((v) << BP_GPMI_CTRL0_ADDRESS) & BM_GPMI_CTRL0_ADDRESS) +#define BV_GPMI_CTRL0_ADDRESS__NAND_DATA 0x0 +#define BV_GPMI_CTRL0_ADDRESS__NAND_CLE 0x1 +#define BV_GPMI_CTRL0_ADDRESS__NAND_ALE 0x2 + +#define BM_GPMI_CTRL0_ADDRESS_INCREMENT (1 << 16) +#define BV_GPMI_CTRL0_ADDRESS_INCREMENT__DISABLED 0x0 +#define BV_GPMI_CTRL0_ADDRESS_INCREMENT__ENABLED 0x1 + +#define BP_GPMI_CTRL0_XFER_COUNT 0 +#define BM_GPMI_CTRL0_XFER_COUNT (0xffff << BP_GPMI_CTRL0_XFER_COUNT) +#define BF_GPMI_CTRL0_XFER_COUNT(v) \ + (((v) << BP_GPMI_CTRL0_XFER_COUNT) & BM_GPMI_CTRL0_XFER_COUNT) + +#define HW_GPMI_COMPARE 0x00000010 + +#define HW_GPMI_ECCCTRL 0x00000020 +#define HW_GPMI_ECCCTRL_SET 0x00000024 +#define HW_GPMI_ECCCTRL_CLR 0x00000028 +#define HW_GPMI_ECCCTRL_TOG 0x0000002c + +#define BP_GPMI_ECCCTRL_ECC_CMD 13 +#define BM_GPMI_ECCCTRL_ECC_CMD (3 << BP_GPMI_ECCCTRL_ECC_CMD) +#define BF_GPMI_ECCCTRL_ECC_CMD(v) \ + (((v) << BP_GPMI_ECCCTRL_ECC_CMD) & BM_GPMI_ECCCTRL_ECC_CMD) +#define BV_GPMI_ECCCTRL_ECC_CMD__BCH_DECODE 0x0 +#define BV_GPMI_ECCCTRL_ECC_CMD__BCH_ENCODE 0x1 + +#define BM_GPMI_ECCCTRL_ENABLE_ECC (1 << 12) +#define BV_GPMI_ECCCTRL_ENABLE_ECC__ENABLE 0x1 +#define BV_GPMI_ECCCTRL_ENABLE_ECC__DISABLE 0x0 + +#define BP_GPMI_ECCCTRL_BUFFER_MASK 0 +#define BM_GPMI_ECCCTRL_BUFFER_MASK (0x1ff << BP_GPMI_ECCCTRL_BUFFER_MASK) +#define BF_GPMI_ECCCTRL_BUFFER_MASK(v) \ + (((v) << BP_GPMI_ECCCTRL_BUFFER_MASK) & BM_GPMI_ECCCTRL_BUFFER_MASK) +#define BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY 0x100 +#define BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_PAGE 0x1FF + +#define HW_GPMI_ECCCOUNT 0x00000030 +#define HW_GPMI_PAYLOAD 0x00000040 +#define HW_GPMI_AUXILIARY 0x00000050 +#define HW_GPMI_CTRL1 0x00000060 +#define HW_GPMI_CTRL1_SET 0x00000064 +#define HW_GPMI_CTRL1_CLR 0x00000068 +#define HW_GPMI_CTRL1_TOG 0x0000006c + +#define BP_GPMI_CTRL1_DECOUPLE_CS 24 +#define BM_GPMI_CTRL1_DECOUPLE_CS (1 << BP_GPMI_CTRL1_DECOUPLE_CS) + +#define BP_GPMI_CTRL1_WRN_DLY_SEL 22 +#define BM_GPMI_CTRL1_WRN_DLY_SEL (0x3 << BP_GPMI_CTRL1_WRN_DLY_SEL) +#define BF_GPMI_CTRL1_WRN_DLY_SEL(v) \ + (((v) << BP_GPMI_CTRL1_WRN_DLY_SEL) & BM_GPMI_CTRL1_WRN_DLY_SEL) +#define BV_GPMI_CTRL1_WRN_DLY_SEL_4_TO_8NS 0x0 +#define BV_GPMI_CTRL1_WRN_DLY_SEL_6_TO_10NS 0x1 +#define BV_GPMI_CTRL1_WRN_DLY_SEL_7_TO_12NS 0x2 +#define BV_GPMI_CTRL1_WRN_DLY_SEL_NO_DELAY 0x3 + +#define BM_GPMI_CTRL1_BCH_MODE (1 << 18) + +#define BP_GPMI_CTRL1_DLL_ENABLE 17 +#define BM_GPMI_CTRL1_DLL_ENABLE (1 << BP_GPMI_CTRL1_DLL_ENABLE) + +#define BP_GPMI_CTRL1_HALF_PERIOD 16 +#define BM_GPMI_CTRL1_HALF_PERIOD (1 << BP_GPMI_CTRL1_HALF_PERIOD) + +#define BP_GPMI_CTRL1_RDN_DELAY 12 +#define BM_GPMI_CTRL1_RDN_DELAY (0xf << BP_GPMI_CTRL1_RDN_DELAY) +#define BF_GPMI_CTRL1_RDN_DELAY(v) \ + (((v) << BP_GPMI_CTRL1_RDN_DELAY) & BM_GPMI_CTRL1_RDN_DELAY) + +#define BM_GPMI_CTRL1_DEV_RESET (1 << 3) +#define BV_GPMI_CTRL1_DEV_RESET__ENABLED 0x0 +#define BV_GPMI_CTRL1_DEV_RESET__DISABLED 0x1 + +#define BM_GPMI_CTRL1_ATA_IRQRDY_POLARITY (1 << 2) +#define BV_GPMI_CTRL1_ATA_IRQRDY_POLARITY__ACTIVELOW 0x0 +#define BV_GPMI_CTRL1_ATA_IRQRDY_POLARITY__ACTIVEHIGH 0x1 + +#define BM_GPMI_CTRL1_CAMERA_MODE (1 << 1) +#define BV_GPMI_CTRL1_GPMI_MODE__NAND 0x0 +#define BV_GPMI_CTRL1_GPMI_MODE__ATA 0x1 + +#define BM_GPMI_CTRL1_GPMI_MODE (1 << 0) + +#define HW_GPMI_TIMING0 0x00000070 + +#define BP_GPMI_TIMING0_ADDRESS_SETUP 16 +#define BM_GPMI_TIMING0_ADDRESS_SETUP (0xff << BP_GPMI_TIMING0_ADDRESS_SETUP) +#define BF_GPMI_TIMING0_ADDRESS_SETUP(v) \ + (((v) << BP_GPMI_TIMING0_ADDRESS_SETUP) & BM_GPMI_TIMING0_ADDRESS_SETUP) + +#define BP_GPMI_TIMING0_DATA_HOLD 8 +#define BM_GPMI_TIMING0_DATA_HOLD (0xff << BP_GPMI_TIMING0_DATA_HOLD) +#define BF_GPMI_TIMING0_DATA_HOLD(v) \ + (((v) << BP_GPMI_TIMING0_DATA_HOLD) & BM_GPMI_TIMING0_DATA_HOLD) + +#define BP_GPMI_TIMING0_DATA_SETUP 0 +#define BM_GPMI_TIMING0_DATA_SETUP (0xff << BP_GPMI_TIMING0_DATA_SETUP) +#define BF_GPMI_TIMING0_DATA_SETUP(v) \ + (((v) << BP_GPMI_TIMING0_DATA_SETUP) & BM_GPMI_TIMING0_DATA_SETUP) + +#define HW_GPMI_TIMING1 0x00000080 +#define BP_GPMI_TIMING1_BUSY_TIMEOUT 16 +#define BM_GPMI_TIMING1_BUSY_TIMEOUT (0xffff << BP_GPMI_TIMING1_BUSY_TIMEOUT) +#define BF_GPMI_TIMING1_BUSY_TIMEOUT(v) \ + (((v) << BP_GPMI_TIMING1_BUSY_TIMEOUT) & BM_GPMI_TIMING1_BUSY_TIMEOUT) + +#define HW_GPMI_TIMING2 0x00000090 +#define HW_GPMI_DATA 0x000000a0 + +/* MX28 uses this to detect READY. */ +#define HW_GPMI_STAT 0x000000b0 +#define MX28_BP_GPMI_STAT_READY_BUSY 24 +#define MX28_BM_GPMI_STAT_READY_BUSY (0xff << MX28_BP_GPMI_STAT_READY_BUSY) +#define MX28_BF_GPMI_STAT_READY_BUSY(v) \ + (((v) << MX28_BP_GPMI_STAT_READY_BUSY) & MX28_BM_GPMI_STAT_READY_BUSY) + +/* MX23 uses this to detect READY. */ +#define HW_GPMI_DEBUG 0x000000c0 +#define MX23_BP_GPMI_DEBUG_READY0 28 +#define MX23_BM_GPMI_DEBUG_READY0 (1 << MX23_BP_GPMI_DEBUG_READY0) +#endif diff --git a/drivers/mtd/nand/h1910.c b/drivers/mtd/nand/h1910.c deleted file mode 100644 index 2d585d2d090..00000000000 --- a/drivers/mtd/nand/h1910.c +++ /dev/null @@ -1,188 +0,0 @@ -/* - * drivers/mtd/nand/h1910.c - * - * Copyright (C) 2003 Joshua Wise (joshua@joshuawise.com) - * - * Derived from drivers/mtd/nand/edb7312.c - * Copyright (C) 2002 Marius Gröger (mag@sysgo.de) - * Copyright (c) 2001 Thomas Gleixner (gleixner@autronix.de) - * - * $Id: h1910.c,v 1.6 2005/11/07 11:14:30 gleixner Exp $ - * - * 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. - * - * Overview: - * This is a device driver for the NAND flash device found on the - * iPAQ h1910 board which utilizes the Samsung K9F2808 part. This is - * a 128Mibit (16MiB x 8 bits) NAND flash device. - */ - -#include <linux/slab.h> -#include <linux/init.h> -#include <linux/module.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/nand.h> -#include <linux/mtd/partitions.h> -#include <asm/io.h> -#include <asm/arch/hardware.h> /* for CLPS7111_VIRT_BASE */ -#include <asm/sizes.h> -#include <asm/arch/h1900-gpio.h> -#include <asm/arch/ipaq.h> - -/* - * MTD structure for EDB7312 board - */ -static struct mtd_info *h1910_nand_mtd = NULL; - -/* - * Module stuff - */ - -#ifdef CONFIG_MTD_PARTITIONS -/* - * Define static partitions for flash device - */ -static struct mtd_partition partition_info[] = { - {name:"h1910 NAND Flash", - offset:0, - size:16 * 1024 * 1024} -}; - -#define NUM_PARTITIONS 1 - -#endif - -/* - * hardware specific access to control-lines - * - * NAND_NCE: bit 0 - don't care - * NAND_CLE: bit 1 - address bit 2 - * NAND_ALE: bit 2 - address bit 3 - */ -static void h1910_hwcontrol(struct mtd_info *mtd, int cmd, - unsigned int ctrl) -{ - struct nand_chip *chip = mtd->priv; - - if (cmd != NAND_CMD_NONE) - writeb(cmd, chip->IO_ADDR_W | ((ctrl & 0x6) << 1)); -} - -/* - * read device ready pin - */ -#if 0 -static int h1910_device_ready(struct mtd_info *mtd) -{ - return (GPLR(55) & GPIO_bit(55)); -} -#endif - -/* - * Main initialization routine - */ -static int __init h1910_init(void) -{ - struct nand_chip *this; - const char *part_type = 0; - int mtd_parts_nb = 0; - struct mtd_partition *mtd_parts = 0; - void __iomem *nandaddr; - - if (!machine_is_h1900()) - return -ENODEV; - - nandaddr = ioremap(0x08000000, 0x1000); - if (!nandaddr) { - printk("Failed to ioremap nand flash.\n"); - return -ENOMEM; - } - - /* Allocate memory for MTD device structure and private data */ - h1910_nand_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL); - if (!h1910_nand_mtd) { - printk("Unable to allocate h1910 NAND MTD device structure.\n"); - iounmap((void *)nandaddr); - return -ENOMEM; - } - - /* Get pointer to private data */ - this = (struct nand_chip *)(&h1910_nand_mtd[1]); - - /* Initialize structures */ - memset(h1910_nand_mtd, 0, sizeof(struct mtd_info)); - memset(this, 0, sizeof(struct nand_chip)); - - /* Link the private data with the MTD structure */ - h1910_nand_mtd->priv = this; - h1910_nand_mtd->owner = THIS_MODULE; - - /* - * Enable VPEN - */ - GPSR(37) = GPIO_bit(37); - - /* insert callbacks */ - this->IO_ADDR_R = nandaddr; - this->IO_ADDR_W = nandaddr; - this->cmd_ctrl = h1910_hwcontrol; - this->dev_ready = NULL; /* unknown whether that was correct or not so we will just do it like this */ - /* 15 us command delay time */ - this->chip_delay = 50; - this->ecc.mode = NAND_ECC_SOFT; - this->options = NAND_NO_AUTOINCR; - - /* Scan to find existence of the device */ - if (nand_scan(h1910_nand_mtd, 1)) { - printk(KERN_NOTICE "No NAND device - returning -ENXIO\n"); - kfree(h1910_nand_mtd); - iounmap((void *)nandaddr); - return -ENXIO; - } -#ifdef CONFIG_MTD_CMDLINE_PARTS - mtd_parts_nb = parse_cmdline_partitions(h1910_nand_mtd, &mtd_parts, "h1910-nand"); - if (mtd_parts_nb > 0) - part_type = "command line"; - else - mtd_parts_nb = 0; -#endif - if (mtd_parts_nb == 0) { - mtd_parts = partition_info; - mtd_parts_nb = NUM_PARTITIONS; - part_type = "static"; - } - - /* Register the partitions */ - printk(KERN_NOTICE "Using %s partition definition\n", part_type); - add_mtd_partitions(h1910_nand_mtd, mtd_parts, mtd_parts_nb); - - /* Return happy */ - return 0; -} - -module_init(h1910_init); - -/* - * Clean up routine - */ -static void __exit h1910_cleanup(void) -{ - struct nand_chip *this = (struct nand_chip *)&h1910_nand_mtd[1]; - - /* Release resources, unregister device */ - nand_release(h1910_nand_mtd); - - /* Release io resource */ - iounmap((void *)this->IO_ADDR_W); - - /* Free the MTD device structure */ - kfree(h1910_nand_mtd); -} - -module_exit(h1910_cleanup); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Joshua Wise <joshua at joshuawise dot com>"); -MODULE_DESCRIPTION("NAND flash driver for iPAQ h1910"); diff --git a/drivers/mtd/nand/jz4740_nand.c b/drivers/mtd/nand/jz4740_nand.c new file mode 100644 index 00000000000..a2c804de156 --- /dev/null +++ b/drivers/mtd/nand/jz4740_nand.c @@ -0,0 +1,589 @@ +/* + * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de> + * JZ4740 SoC NAND controller driver + * + * 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. + * + * 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/ioport.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> + +#include <linux/gpio.h> + +#include <asm/mach-jz4740/jz4740_nand.h> + +#define JZ_REG_NAND_CTRL 0x50 +#define JZ_REG_NAND_ECC_CTRL 0x100 +#define JZ_REG_NAND_DATA 0x104 +#define JZ_REG_NAND_PAR0 0x108 +#define JZ_REG_NAND_PAR1 0x10C +#define JZ_REG_NAND_PAR2 0x110 +#define JZ_REG_NAND_IRQ_STAT 0x114 +#define JZ_REG_NAND_IRQ_CTRL 0x118 +#define JZ_REG_NAND_ERR(x) (0x11C + ((x) << 2)) + +#define JZ_NAND_ECC_CTRL_PAR_READY BIT(4) +#define JZ_NAND_ECC_CTRL_ENCODING BIT(3) +#define JZ_NAND_ECC_CTRL_RS BIT(2) +#define JZ_NAND_ECC_CTRL_RESET BIT(1) +#define JZ_NAND_ECC_CTRL_ENABLE BIT(0) + +#define JZ_NAND_STATUS_ERR_COUNT (BIT(31) | BIT(30) | BIT(29)) +#define JZ_NAND_STATUS_PAD_FINISH BIT(4) +#define JZ_NAND_STATUS_DEC_FINISH BIT(3) +#define JZ_NAND_STATUS_ENC_FINISH BIT(2) +#define JZ_NAND_STATUS_UNCOR_ERROR BIT(1) +#define JZ_NAND_STATUS_ERROR BIT(0) + +#define JZ_NAND_CTRL_ENABLE_CHIP(x) BIT((x) << 1) +#define JZ_NAND_CTRL_ASSERT_CHIP(x) BIT(((x) << 1) + 1) +#define JZ_NAND_CTRL_ASSERT_CHIP_MASK 0xaa + +#define JZ_NAND_MEM_CMD_OFFSET 0x08000 +#define JZ_NAND_MEM_ADDR_OFFSET 0x10000 + +struct jz_nand { + struct mtd_info mtd; + struct nand_chip chip; + void __iomem *base; + struct resource *mem; + + unsigned char banks[JZ_NAND_NUM_BANKS]; + void __iomem *bank_base[JZ_NAND_NUM_BANKS]; + struct resource *bank_mem[JZ_NAND_NUM_BANKS]; + + int selected_bank; + + struct jz_nand_platform_data *pdata; + bool is_reading; +}; + +static inline struct jz_nand *mtd_to_jz_nand(struct mtd_info *mtd) +{ + return container_of(mtd, struct jz_nand, mtd); +} + +static void jz_nand_select_chip(struct mtd_info *mtd, int chipnr) +{ + struct jz_nand *nand = mtd_to_jz_nand(mtd); + struct nand_chip *chip = mtd->priv; + uint32_t ctrl; + int banknr; + + ctrl = readl(nand->base + JZ_REG_NAND_CTRL); + ctrl &= ~JZ_NAND_CTRL_ASSERT_CHIP_MASK; + + if (chipnr == -1) { + banknr = -1; + } else { + banknr = nand->banks[chipnr] - 1; + chip->IO_ADDR_R = nand->bank_base[banknr]; + chip->IO_ADDR_W = nand->bank_base[banknr]; + } + writel(ctrl, nand->base + JZ_REG_NAND_CTRL); + + nand->selected_bank = banknr; +} + +static void jz_nand_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl) +{ + struct jz_nand *nand = mtd_to_jz_nand(mtd); + struct nand_chip *chip = mtd->priv; + uint32_t reg; + void __iomem *bank_base = nand->bank_base[nand->selected_bank]; + + BUG_ON(nand->selected_bank < 0); + + if (ctrl & NAND_CTRL_CHANGE) { + BUG_ON((ctrl & NAND_ALE) && (ctrl & NAND_CLE)); + if (ctrl & NAND_ALE) + bank_base += JZ_NAND_MEM_ADDR_OFFSET; + else if (ctrl & NAND_CLE) + bank_base += JZ_NAND_MEM_CMD_OFFSET; + chip->IO_ADDR_W = bank_base; + + reg = readl(nand->base + JZ_REG_NAND_CTRL); + if (ctrl & NAND_NCE) + reg |= JZ_NAND_CTRL_ASSERT_CHIP(nand->selected_bank); + else + reg &= ~JZ_NAND_CTRL_ASSERT_CHIP(nand->selected_bank); + writel(reg, nand->base + JZ_REG_NAND_CTRL); + } + if (dat != NAND_CMD_NONE) + writeb(dat, chip->IO_ADDR_W); +} + +static int jz_nand_dev_ready(struct mtd_info *mtd) +{ + struct jz_nand *nand = mtd_to_jz_nand(mtd); + return gpio_get_value_cansleep(nand->pdata->busy_gpio); +} + +static void jz_nand_hwctl(struct mtd_info *mtd, int mode) +{ + struct jz_nand *nand = mtd_to_jz_nand(mtd); + uint32_t reg; + + writel(0, nand->base + JZ_REG_NAND_IRQ_STAT); + reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL); + + reg |= JZ_NAND_ECC_CTRL_RESET; + reg |= JZ_NAND_ECC_CTRL_ENABLE; + reg |= JZ_NAND_ECC_CTRL_RS; + + switch (mode) { + case NAND_ECC_READ: + reg &= ~JZ_NAND_ECC_CTRL_ENCODING; + nand->is_reading = true; + break; + case NAND_ECC_WRITE: + reg |= JZ_NAND_ECC_CTRL_ENCODING; + nand->is_reading = false; + break; + default: + break; + } + + writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL); +} + +static int jz_nand_calculate_ecc_rs(struct mtd_info *mtd, const uint8_t *dat, + uint8_t *ecc_code) +{ + struct jz_nand *nand = mtd_to_jz_nand(mtd); + uint32_t reg, status; + int i; + unsigned int timeout = 1000; + static uint8_t empty_block_ecc[] = {0xcd, 0x9d, 0x90, 0x58, 0xf4, + 0x8b, 0xff, 0xb7, 0x6f}; + + if (nand->is_reading) + return 0; + + do { + status = readl(nand->base + JZ_REG_NAND_IRQ_STAT); + } while (!(status & JZ_NAND_STATUS_ENC_FINISH) && --timeout); + + if (timeout == 0) + return -1; + + reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL); + reg &= ~JZ_NAND_ECC_CTRL_ENABLE; + writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL); + + for (i = 0; i < 9; ++i) + ecc_code[i] = readb(nand->base + JZ_REG_NAND_PAR0 + i); + + /* If the written data is completly 0xff, we also want to write 0xff as + * ecc, otherwise we will get in trouble when doing subpage writes. */ + if (memcmp(ecc_code, empty_block_ecc, 9) == 0) + memset(ecc_code, 0xff, 9); + + return 0; +} + +static void jz_nand_correct_data(uint8_t *dat, int index, int mask) +{ + int offset = index & 0x7; + uint16_t data; + + index += (index >> 3); + + data = dat[index]; + data |= dat[index+1] << 8; + + mask ^= (data >> offset) & 0x1ff; + data &= ~(0x1ff << offset); + data |= (mask << offset); + + dat[index] = data & 0xff; + dat[index+1] = (data >> 8) & 0xff; +} + +static int jz_nand_correct_ecc_rs(struct mtd_info *mtd, uint8_t *dat, + uint8_t *read_ecc, uint8_t *calc_ecc) +{ + struct jz_nand *nand = mtd_to_jz_nand(mtd); + int i, error_count, index; + uint32_t reg, status, error; + uint32_t t; + unsigned int timeout = 1000; + + t = read_ecc[0]; + + if (t == 0xff) { + for (i = 1; i < 9; ++i) + t &= read_ecc[i]; + + t &= dat[0]; + t &= dat[nand->chip.ecc.size / 2]; + t &= dat[nand->chip.ecc.size - 1]; + + if (t == 0xff) { + for (i = 1; i < nand->chip.ecc.size - 1; ++i) + t &= dat[i]; + if (t == 0xff) + return 0; + } + } + + for (i = 0; i < 9; ++i) + writeb(read_ecc[i], nand->base + JZ_REG_NAND_PAR0 + i); + + reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL); + reg |= JZ_NAND_ECC_CTRL_PAR_READY; + writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL); + + do { + status = readl(nand->base + JZ_REG_NAND_IRQ_STAT); + } while (!(status & JZ_NAND_STATUS_DEC_FINISH) && --timeout); + + if (timeout == 0) + return -1; + + reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL); + reg &= ~JZ_NAND_ECC_CTRL_ENABLE; + writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL); + + if (status & JZ_NAND_STATUS_ERROR) { + if (status & JZ_NAND_STATUS_UNCOR_ERROR) + return -1; + + error_count = (status & JZ_NAND_STATUS_ERR_COUNT) >> 29; + + for (i = 0; i < error_count; ++i) { + error = readl(nand->base + JZ_REG_NAND_ERR(i)); + index = ((error >> 16) & 0x1ff) - 1; + if (index >= 0 && index < 512) + jz_nand_correct_data(dat, index, error & 0x1ff); + } + + return error_count; + } + + return 0; +} + +static int jz_nand_ioremap_resource(struct platform_device *pdev, + const char *name, struct resource **res, void *__iomem *base) +{ + int ret; + + *res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); + if (!*res) { + dev_err(&pdev->dev, "Failed to get platform %s memory\n", name); + ret = -ENXIO; + goto err; + } + + *res = request_mem_region((*res)->start, resource_size(*res), + pdev->name); + if (!*res) { + dev_err(&pdev->dev, "Failed to request %s memory region\n", name); + ret = -EBUSY; + goto err; + } + + *base = ioremap((*res)->start, resource_size(*res)); + if (!*base) { + dev_err(&pdev->dev, "Failed to ioremap %s memory region\n", name); + ret = -EBUSY; + goto err_release_mem; + } + + return 0; + +err_release_mem: + release_mem_region((*res)->start, resource_size(*res)); +err: + *res = NULL; + *base = NULL; + return ret; +} + +static inline void jz_nand_iounmap_resource(struct resource *res, + void __iomem *base) +{ + iounmap(base); + release_mem_region(res->start, resource_size(res)); +} + +static int jz_nand_detect_bank(struct platform_device *pdev, + struct jz_nand *nand, unsigned char bank, + size_t chipnr, uint8_t *nand_maf_id, + uint8_t *nand_dev_id) +{ + int ret; + int gpio; + char gpio_name[9]; + char res_name[6]; + uint32_t ctrl; + struct mtd_info *mtd = &nand->mtd; + struct nand_chip *chip = &nand->chip; + + /* Request GPIO port. */ + gpio = JZ_GPIO_MEM_CS0 + bank - 1; + sprintf(gpio_name, "NAND CS%d", bank); + ret = gpio_request(gpio, gpio_name); + if (ret) { + dev_warn(&pdev->dev, + "Failed to request %s gpio %d: %d\n", + gpio_name, gpio, ret); + goto notfound_gpio; + } + + /* Request I/O resource. */ + sprintf(res_name, "bank%d", bank); + ret = jz_nand_ioremap_resource(pdev, res_name, + &nand->bank_mem[bank - 1], + &nand->bank_base[bank - 1]); + if (ret) + goto notfound_resource; + + /* Enable chip in bank. */ + jz_gpio_set_function(gpio, JZ_GPIO_FUNC_MEM_CS0); + ctrl = readl(nand->base + JZ_REG_NAND_CTRL); + ctrl |= JZ_NAND_CTRL_ENABLE_CHIP(bank - 1); + writel(ctrl, nand->base + JZ_REG_NAND_CTRL); + + if (chipnr == 0) { + /* Detect first chip. */ + ret = nand_scan_ident(mtd, 1, NULL); + if (ret) + goto notfound_id; + + /* Retrieve the IDs from the first chip. */ + chip->select_chip(mtd, 0); + chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); + *nand_maf_id = chip->read_byte(mtd); + *nand_dev_id = chip->read_byte(mtd); + } else { + /* Detect additional chip. */ + chip->select_chip(mtd, chipnr); + chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); + if (*nand_maf_id != chip->read_byte(mtd) + || *nand_dev_id != chip->read_byte(mtd)) { + ret = -ENODEV; + goto notfound_id; + } + + /* Update size of the MTD. */ + chip->numchips++; + mtd->size += chip->chipsize; + } + + dev_info(&pdev->dev, "Found chip %i on bank %i\n", chipnr, bank); + return 0; + +notfound_id: + dev_info(&pdev->dev, "No chip found on bank %i\n", bank); + ctrl &= ~(JZ_NAND_CTRL_ENABLE_CHIP(bank - 1)); + writel(ctrl, nand->base + JZ_REG_NAND_CTRL); + jz_gpio_set_function(gpio, JZ_GPIO_FUNC_NONE); + jz_nand_iounmap_resource(nand->bank_mem[bank - 1], + nand->bank_base[bank - 1]); +notfound_resource: + gpio_free(gpio); +notfound_gpio: + return ret; +} + +static int jz_nand_probe(struct platform_device *pdev) +{ + int ret; + struct jz_nand *nand; + struct nand_chip *chip; + struct mtd_info *mtd; + struct jz_nand_platform_data *pdata = dev_get_platdata(&pdev->dev); + size_t chipnr, bank_idx; + uint8_t nand_maf_id = 0, nand_dev_id = 0; + + nand = kzalloc(sizeof(*nand), GFP_KERNEL); + if (!nand) + return -ENOMEM; + + ret = jz_nand_ioremap_resource(pdev, "mmio", &nand->mem, &nand->base); + if (ret) + goto err_free; + + if (pdata && gpio_is_valid(pdata->busy_gpio)) { + ret = gpio_request(pdata->busy_gpio, "NAND busy pin"); + if (ret) { + dev_err(&pdev->dev, + "Failed to request busy gpio %d: %d\n", + pdata->busy_gpio, ret); + goto err_iounmap_mmio; + } + } + + mtd = &nand->mtd; + chip = &nand->chip; + mtd->priv = chip; + mtd->owner = THIS_MODULE; + mtd->name = "jz4740-nand"; + + chip->ecc.hwctl = jz_nand_hwctl; + chip->ecc.calculate = jz_nand_calculate_ecc_rs; + chip->ecc.correct = jz_nand_correct_ecc_rs; + chip->ecc.mode = NAND_ECC_HW_OOB_FIRST; + chip->ecc.size = 512; + chip->ecc.bytes = 9; + chip->ecc.strength = 4; + + if (pdata) + chip->ecc.layout = pdata->ecc_layout; + + chip->chip_delay = 50; + chip->cmd_ctrl = jz_nand_cmd_ctrl; + chip->select_chip = jz_nand_select_chip; + + if (pdata && gpio_is_valid(pdata->busy_gpio)) + chip->dev_ready = jz_nand_dev_ready; + + nand->pdata = pdata; + platform_set_drvdata(pdev, nand); + + /* We are going to autodetect NAND chips in the banks specified in the + * platform data. Although nand_scan_ident() can detect multiple chips, + * it requires those chips to be numbered consecuitively, which is not + * always the case for external memory banks. And a fixed chip-to-bank + * mapping is not practical either, since for example Dingoo units + * produced at different times have NAND chips in different banks. + */ + chipnr = 0; + for (bank_idx = 0; bank_idx < JZ_NAND_NUM_BANKS; bank_idx++) { + unsigned char bank; + + /* If there is no platform data, look for NAND in bank 1, + * which is the most likely bank since it is the only one + * that can be booted from. + */ + bank = pdata ? pdata->banks[bank_idx] : bank_idx ^ 1; + if (bank == 0) + break; + if (bank > JZ_NAND_NUM_BANKS) { + dev_warn(&pdev->dev, + "Skipping non-existing bank: %d\n", bank); + continue; + } + /* The detection routine will directly or indirectly call + * jz_nand_select_chip(), so nand->banks has to contain the + * bank we're checking. + */ + nand->banks[chipnr] = bank; + if (jz_nand_detect_bank(pdev, nand, bank, chipnr, + &nand_maf_id, &nand_dev_id) == 0) + chipnr++; + else + nand->banks[chipnr] = 0; + } + if (chipnr == 0) { + dev_err(&pdev->dev, "No NAND chips found\n"); + goto err_gpio_busy; + } + + if (pdata && pdata->ident_callback) { + pdata->ident_callback(pdev, chip, &pdata->partitions, + &pdata->num_partitions); + } + + ret = nand_scan_tail(mtd); + if (ret) { + dev_err(&pdev->dev, "Failed to scan NAND\n"); + goto err_unclaim_banks; + } + + ret = mtd_device_parse_register(mtd, NULL, NULL, + pdata ? pdata->partitions : NULL, + pdata ? pdata->num_partitions : 0); + + if (ret) { + dev_err(&pdev->dev, "Failed to add mtd device\n"); + goto err_nand_release; + } + + dev_info(&pdev->dev, "Successfully registered JZ4740 NAND driver\n"); + + return 0; + +err_nand_release: + nand_release(mtd); +err_unclaim_banks: + while (chipnr--) { + unsigned char bank = nand->banks[chipnr]; + gpio_free(JZ_GPIO_MEM_CS0 + bank - 1); + jz_nand_iounmap_resource(nand->bank_mem[bank - 1], + nand->bank_base[bank - 1]); + } + writel(0, nand->base + JZ_REG_NAND_CTRL); +err_gpio_busy: + if (pdata && gpio_is_valid(pdata->busy_gpio)) + gpio_free(pdata->busy_gpio); +err_iounmap_mmio: + jz_nand_iounmap_resource(nand->mem, nand->base); +err_free: + kfree(nand); + return ret; +} + +static int jz_nand_remove(struct platform_device *pdev) +{ + struct jz_nand *nand = platform_get_drvdata(pdev); + struct jz_nand_platform_data *pdata = dev_get_platdata(&pdev->dev); + size_t i; + + nand_release(&nand->mtd); + + /* Deassert and disable all chips */ + writel(0, nand->base + JZ_REG_NAND_CTRL); + + for (i = 0; i < JZ_NAND_NUM_BANKS; ++i) { + unsigned char bank = nand->banks[i]; + if (bank != 0) { + jz_nand_iounmap_resource(nand->bank_mem[bank - 1], + nand->bank_base[bank - 1]); + gpio_free(JZ_GPIO_MEM_CS0 + bank - 1); + } + } + if (pdata && gpio_is_valid(pdata->busy_gpio)) + gpio_free(pdata->busy_gpio); + + jz_nand_iounmap_resource(nand->mem, nand->base); + + kfree(nand); + + return 0; +} + +static struct platform_driver jz_nand_driver = { + .probe = jz_nand_probe, + .remove = jz_nand_remove, + .driver = { + .name = "jz4740-nand", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(jz_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("NAND controller driver for JZ4740 SoC"); +MODULE_ALIAS("platform:jz4740-nand"); diff --git a/drivers/mtd/nand/lpc32xx_mlc.c b/drivers/mtd/nand/lpc32xx_mlc.c new file mode 100644 index 00000000000..687478c9f09 --- /dev/null +++ b/drivers/mtd/nand/lpc32xx_mlc.c @@ -0,0 +1,896 @@ +/* + * Driver for NAND MLC Controller in LPC32xx + * + * Author: Roland Stigge <stigge@antcom.de> + * + * Copyright © 2011 WORK Microwave GmbH + * Copyright © 2011, 2012 Roland Stigge + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + * NAND Flash Controller Operation: + * - Read: Auto Decode + * - Write: Auto Encode + * - Tested Page Sizes: 2048, 4096 + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/completion.h> +#include <linux/interrupt.h> +#include <linux/of.h> +#include <linux/of_mtd.h> +#include <linux/of_gpio.h> +#include <linux/mtd/lpc32xx_mlc.h> +#include <linux/io.h> +#include <linux/mm.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/mtd/nand_ecc.h> + +#define DRV_NAME "lpc32xx_mlc" + +/********************************************************************** +* MLC NAND controller register offsets +**********************************************************************/ + +#define MLC_BUFF(x) (x + 0x00000) +#define MLC_DATA(x) (x + 0x08000) +#define MLC_CMD(x) (x + 0x10000) +#define MLC_ADDR(x) (x + 0x10004) +#define MLC_ECC_ENC_REG(x) (x + 0x10008) +#define MLC_ECC_DEC_REG(x) (x + 0x1000C) +#define MLC_ECC_AUTO_ENC_REG(x) (x + 0x10010) +#define MLC_ECC_AUTO_DEC_REG(x) (x + 0x10014) +#define MLC_RPR(x) (x + 0x10018) +#define MLC_WPR(x) (x + 0x1001C) +#define MLC_RUBP(x) (x + 0x10020) +#define MLC_ROBP(x) (x + 0x10024) +#define MLC_SW_WP_ADD_LOW(x) (x + 0x10028) +#define MLC_SW_WP_ADD_HIG(x) (x + 0x1002C) +#define MLC_ICR(x) (x + 0x10030) +#define MLC_TIME_REG(x) (x + 0x10034) +#define MLC_IRQ_MR(x) (x + 0x10038) +#define MLC_IRQ_SR(x) (x + 0x1003C) +#define MLC_LOCK_PR(x) (x + 0x10044) +#define MLC_ISR(x) (x + 0x10048) +#define MLC_CEH(x) (x + 0x1004C) + +/********************************************************************** +* MLC_CMD bit definitions +**********************************************************************/ +#define MLCCMD_RESET 0xFF + +/********************************************************************** +* MLC_ICR bit definitions +**********************************************************************/ +#define MLCICR_WPROT (1 << 3) +#define MLCICR_LARGEBLOCK (1 << 2) +#define MLCICR_LONGADDR (1 << 1) +#define MLCICR_16BIT (1 << 0) /* unsupported by LPC32x0! */ + +/********************************************************************** +* MLC_TIME_REG bit definitions +**********************************************************************/ +#define MLCTIMEREG_TCEA_DELAY(n) (((n) & 0x03) << 24) +#define MLCTIMEREG_BUSY_DELAY(n) (((n) & 0x1F) << 19) +#define MLCTIMEREG_NAND_TA(n) (((n) & 0x07) << 16) +#define MLCTIMEREG_RD_HIGH(n) (((n) & 0x0F) << 12) +#define MLCTIMEREG_RD_LOW(n) (((n) & 0x0F) << 8) +#define MLCTIMEREG_WR_HIGH(n) (((n) & 0x0F) << 4) +#define MLCTIMEREG_WR_LOW(n) (((n) & 0x0F) << 0) + +/********************************************************************** +* MLC_IRQ_MR and MLC_IRQ_SR bit definitions +**********************************************************************/ +#define MLCIRQ_NAND_READY (1 << 5) +#define MLCIRQ_CONTROLLER_READY (1 << 4) +#define MLCIRQ_DECODE_FAILURE (1 << 3) +#define MLCIRQ_DECODE_ERROR (1 << 2) +#define MLCIRQ_ECC_READY (1 << 1) +#define MLCIRQ_WRPROT_FAULT (1 << 0) + +/********************************************************************** +* MLC_LOCK_PR bit definitions +**********************************************************************/ +#define MLCLOCKPR_MAGIC 0xA25E + +/********************************************************************** +* MLC_ISR bit definitions +**********************************************************************/ +#define MLCISR_DECODER_FAILURE (1 << 6) +#define MLCISR_ERRORS ((1 << 4) | (1 << 5)) +#define MLCISR_ERRORS_DETECTED (1 << 3) +#define MLCISR_ECC_READY (1 << 2) +#define MLCISR_CONTROLLER_READY (1 << 1) +#define MLCISR_NAND_READY (1 << 0) + +/********************************************************************** +* MLC_CEH bit definitions +**********************************************************************/ +#define MLCCEH_NORMAL (1 << 0) + +struct lpc32xx_nand_cfg_mlc { + uint32_t tcea_delay; + uint32_t busy_delay; + uint32_t nand_ta; + uint32_t rd_high; + uint32_t rd_low; + uint32_t wr_high; + uint32_t wr_low; + int wp_gpio; + struct mtd_partition *parts; + unsigned num_parts; +}; + +static struct nand_ecclayout lpc32xx_nand_oob = { + .eccbytes = 40, + .eccpos = { 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63 }, + .oobfree = { + { .offset = 0, + .length = 6, }, + { .offset = 16, + .length = 6, }, + { .offset = 32, + .length = 6, }, + { .offset = 48, + .length = 6, }, + }, +}; + +static struct nand_bbt_descr lpc32xx_nand_bbt = { + .options = NAND_BBT_ABSPAGE | NAND_BBT_2BIT | NAND_BBT_NO_OOB | + NAND_BBT_WRITE, + .pages = { 524224, 0, 0, 0, 0, 0, 0, 0 }, +}; + +static struct nand_bbt_descr lpc32xx_nand_bbt_mirror = { + .options = NAND_BBT_ABSPAGE | NAND_BBT_2BIT | NAND_BBT_NO_OOB | + NAND_BBT_WRITE, + .pages = { 524160, 0, 0, 0, 0, 0, 0, 0 }, +}; + +struct lpc32xx_nand_host { + struct nand_chip nand_chip; + struct lpc32xx_mlc_platform_data *pdata; + struct clk *clk; + struct mtd_info mtd; + void __iomem *io_base; + int irq; + struct lpc32xx_nand_cfg_mlc *ncfg; + struct completion comp_nand; + struct completion comp_controller; + uint32_t llptr; + /* + * Physical addresses of ECC buffer, DMA data buffers, OOB data buffer + */ + dma_addr_t oob_buf_phy; + /* + * Virtual addresses of ECC buffer, DMA data buffers, OOB data buffer + */ + uint8_t *oob_buf; + /* Physical address of DMA base address */ + dma_addr_t io_base_phy; + + struct completion comp_dma; + struct dma_chan *dma_chan; + struct dma_slave_config dma_slave_config; + struct scatterlist sgl; + uint8_t *dma_buf; + uint8_t *dummy_buf; + int mlcsubpages; /* number of 512bytes-subpages */ +}; + +/* + * Activate/Deactivate DMA Operation: + * + * Using the PL080 DMA Controller for transferring the 512 byte subpages + * instead of doing readl() / writel() in a loop slows it down significantly. + * Measurements via getnstimeofday() upon 512 byte subpage reads reveal: + * + * - readl() of 128 x 32 bits in a loop: ~20us + * - DMA read of 512 bytes (32 bit, 4...128 words bursts): ~60us + * - DMA read of 512 bytes (32 bit, no bursts): ~100us + * + * This applies to the transfer itself. In the DMA case: only the + * wait_for_completion() (DMA setup _not_ included). + * + * Note that the 512 bytes subpage transfer is done directly from/to a + * FIFO/buffer inside the NAND controller. Most of the time (~400-800us for a + * 2048 bytes page) is spent waiting for the NAND IRQ, anyway. (The NAND + * controller transferring data between its internal buffer to/from the NAND + * chip.) + * + * Therefore, using the PL080 DMA is disabled by default, for now. + * + */ +static int use_dma; + +static void lpc32xx_nand_setup(struct lpc32xx_nand_host *host) +{ + uint32_t clkrate, tmp; + + /* Reset MLC controller */ + writel(MLCCMD_RESET, MLC_CMD(host->io_base)); + udelay(1000); + + /* Get base clock for MLC block */ + clkrate = clk_get_rate(host->clk); + if (clkrate == 0) + clkrate = 104000000; + + /* Unlock MLC_ICR + * (among others, will be locked again automatically) */ + writew(MLCLOCKPR_MAGIC, MLC_LOCK_PR(host->io_base)); + + /* Configure MLC Controller: Large Block, 5 Byte Address */ + tmp = MLCICR_LARGEBLOCK | MLCICR_LONGADDR; + writel(tmp, MLC_ICR(host->io_base)); + + /* Unlock MLC_TIME_REG + * (among others, will be locked again automatically) */ + writew(MLCLOCKPR_MAGIC, MLC_LOCK_PR(host->io_base)); + + /* Compute clock setup values, see LPC and NAND manual */ + tmp = 0; + tmp |= MLCTIMEREG_TCEA_DELAY(clkrate / host->ncfg->tcea_delay + 1); + tmp |= MLCTIMEREG_BUSY_DELAY(clkrate / host->ncfg->busy_delay + 1); + tmp |= MLCTIMEREG_NAND_TA(clkrate / host->ncfg->nand_ta + 1); + tmp |= MLCTIMEREG_RD_HIGH(clkrate / host->ncfg->rd_high + 1); + tmp |= MLCTIMEREG_RD_LOW(clkrate / host->ncfg->rd_low); + tmp |= MLCTIMEREG_WR_HIGH(clkrate / host->ncfg->wr_high + 1); + tmp |= MLCTIMEREG_WR_LOW(clkrate / host->ncfg->wr_low); + writel(tmp, MLC_TIME_REG(host->io_base)); + + /* Enable IRQ for CONTROLLER_READY and NAND_READY */ + writeb(MLCIRQ_CONTROLLER_READY | MLCIRQ_NAND_READY, + MLC_IRQ_MR(host->io_base)); + + /* Normal nCE operation: nCE controlled by controller */ + writel(MLCCEH_NORMAL, MLC_CEH(host->io_base)); +} + +/* + * Hardware specific access to control lines + */ +static void lpc32xx_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, + unsigned int ctrl) +{ + struct nand_chip *nand_chip = mtd->priv; + struct lpc32xx_nand_host *host = nand_chip->priv; + + if (cmd != NAND_CMD_NONE) { + if (ctrl & NAND_CLE) + writel(cmd, MLC_CMD(host->io_base)); + else + writel(cmd, MLC_ADDR(host->io_base)); + } +} + +/* + * Read Device Ready (NAND device _and_ controller ready) + */ +static int lpc32xx_nand_device_ready(struct mtd_info *mtd) +{ + struct nand_chip *nand_chip = mtd->priv; + struct lpc32xx_nand_host *host = nand_chip->priv; + + if ((readb(MLC_ISR(host->io_base)) & + (MLCISR_CONTROLLER_READY | MLCISR_NAND_READY)) == + (MLCISR_CONTROLLER_READY | MLCISR_NAND_READY)) + return 1; + + return 0; +} + +static irqreturn_t lpc3xxx_nand_irq(int irq, struct lpc32xx_nand_host *host) +{ + uint8_t sr; + + /* Clear interrupt flag by reading status */ + sr = readb(MLC_IRQ_SR(host->io_base)); + if (sr & MLCIRQ_NAND_READY) + complete(&host->comp_nand); + if (sr & MLCIRQ_CONTROLLER_READY) + complete(&host->comp_controller); + + return IRQ_HANDLED; +} + +static int lpc32xx_waitfunc_nand(struct mtd_info *mtd, struct nand_chip *chip) +{ + struct lpc32xx_nand_host *host = chip->priv; + + if (readb(MLC_ISR(host->io_base)) & MLCISR_NAND_READY) + goto exit; + + wait_for_completion(&host->comp_nand); + + while (!(readb(MLC_ISR(host->io_base)) & MLCISR_NAND_READY)) { + /* Seems to be delayed sometimes by controller */ + dev_dbg(&mtd->dev, "Warning: NAND not ready.\n"); + cpu_relax(); + } + +exit: + return NAND_STATUS_READY; +} + +static int lpc32xx_waitfunc_controller(struct mtd_info *mtd, + struct nand_chip *chip) +{ + struct lpc32xx_nand_host *host = chip->priv; + + if (readb(MLC_ISR(host->io_base)) & MLCISR_CONTROLLER_READY) + goto exit; + + wait_for_completion(&host->comp_controller); + + while (!(readb(MLC_ISR(host->io_base)) & + MLCISR_CONTROLLER_READY)) { + dev_dbg(&mtd->dev, "Warning: Controller not ready.\n"); + cpu_relax(); + } + +exit: + return NAND_STATUS_READY; +} + +static int lpc32xx_waitfunc(struct mtd_info *mtd, struct nand_chip *chip) +{ + lpc32xx_waitfunc_nand(mtd, chip); + lpc32xx_waitfunc_controller(mtd, chip); + + return NAND_STATUS_READY; +} + +/* + * Enable NAND write protect + */ +static void lpc32xx_wp_enable(struct lpc32xx_nand_host *host) +{ + if (gpio_is_valid(host->ncfg->wp_gpio)) + gpio_set_value(host->ncfg->wp_gpio, 0); +} + +/* + * Disable NAND write protect + */ +static void lpc32xx_wp_disable(struct lpc32xx_nand_host *host) +{ + if (gpio_is_valid(host->ncfg->wp_gpio)) + gpio_set_value(host->ncfg->wp_gpio, 1); +} + +static void lpc32xx_dma_complete_func(void *completion) +{ + complete(completion); +} + +static int lpc32xx_xmit_dma(struct mtd_info *mtd, void *mem, int len, + enum dma_transfer_direction dir) +{ + struct nand_chip *chip = mtd->priv; + struct lpc32xx_nand_host *host = chip->priv; + struct dma_async_tx_descriptor *desc; + int flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT; + int res; + + sg_init_one(&host->sgl, mem, len); + + res = dma_map_sg(host->dma_chan->device->dev, &host->sgl, 1, + DMA_BIDIRECTIONAL); + if (res != 1) { + dev_err(mtd->dev.parent, "Failed to map sg list\n"); + return -ENXIO; + } + desc = dmaengine_prep_slave_sg(host->dma_chan, &host->sgl, 1, dir, + flags); + if (!desc) { + dev_err(mtd->dev.parent, "Failed to prepare slave sg\n"); + goto out1; + } + + init_completion(&host->comp_dma); + desc->callback = lpc32xx_dma_complete_func; + desc->callback_param = &host->comp_dma; + + dmaengine_submit(desc); + dma_async_issue_pending(host->dma_chan); + + wait_for_completion_timeout(&host->comp_dma, msecs_to_jiffies(1000)); + + dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1, + DMA_BIDIRECTIONAL); + return 0; +out1: + dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1, + DMA_BIDIRECTIONAL); + return -ENXIO; +} + +static int lpc32xx_read_page(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf, int oob_required, int page) +{ + struct lpc32xx_nand_host *host = chip->priv; + int i, j; + uint8_t *oobbuf = chip->oob_poi; + uint32_t mlc_isr; + int res; + uint8_t *dma_buf; + bool dma_mapped; + + if ((void *)buf <= high_memory) { + dma_buf = buf; + dma_mapped = true; + } else { + dma_buf = host->dma_buf; + dma_mapped = false; + } + + /* Writing Command and Address */ + chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page); + + /* For all sub-pages */ + for (i = 0; i < host->mlcsubpages; i++) { + /* Start Auto Decode Command */ + writeb(0x00, MLC_ECC_AUTO_DEC_REG(host->io_base)); + + /* Wait for Controller Ready */ + lpc32xx_waitfunc_controller(mtd, chip); + + /* Check ECC Error status */ + mlc_isr = readl(MLC_ISR(host->io_base)); + if (mlc_isr & MLCISR_DECODER_FAILURE) { + mtd->ecc_stats.failed++; + dev_warn(&mtd->dev, "%s: DECODER_FAILURE\n", __func__); + } else if (mlc_isr & MLCISR_ERRORS_DETECTED) { + mtd->ecc_stats.corrected += ((mlc_isr >> 4) & 0x3) + 1; + } + + /* Read 512 + 16 Bytes */ + if (use_dma) { + res = lpc32xx_xmit_dma(mtd, dma_buf + i * 512, 512, + DMA_DEV_TO_MEM); + if (res) + return res; + } else { + for (j = 0; j < (512 >> 2); j++) { + *((uint32_t *)(buf)) = + readl(MLC_BUFF(host->io_base)); + buf += 4; + } + } + for (j = 0; j < (16 >> 2); j++) { + *((uint32_t *)(oobbuf)) = + readl(MLC_BUFF(host->io_base)); + oobbuf += 4; + } + } + + if (use_dma && !dma_mapped) + memcpy(buf, dma_buf, mtd->writesize); + + return 0; +} + +static int lpc32xx_write_page_lowlevel(struct mtd_info *mtd, + struct nand_chip *chip, + const uint8_t *buf, int oob_required) +{ + struct lpc32xx_nand_host *host = chip->priv; + const uint8_t *oobbuf = chip->oob_poi; + uint8_t *dma_buf = (uint8_t *)buf; + int res; + int i, j; + + if (use_dma && (void *)buf >= high_memory) { + dma_buf = host->dma_buf; + memcpy(dma_buf, buf, mtd->writesize); + } + + for (i = 0; i < host->mlcsubpages; i++) { + /* Start Encode */ + writeb(0x00, MLC_ECC_ENC_REG(host->io_base)); + + /* Write 512 + 6 Bytes to Buffer */ + if (use_dma) { + res = lpc32xx_xmit_dma(mtd, dma_buf + i * 512, 512, + DMA_MEM_TO_DEV); + if (res) + return res; + } else { + for (j = 0; j < (512 >> 2); j++) { + writel(*((uint32_t *)(buf)), + MLC_BUFF(host->io_base)); + buf += 4; + } + } + writel(*((uint32_t *)(oobbuf)), MLC_BUFF(host->io_base)); + oobbuf += 4; + writew(*((uint16_t *)(oobbuf)), MLC_BUFF(host->io_base)); + oobbuf += 12; + + /* Auto Encode w/ Bit 8 = 0 (see LPC MLC Controller manual) */ + writeb(0x00, MLC_ECC_AUTO_ENC_REG(host->io_base)); + + /* Wait for Controller Ready */ + lpc32xx_waitfunc_controller(mtd, chip); + } + return 0; +} + +static int lpc32xx_read_oob(struct mtd_info *mtd, struct nand_chip *chip, + int page) +{ + struct lpc32xx_nand_host *host = chip->priv; + + /* Read whole page - necessary with MLC controller! */ + lpc32xx_read_page(mtd, chip, host->dummy_buf, 1, page); + + return 0; +} + +static int lpc32xx_write_oob(struct mtd_info *mtd, struct nand_chip *chip, + int page) +{ + /* None, write_oob conflicts with the automatic LPC MLC ECC decoder! */ + return 0; +} + +/* Prepares MLC for transfers with H/W ECC enabled: always enabled anyway */ +static void lpc32xx_ecc_enable(struct mtd_info *mtd, int mode) +{ + /* Always enabled! */ +} + +static int lpc32xx_dma_setup(struct lpc32xx_nand_host *host) +{ + struct mtd_info *mtd = &host->mtd; + dma_cap_mask_t mask; + + if (!host->pdata || !host->pdata->dma_filter) { + dev_err(mtd->dev.parent, "no DMA platform data\n"); + return -ENOENT; + } + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + host->dma_chan = dma_request_channel(mask, host->pdata->dma_filter, + "nand-mlc"); + if (!host->dma_chan) { + dev_err(mtd->dev.parent, "Failed to request DMA channel\n"); + return -EBUSY; + } + + /* + * Set direction to a sensible value even if the dmaengine driver + * should ignore it. With the default (DMA_MEM_TO_MEM), the amba-pl08x + * driver criticizes it as "alien transfer direction". + */ + host->dma_slave_config.direction = DMA_DEV_TO_MEM; + host->dma_slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + host->dma_slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + host->dma_slave_config.src_maxburst = 128; + host->dma_slave_config.dst_maxburst = 128; + /* DMA controller does flow control: */ + host->dma_slave_config.device_fc = false; + host->dma_slave_config.src_addr = MLC_BUFF(host->io_base_phy); + host->dma_slave_config.dst_addr = MLC_BUFF(host->io_base_phy); + if (dmaengine_slave_config(host->dma_chan, &host->dma_slave_config)) { + dev_err(mtd->dev.parent, "Failed to setup DMA slave\n"); + goto out1; + } + + return 0; +out1: + dma_release_channel(host->dma_chan); + return -ENXIO; +} + +static struct lpc32xx_nand_cfg_mlc *lpc32xx_parse_dt(struct device *dev) +{ + struct lpc32xx_nand_cfg_mlc *ncfg; + struct device_node *np = dev->of_node; + + ncfg = devm_kzalloc(dev, sizeof(*ncfg), GFP_KERNEL); + if (!ncfg) + return NULL; + + of_property_read_u32(np, "nxp,tcea-delay", &ncfg->tcea_delay); + of_property_read_u32(np, "nxp,busy-delay", &ncfg->busy_delay); + of_property_read_u32(np, "nxp,nand-ta", &ncfg->nand_ta); + of_property_read_u32(np, "nxp,rd-high", &ncfg->rd_high); + of_property_read_u32(np, "nxp,rd-low", &ncfg->rd_low); + of_property_read_u32(np, "nxp,wr-high", &ncfg->wr_high); + of_property_read_u32(np, "nxp,wr-low", &ncfg->wr_low); + + if (!ncfg->tcea_delay || !ncfg->busy_delay || !ncfg->nand_ta || + !ncfg->rd_high || !ncfg->rd_low || !ncfg->wr_high || + !ncfg->wr_low) { + dev_err(dev, "chip parameters not specified correctly\n"); + return NULL; + } + + ncfg->wp_gpio = of_get_named_gpio(np, "gpios", 0); + + return ncfg; +} + +/* + * Probe for NAND controller + */ +static int lpc32xx_nand_probe(struct platform_device *pdev) +{ + struct lpc32xx_nand_host *host; + struct mtd_info *mtd; + struct nand_chip *nand_chip; + struct resource *rc; + int res; + struct mtd_part_parser_data ppdata = {}; + + /* Allocate memory for the device structure (and zero it) */ + host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + rc = platform_get_resource(pdev, IORESOURCE_MEM, 0); + host->io_base = devm_ioremap_resource(&pdev->dev, rc); + if (IS_ERR(host->io_base)) + return PTR_ERR(host->io_base); + + host->io_base_phy = rc->start; + + mtd = &host->mtd; + nand_chip = &host->nand_chip; + if (pdev->dev.of_node) + host->ncfg = lpc32xx_parse_dt(&pdev->dev); + if (!host->ncfg) { + dev_err(&pdev->dev, + "Missing or bad NAND config from device tree\n"); + return -ENOENT; + } + if (host->ncfg->wp_gpio == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (gpio_is_valid(host->ncfg->wp_gpio) && + gpio_request(host->ncfg->wp_gpio, "NAND WP")) { + dev_err(&pdev->dev, "GPIO not available\n"); + return -EBUSY; + } + lpc32xx_wp_disable(host); + + host->pdata = dev_get_platdata(&pdev->dev); + + nand_chip->priv = host; /* link the private data structures */ + mtd->priv = nand_chip; + mtd->owner = THIS_MODULE; + mtd->dev.parent = &pdev->dev; + + /* Get NAND clock */ + host->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(host->clk)) { + dev_err(&pdev->dev, "Clock initialization failure\n"); + res = -ENOENT; + goto err_exit1; + } + clk_enable(host->clk); + + nand_chip->cmd_ctrl = lpc32xx_nand_cmd_ctrl; + nand_chip->dev_ready = lpc32xx_nand_device_ready; + nand_chip->chip_delay = 25; /* us */ + nand_chip->IO_ADDR_R = MLC_DATA(host->io_base); + nand_chip->IO_ADDR_W = MLC_DATA(host->io_base); + + /* Init NAND controller */ + lpc32xx_nand_setup(host); + + platform_set_drvdata(pdev, host); + + /* Initialize function pointers */ + nand_chip->ecc.hwctl = lpc32xx_ecc_enable; + nand_chip->ecc.read_page_raw = lpc32xx_read_page; + nand_chip->ecc.read_page = lpc32xx_read_page; + nand_chip->ecc.write_page_raw = lpc32xx_write_page_lowlevel; + nand_chip->ecc.write_page = lpc32xx_write_page_lowlevel; + nand_chip->ecc.write_oob = lpc32xx_write_oob; + nand_chip->ecc.read_oob = lpc32xx_read_oob; + nand_chip->ecc.strength = 4; + nand_chip->waitfunc = lpc32xx_waitfunc; + + nand_chip->options = NAND_NO_SUBPAGE_WRITE; + nand_chip->bbt_options = NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB; + nand_chip->bbt_td = &lpc32xx_nand_bbt; + nand_chip->bbt_md = &lpc32xx_nand_bbt_mirror; + + /* bitflip_threshold's default is defined as ecc_strength anyway. + * Unfortunately, it is set only later at add_mtd_device(). Meanwhile + * being 0, it causes bad block table scanning errors in + * nand_scan_tail(), so preparing it here. */ + mtd->bitflip_threshold = nand_chip->ecc.strength; + + if (use_dma) { + res = lpc32xx_dma_setup(host); + if (res) { + res = -EIO; + goto err_exit2; + } + } + + /* + * Scan to find existance of the device and + * Get the type of NAND device SMALL block or LARGE block + */ + if (nand_scan_ident(mtd, 1, NULL)) { + res = -ENXIO; + goto err_exit3; + } + + host->dma_buf = devm_kzalloc(&pdev->dev, mtd->writesize, GFP_KERNEL); + if (!host->dma_buf) { + res = -ENOMEM; + goto err_exit3; + } + + host->dummy_buf = devm_kzalloc(&pdev->dev, mtd->writesize, GFP_KERNEL); + if (!host->dummy_buf) { + res = -ENOMEM; + goto err_exit3; + } + + nand_chip->ecc.mode = NAND_ECC_HW; + nand_chip->ecc.size = mtd->writesize; + nand_chip->ecc.layout = &lpc32xx_nand_oob; + host->mlcsubpages = mtd->writesize / 512; + + /* initially clear interrupt status */ + readb(MLC_IRQ_SR(host->io_base)); + + init_completion(&host->comp_nand); + init_completion(&host->comp_controller); + + host->irq = platform_get_irq(pdev, 0); + if ((host->irq < 0) || (host->irq >= NR_IRQS)) { + dev_err(&pdev->dev, "failed to get platform irq\n"); + res = -EINVAL; + goto err_exit3; + } + + if (request_irq(host->irq, (irq_handler_t)&lpc3xxx_nand_irq, + IRQF_TRIGGER_HIGH, DRV_NAME, host)) { + dev_err(&pdev->dev, "Error requesting NAND IRQ\n"); + res = -ENXIO; + goto err_exit3; + } + + /* + * Fills out all the uninitialized function pointers with the defaults + * And scans for a bad block table if appropriate. + */ + if (nand_scan_tail(mtd)) { + res = -ENXIO; + goto err_exit4; + } + + mtd->name = DRV_NAME; + + ppdata.of_node = pdev->dev.of_node; + res = mtd_device_parse_register(mtd, NULL, &ppdata, host->ncfg->parts, + host->ncfg->num_parts); + if (!res) + return res; + + nand_release(mtd); + +err_exit4: + free_irq(host->irq, host); +err_exit3: + if (use_dma) + dma_release_channel(host->dma_chan); +err_exit2: + clk_disable(host->clk); + clk_put(host->clk); +err_exit1: + lpc32xx_wp_enable(host); + gpio_free(host->ncfg->wp_gpio); + + return res; +} + +/* + * Remove NAND device + */ +static int lpc32xx_nand_remove(struct platform_device *pdev) +{ + struct lpc32xx_nand_host *host = platform_get_drvdata(pdev); + struct mtd_info *mtd = &host->mtd; + + nand_release(mtd); + free_irq(host->irq, host); + if (use_dma) + dma_release_channel(host->dma_chan); + + clk_disable(host->clk); + clk_put(host->clk); + + lpc32xx_wp_enable(host); + gpio_free(host->ncfg->wp_gpio); + + return 0; +} + +#ifdef CONFIG_PM +static int lpc32xx_nand_resume(struct platform_device *pdev) +{ + struct lpc32xx_nand_host *host = platform_get_drvdata(pdev); + + /* Re-enable NAND clock */ + clk_enable(host->clk); + + /* Fresh init of NAND controller */ + lpc32xx_nand_setup(host); + + /* Disable write protect */ + lpc32xx_wp_disable(host); + + return 0; +} + +static int lpc32xx_nand_suspend(struct platform_device *pdev, pm_message_t pm) +{ + struct lpc32xx_nand_host *host = platform_get_drvdata(pdev); + + /* Enable write protect for safety */ + lpc32xx_wp_enable(host); + + /* Disable clock */ + clk_disable(host->clk); + return 0; +} + +#else +#define lpc32xx_nand_resume NULL +#define lpc32xx_nand_suspend NULL +#endif + +static const struct of_device_id lpc32xx_nand_match[] = { + { .compatible = "nxp,lpc3220-mlc" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, lpc32xx_nand_match); + +static struct platform_driver lpc32xx_nand_driver = { + .probe = lpc32xx_nand_probe, + .remove = lpc32xx_nand_remove, + .resume = lpc32xx_nand_resume, + .suspend = lpc32xx_nand_suspend, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = lpc32xx_nand_match, + }, +}; + +module_platform_driver(lpc32xx_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Roland Stigge <stigge@antcom.de>"); +MODULE_DESCRIPTION("NAND driver for the NXP LPC32XX MLC controller"); diff --git a/drivers/mtd/nand/lpc32xx_slc.c b/drivers/mtd/nand/lpc32xx_slc.c new file mode 100644 index 00000000000..53a6742e3da --- /dev/null +++ b/drivers/mtd/nand/lpc32xx_slc.c @@ -0,0 +1,1018 @@ +/* + * NXP LPC32XX NAND SLC driver + * + * Authors: + * Kevin Wells <kevin.wells@nxp.com> + * Roland Stigge <stigge@antcom.de> + * + * Copyright © 2011 NXP Semiconductors + * Copyright © 2012 Roland Stigge + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/mm.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/mtd/nand_ecc.h> +#include <linux/gpio.h> +#include <linux/of.h> +#include <linux/of_mtd.h> +#include <linux/of_gpio.h> +#include <linux/mtd/lpc32xx_slc.h> + +#define LPC32XX_MODNAME "lpc32xx-nand" + +/********************************************************************** +* SLC NAND controller register offsets +**********************************************************************/ + +#define SLC_DATA(x) (x + 0x000) +#define SLC_ADDR(x) (x + 0x004) +#define SLC_CMD(x) (x + 0x008) +#define SLC_STOP(x) (x + 0x00C) +#define SLC_CTRL(x) (x + 0x010) +#define SLC_CFG(x) (x + 0x014) +#define SLC_STAT(x) (x + 0x018) +#define SLC_INT_STAT(x) (x + 0x01C) +#define SLC_IEN(x) (x + 0x020) +#define SLC_ISR(x) (x + 0x024) +#define SLC_ICR(x) (x + 0x028) +#define SLC_TAC(x) (x + 0x02C) +#define SLC_TC(x) (x + 0x030) +#define SLC_ECC(x) (x + 0x034) +#define SLC_DMA_DATA(x) (x + 0x038) + +/********************************************************************** +* slc_ctrl register definitions +**********************************************************************/ +#define SLCCTRL_SW_RESET (1 << 2) /* Reset the NAND controller bit */ +#define SLCCTRL_ECC_CLEAR (1 << 1) /* Reset ECC bit */ +#define SLCCTRL_DMA_START (1 << 0) /* Start DMA channel bit */ + +/********************************************************************** +* slc_cfg register definitions +**********************************************************************/ +#define SLCCFG_CE_LOW (1 << 5) /* Force CE low bit */ +#define SLCCFG_DMA_ECC (1 << 4) /* Enable DMA ECC bit */ +#define SLCCFG_ECC_EN (1 << 3) /* ECC enable bit */ +#define SLCCFG_DMA_BURST (1 << 2) /* DMA burst bit */ +#define SLCCFG_DMA_DIR (1 << 1) /* DMA write(0)/read(1) bit */ +#define SLCCFG_WIDTH (1 << 0) /* External device width, 0=8bit */ + +/********************************************************************** +* slc_stat register definitions +**********************************************************************/ +#define SLCSTAT_DMA_FIFO (1 << 2) /* DMA FIFO has data bit */ +#define SLCSTAT_SLC_FIFO (1 << 1) /* SLC FIFO has data bit */ +#define SLCSTAT_NAND_READY (1 << 0) /* NAND device is ready bit */ + +/********************************************************************** +* slc_int_stat, slc_ien, slc_isr, and slc_icr register definitions +**********************************************************************/ +#define SLCSTAT_INT_TC (1 << 1) /* Transfer count bit */ +#define SLCSTAT_INT_RDY_EN (1 << 0) /* Ready interrupt bit */ + +/********************************************************************** +* slc_tac register definitions +**********************************************************************/ +/* Clock setting for RDY write sample wait time in 2*n clocks */ +#define SLCTAC_WDR(n) (((n) & 0xF) << 28) +/* Write pulse width in clock cycles, 1 to 16 clocks */ +#define SLCTAC_WWIDTH(n) (((n) & 0xF) << 24) +/* Write hold time of control and data signals, 1 to 16 clocks */ +#define SLCTAC_WHOLD(n) (((n) & 0xF) << 20) +/* Write setup time of control and data signals, 1 to 16 clocks */ +#define SLCTAC_WSETUP(n) (((n) & 0xF) << 16) +/* Clock setting for RDY read sample wait time in 2*n clocks */ +#define SLCTAC_RDR(n) (((n) & 0xF) << 12) +/* Read pulse width in clock cycles, 1 to 16 clocks */ +#define SLCTAC_RWIDTH(n) (((n) & 0xF) << 8) +/* Read hold time of control and data signals, 1 to 16 clocks */ +#define SLCTAC_RHOLD(n) (((n) & 0xF) << 4) +/* Read setup time of control and data signals, 1 to 16 clocks */ +#define SLCTAC_RSETUP(n) (((n) & 0xF) << 0) + +/********************************************************************** +* slc_ecc register definitions +**********************************************************************/ +/* ECC line party fetch macro */ +#define SLCECC_TO_LINEPAR(n) (((n) >> 6) & 0x7FFF) +#define SLCECC_TO_COLPAR(n) ((n) & 0x3F) + +/* + * DMA requires storage space for the DMA local buffer and the hardware ECC + * storage area. The DMA local buffer is only used if DMA mapping fails + * during runtime. + */ +#define LPC32XX_DMA_DATA_SIZE 4096 +#define LPC32XX_ECC_SAVE_SIZE ((4096 / 256) * 4) + +/* Number of bytes used for ECC stored in NAND per 256 bytes */ +#define LPC32XX_SLC_DEV_ECC_BYTES 3 + +/* + * If the NAND base clock frequency can't be fetched, this frequency will be + * used instead as the base. This rate is used to setup the timing registers + * used for NAND accesses. + */ +#define LPC32XX_DEF_BUS_RATE 133250000 + +/* Milliseconds for DMA FIFO timeout (unlikely anyway) */ +#define LPC32XX_DMA_TIMEOUT 100 + +/* + * NAND ECC Layout for small page NAND devices + * Note: For large and huge page devices, the default layouts are used + */ +static struct nand_ecclayout lpc32xx_nand_oob_16 = { + .eccbytes = 6, + .eccpos = {10, 11, 12, 13, 14, 15}, + .oobfree = { + { .offset = 0, .length = 4 }, + { .offset = 6, .length = 4 }, + }, +}; + +static u8 bbt_pattern[] = {'B', 'b', 't', '0' }; +static u8 mirror_pattern[] = {'1', 't', 'b', 'B' }; + +/* + * Small page FLASH BBT descriptors, marker at offset 0, version at offset 6 + * Note: Large page devices used the default layout + */ +static struct nand_bbt_descr bbt_smallpage_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 0, + .len = 4, + .veroffs = 6, + .maxblocks = 4, + .pattern = bbt_pattern +}; + +static struct nand_bbt_descr bbt_smallpage_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 0, + .len = 4, + .veroffs = 6, + .maxblocks = 4, + .pattern = mirror_pattern +}; + +/* + * NAND platform configuration structure + */ +struct lpc32xx_nand_cfg_slc { + uint32_t wdr_clks; + uint32_t wwidth; + uint32_t whold; + uint32_t wsetup; + uint32_t rdr_clks; + uint32_t rwidth; + uint32_t rhold; + uint32_t rsetup; + bool use_bbt; + int wp_gpio; + struct mtd_partition *parts; + unsigned num_parts; +}; + +struct lpc32xx_nand_host { + struct nand_chip nand_chip; + struct lpc32xx_slc_platform_data *pdata; + struct clk *clk; + struct mtd_info mtd; + void __iomem *io_base; + struct lpc32xx_nand_cfg_slc *ncfg; + + struct completion comp; + struct dma_chan *dma_chan; + uint32_t dma_buf_len; + struct dma_slave_config dma_slave_config; + struct scatterlist sgl; + + /* + * DMA and CPU addresses of ECC work area and data buffer + */ + uint32_t *ecc_buf; + uint8_t *data_buf; + dma_addr_t io_base_dma; +}; + +static void lpc32xx_nand_setup(struct lpc32xx_nand_host *host) +{ + uint32_t clkrate, tmp; + + /* Reset SLC controller */ + writel(SLCCTRL_SW_RESET, SLC_CTRL(host->io_base)); + udelay(1000); + + /* Basic setup */ + writel(0, SLC_CFG(host->io_base)); + writel(0, SLC_IEN(host->io_base)); + writel((SLCSTAT_INT_TC | SLCSTAT_INT_RDY_EN), + SLC_ICR(host->io_base)); + + /* Get base clock for SLC block */ + clkrate = clk_get_rate(host->clk); + if (clkrate == 0) + clkrate = LPC32XX_DEF_BUS_RATE; + + /* Compute clock setup values */ + tmp = SLCTAC_WDR(host->ncfg->wdr_clks) | + SLCTAC_WWIDTH(1 + (clkrate / host->ncfg->wwidth)) | + SLCTAC_WHOLD(1 + (clkrate / host->ncfg->whold)) | + SLCTAC_WSETUP(1 + (clkrate / host->ncfg->wsetup)) | + SLCTAC_RDR(host->ncfg->rdr_clks) | + SLCTAC_RWIDTH(1 + (clkrate / host->ncfg->rwidth)) | + SLCTAC_RHOLD(1 + (clkrate / host->ncfg->rhold)) | + SLCTAC_RSETUP(1 + (clkrate / host->ncfg->rsetup)); + writel(tmp, SLC_TAC(host->io_base)); +} + +/* + * Hardware specific access to control lines + */ +static void lpc32xx_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, + unsigned int ctrl) +{ + uint32_t tmp; + struct nand_chip *chip = mtd->priv; + struct lpc32xx_nand_host *host = chip->priv; + + /* Does CE state need to be changed? */ + tmp = readl(SLC_CFG(host->io_base)); + if (ctrl & NAND_NCE) + tmp |= SLCCFG_CE_LOW; + else + tmp &= ~SLCCFG_CE_LOW; + writel(tmp, SLC_CFG(host->io_base)); + + if (cmd != NAND_CMD_NONE) { + if (ctrl & NAND_CLE) + writel(cmd, SLC_CMD(host->io_base)); + else + writel(cmd, SLC_ADDR(host->io_base)); + } +} + +/* + * Read the Device Ready pin + */ +static int lpc32xx_nand_device_ready(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct lpc32xx_nand_host *host = chip->priv; + int rdy = 0; + + if ((readl(SLC_STAT(host->io_base)) & SLCSTAT_NAND_READY) != 0) + rdy = 1; + + return rdy; +} + +/* + * Enable NAND write protect + */ +static void lpc32xx_wp_enable(struct lpc32xx_nand_host *host) +{ + if (gpio_is_valid(host->ncfg->wp_gpio)) + gpio_set_value(host->ncfg->wp_gpio, 0); +} + +/* + * Disable NAND write protect + */ +static void lpc32xx_wp_disable(struct lpc32xx_nand_host *host) +{ + if (gpio_is_valid(host->ncfg->wp_gpio)) + gpio_set_value(host->ncfg->wp_gpio, 1); +} + +/* + * Prepares SLC for transfers with H/W ECC enabled + */ +static void lpc32xx_nand_ecc_enable(struct mtd_info *mtd, int mode) +{ + /* Hardware ECC is enabled automatically in hardware as needed */ +} + +/* + * Calculates the ECC for the data + */ +static int lpc32xx_nand_ecc_calculate(struct mtd_info *mtd, + const unsigned char *buf, + unsigned char *code) +{ + /* + * ECC is calculated automatically in hardware during syndrome read + * and write operations, so it doesn't need to be calculated here. + */ + return 0; +} + +/* + * Read a single byte from NAND device + */ +static uint8_t lpc32xx_nand_read_byte(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct lpc32xx_nand_host *host = chip->priv; + + return (uint8_t)readl(SLC_DATA(host->io_base)); +} + +/* + * Simple device read without ECC + */ +static void lpc32xx_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct lpc32xx_nand_host *host = chip->priv; + + /* Direct device read with no ECC */ + while (len-- > 0) + *buf++ = (uint8_t)readl(SLC_DATA(host->io_base)); +} + +/* + * Simple device write without ECC + */ +static void lpc32xx_nand_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct lpc32xx_nand_host *host = chip->priv; + + /* Direct device write with no ECC */ + while (len-- > 0) + writel((uint32_t)*buf++, SLC_DATA(host->io_base)); +} + +/* + * Read the OOB data from the device without ECC using FIFO method + */ +static int lpc32xx_nand_read_oob_syndrome(struct mtd_info *mtd, + struct nand_chip *chip, int page) +{ + chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page); + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + + return 0; +} + +/* + * Write the OOB data to the device without ECC using FIFO method + */ +static int lpc32xx_nand_write_oob_syndrome(struct mtd_info *mtd, + struct nand_chip *chip, int page) +{ + int status; + + chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize, page); + chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); + + /* Send command to program the OOB data */ + chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + + status = chip->waitfunc(mtd, chip); + + return status & NAND_STATUS_FAIL ? -EIO : 0; +} + +/* + * Fills in the ECC fields in the OOB buffer with the hardware generated ECC + */ +static void lpc32xx_slc_ecc_copy(uint8_t *spare, const uint32_t *ecc, int count) +{ + int i; + + for (i = 0; i < (count * 3); i += 3) { + uint32_t ce = ecc[i / 3]; + ce = ~(ce << 2) & 0xFFFFFF; + spare[i + 2] = (uint8_t)(ce & 0xFF); + ce >>= 8; + spare[i + 1] = (uint8_t)(ce & 0xFF); + ce >>= 8; + spare[i] = (uint8_t)(ce & 0xFF); + } +} + +static void lpc32xx_dma_complete_func(void *completion) +{ + complete(completion); +} + +static int lpc32xx_xmit_dma(struct mtd_info *mtd, dma_addr_t dma, + void *mem, int len, enum dma_transfer_direction dir) +{ + struct nand_chip *chip = mtd->priv; + struct lpc32xx_nand_host *host = chip->priv; + struct dma_async_tx_descriptor *desc; + int flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT; + int res; + + host->dma_slave_config.direction = dir; + host->dma_slave_config.src_addr = dma; + host->dma_slave_config.dst_addr = dma; + host->dma_slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + host->dma_slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + host->dma_slave_config.src_maxburst = 4; + host->dma_slave_config.dst_maxburst = 4; + /* DMA controller does flow control: */ + host->dma_slave_config.device_fc = false; + if (dmaengine_slave_config(host->dma_chan, &host->dma_slave_config)) { + dev_err(mtd->dev.parent, "Failed to setup DMA slave\n"); + return -ENXIO; + } + + sg_init_one(&host->sgl, mem, len); + + res = dma_map_sg(host->dma_chan->device->dev, &host->sgl, 1, + DMA_BIDIRECTIONAL); + if (res != 1) { + dev_err(mtd->dev.parent, "Failed to map sg list\n"); + return -ENXIO; + } + desc = dmaengine_prep_slave_sg(host->dma_chan, &host->sgl, 1, dir, + flags); + if (!desc) { + dev_err(mtd->dev.parent, "Failed to prepare slave sg\n"); + goto out1; + } + + init_completion(&host->comp); + desc->callback = lpc32xx_dma_complete_func; + desc->callback_param = &host->comp; + + dmaengine_submit(desc); + dma_async_issue_pending(host->dma_chan); + + wait_for_completion_timeout(&host->comp, msecs_to_jiffies(1000)); + + dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1, + DMA_BIDIRECTIONAL); + + return 0; +out1: + dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1, + DMA_BIDIRECTIONAL); + return -ENXIO; +} + +/* + * DMA read/write transfers with ECC support + */ +static int lpc32xx_xfer(struct mtd_info *mtd, uint8_t *buf, int eccsubpages, + int read) +{ + struct nand_chip *chip = mtd->priv; + struct lpc32xx_nand_host *host = chip->priv; + int i, status = 0; + unsigned long timeout; + int res; + enum dma_transfer_direction dir = + read ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV; + uint8_t *dma_buf; + bool dma_mapped; + + if ((void *)buf <= high_memory) { + dma_buf = buf; + dma_mapped = true; + } else { + dma_buf = host->data_buf; + dma_mapped = false; + if (!read) + memcpy(host->data_buf, buf, mtd->writesize); + } + + if (read) { + writel(readl(SLC_CFG(host->io_base)) | + SLCCFG_DMA_DIR | SLCCFG_ECC_EN | SLCCFG_DMA_ECC | + SLCCFG_DMA_BURST, SLC_CFG(host->io_base)); + } else { + writel((readl(SLC_CFG(host->io_base)) | + SLCCFG_ECC_EN | SLCCFG_DMA_ECC | SLCCFG_DMA_BURST) & + ~SLCCFG_DMA_DIR, + SLC_CFG(host->io_base)); + } + + /* Clear initial ECC */ + writel(SLCCTRL_ECC_CLEAR, SLC_CTRL(host->io_base)); + + /* Transfer size is data area only */ + writel(mtd->writesize, SLC_TC(host->io_base)); + + /* Start transfer in the NAND controller */ + writel(readl(SLC_CTRL(host->io_base)) | SLCCTRL_DMA_START, + SLC_CTRL(host->io_base)); + + for (i = 0; i < chip->ecc.steps; i++) { + /* Data */ + res = lpc32xx_xmit_dma(mtd, SLC_DMA_DATA(host->io_base_dma), + dma_buf + i * chip->ecc.size, + mtd->writesize / chip->ecc.steps, dir); + if (res) + return res; + + /* Always _read_ ECC */ + if (i == chip->ecc.steps - 1) + break; + if (!read) /* ECC availability delayed on write */ + udelay(10); + res = lpc32xx_xmit_dma(mtd, SLC_ECC(host->io_base_dma), + &host->ecc_buf[i], 4, DMA_DEV_TO_MEM); + if (res) + return res; + } + + /* + * According to NXP, the DMA can be finished here, but the NAND + * controller may still have buffered data. After porting to using the + * dmaengine DMA driver (amba-pl080), the condition (DMA_FIFO empty) + * appears to be always true, according to tests. Keeping the check for + * safety reasons for now. + */ + if (readl(SLC_STAT(host->io_base)) & SLCSTAT_DMA_FIFO) { + dev_warn(mtd->dev.parent, "FIFO not empty!\n"); + timeout = jiffies + msecs_to_jiffies(LPC32XX_DMA_TIMEOUT); + while ((readl(SLC_STAT(host->io_base)) & SLCSTAT_DMA_FIFO) && + time_before(jiffies, timeout)) + cpu_relax(); + if (!time_before(jiffies, timeout)) { + dev_err(mtd->dev.parent, "FIFO held data too long\n"); + status = -EIO; + } + } + + /* Read last calculated ECC value */ + if (!read) + udelay(10); + host->ecc_buf[chip->ecc.steps - 1] = + readl(SLC_ECC(host->io_base)); + + /* Flush DMA */ + dmaengine_terminate_all(host->dma_chan); + + if (readl(SLC_STAT(host->io_base)) & SLCSTAT_DMA_FIFO || + readl(SLC_TC(host->io_base))) { + /* Something is left in the FIFO, something is wrong */ + dev_err(mtd->dev.parent, "DMA FIFO failure\n"); + status = -EIO; + } + + /* Stop DMA & HW ECC */ + writel(readl(SLC_CTRL(host->io_base)) & ~SLCCTRL_DMA_START, + SLC_CTRL(host->io_base)); + writel(readl(SLC_CFG(host->io_base)) & + ~(SLCCFG_DMA_DIR | SLCCFG_ECC_EN | SLCCFG_DMA_ECC | + SLCCFG_DMA_BURST), SLC_CFG(host->io_base)); + + if (!dma_mapped && read) + memcpy(buf, host->data_buf, mtd->writesize); + + return status; +} + +/* + * Read the data and OOB data from the device, use ECC correction with the + * data, disable ECC for the OOB data + */ +static int lpc32xx_nand_read_page_syndrome(struct mtd_info *mtd, + struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct lpc32xx_nand_host *host = chip->priv; + int stat, i, status; + uint8_t *oobecc, tmpecc[LPC32XX_ECC_SAVE_SIZE]; + + /* Issue read command */ + chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page); + + /* Read data and oob, calculate ECC */ + status = lpc32xx_xfer(mtd, buf, chip->ecc.steps, 1); + + /* Get OOB data */ + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + + /* Convert to stored ECC format */ + lpc32xx_slc_ecc_copy(tmpecc, (uint32_t *) host->ecc_buf, chip->ecc.steps); + + /* Pointer to ECC data retrieved from NAND spare area */ + oobecc = chip->oob_poi + chip->ecc.layout->eccpos[0]; + + for (i = 0; i < chip->ecc.steps; i++) { + stat = chip->ecc.correct(mtd, buf, oobecc, + &tmpecc[i * chip->ecc.bytes]); + if (stat < 0) + mtd->ecc_stats.failed++; + else + mtd->ecc_stats.corrected += stat; + + buf += chip->ecc.size; + oobecc += chip->ecc.bytes; + } + + return status; +} + +/* + * Read the data and OOB data from the device, no ECC correction with the + * data or OOB data + */ +static int lpc32xx_nand_read_page_raw_syndrome(struct mtd_info *mtd, + struct nand_chip *chip, + uint8_t *buf, int oob_required, + int page) +{ + /* Issue read command */ + chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page); + + /* Raw reads can just use the FIFO interface */ + chip->read_buf(mtd, buf, chip->ecc.size * chip->ecc.steps); + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + + return 0; +} + +/* + * Write the data and OOB data to the device, use ECC with the data, + * disable ECC for the OOB data + */ +static int lpc32xx_nand_write_page_syndrome(struct mtd_info *mtd, + struct nand_chip *chip, + const uint8_t *buf, int oob_required) +{ + struct lpc32xx_nand_host *host = chip->priv; + uint8_t *pb = chip->oob_poi + chip->ecc.layout->eccpos[0]; + int error; + + /* Write data, calculate ECC on outbound data */ + error = lpc32xx_xfer(mtd, (uint8_t *)buf, chip->ecc.steps, 0); + if (error) + return error; + + /* + * The calculated ECC needs some manual work done to it before + * committing it to NAND. Process the calculated ECC and place + * the resultant values directly into the OOB buffer. */ + lpc32xx_slc_ecc_copy(pb, (uint32_t *)host->ecc_buf, chip->ecc.steps); + + /* Write ECC data to device */ + chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); + return 0; +} + +/* + * Write the data and OOB data to the device, no ECC correction with the + * data or OOB data + */ +static int lpc32xx_nand_write_page_raw_syndrome(struct mtd_info *mtd, + struct nand_chip *chip, + const uint8_t *buf, + int oob_required) +{ + /* Raw writes can just use the FIFO interface */ + chip->write_buf(mtd, buf, chip->ecc.size * chip->ecc.steps); + chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); + return 0; +} + +static int lpc32xx_nand_dma_setup(struct lpc32xx_nand_host *host) +{ + struct mtd_info *mtd = &host->mtd; + dma_cap_mask_t mask; + + if (!host->pdata || !host->pdata->dma_filter) { + dev_err(mtd->dev.parent, "no DMA platform data\n"); + return -ENOENT; + } + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + host->dma_chan = dma_request_channel(mask, host->pdata->dma_filter, + "nand-slc"); + if (!host->dma_chan) { + dev_err(mtd->dev.parent, "Failed to request DMA channel\n"); + return -EBUSY; + } + + return 0; +} + +static struct lpc32xx_nand_cfg_slc *lpc32xx_parse_dt(struct device *dev) +{ + struct lpc32xx_nand_cfg_slc *ncfg; + struct device_node *np = dev->of_node; + + ncfg = devm_kzalloc(dev, sizeof(*ncfg), GFP_KERNEL); + if (!ncfg) + return NULL; + + of_property_read_u32(np, "nxp,wdr-clks", &ncfg->wdr_clks); + of_property_read_u32(np, "nxp,wwidth", &ncfg->wwidth); + of_property_read_u32(np, "nxp,whold", &ncfg->whold); + of_property_read_u32(np, "nxp,wsetup", &ncfg->wsetup); + of_property_read_u32(np, "nxp,rdr-clks", &ncfg->rdr_clks); + of_property_read_u32(np, "nxp,rwidth", &ncfg->rwidth); + of_property_read_u32(np, "nxp,rhold", &ncfg->rhold); + of_property_read_u32(np, "nxp,rsetup", &ncfg->rsetup); + + if (!ncfg->wdr_clks || !ncfg->wwidth || !ncfg->whold || + !ncfg->wsetup || !ncfg->rdr_clks || !ncfg->rwidth || + !ncfg->rhold || !ncfg->rsetup) { + dev_err(dev, "chip parameters not specified correctly\n"); + return NULL; + } + + ncfg->use_bbt = of_get_nand_on_flash_bbt(np); + ncfg->wp_gpio = of_get_named_gpio(np, "gpios", 0); + + return ncfg; +} + +/* + * Probe for NAND controller + */ +static int lpc32xx_nand_probe(struct platform_device *pdev) +{ + struct lpc32xx_nand_host *host; + struct mtd_info *mtd; + struct nand_chip *chip; + struct resource *rc; + struct mtd_part_parser_data ppdata = {}; + int res; + + rc = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (rc == NULL) { + dev_err(&pdev->dev, "No memory resource found for device\n"); + return -EBUSY; + } + + /* Allocate memory for the device structure (and zero it) */ + host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + host->io_base_dma = rc->start; + + host->io_base = devm_ioremap_resource(&pdev->dev, rc); + if (IS_ERR(host->io_base)) + return PTR_ERR(host->io_base); + + if (pdev->dev.of_node) + host->ncfg = lpc32xx_parse_dt(&pdev->dev); + if (!host->ncfg) { + dev_err(&pdev->dev, + "Missing or bad NAND config from device tree\n"); + return -ENOENT; + } + if (host->ncfg->wp_gpio == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (gpio_is_valid(host->ncfg->wp_gpio) && devm_gpio_request(&pdev->dev, + host->ncfg->wp_gpio, "NAND WP")) { + dev_err(&pdev->dev, "GPIO not available\n"); + return -EBUSY; + } + lpc32xx_wp_disable(host); + + host->pdata = dev_get_platdata(&pdev->dev); + + mtd = &host->mtd; + chip = &host->nand_chip; + chip->priv = host; + mtd->priv = chip; + mtd->owner = THIS_MODULE; + mtd->dev.parent = &pdev->dev; + + /* Get NAND clock */ + host->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(host->clk)) { + dev_err(&pdev->dev, "Clock failure\n"); + res = -ENOENT; + goto err_exit1; + } + clk_enable(host->clk); + + /* Set NAND IO addresses and command/ready functions */ + chip->IO_ADDR_R = SLC_DATA(host->io_base); + chip->IO_ADDR_W = SLC_DATA(host->io_base); + chip->cmd_ctrl = lpc32xx_nand_cmd_ctrl; + chip->dev_ready = lpc32xx_nand_device_ready; + chip->chip_delay = 20; /* 20us command delay time */ + + /* Init NAND controller */ + lpc32xx_nand_setup(host); + + platform_set_drvdata(pdev, host); + + /* NAND callbacks for LPC32xx SLC hardware */ + chip->ecc.mode = NAND_ECC_HW_SYNDROME; + chip->read_byte = lpc32xx_nand_read_byte; + chip->read_buf = lpc32xx_nand_read_buf; + chip->write_buf = lpc32xx_nand_write_buf; + chip->ecc.read_page_raw = lpc32xx_nand_read_page_raw_syndrome; + chip->ecc.read_page = lpc32xx_nand_read_page_syndrome; + chip->ecc.write_page_raw = lpc32xx_nand_write_page_raw_syndrome; + chip->ecc.write_page = lpc32xx_nand_write_page_syndrome; + chip->ecc.write_oob = lpc32xx_nand_write_oob_syndrome; + chip->ecc.read_oob = lpc32xx_nand_read_oob_syndrome; + chip->ecc.calculate = lpc32xx_nand_ecc_calculate; + chip->ecc.correct = nand_correct_data; + chip->ecc.strength = 1; + chip->ecc.hwctl = lpc32xx_nand_ecc_enable; + + /* bitflip_threshold's default is defined as ecc_strength anyway. + * Unfortunately, it is set only later at add_mtd_device(). Meanwhile + * being 0, it causes bad block table scanning errors in + * nand_scan_tail(), so preparing it here already. */ + mtd->bitflip_threshold = chip->ecc.strength; + + /* + * Allocate a large enough buffer for a single huge page plus + * extra space for the spare area and ECC storage area + */ + host->dma_buf_len = LPC32XX_DMA_DATA_SIZE + LPC32XX_ECC_SAVE_SIZE; + host->data_buf = devm_kzalloc(&pdev->dev, host->dma_buf_len, + GFP_KERNEL); + if (host->data_buf == NULL) { + res = -ENOMEM; + goto err_exit2; + } + + res = lpc32xx_nand_dma_setup(host); + if (res) { + res = -EIO; + goto err_exit2; + } + + /* Find NAND device */ + if (nand_scan_ident(mtd, 1, NULL)) { + res = -ENXIO; + goto err_exit3; + } + + /* OOB and ECC CPU and DMA work areas */ + host->ecc_buf = (uint32_t *)(host->data_buf + LPC32XX_DMA_DATA_SIZE); + + /* + * Small page FLASH has a unique OOB layout, but large and huge + * page FLASH use the standard layout. Small page FLASH uses a + * custom BBT marker layout. + */ + if (mtd->writesize <= 512) + chip->ecc.layout = &lpc32xx_nand_oob_16; + + /* These sizes remain the same regardless of page size */ + chip->ecc.size = 256; + chip->ecc.bytes = LPC32XX_SLC_DEV_ECC_BYTES; + chip->ecc.prepad = chip->ecc.postpad = 0; + + /* Avoid extra scan if using BBT, setup BBT support */ + if (host->ncfg->use_bbt) { + chip->bbt_options |= NAND_BBT_USE_FLASH; + + /* + * Use a custom BBT marker setup for small page FLASH that + * won't interfere with the ECC layout. Large and huge page + * FLASH use the standard layout. + */ + if (mtd->writesize <= 512) { + chip->bbt_td = &bbt_smallpage_main_descr; + chip->bbt_md = &bbt_smallpage_mirror_descr; + } + } + + /* + * Fills out all the uninitialized function pointers with the defaults + */ + if (nand_scan_tail(mtd)) { + res = -ENXIO; + goto err_exit3; + } + + mtd->name = "nxp_lpc3220_slc"; + ppdata.of_node = pdev->dev.of_node; + res = mtd_device_parse_register(mtd, NULL, &ppdata, host->ncfg->parts, + host->ncfg->num_parts); + if (!res) + return res; + + nand_release(mtd); + +err_exit3: + dma_release_channel(host->dma_chan); +err_exit2: + clk_disable(host->clk); +err_exit1: + lpc32xx_wp_enable(host); + + return res; +} + +/* + * Remove NAND device. + */ +static int lpc32xx_nand_remove(struct platform_device *pdev) +{ + uint32_t tmp; + struct lpc32xx_nand_host *host = platform_get_drvdata(pdev); + struct mtd_info *mtd = &host->mtd; + + nand_release(mtd); + dma_release_channel(host->dma_chan); + + /* Force CE high */ + tmp = readl(SLC_CTRL(host->io_base)); + tmp &= ~SLCCFG_CE_LOW; + writel(tmp, SLC_CTRL(host->io_base)); + + clk_disable(host->clk); + lpc32xx_wp_enable(host); + + return 0; +} + +#ifdef CONFIG_PM +static int lpc32xx_nand_resume(struct platform_device *pdev) +{ + struct lpc32xx_nand_host *host = platform_get_drvdata(pdev); + + /* Re-enable NAND clock */ + clk_enable(host->clk); + + /* Fresh init of NAND controller */ + lpc32xx_nand_setup(host); + + /* Disable write protect */ + lpc32xx_wp_disable(host); + + return 0; +} + +static int lpc32xx_nand_suspend(struct platform_device *pdev, pm_message_t pm) +{ + uint32_t tmp; + struct lpc32xx_nand_host *host = platform_get_drvdata(pdev); + + /* Force CE high */ + tmp = readl(SLC_CTRL(host->io_base)); + tmp &= ~SLCCFG_CE_LOW; + writel(tmp, SLC_CTRL(host->io_base)); + + /* Enable write protect for safety */ + lpc32xx_wp_enable(host); + + /* Disable clock */ + clk_disable(host->clk); + + return 0; +} + +#else +#define lpc32xx_nand_resume NULL +#define lpc32xx_nand_suspend NULL +#endif + +static const struct of_device_id lpc32xx_nand_match[] = { + { .compatible = "nxp,lpc3220-slc" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, lpc32xx_nand_match); + +static struct platform_driver lpc32xx_nand_driver = { + .probe = lpc32xx_nand_probe, + .remove = lpc32xx_nand_remove, + .resume = lpc32xx_nand_resume, + .suspend = lpc32xx_nand_suspend, + .driver = { + .name = LPC32XX_MODNAME, + .owner = THIS_MODULE, + .of_match_table = lpc32xx_nand_match, + }, +}; + +module_platform_driver(lpc32xx_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kevin Wells <kevin.wells@nxp.com>"); +MODULE_AUTHOR("Roland Stigge <stigge@antcom.de>"); +MODULE_DESCRIPTION("NAND driver for the NXP LPC32XX SLC controller"); diff --git a/drivers/mtd/nand/mpc5121_nfc.c b/drivers/mtd/nand/mpc5121_nfc.c new file mode 100644 index 00000000000..e78841a2dcc --- /dev/null +++ b/drivers/mtd/nand/mpc5121_nfc.c @@ -0,0 +1,859 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. + * Copyright 2009 Semihalf. + * + * Approved as OSADL project by a majority of OSADL members and funded + * by OSADL membership fees in 2009; for details see www.osadl.org. + * + * Based on original driver from Freescale Semiconductor + * written by John Rigby <jrigby@freescale.com> on basis + * of drivers/mtd/nand/mxc_nand.c. Reworked and extended + * Piotr Ziecik <kosmo@semihalf.com>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/gfp.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> + +#include <asm/mpc5121.h> + +/* Addresses for NFC MAIN RAM BUFFER areas */ +#define NFC_MAIN_AREA(n) ((n) * 0x200) + +/* Addresses for NFC SPARE BUFFER areas */ +#define NFC_SPARE_BUFFERS 8 +#define NFC_SPARE_LEN 0x40 +#define NFC_SPARE_AREA(n) (0x1000 + ((n) * NFC_SPARE_LEN)) + +/* MPC5121 NFC registers */ +#define NFC_BUF_ADDR 0x1E04 +#define NFC_FLASH_ADDR 0x1E06 +#define NFC_FLASH_CMD 0x1E08 +#define NFC_CONFIG 0x1E0A +#define NFC_ECC_STATUS1 0x1E0C +#define NFC_ECC_STATUS2 0x1E0E +#define NFC_SPAS 0x1E10 +#define NFC_WRPROT 0x1E12 +#define NFC_NF_WRPRST 0x1E18 +#define NFC_CONFIG1 0x1E1A +#define NFC_CONFIG2 0x1E1C +#define NFC_UNLOCKSTART_BLK0 0x1E20 +#define NFC_UNLOCKEND_BLK0 0x1E22 +#define NFC_UNLOCKSTART_BLK1 0x1E24 +#define NFC_UNLOCKEND_BLK1 0x1E26 +#define NFC_UNLOCKSTART_BLK2 0x1E28 +#define NFC_UNLOCKEND_BLK2 0x1E2A +#define NFC_UNLOCKSTART_BLK3 0x1E2C +#define NFC_UNLOCKEND_BLK3 0x1E2E + +/* Bit Definitions: NFC_BUF_ADDR */ +#define NFC_RBA_MASK (7 << 0) +#define NFC_ACTIVE_CS_SHIFT 5 +#define NFC_ACTIVE_CS_MASK (3 << NFC_ACTIVE_CS_SHIFT) + +/* Bit Definitions: NFC_CONFIG */ +#define NFC_BLS_UNLOCKED (1 << 1) + +/* Bit Definitions: NFC_CONFIG1 */ +#define NFC_ECC_4BIT (1 << 0) +#define NFC_FULL_PAGE_DMA (1 << 1) +#define NFC_SPARE_ONLY (1 << 2) +#define NFC_ECC_ENABLE (1 << 3) +#define NFC_INT_MASK (1 << 4) +#define NFC_BIG_ENDIAN (1 << 5) +#define NFC_RESET (1 << 6) +#define NFC_CE (1 << 7) +#define NFC_ONE_CYCLE (1 << 8) +#define NFC_PPB_32 (0 << 9) +#define NFC_PPB_64 (1 << 9) +#define NFC_PPB_128 (2 << 9) +#define NFC_PPB_256 (3 << 9) +#define NFC_PPB_MASK (3 << 9) +#define NFC_FULL_PAGE_INT (1 << 11) + +/* Bit Definitions: NFC_CONFIG2 */ +#define NFC_COMMAND (1 << 0) +#define NFC_ADDRESS (1 << 1) +#define NFC_INPUT (1 << 2) +#define NFC_OUTPUT (1 << 3) +#define NFC_ID (1 << 4) +#define NFC_STATUS (1 << 5) +#define NFC_CMD_FAIL (1 << 15) +#define NFC_INT (1 << 15) + +/* Bit Definitions: NFC_WRPROT */ +#define NFC_WPC_LOCK_TIGHT (1 << 0) +#define NFC_WPC_LOCK (1 << 1) +#define NFC_WPC_UNLOCK (1 << 2) + +#define DRV_NAME "mpc5121_nfc" + +/* Timeouts */ +#define NFC_RESET_TIMEOUT 1000 /* 1 ms */ +#define NFC_TIMEOUT (HZ / 10) /* 1/10 s */ + +struct mpc5121_nfc_prv { + struct mtd_info mtd; + struct nand_chip chip; + int irq; + void __iomem *regs; + struct clk *clk; + wait_queue_head_t irq_waitq; + uint column; + int spareonly; + void __iomem *csreg; + struct device *dev; +}; + +static void mpc5121_nfc_done(struct mtd_info *mtd); + +/* Read NFC register */ +static inline u16 nfc_read(struct mtd_info *mtd, uint reg) +{ + struct nand_chip *chip = mtd->priv; + struct mpc5121_nfc_prv *prv = chip->priv; + + return in_be16(prv->regs + reg); +} + +/* Write NFC register */ +static inline void nfc_write(struct mtd_info *mtd, uint reg, u16 val) +{ + struct nand_chip *chip = mtd->priv; + struct mpc5121_nfc_prv *prv = chip->priv; + + out_be16(prv->regs + reg, val); +} + +/* Set bits in NFC register */ +static inline void nfc_set(struct mtd_info *mtd, uint reg, u16 bits) +{ + nfc_write(mtd, reg, nfc_read(mtd, reg) | bits); +} + +/* Clear bits in NFC register */ +static inline void nfc_clear(struct mtd_info *mtd, uint reg, u16 bits) +{ + nfc_write(mtd, reg, nfc_read(mtd, reg) & ~bits); +} + +/* Invoke address cycle */ +static inline void mpc5121_nfc_send_addr(struct mtd_info *mtd, u16 addr) +{ + nfc_write(mtd, NFC_FLASH_ADDR, addr); + nfc_write(mtd, NFC_CONFIG2, NFC_ADDRESS); + mpc5121_nfc_done(mtd); +} + +/* Invoke command cycle */ +static inline void mpc5121_nfc_send_cmd(struct mtd_info *mtd, u16 cmd) +{ + nfc_write(mtd, NFC_FLASH_CMD, cmd); + nfc_write(mtd, NFC_CONFIG2, NFC_COMMAND); + mpc5121_nfc_done(mtd); +} + +/* Send data from NFC buffers to NAND flash */ +static inline void mpc5121_nfc_send_prog_page(struct mtd_info *mtd) +{ + nfc_clear(mtd, NFC_BUF_ADDR, NFC_RBA_MASK); + nfc_write(mtd, NFC_CONFIG2, NFC_INPUT); + mpc5121_nfc_done(mtd); +} + +/* Receive data from NAND flash */ +static inline void mpc5121_nfc_send_read_page(struct mtd_info *mtd) +{ + nfc_clear(mtd, NFC_BUF_ADDR, NFC_RBA_MASK); + nfc_write(mtd, NFC_CONFIG2, NFC_OUTPUT); + mpc5121_nfc_done(mtd); +} + +/* Receive ID from NAND flash */ +static inline void mpc5121_nfc_send_read_id(struct mtd_info *mtd) +{ + nfc_clear(mtd, NFC_BUF_ADDR, NFC_RBA_MASK); + nfc_write(mtd, NFC_CONFIG2, NFC_ID); + mpc5121_nfc_done(mtd); +} + +/* Receive status from NAND flash */ +static inline void mpc5121_nfc_send_read_status(struct mtd_info *mtd) +{ + nfc_clear(mtd, NFC_BUF_ADDR, NFC_RBA_MASK); + nfc_write(mtd, NFC_CONFIG2, NFC_STATUS); + mpc5121_nfc_done(mtd); +} + +/* NFC interrupt handler */ +static irqreturn_t mpc5121_nfc_irq(int irq, void *data) +{ + struct mtd_info *mtd = data; + struct nand_chip *chip = mtd->priv; + struct mpc5121_nfc_prv *prv = chip->priv; + + nfc_set(mtd, NFC_CONFIG1, NFC_INT_MASK); + wake_up(&prv->irq_waitq); + + return IRQ_HANDLED; +} + +/* Wait for operation complete */ +static void mpc5121_nfc_done(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct mpc5121_nfc_prv *prv = chip->priv; + int rv; + + if ((nfc_read(mtd, NFC_CONFIG2) & NFC_INT) == 0) { + nfc_clear(mtd, NFC_CONFIG1, NFC_INT_MASK); + rv = wait_event_timeout(prv->irq_waitq, + (nfc_read(mtd, NFC_CONFIG2) & NFC_INT), NFC_TIMEOUT); + + if (!rv) + dev_warn(prv->dev, + "Timeout while waiting for interrupt.\n"); + } + + nfc_clear(mtd, NFC_CONFIG2, NFC_INT); +} + +/* Do address cycle(s) */ +static void mpc5121_nfc_addr_cycle(struct mtd_info *mtd, int column, int page) +{ + struct nand_chip *chip = mtd->priv; + u32 pagemask = chip->pagemask; + + if (column != -1) { + mpc5121_nfc_send_addr(mtd, column); + if (mtd->writesize > 512) + mpc5121_nfc_send_addr(mtd, column >> 8); + } + + if (page != -1) { + do { + mpc5121_nfc_send_addr(mtd, page & 0xFF); + page >>= 8; + pagemask >>= 8; + } while (pagemask); + } +} + +/* Control chip select signals */ +static void mpc5121_nfc_select_chip(struct mtd_info *mtd, int chip) +{ + if (chip < 0) { + nfc_clear(mtd, NFC_CONFIG1, NFC_CE); + return; + } + + nfc_clear(mtd, NFC_BUF_ADDR, NFC_ACTIVE_CS_MASK); + nfc_set(mtd, NFC_BUF_ADDR, (chip << NFC_ACTIVE_CS_SHIFT) & + NFC_ACTIVE_CS_MASK); + nfc_set(mtd, NFC_CONFIG1, NFC_CE); +} + +/* Init external chip select logic on ADS5121 board */ +static int ads5121_chipselect_init(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct mpc5121_nfc_prv *prv = chip->priv; + struct device_node *dn; + + dn = of_find_compatible_node(NULL, NULL, "fsl,mpc5121ads-cpld"); + if (dn) { + prv->csreg = of_iomap(dn, 0); + of_node_put(dn); + if (!prv->csreg) + return -ENOMEM; + + /* CPLD Register 9 controls NAND /CE Lines */ + prv->csreg += 9; + return 0; + } + + return -EINVAL; +} + +/* Control chips select signal on ADS5121 board */ +static void ads5121_select_chip(struct mtd_info *mtd, int chip) +{ + struct nand_chip *nand = mtd->priv; + struct mpc5121_nfc_prv *prv = nand->priv; + u8 v; + + v = in_8(prv->csreg); + v |= 0x0F; + + if (chip >= 0) { + mpc5121_nfc_select_chip(mtd, 0); + v &= ~(1 << chip); + } else + mpc5121_nfc_select_chip(mtd, -1); + + out_8(prv->csreg, v); +} + +/* Read NAND Ready/Busy signal */ +static int mpc5121_nfc_dev_ready(struct mtd_info *mtd) +{ + /* + * NFC handles ready/busy signal internally. Therefore, this function + * always returns status as ready. + */ + return 1; +} + +/* Write command to NAND flash */ +static void mpc5121_nfc_command(struct mtd_info *mtd, unsigned command, + int column, int page) +{ + struct nand_chip *chip = mtd->priv; + struct mpc5121_nfc_prv *prv = chip->priv; + + prv->column = (column >= 0) ? column : 0; + prv->spareonly = 0; + + switch (command) { + case NAND_CMD_PAGEPROG: + mpc5121_nfc_send_prog_page(mtd); + break; + /* + * NFC does not support sub-page reads and writes, + * so emulate them using full page transfers. + */ + case NAND_CMD_READ0: + column = 0; + break; + + case NAND_CMD_READ1: + prv->column += 256; + command = NAND_CMD_READ0; + column = 0; + break; + + case NAND_CMD_READOOB: + prv->spareonly = 1; + command = NAND_CMD_READ0; + column = 0; + break; + + case NAND_CMD_SEQIN: + mpc5121_nfc_command(mtd, NAND_CMD_READ0, column, page); + column = 0; + break; + + case NAND_CMD_ERASE1: + case NAND_CMD_ERASE2: + case NAND_CMD_READID: + case NAND_CMD_STATUS: + break; + + default: + return; + } + + mpc5121_nfc_send_cmd(mtd, command); + mpc5121_nfc_addr_cycle(mtd, column, page); + + switch (command) { + case NAND_CMD_READ0: + if (mtd->writesize > 512) + mpc5121_nfc_send_cmd(mtd, NAND_CMD_READSTART); + mpc5121_nfc_send_read_page(mtd); + break; + + case NAND_CMD_READID: + mpc5121_nfc_send_read_id(mtd); + break; + + case NAND_CMD_STATUS: + mpc5121_nfc_send_read_status(mtd); + if (chip->options & NAND_BUSWIDTH_16) + prv->column = 1; + else + prv->column = 0; + break; + } +} + +/* Copy data from/to NFC spare buffers. */ +static void mpc5121_nfc_copy_spare(struct mtd_info *mtd, uint offset, + u8 *buffer, uint size, int wr) +{ + struct nand_chip *nand = mtd->priv; + struct mpc5121_nfc_prv *prv = nand->priv; + uint o, s, sbsize, blksize; + + /* + * NAND spare area is available through NFC spare buffers. + * The NFC divides spare area into (page_size / 512) chunks. + * Each chunk is placed into separate spare memory area, using + * first (spare_size / num_of_chunks) bytes of the buffer. + * + * For NAND device in which the spare area is not divided fully + * by the number of chunks, number of used bytes in each spare + * buffer is rounded down to the nearest even number of bytes, + * and all remaining bytes are added to the last used spare area. + * + * For more information read section 26.6.10 of MPC5121e + * Microcontroller Reference Manual, Rev. 3. + */ + + /* Calculate number of valid bytes in each spare buffer */ + sbsize = (mtd->oobsize / (mtd->writesize / 512)) & ~1; + + while (size) { + /* Calculate spare buffer number */ + s = offset / sbsize; + if (s > NFC_SPARE_BUFFERS - 1) + s = NFC_SPARE_BUFFERS - 1; + + /* + * Calculate offset to requested data block in selected spare + * buffer and its size. + */ + o = offset - (s * sbsize); + blksize = min(sbsize - o, size); + + if (wr) + memcpy_toio(prv->regs + NFC_SPARE_AREA(s) + o, + buffer, blksize); + else + memcpy_fromio(buffer, + prv->regs + NFC_SPARE_AREA(s) + o, blksize); + + buffer += blksize; + offset += blksize; + size -= blksize; + }; +} + +/* Copy data from/to NFC main and spare buffers */ +static void mpc5121_nfc_buf_copy(struct mtd_info *mtd, u_char *buf, int len, + int wr) +{ + struct nand_chip *chip = mtd->priv; + struct mpc5121_nfc_prv *prv = chip->priv; + uint c = prv->column; + uint l; + + /* Handle spare area access */ + if (prv->spareonly || c >= mtd->writesize) { + /* Calculate offset from beginning of spare area */ + if (c >= mtd->writesize) + c -= mtd->writesize; + + prv->column += len; + mpc5121_nfc_copy_spare(mtd, c, buf, len, wr); + return; + } + + /* + * Handle main area access - limit copy length to prevent + * crossing main/spare boundary. + */ + l = min((uint)len, mtd->writesize - c); + prv->column += l; + + if (wr) + memcpy_toio(prv->regs + NFC_MAIN_AREA(0) + c, buf, l); + else + memcpy_fromio(buf, prv->regs + NFC_MAIN_AREA(0) + c, l); + + /* Handle crossing main/spare boundary */ + if (l != len) { + buf += l; + len -= l; + mpc5121_nfc_buf_copy(mtd, buf, len, wr); + } +} + +/* Read data from NFC buffers */ +static void mpc5121_nfc_read_buf(struct mtd_info *mtd, u_char *buf, int len) +{ + mpc5121_nfc_buf_copy(mtd, buf, len, 0); +} + +/* Write data to NFC buffers */ +static void mpc5121_nfc_write_buf(struct mtd_info *mtd, + const u_char *buf, int len) +{ + mpc5121_nfc_buf_copy(mtd, (u_char *)buf, len, 1); +} + +/* Read byte from NFC buffers */ +static u8 mpc5121_nfc_read_byte(struct mtd_info *mtd) +{ + u8 tmp; + + mpc5121_nfc_read_buf(mtd, &tmp, sizeof(tmp)); + + return tmp; +} + +/* Read word from NFC buffers */ +static u16 mpc5121_nfc_read_word(struct mtd_info *mtd) +{ + u16 tmp; + + mpc5121_nfc_read_buf(mtd, (u_char *)&tmp, sizeof(tmp)); + + return tmp; +} + +/* + * Read NFC configuration from Reset Config Word + * + * NFC is configured during reset in basis of information stored + * in Reset Config Word. There is no other way to set NAND block + * size, spare size and bus width. + */ +static int mpc5121_nfc_read_hw_config(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct mpc5121_nfc_prv *prv = chip->priv; + struct mpc512x_reset_module *rm; + struct device_node *rmnode; + uint rcw_pagesize = 0; + uint rcw_sparesize = 0; + uint rcw_width; + uint rcwh; + uint romloc, ps; + int ret = 0; + + rmnode = of_find_compatible_node(NULL, NULL, "fsl,mpc5121-reset"); + if (!rmnode) { + dev_err(prv->dev, "Missing 'fsl,mpc5121-reset' " + "node in device tree!\n"); + return -ENODEV; + } + + rm = of_iomap(rmnode, 0); + if (!rm) { + dev_err(prv->dev, "Error mapping reset module node!\n"); + ret = -EBUSY; + goto out; + } + + rcwh = in_be32(&rm->rcwhr); + + /* Bit 6: NFC bus width */ + rcw_width = ((rcwh >> 6) & 0x1) ? 2 : 1; + + /* Bit 7: NFC Page/Spare size */ + ps = (rcwh >> 7) & 0x1; + + /* Bits [22:21]: ROM Location */ + romloc = (rcwh >> 21) & 0x3; + + /* Decode RCW bits */ + switch ((ps << 2) | romloc) { + case 0x00: + case 0x01: + rcw_pagesize = 512; + rcw_sparesize = 16; + break; + case 0x02: + case 0x03: + rcw_pagesize = 4096; + rcw_sparesize = 128; + break; + case 0x04: + case 0x05: + rcw_pagesize = 2048; + rcw_sparesize = 64; + break; + case 0x06: + case 0x07: + rcw_pagesize = 4096; + rcw_sparesize = 218; + break; + } + + mtd->writesize = rcw_pagesize; + mtd->oobsize = rcw_sparesize; + if (rcw_width == 2) + chip->options |= NAND_BUSWIDTH_16; + + dev_notice(prv->dev, "Configured for " + "%u-bit NAND, page size %u " + "with %u spare.\n", + rcw_width * 8, rcw_pagesize, + rcw_sparesize); + iounmap(rm); +out: + of_node_put(rmnode); + return ret; +} + +/* Free driver resources */ +static void mpc5121_nfc_free(struct device *dev, struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct mpc5121_nfc_prv *prv = chip->priv; + + if (prv->clk) + clk_disable_unprepare(prv->clk); + + if (prv->csreg) + iounmap(prv->csreg); +} + +static int mpc5121_nfc_probe(struct platform_device *op) +{ + struct device_node *rootnode, *dn = op->dev.of_node; + struct clk *clk; + struct device *dev = &op->dev; + struct mpc5121_nfc_prv *prv; + struct resource res; + struct mtd_info *mtd; + struct nand_chip *chip; + unsigned long regs_paddr, regs_size; + const __be32 *chips_no; + int resettime = 0; + int retval = 0; + int rev, len; + struct mtd_part_parser_data ppdata; + + /* + * Check SoC revision. This driver supports only NFC + * in MPC5121 revision 2 and MPC5123 revision 3. + */ + rev = (mfspr(SPRN_SVR) >> 4) & 0xF; + if ((rev != 2) && (rev != 3)) { + dev_err(dev, "SoC revision %u is not supported!\n", rev); + return -ENXIO; + } + + prv = devm_kzalloc(dev, sizeof(*prv), GFP_KERNEL); + if (!prv) + return -ENOMEM; + + mtd = &prv->mtd; + chip = &prv->chip; + + mtd->priv = chip; + chip->priv = prv; + prv->dev = dev; + + /* Read NFC configuration from Reset Config Word */ + retval = mpc5121_nfc_read_hw_config(mtd); + if (retval) { + dev_err(dev, "Unable to read NFC config!\n"); + return retval; + } + + prv->irq = irq_of_parse_and_map(dn, 0); + if (prv->irq == NO_IRQ) { + dev_err(dev, "Error mapping IRQ!\n"); + return -EINVAL; + } + + retval = of_address_to_resource(dn, 0, &res); + if (retval) { + dev_err(dev, "Error parsing memory region!\n"); + return retval; + } + + chips_no = of_get_property(dn, "chips", &len); + if (!chips_no || len != sizeof(*chips_no)) { + dev_err(dev, "Invalid/missing 'chips' property!\n"); + return -EINVAL; + } + + regs_paddr = res.start; + regs_size = resource_size(&res); + + if (!devm_request_mem_region(dev, regs_paddr, regs_size, DRV_NAME)) { + dev_err(dev, "Error requesting memory region!\n"); + return -EBUSY; + } + + prv->regs = devm_ioremap(dev, regs_paddr, regs_size); + if (!prv->regs) { + dev_err(dev, "Error mapping memory region!\n"); + return -ENOMEM; + } + + mtd->name = "MPC5121 NAND"; + ppdata.of_node = dn; + chip->dev_ready = mpc5121_nfc_dev_ready; + chip->cmdfunc = mpc5121_nfc_command; + chip->read_byte = mpc5121_nfc_read_byte; + chip->read_word = mpc5121_nfc_read_word; + chip->read_buf = mpc5121_nfc_read_buf; + chip->write_buf = mpc5121_nfc_write_buf; + chip->select_chip = mpc5121_nfc_select_chip; + chip->bbt_options = NAND_BBT_USE_FLASH; + chip->ecc.mode = NAND_ECC_SOFT; + + /* Support external chip-select logic on ADS5121 board */ + rootnode = of_find_node_by_path("/"); + if (of_device_is_compatible(rootnode, "fsl,mpc5121ads")) { + retval = ads5121_chipselect_init(mtd); + if (retval) { + dev_err(dev, "Chipselect init error!\n"); + of_node_put(rootnode); + return retval; + } + + chip->select_chip = ads5121_select_chip; + } + of_node_put(rootnode); + + /* Enable NFC clock */ + clk = devm_clk_get(dev, "ipg"); + if (IS_ERR(clk)) { + dev_err(dev, "Unable to acquire NFC clock!\n"); + retval = PTR_ERR(clk); + goto error; + } + retval = clk_prepare_enable(clk); + if (retval) { + dev_err(dev, "Unable to enable NFC clock!\n"); + goto error; + } + prv->clk = clk; + + /* Reset NAND Flash controller */ + nfc_set(mtd, NFC_CONFIG1, NFC_RESET); + while (nfc_read(mtd, NFC_CONFIG1) & NFC_RESET) { + if (resettime++ >= NFC_RESET_TIMEOUT) { + dev_err(dev, "Timeout while resetting NFC!\n"); + retval = -EINVAL; + goto error; + } + + udelay(1); + } + + /* Enable write to NFC memory */ + nfc_write(mtd, NFC_CONFIG, NFC_BLS_UNLOCKED); + + /* Enable write to all NAND pages */ + nfc_write(mtd, NFC_UNLOCKSTART_BLK0, 0x0000); + nfc_write(mtd, NFC_UNLOCKEND_BLK0, 0xFFFF); + nfc_write(mtd, NFC_WRPROT, NFC_WPC_UNLOCK); + + /* + * Setup NFC: + * - Big Endian transfers, + * - Interrupt after full page read/write. + */ + nfc_write(mtd, NFC_CONFIG1, NFC_BIG_ENDIAN | NFC_INT_MASK | + NFC_FULL_PAGE_INT); + + /* Set spare area size */ + nfc_write(mtd, NFC_SPAS, mtd->oobsize >> 1); + + init_waitqueue_head(&prv->irq_waitq); + retval = devm_request_irq(dev, prv->irq, &mpc5121_nfc_irq, 0, DRV_NAME, + mtd); + if (retval) { + dev_err(dev, "Error requesting IRQ!\n"); + goto error; + } + + /* Detect NAND chips */ + if (nand_scan(mtd, be32_to_cpup(chips_no))) { + dev_err(dev, "NAND Flash not found !\n"); + retval = -ENXIO; + goto error; + } + + /* Set erase block size */ + switch (mtd->erasesize / mtd->writesize) { + case 32: + nfc_set(mtd, NFC_CONFIG1, NFC_PPB_32); + break; + + case 64: + nfc_set(mtd, NFC_CONFIG1, NFC_PPB_64); + break; + + case 128: + nfc_set(mtd, NFC_CONFIG1, NFC_PPB_128); + break; + + case 256: + nfc_set(mtd, NFC_CONFIG1, NFC_PPB_256); + break; + + default: + dev_err(dev, "Unsupported NAND flash!\n"); + retval = -ENXIO; + goto error; + } + + dev_set_drvdata(dev, mtd); + + /* Register device in MTD */ + retval = mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0); + if (retval) { + dev_err(dev, "Error adding MTD device!\n"); + goto error; + } + + return 0; +error: + mpc5121_nfc_free(dev, mtd); + return retval; +} + +static int mpc5121_nfc_remove(struct platform_device *op) +{ + struct device *dev = &op->dev; + struct mtd_info *mtd = dev_get_drvdata(dev); + + nand_release(mtd); + mpc5121_nfc_free(dev, mtd); + + return 0; +} + +static struct of_device_id mpc5121_nfc_match[] = { + { .compatible = "fsl,mpc5121-nfc", }, + {}, +}; + +static struct platform_driver mpc5121_nfc_driver = { + .probe = mpc5121_nfc_probe, + .remove = mpc5121_nfc_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = mpc5121_nfc_match, + }, +}; + +module_platform_driver(mpc5121_nfc_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MPC5121 NAND MTD driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/mxc_nand.c b/drivers/mtd/nand/mxc_nand.c new file mode 100644 index 00000000000..dba262bf766 --- /dev/null +++ b/drivers/mtd/nand/mxc_nand.c @@ -0,0 +1,1614 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Sascha Hauer, kernel@pengutronix.de + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/completion.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_mtd.h> + +#include <asm/mach/flash.h> +#include <linux/platform_data/mtd-mxc_nand.h> + +#define DRIVER_NAME "mxc_nand" + +/* Addresses for NFC registers */ +#define NFC_V1_V2_BUF_SIZE (host->regs + 0x00) +#define NFC_V1_V2_BUF_ADDR (host->regs + 0x04) +#define NFC_V1_V2_FLASH_ADDR (host->regs + 0x06) +#define NFC_V1_V2_FLASH_CMD (host->regs + 0x08) +#define NFC_V1_V2_CONFIG (host->regs + 0x0a) +#define NFC_V1_V2_ECC_STATUS_RESULT (host->regs + 0x0c) +#define NFC_V1_V2_RSLTMAIN_AREA (host->regs + 0x0e) +#define NFC_V1_V2_RSLTSPARE_AREA (host->regs + 0x10) +#define NFC_V1_V2_WRPROT (host->regs + 0x12) +#define NFC_V1_UNLOCKSTART_BLKADDR (host->regs + 0x14) +#define NFC_V1_UNLOCKEND_BLKADDR (host->regs + 0x16) +#define NFC_V21_UNLOCKSTART_BLKADDR0 (host->regs + 0x20) +#define NFC_V21_UNLOCKSTART_BLKADDR1 (host->regs + 0x24) +#define NFC_V21_UNLOCKSTART_BLKADDR2 (host->regs + 0x28) +#define NFC_V21_UNLOCKSTART_BLKADDR3 (host->regs + 0x2c) +#define NFC_V21_UNLOCKEND_BLKADDR0 (host->regs + 0x22) +#define NFC_V21_UNLOCKEND_BLKADDR1 (host->regs + 0x26) +#define NFC_V21_UNLOCKEND_BLKADDR2 (host->regs + 0x2a) +#define NFC_V21_UNLOCKEND_BLKADDR3 (host->regs + 0x2e) +#define NFC_V1_V2_NF_WRPRST (host->regs + 0x18) +#define NFC_V1_V2_CONFIG1 (host->regs + 0x1a) +#define NFC_V1_V2_CONFIG2 (host->regs + 0x1c) + +#define NFC_V2_CONFIG1_ECC_MODE_4 (1 << 0) +#define NFC_V1_V2_CONFIG1_SP_EN (1 << 2) +#define NFC_V1_V2_CONFIG1_ECC_EN (1 << 3) +#define NFC_V1_V2_CONFIG1_INT_MSK (1 << 4) +#define NFC_V1_V2_CONFIG1_BIG (1 << 5) +#define NFC_V1_V2_CONFIG1_RST (1 << 6) +#define NFC_V1_V2_CONFIG1_CE (1 << 7) +#define NFC_V2_CONFIG1_ONE_CYCLE (1 << 8) +#define NFC_V2_CONFIG1_PPB(x) (((x) & 0x3) << 9) +#define NFC_V2_CONFIG1_FP_INT (1 << 11) + +#define NFC_V1_V2_CONFIG2_INT (1 << 15) + +/* + * Operation modes for the NFC. Valid for v1, v2 and v3 + * type controllers. + */ +#define NFC_CMD (1 << 0) +#define NFC_ADDR (1 << 1) +#define NFC_INPUT (1 << 2) +#define NFC_OUTPUT (1 << 3) +#define NFC_ID (1 << 4) +#define NFC_STATUS (1 << 5) + +#define NFC_V3_FLASH_CMD (host->regs_axi + 0x00) +#define NFC_V3_FLASH_ADDR0 (host->regs_axi + 0x04) + +#define NFC_V3_CONFIG1 (host->regs_axi + 0x34) +#define NFC_V3_CONFIG1_SP_EN (1 << 0) +#define NFC_V3_CONFIG1_RBA(x) (((x) & 0x7 ) << 4) + +#define NFC_V3_ECC_STATUS_RESULT (host->regs_axi + 0x38) + +#define NFC_V3_LAUNCH (host->regs_axi + 0x40) + +#define NFC_V3_WRPROT (host->regs_ip + 0x0) +#define NFC_V3_WRPROT_LOCK_TIGHT (1 << 0) +#define NFC_V3_WRPROT_LOCK (1 << 1) +#define NFC_V3_WRPROT_UNLOCK (1 << 2) +#define NFC_V3_WRPROT_BLS_UNLOCK (2 << 6) + +#define NFC_V3_WRPROT_UNLOCK_BLK_ADD0 (host->regs_ip + 0x04) + +#define NFC_V3_CONFIG2 (host->regs_ip + 0x24) +#define NFC_V3_CONFIG2_PS_512 (0 << 0) +#define NFC_V3_CONFIG2_PS_2048 (1 << 0) +#define NFC_V3_CONFIG2_PS_4096 (2 << 0) +#define NFC_V3_CONFIG2_ONE_CYCLE (1 << 2) +#define NFC_V3_CONFIG2_ECC_EN (1 << 3) +#define NFC_V3_CONFIG2_2CMD_PHASES (1 << 4) +#define NFC_V3_CONFIG2_NUM_ADDR_PHASE0 (1 << 5) +#define NFC_V3_CONFIG2_ECC_MODE_8 (1 << 6) +#define NFC_V3_CONFIG2_PPB(x, shift) (((x) & 0x3) << shift) +#define NFC_V3_CONFIG2_NUM_ADDR_PHASE1(x) (((x) & 0x3) << 12) +#define NFC_V3_CONFIG2_INT_MSK (1 << 15) +#define NFC_V3_CONFIG2_ST_CMD(x) (((x) & 0xff) << 24) +#define NFC_V3_CONFIG2_SPAS(x) (((x) & 0xff) << 16) + +#define NFC_V3_CONFIG3 (host->regs_ip + 0x28) +#define NFC_V3_CONFIG3_ADD_OP(x) (((x) & 0x3) << 0) +#define NFC_V3_CONFIG3_FW8 (1 << 3) +#define NFC_V3_CONFIG3_SBB(x) (((x) & 0x7) << 8) +#define NFC_V3_CONFIG3_NUM_OF_DEVICES(x) (((x) & 0x7) << 12) +#define NFC_V3_CONFIG3_RBB_MODE (1 << 15) +#define NFC_V3_CONFIG3_NO_SDMA (1 << 20) + +#define NFC_V3_IPC (host->regs_ip + 0x2C) +#define NFC_V3_IPC_CREQ (1 << 0) +#define NFC_V3_IPC_INT (1 << 31) + +#define NFC_V3_DELAY_LINE (host->regs_ip + 0x34) + +struct mxc_nand_host; + +struct mxc_nand_devtype_data { + void (*preset)(struct mtd_info *); + void (*send_cmd)(struct mxc_nand_host *, uint16_t, int); + void (*send_addr)(struct mxc_nand_host *, uint16_t, int); + void (*send_page)(struct mtd_info *, unsigned int); + void (*send_read_id)(struct mxc_nand_host *); + uint16_t (*get_dev_status)(struct mxc_nand_host *); + int (*check_int)(struct mxc_nand_host *); + void (*irq_control)(struct mxc_nand_host *, int); + u32 (*get_ecc_status)(struct mxc_nand_host *); + struct nand_ecclayout *ecclayout_512, *ecclayout_2k, *ecclayout_4k; + void (*select_chip)(struct mtd_info *mtd, int chip); + int (*correct_data)(struct mtd_info *mtd, u_char *dat, + u_char *read_ecc, u_char *calc_ecc); + + /* + * On i.MX21 the CONFIG2:INT bit cannot be read if interrupts are masked + * (CONFIG1:INT_MSK is set). To handle this the driver uses + * enable_irq/disable_irq_nosync instead of CONFIG1:INT_MSK + */ + int irqpending_quirk; + int needs_ip; + + size_t regs_offset; + size_t spare0_offset; + size_t axi_offset; + + int spare_len; + int eccbytes; + int eccsize; + int ppb_shift; +}; + +struct mxc_nand_host { + struct mtd_info mtd; + struct nand_chip nand; + struct device *dev; + + void __iomem *spare0; + void __iomem *main_area0; + + void __iomem *base; + void __iomem *regs; + void __iomem *regs_axi; + void __iomem *regs_ip; + int status_request; + struct clk *clk; + int clk_act; + int irq; + int eccsize; + int active_cs; + + struct completion op_completion; + + uint8_t *data_buf; + unsigned int buf_start; + + const struct mxc_nand_devtype_data *devtype_data; + struct mxc_nand_platform_data pdata; +}; + +/* OOB placement block for use with hardware ecc generation */ +static struct nand_ecclayout nandv1_hw_eccoob_smallpage = { + .eccbytes = 5, + .eccpos = {6, 7, 8, 9, 10}, + .oobfree = {{0, 5}, {12, 4}, } +}; + +static struct nand_ecclayout nandv1_hw_eccoob_largepage = { + .eccbytes = 20, + .eccpos = {6, 7, 8, 9, 10, 22, 23, 24, 25, 26, + 38, 39, 40, 41, 42, 54, 55, 56, 57, 58}, + .oobfree = {{2, 4}, {11, 10}, {27, 10}, {43, 10}, {59, 5}, } +}; + +/* OOB description for 512 byte pages with 16 byte OOB */ +static struct nand_ecclayout nandv2_hw_eccoob_smallpage = { + .eccbytes = 1 * 9, + .eccpos = { + 7, 8, 9, 10, 11, 12, 13, 14, 15 + }, + .oobfree = { + {.offset = 0, .length = 5} + } +}; + +/* OOB description for 2048 byte pages with 64 byte OOB */ +static struct nand_ecclayout nandv2_hw_eccoob_largepage = { + .eccbytes = 4 * 9, + .eccpos = { + 7, 8, 9, 10, 11, 12, 13, 14, 15, + 23, 24, 25, 26, 27, 28, 29, 30, 31, + 39, 40, 41, 42, 43, 44, 45, 46, 47, + 55, 56, 57, 58, 59, 60, 61, 62, 63 + }, + .oobfree = { + {.offset = 2, .length = 4}, + {.offset = 16, .length = 7}, + {.offset = 32, .length = 7}, + {.offset = 48, .length = 7} + } +}; + +/* OOB description for 4096 byte pages with 128 byte OOB */ +static struct nand_ecclayout nandv2_hw_eccoob_4k = { + .eccbytes = 8 * 9, + .eccpos = { + 7, 8, 9, 10, 11, 12, 13, 14, 15, + 23, 24, 25, 26, 27, 28, 29, 30, 31, + 39, 40, 41, 42, 43, 44, 45, 46, 47, + 55, 56, 57, 58, 59, 60, 61, 62, 63, + 71, 72, 73, 74, 75, 76, 77, 78, 79, + 87, 88, 89, 90, 91, 92, 93, 94, 95, + 103, 104, 105, 106, 107, 108, 109, 110, 111, + 119, 120, 121, 122, 123, 124, 125, 126, 127, + }, + .oobfree = { + {.offset = 2, .length = 4}, + {.offset = 16, .length = 7}, + {.offset = 32, .length = 7}, + {.offset = 48, .length = 7}, + {.offset = 64, .length = 7}, + {.offset = 80, .length = 7}, + {.offset = 96, .length = 7}, + {.offset = 112, .length = 7}, + } +}; + +static const char * const part_probes[] = { + "cmdlinepart", "RedBoot", "ofpart", NULL }; + +static void memcpy32_fromio(void *trg, const void __iomem *src, size_t size) +{ + int i; + u32 *t = trg; + const __iomem u32 *s = src; + + for (i = 0; i < (size >> 2); i++) + *t++ = __raw_readl(s++); +} + +static void memcpy32_toio(void __iomem *trg, const void *src, int size) +{ + int i; + u32 __iomem *t = trg; + const u32 *s = src; + + for (i = 0; i < (size >> 2); i++) + __raw_writel(*s++, t++); +} + +static int check_int_v3(struct mxc_nand_host *host) +{ + uint32_t tmp; + + tmp = readl(NFC_V3_IPC); + if (!(tmp & NFC_V3_IPC_INT)) + return 0; + + tmp &= ~NFC_V3_IPC_INT; + writel(tmp, NFC_V3_IPC); + + return 1; +} + +static int check_int_v1_v2(struct mxc_nand_host *host) +{ + uint32_t tmp; + + tmp = readw(NFC_V1_V2_CONFIG2); + if (!(tmp & NFC_V1_V2_CONFIG2_INT)) + return 0; + + if (!host->devtype_data->irqpending_quirk) + writew(tmp & ~NFC_V1_V2_CONFIG2_INT, NFC_V1_V2_CONFIG2); + + return 1; +} + +static void irq_control_v1_v2(struct mxc_nand_host *host, int activate) +{ + uint16_t tmp; + + tmp = readw(NFC_V1_V2_CONFIG1); + + if (activate) + tmp &= ~NFC_V1_V2_CONFIG1_INT_MSK; + else + tmp |= NFC_V1_V2_CONFIG1_INT_MSK; + + writew(tmp, NFC_V1_V2_CONFIG1); +} + +static void irq_control_v3(struct mxc_nand_host *host, int activate) +{ + uint32_t tmp; + + tmp = readl(NFC_V3_CONFIG2); + + if (activate) + tmp &= ~NFC_V3_CONFIG2_INT_MSK; + else + tmp |= NFC_V3_CONFIG2_INT_MSK; + + writel(tmp, NFC_V3_CONFIG2); +} + +static void irq_control(struct mxc_nand_host *host, int activate) +{ + if (host->devtype_data->irqpending_quirk) { + if (activate) + enable_irq(host->irq); + else + disable_irq_nosync(host->irq); + } else { + host->devtype_data->irq_control(host, activate); + } +} + +static u32 get_ecc_status_v1(struct mxc_nand_host *host) +{ + return readw(NFC_V1_V2_ECC_STATUS_RESULT); +} + +static u32 get_ecc_status_v2(struct mxc_nand_host *host) +{ + return readl(NFC_V1_V2_ECC_STATUS_RESULT); +} + +static u32 get_ecc_status_v3(struct mxc_nand_host *host) +{ + return readl(NFC_V3_ECC_STATUS_RESULT); +} + +static irqreturn_t mxc_nfc_irq(int irq, void *dev_id) +{ + struct mxc_nand_host *host = dev_id; + + if (!host->devtype_data->check_int(host)) + return IRQ_NONE; + + irq_control(host, 0); + + complete(&host->op_completion); + + return IRQ_HANDLED; +} + +/* This function polls the NANDFC to wait for the basic operation to + * complete by checking the INT bit of config2 register. + */ +static void wait_op_done(struct mxc_nand_host *host, int useirq) +{ + int max_retries = 8000; + + if (useirq) { + if (!host->devtype_data->check_int(host)) { + reinit_completion(&host->op_completion); + irq_control(host, 1); + wait_for_completion(&host->op_completion); + } + } else { + while (max_retries-- > 0) { + if (host->devtype_data->check_int(host)) + break; + + udelay(1); + } + if (max_retries < 0) + pr_debug("%s: INT not set\n", __func__); + } +} + +static void send_cmd_v3(struct mxc_nand_host *host, uint16_t cmd, int useirq) +{ + /* fill command */ + writel(cmd, NFC_V3_FLASH_CMD); + + /* send out command */ + writel(NFC_CMD, NFC_V3_LAUNCH); + + /* Wait for operation to complete */ + wait_op_done(host, useirq); +} + +/* This function issues the specified command to the NAND device and + * waits for completion. */ +static void send_cmd_v1_v2(struct mxc_nand_host *host, uint16_t cmd, int useirq) +{ + pr_debug("send_cmd(host, 0x%x, %d)\n", cmd, useirq); + + writew(cmd, NFC_V1_V2_FLASH_CMD); + writew(NFC_CMD, NFC_V1_V2_CONFIG2); + + if (host->devtype_data->irqpending_quirk && (cmd == NAND_CMD_RESET)) { + int max_retries = 100; + /* Reset completion is indicated by NFC_CONFIG2 */ + /* being set to 0 */ + while (max_retries-- > 0) { + if (readw(NFC_V1_V2_CONFIG2) == 0) { + break; + } + udelay(1); + } + if (max_retries < 0) + pr_debug("%s: RESET failed\n", __func__); + } else { + /* Wait for operation to complete */ + wait_op_done(host, useirq); + } +} + +static void send_addr_v3(struct mxc_nand_host *host, uint16_t addr, int islast) +{ + /* fill address */ + writel(addr, NFC_V3_FLASH_ADDR0); + + /* send out address */ + writel(NFC_ADDR, NFC_V3_LAUNCH); + + wait_op_done(host, 0); +} + +/* This function sends an address (or partial address) to the + * NAND device. The address is used to select the source/destination for + * a NAND command. */ +static void send_addr_v1_v2(struct mxc_nand_host *host, uint16_t addr, int islast) +{ + pr_debug("send_addr(host, 0x%x %d)\n", addr, islast); + + writew(addr, NFC_V1_V2_FLASH_ADDR); + writew(NFC_ADDR, NFC_V1_V2_CONFIG2); + + /* Wait for operation to complete */ + wait_op_done(host, islast); +} + +static void send_page_v3(struct mtd_info *mtd, unsigned int ops) +{ + struct nand_chip *nand_chip = mtd->priv; + struct mxc_nand_host *host = nand_chip->priv; + uint32_t tmp; + + tmp = readl(NFC_V3_CONFIG1); + tmp &= ~(7 << 4); + writel(tmp, NFC_V3_CONFIG1); + + /* transfer data from NFC ram to nand */ + writel(ops, NFC_V3_LAUNCH); + + wait_op_done(host, false); +} + +static void send_page_v2(struct mtd_info *mtd, unsigned int ops) +{ + struct nand_chip *nand_chip = mtd->priv; + struct mxc_nand_host *host = nand_chip->priv; + + /* NANDFC buffer 0 is used for page read/write */ + writew(host->active_cs << 4, NFC_V1_V2_BUF_ADDR); + + writew(ops, NFC_V1_V2_CONFIG2); + + /* Wait for operation to complete */ + wait_op_done(host, true); +} + +static void send_page_v1(struct mtd_info *mtd, unsigned int ops) +{ + struct nand_chip *nand_chip = mtd->priv; + struct mxc_nand_host *host = nand_chip->priv; + int bufs, i; + + if (mtd->writesize > 512) + bufs = 4; + else + bufs = 1; + + for (i = 0; i < bufs; i++) { + + /* NANDFC buffer 0 is used for page read/write */ + writew((host->active_cs << 4) | i, NFC_V1_V2_BUF_ADDR); + + writew(ops, NFC_V1_V2_CONFIG2); + + /* Wait for operation to complete */ + wait_op_done(host, true); + } +} + +static void send_read_id_v3(struct mxc_nand_host *host) +{ + struct nand_chip *this = &host->nand; + + /* Read ID into main buffer */ + writel(NFC_ID, NFC_V3_LAUNCH); + + wait_op_done(host, true); + + memcpy32_fromio(host->data_buf, host->main_area0, 16); + + if (this->options & NAND_BUSWIDTH_16) { + /* compress the ID info */ + host->data_buf[1] = host->data_buf[2]; + host->data_buf[2] = host->data_buf[4]; + host->data_buf[3] = host->data_buf[6]; + host->data_buf[4] = host->data_buf[8]; + host->data_buf[5] = host->data_buf[10]; + } +} + +/* Request the NANDFC to perform a read of the NAND device ID. */ +static void send_read_id_v1_v2(struct mxc_nand_host *host) +{ + struct nand_chip *this = &host->nand; + + /* NANDFC buffer 0 is used for device ID output */ + writew(host->active_cs << 4, NFC_V1_V2_BUF_ADDR); + + writew(NFC_ID, NFC_V1_V2_CONFIG2); + + /* Wait for operation to complete */ + wait_op_done(host, true); + + memcpy32_fromio(host->data_buf, host->main_area0, 16); + + if (this->options & NAND_BUSWIDTH_16) { + /* compress the ID info */ + host->data_buf[1] = host->data_buf[2]; + host->data_buf[2] = host->data_buf[4]; + host->data_buf[3] = host->data_buf[6]; + host->data_buf[4] = host->data_buf[8]; + host->data_buf[5] = host->data_buf[10]; + } +} + +static uint16_t get_dev_status_v3(struct mxc_nand_host *host) +{ + writew(NFC_STATUS, NFC_V3_LAUNCH); + wait_op_done(host, true); + + return readl(NFC_V3_CONFIG1) >> 16; +} + +/* This function requests the NANDFC to perform a read of the + * NAND device status and returns the current status. */ +static uint16_t get_dev_status_v1_v2(struct mxc_nand_host *host) +{ + void __iomem *main_buf = host->main_area0; + uint32_t store; + uint16_t ret; + + writew(host->active_cs << 4, NFC_V1_V2_BUF_ADDR); + + /* + * The device status is stored in main_area0. To + * prevent corruption of the buffer save the value + * and restore it afterwards. + */ + store = readl(main_buf); + + writew(NFC_STATUS, NFC_V1_V2_CONFIG2); + wait_op_done(host, true); + + ret = readw(main_buf); + + writel(store, main_buf); + + return ret; +} + +/* This functions is used by upper layer to checks if device is ready */ +static int mxc_nand_dev_ready(struct mtd_info *mtd) +{ + /* + * NFC handles R/B internally. Therefore, this function + * always returns status as ready. + */ + return 1; +} + +static void mxc_nand_enable_hwecc(struct mtd_info *mtd, int mode) +{ + /* + * If HW ECC is enabled, we turn it on during init. There is + * no need to enable again here. + */ +} + +static int mxc_nand_correct_data_v1(struct mtd_info *mtd, u_char *dat, + u_char *read_ecc, u_char *calc_ecc) +{ + struct nand_chip *nand_chip = mtd->priv; + struct mxc_nand_host *host = nand_chip->priv; + + /* + * 1-Bit errors are automatically corrected in HW. No need for + * additional correction. 2-Bit errors cannot be corrected by + * HW ECC, so we need to return failure + */ + uint16_t ecc_status = get_ecc_status_v1(host); + + if (((ecc_status & 0x3) == 2) || ((ecc_status >> 2) == 2)) { + pr_debug("MXC_NAND: HWECC uncorrectable 2-bit ECC error\n"); + return -1; + } + + return 0; +} + +static int mxc_nand_correct_data_v2_v3(struct mtd_info *mtd, u_char *dat, + u_char *read_ecc, u_char *calc_ecc) +{ + struct nand_chip *nand_chip = mtd->priv; + struct mxc_nand_host *host = nand_chip->priv; + u32 ecc_stat, err; + int no_subpages = 1; + int ret = 0; + u8 ecc_bit_mask, err_limit; + + ecc_bit_mask = (host->eccsize == 4) ? 0x7 : 0xf; + err_limit = (host->eccsize == 4) ? 0x4 : 0x8; + + no_subpages = mtd->writesize >> 9; + + ecc_stat = host->devtype_data->get_ecc_status(host); + + do { + err = ecc_stat & ecc_bit_mask; + if (err > err_limit) { + printk(KERN_WARNING "UnCorrectable RS-ECC Error\n"); + return -1; + } else { + ret += err; + } + ecc_stat >>= 4; + } while (--no_subpages); + + pr_debug("%d Symbol Correctable RS-ECC Error\n", ret); + + return ret; +} + +static int mxc_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, + u_char *ecc_code) +{ + return 0; +} + +static u_char mxc_nand_read_byte(struct mtd_info *mtd) +{ + struct nand_chip *nand_chip = mtd->priv; + struct mxc_nand_host *host = nand_chip->priv; + uint8_t ret; + + /* Check for status request */ + if (host->status_request) + return host->devtype_data->get_dev_status(host) & 0xFF; + + ret = *(uint8_t *)(host->data_buf + host->buf_start); + host->buf_start++; + + return ret; +} + +static uint16_t mxc_nand_read_word(struct mtd_info *mtd) +{ + struct nand_chip *nand_chip = mtd->priv; + struct mxc_nand_host *host = nand_chip->priv; + uint16_t ret; + + ret = *(uint16_t *)(host->data_buf + host->buf_start); + host->buf_start += 2; + + return ret; +} + +/* Write data of length len to buffer buf. The data to be + * written on NAND Flash is first copied to RAMbuffer. After the Data Input + * Operation by the NFC, the data is written to NAND Flash */ +static void mxc_nand_write_buf(struct mtd_info *mtd, + const u_char *buf, int len) +{ + struct nand_chip *nand_chip = mtd->priv; + struct mxc_nand_host *host = nand_chip->priv; + u16 col = host->buf_start; + int n = mtd->oobsize + mtd->writesize - col; + + n = min(n, len); + + memcpy(host->data_buf + col, buf, n); + + host->buf_start += n; +} + +/* Read the data buffer from the NAND Flash. To read the data from NAND + * Flash first the data output cycle is initiated by the NFC, which copies + * the data to RAMbuffer. This data of length len is then copied to buffer buf. + */ +static void mxc_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len) +{ + struct nand_chip *nand_chip = mtd->priv; + struct mxc_nand_host *host = nand_chip->priv; + u16 col = host->buf_start; + int n = mtd->oobsize + mtd->writesize - col; + + n = min(n, len); + + memcpy(buf, host->data_buf + col, n); + + host->buf_start += n; +} + +/* This function is used by upper layer for select and + * deselect of the NAND chip */ +static void mxc_nand_select_chip_v1_v3(struct mtd_info *mtd, int chip) +{ + struct nand_chip *nand_chip = mtd->priv; + struct mxc_nand_host *host = nand_chip->priv; + + if (chip == -1) { + /* Disable the NFC clock */ + if (host->clk_act) { + clk_disable_unprepare(host->clk); + host->clk_act = 0; + } + return; + } + + if (!host->clk_act) { + /* Enable the NFC clock */ + clk_prepare_enable(host->clk); + host->clk_act = 1; + } +} + +static void mxc_nand_select_chip_v2(struct mtd_info *mtd, int chip) +{ + struct nand_chip *nand_chip = mtd->priv; + struct mxc_nand_host *host = nand_chip->priv; + + if (chip == -1) { + /* Disable the NFC clock */ + if (host->clk_act) { + clk_disable_unprepare(host->clk); + host->clk_act = 0; + } + return; + } + + if (!host->clk_act) { + /* Enable the NFC clock */ + clk_prepare_enable(host->clk); + host->clk_act = 1; + } + + host->active_cs = chip; + writew(host->active_cs << 4, NFC_V1_V2_BUF_ADDR); +} + +/* + * Function to transfer data to/from spare area. + */ +static void copy_spare(struct mtd_info *mtd, bool bfrom) +{ + struct nand_chip *this = mtd->priv; + struct mxc_nand_host *host = this->priv; + u16 i, j; + u16 n = mtd->writesize >> 9; + u8 *d = host->data_buf + mtd->writesize; + u8 __iomem *s = host->spare0; + u16 t = host->devtype_data->spare_len; + + j = (mtd->oobsize / n >> 1) << 1; + + if (bfrom) { + for (i = 0; i < n - 1; i++) + memcpy32_fromio(d + i * j, s + i * t, j); + + /* the last section */ + memcpy32_fromio(d + i * j, s + i * t, mtd->oobsize - i * j); + } else { + for (i = 0; i < n - 1; i++) + memcpy32_toio(&s[i * t], &d[i * j], j); + + /* the last section */ + memcpy32_toio(&s[i * t], &d[i * j], mtd->oobsize - i * j); + } +} + +static void mxc_do_addr_cycle(struct mtd_info *mtd, int column, int page_addr) +{ + struct nand_chip *nand_chip = mtd->priv; + struct mxc_nand_host *host = nand_chip->priv; + + /* Write out column address, if necessary */ + if (column != -1) { + /* + * MXC NANDFC can only perform full page+spare or + * spare-only read/write. When the upper layers + * perform a read/write buf operation, the saved column + * address is used to index into the full page. + */ + host->devtype_data->send_addr(host, 0, page_addr == -1); + if (mtd->writesize > 512) + /* another col addr cycle for 2k page */ + host->devtype_data->send_addr(host, 0, false); + } + + /* Write out page address, if necessary */ + if (page_addr != -1) { + /* paddr_0 - p_addr_7 */ + host->devtype_data->send_addr(host, (page_addr & 0xff), false); + + if (mtd->writesize > 512) { + if (mtd->size >= 0x10000000) { + /* paddr_8 - paddr_15 */ + host->devtype_data->send_addr(host, + (page_addr >> 8) & 0xff, + false); + host->devtype_data->send_addr(host, + (page_addr >> 16) & 0xff, + true); + } else + /* paddr_8 - paddr_15 */ + host->devtype_data->send_addr(host, + (page_addr >> 8) & 0xff, true); + } else { + /* One more address cycle for higher density devices */ + if (mtd->size >= 0x4000000) { + /* paddr_8 - paddr_15 */ + host->devtype_data->send_addr(host, + (page_addr >> 8) & 0xff, + false); + host->devtype_data->send_addr(host, + (page_addr >> 16) & 0xff, + true); + } else + /* paddr_8 - paddr_15 */ + host->devtype_data->send_addr(host, + (page_addr >> 8) & 0xff, true); + } + } +} + +/* + * v2 and v3 type controllers can do 4bit or 8bit ecc depending + * on how much oob the nand chip has. For 8bit ecc we need at least + * 26 bytes of oob data per 512 byte block. + */ +static int get_eccsize(struct mtd_info *mtd) +{ + int oobbytes_per_512 = 0; + + oobbytes_per_512 = mtd->oobsize * 512 / mtd->writesize; + + if (oobbytes_per_512 < 26) + return 4; + else + return 8; +} + +static void preset_v1(struct mtd_info *mtd) +{ + struct nand_chip *nand_chip = mtd->priv; + struct mxc_nand_host *host = nand_chip->priv; + uint16_t config1 = 0; + + if (nand_chip->ecc.mode == NAND_ECC_HW) + config1 |= NFC_V1_V2_CONFIG1_ECC_EN; + + if (!host->devtype_data->irqpending_quirk) + config1 |= NFC_V1_V2_CONFIG1_INT_MSK; + + host->eccsize = 1; + + writew(config1, NFC_V1_V2_CONFIG1); + /* preset operation */ + + /* Unlock the internal RAM Buffer */ + writew(0x2, NFC_V1_V2_CONFIG); + + /* Blocks to be unlocked */ + writew(0x0, NFC_V1_UNLOCKSTART_BLKADDR); + writew(0xffff, NFC_V1_UNLOCKEND_BLKADDR); + + /* Unlock Block Command for given address range */ + writew(0x4, NFC_V1_V2_WRPROT); +} + +static void preset_v2(struct mtd_info *mtd) +{ + struct nand_chip *nand_chip = mtd->priv; + struct mxc_nand_host *host = nand_chip->priv; + uint16_t config1 = 0; + + if (nand_chip->ecc.mode == NAND_ECC_HW) + config1 |= NFC_V1_V2_CONFIG1_ECC_EN; + + config1 |= NFC_V2_CONFIG1_FP_INT; + + if (!host->devtype_data->irqpending_quirk) + config1 |= NFC_V1_V2_CONFIG1_INT_MSK; + + if (mtd->writesize) { + uint16_t pages_per_block = mtd->erasesize / mtd->writesize; + + host->eccsize = get_eccsize(mtd); + if (host->eccsize == 4) + config1 |= NFC_V2_CONFIG1_ECC_MODE_4; + + config1 |= NFC_V2_CONFIG1_PPB(ffs(pages_per_block) - 6); + } else { + host->eccsize = 1; + } + + writew(config1, NFC_V1_V2_CONFIG1); + /* preset operation */ + + /* Unlock the internal RAM Buffer */ + writew(0x2, NFC_V1_V2_CONFIG); + + /* Blocks to be unlocked */ + writew(0x0, NFC_V21_UNLOCKSTART_BLKADDR0); + writew(0x0, NFC_V21_UNLOCKSTART_BLKADDR1); + writew(0x0, NFC_V21_UNLOCKSTART_BLKADDR2); + writew(0x0, NFC_V21_UNLOCKSTART_BLKADDR3); + writew(0xffff, NFC_V21_UNLOCKEND_BLKADDR0); + writew(0xffff, NFC_V21_UNLOCKEND_BLKADDR1); + writew(0xffff, NFC_V21_UNLOCKEND_BLKADDR2); + writew(0xffff, NFC_V21_UNLOCKEND_BLKADDR3); + + /* Unlock Block Command for given address range */ + writew(0x4, NFC_V1_V2_WRPROT); +} + +static void preset_v3(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct mxc_nand_host *host = chip->priv; + uint32_t config2, config3; + int i, addr_phases; + + writel(NFC_V3_CONFIG1_RBA(0), NFC_V3_CONFIG1); + writel(NFC_V3_IPC_CREQ, NFC_V3_IPC); + + /* Unlock the internal RAM Buffer */ + writel(NFC_V3_WRPROT_BLS_UNLOCK | NFC_V3_WRPROT_UNLOCK, + NFC_V3_WRPROT); + + /* Blocks to be unlocked */ + for (i = 0; i < NAND_MAX_CHIPS; i++) + writel(0x0 | (0xffff << 16), + NFC_V3_WRPROT_UNLOCK_BLK_ADD0 + (i << 2)); + + writel(0, NFC_V3_IPC); + + config2 = NFC_V3_CONFIG2_ONE_CYCLE | + NFC_V3_CONFIG2_2CMD_PHASES | + NFC_V3_CONFIG2_SPAS(mtd->oobsize >> 1) | + NFC_V3_CONFIG2_ST_CMD(0x70) | + NFC_V3_CONFIG2_INT_MSK | + NFC_V3_CONFIG2_NUM_ADDR_PHASE0; + + if (chip->ecc.mode == NAND_ECC_HW) + config2 |= NFC_V3_CONFIG2_ECC_EN; + + addr_phases = fls(chip->pagemask) >> 3; + + if (mtd->writesize == 2048) { + config2 |= NFC_V3_CONFIG2_PS_2048; + config2 |= NFC_V3_CONFIG2_NUM_ADDR_PHASE1(addr_phases); + } else if (mtd->writesize == 4096) { + config2 |= NFC_V3_CONFIG2_PS_4096; + config2 |= NFC_V3_CONFIG2_NUM_ADDR_PHASE1(addr_phases); + } else { + config2 |= NFC_V3_CONFIG2_PS_512; + config2 |= NFC_V3_CONFIG2_NUM_ADDR_PHASE1(addr_phases - 1); + } + + if (mtd->writesize) { + config2 |= NFC_V3_CONFIG2_PPB( + ffs(mtd->erasesize / mtd->writesize) - 6, + host->devtype_data->ppb_shift); + host->eccsize = get_eccsize(mtd); + if (host->eccsize == 8) + config2 |= NFC_V3_CONFIG2_ECC_MODE_8; + } + + writel(config2, NFC_V3_CONFIG2); + + config3 = NFC_V3_CONFIG3_NUM_OF_DEVICES(0) | + NFC_V3_CONFIG3_NO_SDMA | + NFC_V3_CONFIG3_RBB_MODE | + NFC_V3_CONFIG3_SBB(6) | /* Reset default */ + NFC_V3_CONFIG3_ADD_OP(0); + + if (!(chip->options & NAND_BUSWIDTH_16)) + config3 |= NFC_V3_CONFIG3_FW8; + + writel(config3, NFC_V3_CONFIG3); + + writel(0, NFC_V3_DELAY_LINE); +} + +/* Used by the upper layer to write command to NAND Flash for + * different operations to be carried out on NAND Flash */ +static void mxc_nand_command(struct mtd_info *mtd, unsigned command, + int column, int page_addr) +{ + struct nand_chip *nand_chip = mtd->priv; + struct mxc_nand_host *host = nand_chip->priv; + + pr_debug("mxc_nand_command (cmd = 0x%x, col = 0x%x, page = 0x%x)\n", + command, column, page_addr); + + /* Reset command state information */ + host->status_request = false; + + /* Command pre-processing step */ + switch (command) { + case NAND_CMD_RESET: + host->devtype_data->preset(mtd); + host->devtype_data->send_cmd(host, command, false); + break; + + case NAND_CMD_STATUS: + host->buf_start = 0; + host->status_request = true; + + host->devtype_data->send_cmd(host, command, true); + mxc_do_addr_cycle(mtd, column, page_addr); + break; + + case NAND_CMD_READ0: + case NAND_CMD_READOOB: + if (command == NAND_CMD_READ0) + host->buf_start = column; + else + host->buf_start = column + mtd->writesize; + + command = NAND_CMD_READ0; /* only READ0 is valid */ + + host->devtype_data->send_cmd(host, command, false); + mxc_do_addr_cycle(mtd, column, page_addr); + + if (mtd->writesize > 512) + host->devtype_data->send_cmd(host, + NAND_CMD_READSTART, true); + + host->devtype_data->send_page(mtd, NFC_OUTPUT); + + memcpy32_fromio(host->data_buf, host->main_area0, + mtd->writesize); + copy_spare(mtd, true); + break; + + case NAND_CMD_SEQIN: + if (column >= mtd->writesize) + /* call ourself to read a page */ + mxc_nand_command(mtd, NAND_CMD_READ0, 0, page_addr); + + host->buf_start = column; + + host->devtype_data->send_cmd(host, command, false); + mxc_do_addr_cycle(mtd, column, page_addr); + break; + + case NAND_CMD_PAGEPROG: + memcpy32_toio(host->main_area0, host->data_buf, mtd->writesize); + copy_spare(mtd, false); + host->devtype_data->send_page(mtd, NFC_INPUT); + host->devtype_data->send_cmd(host, command, true); + mxc_do_addr_cycle(mtd, column, page_addr); + break; + + case NAND_CMD_READID: + host->devtype_data->send_cmd(host, command, true); + mxc_do_addr_cycle(mtd, column, page_addr); + host->devtype_data->send_read_id(host); + host->buf_start = column; + break; + + case NAND_CMD_ERASE1: + case NAND_CMD_ERASE2: + host->devtype_data->send_cmd(host, command, false); + mxc_do_addr_cycle(mtd, column, page_addr); + + break; + } +} + +/* + * The generic flash bbt decriptors overlap with our ecc + * hardware, so define some i.MX specific ones. + */ +static uint8_t bbt_pattern[] = { 'B', 'b', 't', '0' }; +static uint8_t mirror_pattern[] = { '1', 't', 'b', 'B' }; + +static struct nand_bbt_descr bbt_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 0, + .len = 4, + .veroffs = 4, + .maxblocks = 4, + .pattern = bbt_pattern, +}; + +static struct nand_bbt_descr bbt_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 0, + .len = 4, + .veroffs = 4, + .maxblocks = 4, + .pattern = mirror_pattern, +}; + +/* v1 + irqpending_quirk: i.MX21 */ +static const struct mxc_nand_devtype_data imx21_nand_devtype_data = { + .preset = preset_v1, + .send_cmd = send_cmd_v1_v2, + .send_addr = send_addr_v1_v2, + .send_page = send_page_v1, + .send_read_id = send_read_id_v1_v2, + .get_dev_status = get_dev_status_v1_v2, + .check_int = check_int_v1_v2, + .irq_control = irq_control_v1_v2, + .get_ecc_status = get_ecc_status_v1, + .ecclayout_512 = &nandv1_hw_eccoob_smallpage, + .ecclayout_2k = &nandv1_hw_eccoob_largepage, + .ecclayout_4k = &nandv1_hw_eccoob_smallpage, /* XXX: needs fix */ + .select_chip = mxc_nand_select_chip_v1_v3, + .correct_data = mxc_nand_correct_data_v1, + .irqpending_quirk = 1, + .needs_ip = 0, + .regs_offset = 0xe00, + .spare0_offset = 0x800, + .spare_len = 16, + .eccbytes = 3, + .eccsize = 1, +}; + +/* v1 + !irqpending_quirk: i.MX27, i.MX31 */ +static const struct mxc_nand_devtype_data imx27_nand_devtype_data = { + .preset = preset_v1, + .send_cmd = send_cmd_v1_v2, + .send_addr = send_addr_v1_v2, + .send_page = send_page_v1, + .send_read_id = send_read_id_v1_v2, + .get_dev_status = get_dev_status_v1_v2, + .check_int = check_int_v1_v2, + .irq_control = irq_control_v1_v2, + .get_ecc_status = get_ecc_status_v1, + .ecclayout_512 = &nandv1_hw_eccoob_smallpage, + .ecclayout_2k = &nandv1_hw_eccoob_largepage, + .ecclayout_4k = &nandv1_hw_eccoob_smallpage, /* XXX: needs fix */ + .select_chip = mxc_nand_select_chip_v1_v3, + .correct_data = mxc_nand_correct_data_v1, + .irqpending_quirk = 0, + .needs_ip = 0, + .regs_offset = 0xe00, + .spare0_offset = 0x800, + .axi_offset = 0, + .spare_len = 16, + .eccbytes = 3, + .eccsize = 1, +}; + +/* v21: i.MX25, i.MX35 */ +static const struct mxc_nand_devtype_data imx25_nand_devtype_data = { + .preset = preset_v2, + .send_cmd = send_cmd_v1_v2, + .send_addr = send_addr_v1_v2, + .send_page = send_page_v2, + .send_read_id = send_read_id_v1_v2, + .get_dev_status = get_dev_status_v1_v2, + .check_int = check_int_v1_v2, + .irq_control = irq_control_v1_v2, + .get_ecc_status = get_ecc_status_v2, + .ecclayout_512 = &nandv2_hw_eccoob_smallpage, + .ecclayout_2k = &nandv2_hw_eccoob_largepage, + .ecclayout_4k = &nandv2_hw_eccoob_4k, + .select_chip = mxc_nand_select_chip_v2, + .correct_data = mxc_nand_correct_data_v2_v3, + .irqpending_quirk = 0, + .needs_ip = 0, + .regs_offset = 0x1e00, + .spare0_offset = 0x1000, + .axi_offset = 0, + .spare_len = 64, + .eccbytes = 9, + .eccsize = 0, +}; + +/* v3.2a: i.MX51 */ +static const struct mxc_nand_devtype_data imx51_nand_devtype_data = { + .preset = preset_v3, + .send_cmd = send_cmd_v3, + .send_addr = send_addr_v3, + .send_page = send_page_v3, + .send_read_id = send_read_id_v3, + .get_dev_status = get_dev_status_v3, + .check_int = check_int_v3, + .irq_control = irq_control_v3, + .get_ecc_status = get_ecc_status_v3, + .ecclayout_512 = &nandv2_hw_eccoob_smallpage, + .ecclayout_2k = &nandv2_hw_eccoob_largepage, + .ecclayout_4k = &nandv2_hw_eccoob_smallpage, /* XXX: needs fix */ + .select_chip = mxc_nand_select_chip_v1_v3, + .correct_data = mxc_nand_correct_data_v2_v3, + .irqpending_quirk = 0, + .needs_ip = 1, + .regs_offset = 0, + .spare0_offset = 0x1000, + .axi_offset = 0x1e00, + .spare_len = 64, + .eccbytes = 0, + .eccsize = 0, + .ppb_shift = 7, +}; + +/* v3.2b: i.MX53 */ +static const struct mxc_nand_devtype_data imx53_nand_devtype_data = { + .preset = preset_v3, + .send_cmd = send_cmd_v3, + .send_addr = send_addr_v3, + .send_page = send_page_v3, + .send_read_id = send_read_id_v3, + .get_dev_status = get_dev_status_v3, + .check_int = check_int_v3, + .irq_control = irq_control_v3, + .get_ecc_status = get_ecc_status_v3, + .ecclayout_512 = &nandv2_hw_eccoob_smallpage, + .ecclayout_2k = &nandv2_hw_eccoob_largepage, + .ecclayout_4k = &nandv2_hw_eccoob_smallpage, /* XXX: needs fix */ + .select_chip = mxc_nand_select_chip_v1_v3, + .correct_data = mxc_nand_correct_data_v2_v3, + .irqpending_quirk = 0, + .needs_ip = 1, + .regs_offset = 0, + .spare0_offset = 0x1000, + .axi_offset = 0x1e00, + .spare_len = 64, + .eccbytes = 0, + .eccsize = 0, + .ppb_shift = 8, +}; + +static inline int is_imx21_nfc(struct mxc_nand_host *host) +{ + return host->devtype_data == &imx21_nand_devtype_data; +} + +static inline int is_imx27_nfc(struct mxc_nand_host *host) +{ + return host->devtype_data == &imx27_nand_devtype_data; +} + +static inline int is_imx25_nfc(struct mxc_nand_host *host) +{ + return host->devtype_data == &imx25_nand_devtype_data; +} + +static inline int is_imx51_nfc(struct mxc_nand_host *host) +{ + return host->devtype_data == &imx51_nand_devtype_data; +} + +static inline int is_imx53_nfc(struct mxc_nand_host *host) +{ + return host->devtype_data == &imx53_nand_devtype_data; +} + +static struct platform_device_id mxcnd_devtype[] = { + { + .name = "imx21-nand", + .driver_data = (kernel_ulong_t) &imx21_nand_devtype_data, + }, { + .name = "imx27-nand", + .driver_data = (kernel_ulong_t) &imx27_nand_devtype_data, + }, { + .name = "imx25-nand", + .driver_data = (kernel_ulong_t) &imx25_nand_devtype_data, + }, { + .name = "imx51-nand", + .driver_data = (kernel_ulong_t) &imx51_nand_devtype_data, + }, { + .name = "imx53-nand", + .driver_data = (kernel_ulong_t) &imx53_nand_devtype_data, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(platform, mxcnd_devtype); + +#ifdef CONFIG_OF_MTD +static const struct of_device_id mxcnd_dt_ids[] = { + { + .compatible = "fsl,imx21-nand", + .data = &imx21_nand_devtype_data, + }, { + .compatible = "fsl,imx27-nand", + .data = &imx27_nand_devtype_data, + }, { + .compatible = "fsl,imx25-nand", + .data = &imx25_nand_devtype_data, + }, { + .compatible = "fsl,imx51-nand", + .data = &imx51_nand_devtype_data, + }, { + .compatible = "fsl,imx53-nand", + .data = &imx53_nand_devtype_data, + }, + { /* sentinel */ } +}; + +static int __init mxcnd_probe_dt(struct mxc_nand_host *host) +{ + struct device_node *np = host->dev->of_node; + struct mxc_nand_platform_data *pdata = &host->pdata; + const struct of_device_id *of_id = + of_match_device(mxcnd_dt_ids, host->dev); + int buswidth; + + if (!np) + return 1; + + if (of_get_nand_ecc_mode(np) >= 0) + pdata->hw_ecc = 1; + + pdata->flash_bbt = of_get_nand_on_flash_bbt(np); + + buswidth = of_get_nand_bus_width(np); + if (buswidth < 0) + return buswidth; + + pdata->width = buswidth / 8; + + host->devtype_data = of_id->data; + + return 0; +} +#else +static int __init mxcnd_probe_dt(struct mxc_nand_host *host) +{ + return 1; +} +#endif + +static int mxcnd_probe(struct platform_device *pdev) +{ + struct nand_chip *this; + struct mtd_info *mtd; + struct mxc_nand_host *host; + struct resource *res; + int err = 0; + + /* Allocate memory for MTD device structure and private data */ + host = devm_kzalloc(&pdev->dev, sizeof(struct mxc_nand_host), + GFP_KERNEL); + if (!host) + return -ENOMEM; + + /* allocate a temporary buffer for the nand_scan_ident() */ + host->data_buf = devm_kzalloc(&pdev->dev, PAGE_SIZE, GFP_KERNEL); + if (!host->data_buf) + return -ENOMEM; + + host->dev = &pdev->dev; + /* structures must be linked */ + this = &host->nand; + mtd = &host->mtd; + mtd->priv = this; + mtd->owner = THIS_MODULE; + mtd->dev.parent = &pdev->dev; + mtd->name = DRIVER_NAME; + + /* 50 us command delay time */ + this->chip_delay = 5; + + this->priv = host; + this->dev_ready = mxc_nand_dev_ready; + this->cmdfunc = mxc_nand_command; + this->read_byte = mxc_nand_read_byte; + this->read_word = mxc_nand_read_word; + this->write_buf = mxc_nand_write_buf; + this->read_buf = mxc_nand_read_buf; + + host->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(host->clk)) + return PTR_ERR(host->clk); + + err = mxcnd_probe_dt(host); + if (err > 0) { + struct mxc_nand_platform_data *pdata = + dev_get_platdata(&pdev->dev); + if (pdata) { + host->pdata = *pdata; + host->devtype_data = (struct mxc_nand_devtype_data *) + pdev->id_entry->driver_data; + } else { + err = -ENODEV; + } + } + if (err < 0) + return err; + + if (host->devtype_data->needs_ip) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + host->regs_ip = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(host->regs_ip)) + return PTR_ERR(host->regs_ip); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + } else { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + } + + host->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(host->base)) + return PTR_ERR(host->base); + + host->main_area0 = host->base; + + if (host->devtype_data->regs_offset) + host->regs = host->base + host->devtype_data->regs_offset; + host->spare0 = host->base + host->devtype_data->spare0_offset; + if (host->devtype_data->axi_offset) + host->regs_axi = host->base + host->devtype_data->axi_offset; + + this->ecc.bytes = host->devtype_data->eccbytes; + host->eccsize = host->devtype_data->eccsize; + + this->select_chip = host->devtype_data->select_chip; + this->ecc.size = 512; + this->ecc.layout = host->devtype_data->ecclayout_512; + + if (host->pdata.hw_ecc) { + this->ecc.calculate = mxc_nand_calculate_ecc; + this->ecc.hwctl = mxc_nand_enable_hwecc; + this->ecc.correct = host->devtype_data->correct_data; + this->ecc.mode = NAND_ECC_HW; + } else { + this->ecc.mode = NAND_ECC_SOFT; + } + + /* NAND bus width determines access functions used by upper layer */ + if (host->pdata.width == 2) + this->options |= NAND_BUSWIDTH_16; + + if (host->pdata.flash_bbt) { + this->bbt_td = &bbt_main_descr; + this->bbt_md = &bbt_mirror_descr; + /* update flash based bbt */ + this->bbt_options |= NAND_BBT_USE_FLASH; + } + + init_completion(&host->op_completion); + + host->irq = platform_get_irq(pdev, 0); + if (host->irq < 0) + return host->irq; + + /* + * Use host->devtype_data->irq_control() here instead of irq_control() + * because we must not disable_irq_nosync without having requested the + * irq. + */ + host->devtype_data->irq_control(host, 0); + + err = devm_request_irq(&pdev->dev, host->irq, mxc_nfc_irq, + 0, DRIVER_NAME, host); + if (err) + return err; + + err = clk_prepare_enable(host->clk); + if (err) + return err; + host->clk_act = 1; + + /* + * Now that we "own" the interrupt make sure the interrupt mask bit is + * cleared on i.MX21. Otherwise we can't read the interrupt status bit + * on this machine. + */ + if (host->devtype_data->irqpending_quirk) { + disable_irq_nosync(host->irq); + host->devtype_data->irq_control(host, 1); + } + + /* first scan to find the device and get the page size */ + if (nand_scan_ident(mtd, is_imx25_nfc(host) ? 4 : 1, NULL)) { + err = -ENXIO; + goto escan; + } + + /* allocate the right size buffer now */ + devm_kfree(&pdev->dev, (void *)host->data_buf); + host->data_buf = devm_kzalloc(&pdev->dev, mtd->writesize + mtd->oobsize, + GFP_KERNEL); + if (!host->data_buf) { + err = -ENOMEM; + goto escan; + } + + /* Call preset again, with correct writesize this time */ + host->devtype_data->preset(mtd); + + if (mtd->writesize == 2048) + this->ecc.layout = host->devtype_data->ecclayout_2k; + else if (mtd->writesize == 4096) + this->ecc.layout = host->devtype_data->ecclayout_4k; + + if (this->ecc.mode == NAND_ECC_HW) { + if (is_imx21_nfc(host) || is_imx27_nfc(host)) + this->ecc.strength = 1; + else + this->ecc.strength = (host->eccsize == 4) ? 4 : 8; + } + + /* second phase scan */ + if (nand_scan_tail(mtd)) { + err = -ENXIO; + goto escan; + } + + /* Register the partitions */ + mtd_device_parse_register(mtd, part_probes, + &(struct mtd_part_parser_data){ + .of_node = pdev->dev.of_node, + }, + host->pdata.parts, + host->pdata.nr_parts); + + platform_set_drvdata(pdev, host); + + return 0; + +escan: + if (host->clk_act) + clk_disable_unprepare(host->clk); + + return err; +} + +static int mxcnd_remove(struct platform_device *pdev) +{ + struct mxc_nand_host *host = platform_get_drvdata(pdev); + + nand_release(&host->mtd); + if (host->clk_act) + clk_disable_unprepare(host->clk); + + return 0; +} + +static struct platform_driver mxcnd_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(mxcnd_dt_ids), + }, + .id_table = mxcnd_devtype, + .probe = mxcnd_probe, + .remove = mxcnd_remove, +}; +module_platform_driver(mxcnd_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC NAND MTD driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 975b2ef6112..4f3e80c68a2 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -4,10 +4,9 @@ * Overview: * This is the generic MTD driver for NAND flash devices. It should be * capable of working with almost all NAND chips currently available. - * Basic support for AG-AND chips is provided. * * Additional technical information is available on - * http://www.linux-mtd.infradead.org/tech/nand.html + * http://www.linux-mtd.infradead.org/doc/nand.html * * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com) * 2002-2006 Thomas Gleixner (tglx@linutronix.de) @@ -21,9 +20,8 @@ * TODO: * Enable cached programming for 2k page size chips * Check, if mtd->ecctype should be set to MTD_ECC_HW - * if we have HW ecc support. - * The AG-AND chips have nice features for speed improvement, - * which are not supported yet. Read / program 4 pages in one go. + * if we have HW ECC support. + * BBT table is not serialized, has to be fixed * * 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 @@ -31,25 +29,25 @@ * */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/delay.h> #include <linux/errno.h> #include <linux/err.h> #include <linux/sched.h> #include <linux/slab.h> +#include <linux/mm.h> #include <linux/types.h> #include <linux/mtd/mtd.h> #include <linux/mtd/nand.h> #include <linux/mtd/nand_ecc.h> -#include <linux/mtd/compatmac.h> +#include <linux/mtd/nand_bch.h> #include <linux/interrupt.h> #include <linux/bitops.h> #include <linux/leds.h> -#include <asm/io.h> - -#ifdef CONFIG_MTD_PARTITIONS +#include <linux/io.h> #include <linux/mtd/partitions.h> -#endif /* Define default oob placement schemes for large and small page devices */ static struct nand_ecclayout nand_oob_8 = { @@ -59,7 +57,7 @@ static struct nand_ecclayout nand_oob_8 = { {.offset = 3, .length = 2}, {.offset = 6, - .length = 2}} + .length = 2} } }; static struct nand_ecclayout nand_oob_16 = { @@ -67,7 +65,7 @@ static struct nand_ecclayout nand_oob_16 = { .eccpos = {0, 1, 2, 3, 6, 7}, .oobfree = { {.offset = 8, - . length = 8}} + . length = 8} } }; static struct nand_ecclayout nand_oob_64 = { @@ -78,34 +76,65 @@ static struct nand_ecclayout nand_oob_64 = { 56, 57, 58, 59, 60, 61, 62, 63}, .oobfree = { {.offset = 2, - .length = 38}} + .length = 38} } }; -static int nand_get_device(struct nand_chip *chip, struct mtd_info *mtd, - int new_state); +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 mtd_info *mtd, int new_state); static int nand_do_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops); /* - * For devices which display every fart in the system on a seperate LED. Is + * For devices which display every fart in the system on a separate LED. Is * compiled away when LED support is disabled. */ DEFINE_LED_TRIGGER(nand_led_trigger); +static int check_offs_len(struct mtd_info *mtd, + loff_t ofs, uint64_t len) +{ + struct nand_chip *chip = mtd->priv; + int ret = 0; + + /* Start address must align on block boundary */ + if (ofs & ((1ULL << chip->phys_erase_shift) - 1)) { + pr_debug("%s: unaligned address\n", __func__); + ret = -EINVAL; + } + + /* Length must align on block boundary */ + if (len & ((1ULL << chip->phys_erase_shift) - 1)) { + pr_debug("%s: length not block aligned\n", __func__); + ret = -EINVAL; + } + + return ret; +} + /** * nand_release_device - [GENERIC] release chip - * @mtd: MTD device structure + * @mtd: MTD device structure * - * Deselect, release chip lock and wake up anyone waiting on the device + * Release chip lock and wake up anyone waiting on the device. */ static void nand_release_device(struct mtd_info *mtd) { struct nand_chip *chip = mtd->priv; - /* De-select the NAND device */ - chip->select_chip(mtd, -1); - /* Release the controller and the chip */ spin_lock(&chip->controller->lock); chip->controller->active = NULL; @@ -116,9 +145,9 @@ static void nand_release_device(struct mtd_info *mtd) /** * nand_read_byte - [DEFAULT] read one byte from the chip - * @mtd: MTD device structure + * @mtd: MTD device structure * - * Default read function for 8bit buswith + * Default read function for 8bit buswidth */ static uint8_t nand_read_byte(struct mtd_info *mtd) { @@ -127,11 +156,12 @@ static uint8_t nand_read_byte(struct mtd_info *mtd) } /** - * nand_read_byte16 - [DEFAULT] read one byte endianess aware from the chip - * @mtd: MTD device structure + * nand_read_byte16 - [DEFAULT] read one byte endianness aware from the chip + * nand_read_byte16 - [DEFAULT] read one byte endianness aware from the chip + * @mtd: MTD device structure + * + * Default read function for 16bit buswidth with endianness conversion. * - * Default read function for 16bit buswith with - * endianess conversion */ static uint8_t nand_read_byte16(struct mtd_info *mtd) { @@ -141,10 +171,9 @@ static uint8_t nand_read_byte16(struct mtd_info *mtd) /** * nand_read_word - [DEFAULT] read one word from the chip - * @mtd: MTD device structure + * @mtd: MTD device structure * - * Default read function for 16bit buswith without - * endianess conversion + * Default read function for 16bit buswidth without endianness conversion. */ static u16 nand_read_word(struct mtd_info *mtd) { @@ -154,8 +183,8 @@ static u16 nand_read_word(struct mtd_info *mtd) /** * nand_select_chip - [DEFAULT] control CE line - * @mtd: MTD device structure - * @chipnr: chipnumber to select, -1 for deselect + * @mtd: MTD device structure + * @chipnr: chipnumber to select, -1 for deselect * * Default select function for 1 chip devices. */ @@ -176,214 +205,280 @@ static void nand_select_chip(struct mtd_info *mtd, int chipnr) } /** - * nand_write_buf - [DEFAULT] write buffer to chip - * @mtd: MTD device structure - * @buf: data buffer - * @len: number of bytes to write + * nand_write_byte - [DEFAULT] write single byte to chip + * @mtd: MTD device structure + * @byte: value to write * - * Default write function for 8bit buswith + * Default function to write a byte to I/O[7:0] */ -static void nand_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) +static void nand_write_byte(struct mtd_info *mtd, uint8_t byte) { - int i; struct nand_chip *chip = mtd->priv; - for (i = 0; i < len; i++) - writeb(buf[i], chip->IO_ADDR_W); + chip->write_buf(mtd, &byte, 1); } /** - * nand_read_buf - [DEFAULT] read chip data into buffer - * @mtd: MTD device structure - * @buf: buffer to store date - * @len: number of bytes to read + * nand_write_byte16 - [DEFAULT] write single byte to a chip with width 16 + * @mtd: MTD device structure + * @byte: value to write * - * Default read function for 8bit buswith + * Default function to write a byte to I/O[7:0] on a 16-bit wide chip. */ -static void nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +static void nand_write_byte16(struct mtd_info *mtd, uint8_t byte) { - int i; struct nand_chip *chip = mtd->priv; + uint16_t word = byte; - for (i = 0; i < len; i++) - buf[i] = readb(chip->IO_ADDR_R); + /* + * It's not entirely clear what should happen to I/O[15:8] when writing + * a byte. The ONFi spec (Revision 3.1; 2012-09-19, Section 2.16) reads: + * + * When the host supports a 16-bit bus width, only data is + * transferred at the 16-bit width. All address and command line + * transfers shall use only the lower 8-bits of the data bus. During + * command transfers, the host may place any value on the upper + * 8-bits of the data bus. During address transfers, the host shall + * set the upper 8-bits of the data bus to 00h. + * + * One user of the write_byte callback is nand_onfi_set_features. The + * four parameters are specified to be written to I/O[7:0], but this is + * neither an address nor a command transfer. Let's assume a 0 on the + * upper I/O lines is OK. + */ + chip->write_buf(mtd, (uint8_t *)&word, 2); } /** - * nand_verify_buf - [DEFAULT] Verify chip data against buffer - * @mtd: MTD device structure - * @buf: buffer containing the data to compare - * @len: number of bytes to compare + * nand_write_buf - [DEFAULT] write buffer to chip + * @mtd: MTD device structure + * @buf: data buffer + * @len: number of bytes to write * - * Default verify function for 8bit buswith + * Default write function for 8bit buswidth. */ -static int nand_verify_buf(struct mtd_info *mtd, const uint8_t *buf, int len) +static void nand_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) { - int i; struct nand_chip *chip = mtd->priv; - for (i = 0; i < len; i++) - if (buf[i] != readb(chip->IO_ADDR_R)) - return -EFAULT; - return 0; + iowrite8_rep(chip->IO_ADDR_W, buf, len); } /** - * nand_write_buf16 - [DEFAULT] write buffer to chip - * @mtd: MTD device structure - * @buf: data buffer - * @len: number of bytes to write + * nand_read_buf - [DEFAULT] read chip data into buffer + * @mtd: MTD device structure + * @buf: buffer to store date + * @len: number of bytes to read * - * Default write function for 16bit buswith + * Default read function for 8bit buswidth. */ -static void nand_write_buf16(struct mtd_info *mtd, const uint8_t *buf, int len) +static void nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) { - int i; struct nand_chip *chip = mtd->priv; - u16 *p = (u16 *) buf; - len >>= 1; - - for (i = 0; i < len; i++) - writew(p[i], chip->IO_ADDR_W); + ioread8_rep(chip->IO_ADDR_R, buf, len); } /** - * nand_read_buf16 - [DEFAULT] read chip data into buffer - * @mtd: MTD device structure - * @buf: buffer to store date - * @len: number of bytes to read + * nand_write_buf16 - [DEFAULT] write buffer to chip + * @mtd: MTD device structure + * @buf: data buffer + * @len: number of bytes to write * - * Default read function for 16bit buswith + * Default write function for 16bit buswidth. */ -static void nand_read_buf16(struct mtd_info *mtd, uint8_t *buf, int len) +static void nand_write_buf16(struct mtd_info *mtd, const uint8_t *buf, int len) { - int i; struct nand_chip *chip = mtd->priv; u16 *p = (u16 *) buf; - len >>= 1; - for (i = 0; i < len; i++) - p[i] = readw(chip->IO_ADDR_R); + iowrite16_rep(chip->IO_ADDR_W, p, len >> 1); } /** - * nand_verify_buf16 - [DEFAULT] Verify chip data against buffer - * @mtd: MTD device structure - * @buf: buffer containing the data to compare - * @len: number of bytes to compare + * nand_read_buf16 - [DEFAULT] read chip data into buffer + * @mtd: MTD device structure + * @buf: buffer to store date + * @len: number of bytes to read * - * Default verify function for 16bit buswith + * Default read function for 16bit buswidth. */ -static int nand_verify_buf16(struct mtd_info *mtd, const uint8_t *buf, int len) +static void nand_read_buf16(struct mtd_info *mtd, uint8_t *buf, int len) { - int i; struct nand_chip *chip = mtd->priv; u16 *p = (u16 *) buf; - len >>= 1; - for (i = 0; i < len; i++) - if (p[i] != readw(chip->IO_ADDR_R)) - return -EFAULT; - - return 0; + ioread16_rep(chip->IO_ADDR_R, p, len >> 1); } /** * nand_block_bad - [DEFAULT] Read bad block marker from the chip - * @mtd: MTD device structure - * @ofs: offset from device start - * @getchip: 0, if the chip is already selected + * @mtd: MTD device structure + * @ofs: offset from device start + * @getchip: 0, if the chip is already selected * * Check, if the block is bad. */ static int nand_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip) { - int page, chipnr, res = 0; + int page, chipnr, res = 0, i = 0; struct nand_chip *chip = mtd->priv; u16 bad; + if (chip->bbt_options & NAND_BBT_SCANLASTPAGE) + ofs += mtd->erasesize - mtd->writesize; + + page = (int)(ofs >> chip->page_shift) & chip->pagemask; + if (getchip) { - page = (int)(ofs >> chip->page_shift); chipnr = (int)(ofs >> chip->chip_shift); - nand_get_device(chip, mtd, FL_READING); + nand_get_device(mtd, FL_READING); /* Select the NAND device */ chip->select_chip(mtd, chipnr); - } else - page = (int)ofs; - - if (chip->options & NAND_BUSWIDTH_16) { - chip->cmdfunc(mtd, NAND_CMD_READOOB, chip->badblockpos & 0xFE, - page & chip->pagemask); - bad = cpu_to_le16(chip->read_word(mtd)); - if (chip->badblockpos & 0x1) - bad >>= 8; - if ((bad & 0xFF) != 0xff) - res = 1; - } else { - chip->cmdfunc(mtd, NAND_CMD_READOOB, chip->badblockpos, - page & chip->pagemask); - if (chip->read_byte(mtd) != 0xff) - res = 1; } - if (getchip) + do { + if (chip->options & NAND_BUSWIDTH_16) { + chip->cmdfunc(mtd, NAND_CMD_READOOB, + chip->badblockpos & 0xFE, page); + bad = cpu_to_le16(chip->read_word(mtd)); + if (chip->badblockpos & 0x1) + bad >>= 8; + else + bad &= 0xFF; + } else { + chip->cmdfunc(mtd, NAND_CMD_READOOB, chip->badblockpos, + page); + bad = chip->read_byte(mtd); + } + + if (likely(chip->badblockbits == 8)) + res = bad != 0xFF; + else + res = hweight8(bad) < chip->badblockbits; + ofs += mtd->writesize; + page = (int)(ofs >> chip->page_shift) & chip->pagemask; + i++; + } while (!res && i < 2 && (chip->bbt_options & NAND_BBT_SCAN2NDPAGE)); + + if (getchip) { + chip->select_chip(mtd, -1); nand_release_device(mtd); + } return res; } /** - * nand_default_block_markbad - [DEFAULT] mark a block bad - * @mtd: MTD device structure - * @ofs: offset from device start + * nand_default_block_markbad - [DEFAULT] mark a block bad via bad block marker + * @mtd: MTD device structure + * @ofs: offset from device start * - * This is the default implementation, which can be overridden by - * a hardware specific driver. -*/ + * This is the default implementation, which can be overridden by a hardware + * specific driver. It provides the details for writing a bad block marker to a + * block. + */ static int nand_default_block_markbad(struct mtd_info *mtd, loff_t ofs) { struct nand_chip *chip = mtd->priv; + struct mtd_oob_ops ops; uint8_t buf[2] = { 0, 0 }; - int block, ret; + int ret = 0, res, i = 0; - /* Get block number */ - block = ((int)ofs) >> chip->bbt_erase_shift; - if (chip->bbt) - chip->bbt[block >> 2] |= 0x01 << ((block & 0x03) << 1); + ops.datbuf = NULL; + ops.oobbuf = buf; + ops.ooboffs = chip->badblockpos; + if (chip->options & NAND_BUSWIDTH_16) { + ops.ooboffs &= ~0x01; + ops.len = ops.ooblen = 2; + } else { + ops.len = ops.ooblen = 1; + } + ops.mode = MTD_OPS_PLACE_OOB; - /* Do we have a flash based bad block table ? */ - if (chip->options & NAND_USE_FLASH_BBT) - ret = nand_update_bbt(mtd, ofs); - else { - /* We write two bytes, so we dont have to mess with 16 bit - * access - */ - ofs += mtd->oobsize; - chip->ops.len = 2; - chip->ops.datbuf = NULL; - chip->ops.oobbuf = buf; - chip->ops.ooboffs = chip->badblockpos & ~0x01; + /* Write to first/last page(s) if necessary */ + if (chip->bbt_options & NAND_BBT_SCANLASTPAGE) + ofs += mtd->erasesize - mtd->writesize; + do { + res = nand_do_write_oob(mtd, ofs, &ops); + if (!ret) + ret = res; + + i++; + ofs += mtd->writesize; + } while ((chip->bbt_options & NAND_BBT_SCAN2NDPAGE) && i < 2); - ret = nand_do_write_oob(mtd, ofs, &chip->ops); + return ret; +} + +/** + * nand_block_markbad_lowlevel - mark a block bad + * @mtd: MTD device structure + * @ofs: offset from device start + * + * This function performs the generic NAND bad block marking steps (i.e., bad + * block table(s) and/or marker(s)). We only allow the hardware driver to + * specify how to write bad block markers to OOB (chip->block_markbad). + * + * We try operations in the following order: + * (1) erase the affected block, to allow OOB marker to be written cleanly + * (2) write bad block marker to OOB area of affected block (unless flag + * NAND_BBT_NO_OOB_BBM is present) + * (3) update the BBT + * Note that we retain the first error encountered in (2) or (3), finish the + * procedures, and dump the error in the end. +*/ +static int nand_block_markbad_lowlevel(struct mtd_info *mtd, loff_t ofs) +{ + struct nand_chip *chip = mtd->priv; + int res, ret = 0; + + if (!(chip->bbt_options & NAND_BBT_NO_OOB_BBM)) { + struct erase_info einfo; + + /* Attempt erase before marking OOB */ + memset(&einfo, 0, sizeof(einfo)); + einfo.mtd = mtd; + einfo.addr = ofs; + einfo.len = 1ULL << chip->phys_erase_shift; + nand_erase_nand(mtd, &einfo, 0); + + /* Write bad block marker to OOB */ + nand_get_device(mtd, FL_WRITING); + ret = chip->block_markbad(mtd, ofs); + nand_release_device(mtd); + } + + /* Mark block bad in BBT */ + if (chip->bbt) { + res = nand_markbad_bbt(mtd, ofs); + if (!ret) + ret = res; } + if (!ret) mtd->ecc_stats.badblocks++; + return ret; } /** * nand_check_wp - [GENERIC] check if the chip is write protected - * @mtd: MTD device structure - * Check, if the device is write protected + * @mtd: MTD device structure * - * The function expects, that the device is already selected + * Check, if the device is write protected. The function expects, that the + * device is already selected. */ static int nand_check_wp(struct mtd_info *mtd) { struct nand_chip *chip = mtd->priv; + + /* Broken xD cards report WP despite being writable */ + if (chip->options & NAND_BROKEN_XD) + return 0; + /* Check the WP bit */ chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); return (chip->read_byte(mtd) & NAND_STATUS_WP) ? 0 : 1; @@ -391,10 +486,10 @@ static int nand_check_wp(struct mtd_info *mtd) /** * nand_block_checkbad - [GENERIC] Check if a block is marked bad - * @mtd: MTD device structure - * @ofs: offset from device start - * @getchip: 0, if the chip is already selected - * @allowbbt: 1, if its allowed to access the bbt area + * @mtd: MTD device structure + * @ofs: offset from device start + * @getchip: 0, if the chip is already selected + * @allowbbt: 1, if its allowed to access the bbt area * * Check, if the block is bad. Either by reading the bad block table or * calling of the scan function. @@ -411,17 +506,40 @@ static int nand_block_checkbad(struct mtd_info *mtd, loff_t ofs, int getchip, return nand_isbad_bbt(mtd, ofs, allowbbt); } -/* - * Wait for the ready pin, after a command - * The timeout is catched later. +/** + * panic_nand_wait_ready - [GENERIC] Wait for the ready pin after commands. + * @mtd: MTD device structure + * @timeo: Timeout + * + * Helper function for nand_wait_ready used when needing to wait in interrupt + * context. */ -static void nand_wait_ready(struct mtd_info *mtd) +static void panic_nand_wait_ready(struct mtd_info *mtd, unsigned long timeo) { struct nand_chip *chip = mtd->priv; - unsigned long timeo = jiffies + 2; + int i; + + /* Wait for the device to get ready */ + for (i = 0; i < timeo; i++) { + if (chip->dev_ready(mtd)) + break; + touch_softlockup_watchdog(); + mdelay(1); + } +} + +/* Wait for the ready pin, after a command. The timeout is caught later. */ +void nand_wait_ready(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + unsigned long timeo = jiffies + msecs_to_jiffies(20); + + /* 400ms timeout */ + if (in_interrupt() || oops_in_progress) + return panic_nand_wait_ready(mtd, 400); led_trigger_event(nand_led_trigger, LED_FULL); - /* wait until command is processed or timeout occures */ + /* Wait until command is processed or timeout occurs */ do { if (chip->dev_ready(mtd)) break; @@ -429,16 +547,17 @@ static void nand_wait_ready(struct mtd_info *mtd) } while (time_before(jiffies, timeo)); led_trigger_event(nand_led_trigger, LED_OFF); } +EXPORT_SYMBOL_GPL(nand_wait_ready); /** * nand_command - [DEFAULT] Send command to NAND device - * @mtd: MTD device structure - * @command: the command to be sent - * @column: the column address for this command, -1 if none - * @page_addr: the page address for this command, -1 if none + * @mtd: MTD device structure + * @command: the command to be sent + * @column: the column address for this command, -1 if none + * @page_addr: the page address for this command, -1 if none * - * Send command to NAND device. This function is used for small page - * devices (256/512 Bytes per page) + * Send command to NAND device. This function is used for small page devices + * (512 Bytes per page). */ static void nand_command(struct mtd_info *mtd, unsigned int command, int column, int page_addr) @@ -446,9 +565,7 @@ static void nand_command(struct mtd_info *mtd, unsigned int command, register struct nand_chip *chip = mtd->priv; int ctrl = NAND_CTRL_CLE | NAND_CTRL_CHANGE; - /* - * Write out the command to the device. - */ + /* Write out the command to the device */ if (command == NAND_CMD_SEQIN) { int readcmd; @@ -468,14 +585,13 @@ static void nand_command(struct mtd_info *mtd, unsigned int command, } chip->cmd_ctrl(mtd, command, ctrl); - /* - * Address cycle, when necessary - */ + /* Address cycle, when necessary */ ctrl = NAND_CTRL_ALE | NAND_CTRL_CHANGE; /* Serially input address */ if (column != -1) { /* Adjust columns for 16 bit buswidth */ - if (chip->options & NAND_BUSWIDTH_16) + if (chip->options & NAND_BUSWIDTH_16 && + !nand_opcode_8bits(command)) column >>= 1; chip->cmd_ctrl(mtd, column, ctrl); ctrl &= ~NAND_CTRL_CHANGE; @@ -491,8 +607,8 @@ static void nand_command(struct mtd_info *mtd, unsigned int command, chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE); /* - * program and erase have their own busy handlers - * status and sequential in needs no delay + * Program and erase have their own busy handlers status and sequential + * in needs no delay */ switch (command) { @@ -511,7 +627,8 @@ static void nand_command(struct mtd_info *mtd, unsigned int command, NAND_CTRL_CLE | NAND_CTRL_CHANGE); chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE); - while (!(chip->read_byte(mtd) & NAND_STATUS_READY)) ; + while (!(chip->read_byte(mtd) & NAND_STATUS_READY)) + ; return; /* This applies to read commands */ @@ -525,8 +642,10 @@ static void nand_command(struct mtd_info *mtd, unsigned int command, return; } } - /* Apply this short delay always to ensure that we do wait tWB in - * any case on any machine. */ + /* + * Apply this short delay always to ensure that we do wait tWB in + * any case on any machine. + */ ndelay(100); nand_wait_ready(mtd); @@ -534,14 +653,14 @@ static void nand_command(struct mtd_info *mtd, unsigned int command, /** * nand_command_lp - [DEFAULT] Send command to NAND large page device - * @mtd: MTD device structure - * @command: the command to be sent - * @column: the column address for this command, -1 if none - * @page_addr: the page address for this command, -1 if none + * @mtd: MTD device structure + * @command: the command to be sent + * @column: the column address for this command, -1 if none + * @page_addr: the page address for this command, -1 if none * * Send command to NAND device. This is the version for the new large page - * devices We dont have the separate regions as we have in the small page - * devices. We must emulate NAND_CMD_READOOB to keep the code compatible. + * devices. We don't have the separate regions as we have in the small page + * devices. We must emulate NAND_CMD_READOOB to keep the code compatible. */ static void nand_command_lp(struct mtd_info *mtd, unsigned int command, int column, int page_addr) @@ -555,8 +674,7 @@ static void nand_command_lp(struct mtd_info *mtd, unsigned int command, } /* Command latch cycle */ - chip->cmd_ctrl(mtd, command & 0xff, - NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE); + chip->cmd_ctrl(mtd, command, NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE); if (column != -1 || page_addr != -1) { int ctrl = NAND_CTRL_CHANGE | NAND_NCE | NAND_ALE; @@ -564,7 +682,8 @@ static void nand_command_lp(struct mtd_info *mtd, unsigned int command, /* Serially input address */ if (column != -1) { /* Adjust columns for 16 bit buswidth */ - if (chip->options & NAND_BUSWIDTH_16) + if (chip->options & NAND_BUSWIDTH_16 && + !nand_opcode_8bits(command)) column >>= 1; chip->cmd_ctrl(mtd, column, ctrl); ctrl &= ~NAND_CTRL_CHANGE; @@ -583,8 +702,8 @@ static void nand_command_lp(struct mtd_info *mtd, unsigned int command, chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE); /* - * program and erase have their own busy handlers - * status, sequential in, and deplete1 need no delay + * Program and erase have their own busy handlers status, sequential + * in, and deplete1 need no delay. */ switch (command) { @@ -595,18 +714,6 @@ static void nand_command_lp(struct mtd_info *mtd, unsigned int command, case NAND_CMD_SEQIN: case NAND_CMD_RNDIN: case NAND_CMD_STATUS: - case NAND_CMD_DEPLETE1: - return; - - /* - * read error status commands require only a short delay - */ - case NAND_CMD_STATUS_ERROR: - case NAND_CMD_STATUS_ERROR0: - case NAND_CMD_STATUS_ERROR1: - case NAND_CMD_STATUS_ERROR2: - case NAND_CMD_STATUS_ERROR3: - udelay(chip->chip_delay); return; case NAND_CMD_RESET: @@ -617,7 +724,8 @@ static void nand_command_lp(struct mtd_info *mtd, unsigned int command, NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE); chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE); - while (!(chip->read_byte(mtd) & NAND_STATUS_READY)) ; + while (!(chip->read_byte(mtd) & NAND_STATUS_READY)) + ; return; case NAND_CMD_RNDOUT: @@ -638,7 +746,7 @@ static void nand_command_lp(struct mtd_info *mtd, unsigned int command, default: /* * If we don't have access to the busy pin, we apply the given - * command delay + * command delay. */ if (!chip->dev_ready) { udelay(chip->chip_delay); @@ -646,32 +754,49 @@ static void nand_command_lp(struct mtd_info *mtd, unsigned int command, } } - /* Apply this short delay always to ensure that we do wait tWB in - * any case on any machine. */ + /* + * Apply this short delay always to ensure that we do wait tWB in + * any case on any machine. + */ ndelay(100); nand_wait_ready(mtd); } /** + * panic_nand_get_device - [GENERIC] Get chip for selected access + * @chip: the nand chip descriptor + * @mtd: MTD device structure + * @new_state: the state which is requested + * + * Used when in panic, no locks are taken. + */ +static void panic_nand_get_device(struct nand_chip *chip, + struct mtd_info *mtd, int new_state) +{ + /* Hardware controller shared among independent devices */ + chip->controller->active = chip; + chip->state = new_state; +} + +/** * nand_get_device - [GENERIC] Get chip for selected access - * @chip: the nand chip descriptor - * @mtd: MTD device structure - * @new_state: the state which is requested + * @mtd: MTD device structure + * @new_state: the state which is requested * * Get the device and lock it for exclusive access */ static int -nand_get_device(struct nand_chip *chip, struct mtd_info *mtd, int new_state) +nand_get_device(struct mtd_info *mtd, int new_state) { + struct nand_chip *chip = mtd->priv; spinlock_t *lock = &chip->controller->lock; wait_queue_head_t *wq = &chip->controller->wq; DECLARE_WAITQUEUE(wait, current); - retry: +retry: spin_lock(lock); - /* Hardware controller shared among independend devices */ - /* Hardware controller shared among independend devices */ + /* Hardware controller shared among independent devices */ if (!chip->controller->active) chip->controller->active = chip; @@ -681,8 +806,11 @@ nand_get_device(struct nand_chip *chip, struct mtd_info *mtd, int new_state) return 0; } if (new_state == FL_PM_SUSPENDED) { - spin_unlock(lock); - return (chip->state == FL_PM_SUSPENDED) ? 0 : -EAGAIN; + if (chip->controller->active->state == FL_PM_SUSPENDED) { + chip->state = FL_PM_SUSPENDED; + spin_unlock(lock); + return 0; + } } set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(wq, &wait); @@ -693,84 +821,317 @@ nand_get_device(struct nand_chip *chip, struct mtd_info *mtd, int new_state) } /** - * nand_wait - [DEFAULT] wait until the command is done - * @mtd: MTD device structure - * @chip: NAND chip structure + * panic_nand_wait - [GENERIC] wait until the command is done + * @mtd: MTD device structure + * @chip: NAND chip structure + * @timeo: timeout * - * Wait for command done. This applies to erase and program only - * Erase can take up to 400ms and program up to 20ms according to - * general NAND and SmartMedia specs + * Wait for command done. This is a helper function for nand_wait used when + * we are in interrupt context. May happen when in panic and trying to write + * an oops through mtdoops. + */ +static void panic_nand_wait(struct mtd_info *mtd, struct nand_chip *chip, + unsigned long timeo) +{ + int i; + for (i = 0; i < timeo; i++) { + if (chip->dev_ready) { + if (chip->dev_ready(mtd)) + break; + } else { + if (chip->read_byte(mtd) & NAND_STATUS_READY) + break; + } + mdelay(1); + } +} + +/** + * nand_wait - [DEFAULT] wait until the command is done + * @mtd: MTD device structure + * @chip: NAND chip structure + * + * Wait for command done. This applies to erase and program only. Erase can + * take up to 400ms and program up to 20ms according to general NAND and + * SmartMedia specs. */ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip) { - unsigned long timeo = jiffies; int status, state = chip->state; - - if (state == FL_ERASING) - timeo += (HZ * 400) / 1000; - else - timeo += (HZ * 20) / 1000; + unsigned long timeo = (state == FL_ERASING ? 400 : 20); led_trigger_event(nand_led_trigger, LED_FULL); - /* Apply this short delay always to ensure that we do wait tWB in - * any case on any machine. */ + /* + * Apply this short delay always to ensure that we do wait tWB in any + * case on any machine. + */ ndelay(100); - if ((state == FL_ERASING) && (chip->options & NAND_IS_AND)) - chip->cmdfunc(mtd, NAND_CMD_STATUS_MULTI, -1, -1); - else - chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); + chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); - while (time_before(jiffies, timeo)) { - if (chip->dev_ready) { - if (chip->dev_ready(mtd)) - break; - } else { - if (chip->read_byte(mtd) & NAND_STATUS_READY) - break; + if (in_interrupt() || oops_in_progress) + panic_nand_wait(mtd, chip, timeo); + else { + timeo = jiffies + msecs_to_jiffies(timeo); + while (time_before(jiffies, timeo)) { + if (chip->dev_ready) { + if (chip->dev_ready(mtd)) + break; + } else { + if (chip->read_byte(mtd) & NAND_STATUS_READY) + break; + } + cond_resched(); } - cond_resched(); } led_trigger_event(nand_led_trigger, LED_OFF); status = (int)chip->read_byte(mtd); + /* This can happen if in case of timeout or buggy dev_ready */ + WARN_ON(!(status & NAND_STATUS_READY)); return status; } /** - * nand_read_page_raw - [Intern] read raw page data without ecc - * @mtd: mtd info structure - * @chip: nand chip info structure - * @buf: buffer to store read data + * __nand_unlock - [REPLACEABLE] unlocks specified locked blocks + * @mtd: mtd info + * @ofs: offset to start unlock from + * @len: length to unlock + * @invert: when = 0, unlock the range of blocks within the lower and + * upper boundary address + * when = 1, unlock the range of blocks outside the boundaries + * of the lower and upper boundary address + * + * Returs unlock status. + */ +static int __nand_unlock(struct mtd_info *mtd, loff_t ofs, + uint64_t len, int invert) +{ + int ret = 0; + int status, page; + struct nand_chip *chip = mtd->priv; + + /* Submit address of first page to unlock */ + page = ofs >> chip->page_shift; + chip->cmdfunc(mtd, NAND_CMD_UNLOCK1, -1, page & chip->pagemask); + + /* Submit address of last page to unlock */ + page = (ofs + len) >> chip->page_shift; + chip->cmdfunc(mtd, NAND_CMD_UNLOCK2, -1, + (page | invert) & chip->pagemask); + + /* Call wait ready function */ + status = chip->waitfunc(mtd, chip); + /* See if device thinks it succeeded */ + if (status & NAND_STATUS_FAIL) { + pr_debug("%s: error status = 0x%08x\n", + __func__, status); + ret = -EIO; + } + + return ret; +} + +/** + * nand_unlock - [REPLACEABLE] unlocks specified locked blocks + * @mtd: mtd info + * @ofs: offset to start unlock from + * @len: length to unlock + * + * Returns unlock status. + */ +int nand_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + int ret = 0; + int chipnr; + struct nand_chip *chip = mtd->priv; + + pr_debug("%s: start = 0x%012llx, len = %llu\n", + __func__, (unsigned long long)ofs, len); + + if (check_offs_len(mtd, ofs, len)) + ret = -EINVAL; + + /* Align to last block address if size addresses end of the device */ + if (ofs + len == mtd->size) + len -= mtd->erasesize; + + nand_get_device(mtd, FL_UNLOCKING); + + /* Shift to get chip number */ + chipnr = ofs >> chip->chip_shift; + + chip->select_chip(mtd, chipnr); + + /* Check, if it is write protected */ + if (nand_check_wp(mtd)) { + pr_debug("%s: device is write protected!\n", + __func__); + ret = -EIO; + goto out; + } + + ret = __nand_unlock(mtd, ofs, len, 0); + +out: + chip->select_chip(mtd, -1); + nand_release_device(mtd); + + return ret; +} +EXPORT_SYMBOL(nand_unlock); + +/** + * nand_lock - [REPLACEABLE] locks all blocks present in the device + * @mtd: mtd info + * @ofs: offset to start unlock from + * @len: length to unlock + * + * This feature is not supported in many NAND parts. 'Micron' NAND parts do + * have this feature, but it allows only to lock all blocks, not for specified + * range for block. Implementing 'lock' feature by making use of 'unlock', for + * now. + * + * Returns lock status. + */ +int nand_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + int ret = 0; + int chipnr, status, page; + struct nand_chip *chip = mtd->priv; + + pr_debug("%s: start = 0x%012llx, len = %llu\n", + __func__, (unsigned long long)ofs, len); + + if (check_offs_len(mtd, ofs, len)) + ret = -EINVAL; + + nand_get_device(mtd, FL_LOCKING); + + /* Shift to get chip number */ + chipnr = ofs >> chip->chip_shift; + + chip->select_chip(mtd, chipnr); + + /* Check, if it is write protected */ + if (nand_check_wp(mtd)) { + pr_debug("%s: device is write protected!\n", + __func__); + status = MTD_ERASE_FAILED; + ret = -EIO; + goto out; + } + + /* Submit address of first page to lock */ + page = ofs >> chip->page_shift; + chip->cmdfunc(mtd, NAND_CMD_LOCK, -1, page & chip->pagemask); + + /* Call wait ready function */ + status = chip->waitfunc(mtd, chip); + /* See if device thinks it succeeded */ + if (status & NAND_STATUS_FAIL) { + pr_debug("%s: error status = 0x%08x\n", + __func__, status); + ret = -EIO; + goto out; + } + + ret = __nand_unlock(mtd, ofs, len, 0x1); + +out: + chip->select_chip(mtd, -1); + nand_release_device(mtd); + + return ret; +} +EXPORT_SYMBOL(nand_lock); + +/** + * nand_read_page_raw - [INTERN] read raw page data without ecc + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: buffer to store read data + * @oob_required: caller requires OOB data read to chip->oob_poi + * @page: page number to read + * + * 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) + uint8_t *buf, int oob_required, int page) { chip->read_buf(mtd, buf, mtd->writesize); - chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + if (oob_required) + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); return 0; } /** - * nand_read_page_swecc - {REPLACABLE] software ecc based page read function - * @mtd: mtd info structure - * @chip: nand chip info structure - * @buf: buffer to store read data + * 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 + * @oob_required: caller requires OOB data read to chip->oob_poi + * @page: page number to read + * + * 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 oob_required, int page) +{ + 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 - [REPLACEABLE] software ECC based page read function + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: buffer to store read data + * @oob_required: caller requires OOB data read to chip->oob_poi + * @page: page number to read */ static int nand_read_page_swecc(struct mtd_info *mtd, struct nand_chip *chip, - uint8_t *buf) + uint8_t *buf, int oob_required, int page) { int i, eccsize = chip->ecc.size; int eccbytes = chip->ecc.bytes; int eccsteps = chip->ecc.steps; uint8_t *p = buf; - uint8_t *ecc_calc = chip->buffers.ecccalc; - uint8_t *ecc_code = chip->buffers.ecccode; - int *eccpos = chip->ecc.layout->eccpos; + uint8_t *ecc_calc = chip->buffers->ecccalc; + uint8_t *ecc_code = chip->buffers->ecccode; + uint32_t *eccpos = chip->ecc.layout->eccpos; + unsigned int max_bitflips = 0; - nand_read_page_raw(mtd, chip, buf); + chip->ecc.read_page_raw(mtd, chip, buf, 1, page); for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) chip->ecc.calculate(mtd, p, &ecc_calc[i]); @@ -785,32 +1146,130 @@ static int nand_read_page_swecc(struct mtd_info *mtd, struct nand_chip *chip, int stat; stat = chip->ecc.correct(mtd, p, &ecc_code[i], &ecc_calc[i]); - if (stat == -1) + if (stat < 0) { mtd->ecc_stats.failed++; - else + } else { mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } } - return 0; + return max_bitflips; } /** - * nand_read_page_hwecc - {REPLACABLE] hardware ecc based page read function - * @mtd: mtd info structure - * @chip: nand chip info structure - * @buf: buffer to store read data + * nand_read_subpage - [REPLACEABLE] ECC based sub-page read function + * @mtd: mtd info structure + * @chip: nand chip info structure + * @data_offs: offset of requested data within the page + * @readlen: data length + * @bufpoi: buffer to store read data + * @page: page number to read + */ +static int nand_read_subpage(struct mtd_info *mtd, struct nand_chip *chip, + uint32_t data_offs, uint32_t readlen, uint8_t *bufpoi, + int page) +{ + int start_step, end_step, num_steps; + uint32_t *eccpos = chip->ecc.layout->eccpos; + uint8_t *p; + int data_col_addr, i, gaps = 0; + int datafrag_len, eccfrag_len, aligned_len, aligned_pos; + int busw = (chip->options & NAND_BUSWIDTH_16) ? 2 : 1; + int index; + unsigned int max_bitflips = 0; + + /* Column address within the page aligned to ECC size (256bytes) */ + start_step = data_offs / chip->ecc.size; + end_step = (data_offs + readlen - 1) / chip->ecc.size; + num_steps = end_step - start_step + 1; + index = start_step * chip->ecc.bytes; + + /* Data size aligned to ECC ecc.size */ + datafrag_len = num_steps * chip->ecc.size; + eccfrag_len = num_steps * chip->ecc.bytes; + + data_col_addr = start_step * chip->ecc.size; + /* If we read not a page aligned data */ + if (data_col_addr != 0) + chip->cmdfunc(mtd, NAND_CMD_RNDOUT, data_col_addr, -1); + + p = bufpoi + data_col_addr; + chip->read_buf(mtd, p, datafrag_len); + + /* Calculate ECC */ + for (i = 0; i < eccfrag_len ; i += chip->ecc.bytes, p += chip->ecc.size) + chip->ecc.calculate(mtd, p, &chip->buffers->ecccalc[i]); + + /* + * The performance is faster if we position offsets according to + * ecc.pos. Let's make sure that there are no gaps in ECC positions. + */ + for (i = 0; i < eccfrag_len - 1; i++) { + if (eccpos[i + index] + 1 != eccpos[i + index + 1]) { + gaps = 1; + break; + } + } + if (gaps) { + chip->cmdfunc(mtd, NAND_CMD_RNDOUT, mtd->writesize, -1); + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + } else { + /* + * Send the command to read the particular ECC bytes take care + * about buswidth alignment in read_buf. + */ + aligned_pos = eccpos[index] & ~(busw - 1); + aligned_len = eccfrag_len; + if (eccpos[index] & (busw - 1)) + aligned_len++; + if (eccpos[index + (num_steps * chip->ecc.bytes)] & (busw - 1)) + aligned_len++; + + chip->cmdfunc(mtd, NAND_CMD_RNDOUT, + mtd->writesize + aligned_pos, -1); + chip->read_buf(mtd, &chip->oob_poi[aligned_pos], aligned_len); + } + + for (i = 0; i < eccfrag_len; i++) + chip->buffers->ecccode[i] = chip->oob_poi[eccpos[i + index]]; + + p = bufpoi + data_col_addr; + for (i = 0; i < eccfrag_len ; i += chip->ecc.bytes, p += chip->ecc.size) { + int stat; + + stat = chip->ecc.correct(mtd, p, + &chip->buffers->ecccode[i], &chip->buffers->ecccalc[i]); + if (stat < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } + } + return max_bitflips; +} + +/** + * nand_read_page_hwecc - [REPLACEABLE] hardware ECC based page read function + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: buffer to store read data + * @oob_required: caller requires OOB data read to chip->oob_poi + * @page: page number to read * - * Not for syndrome calculating ecc controllers which need a special oob layout + * Not for syndrome calculating ECC controllers which need a special oob layout. */ static int nand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip, - uint8_t *buf) + uint8_t *buf, int oob_required, int page) { int i, eccsize = chip->ecc.size; int eccbytes = chip->ecc.bytes; int eccsteps = chip->ecc.steps; uint8_t *p = buf; - uint8_t *ecc_calc = chip->buffers.ecccalc; - uint8_t *ecc_code = chip->buffers.ecccode; - int *eccpos = chip->ecc.layout->eccpos; + uint8_t *ecc_calc = chip->buffers->ecccalc; + uint8_t *ecc_code = chip->buffers->ecccode; + uint32_t *eccpos = chip->ecc.layout->eccpos; + unsigned int max_bitflips = 0; for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { chip->ecc.hwctl(mtd, NAND_ECC_READ); @@ -829,31 +1288,88 @@ static int nand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip, int stat; stat = chip->ecc.correct(mtd, p, &ecc_code[i], &ecc_calc[i]); - if (stat == -1) + if (stat < 0) { mtd->ecc_stats.failed++; - else + } else { mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } } - return 0; + return max_bitflips; } /** - * nand_read_page_syndrome - {REPLACABLE] hardware ecc syndrom based page read - * @mtd: mtd info structure - * @chip: nand chip info structure - * @buf: buffer to store read data + * nand_read_page_hwecc_oob_first - [REPLACEABLE] hw ecc, read oob first + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: buffer to store read data + * @oob_required: caller requires OOB data read to chip->oob_poi + * @page: page number to read + * + * Hardware ECC for large page chips, require OOB to be read first. For this + * ECC mode, the write_page method is re-used from ECC_HW. These methods + * read/write ECC from the OOB area, unlike the ECC_HW_SYNDROME support with + * multiple ECC steps, follows the "infix ECC" scheme and reads/writes ECC from + * the data area, by overwriting the NAND manufacturer bad block markings. + */ +static int nand_read_page_hwecc_oob_first(struct mtd_info *mtd, + struct nand_chip *chip, uint8_t *buf, int oob_required, int page) +{ + int i, eccsize = chip->ecc.size; + int eccbytes = chip->ecc.bytes; + int eccsteps = chip->ecc.steps; + uint8_t *p = buf; + uint8_t *ecc_code = chip->buffers->ecccode; + uint32_t *eccpos = chip->ecc.layout->eccpos; + uint8_t *ecc_calc = chip->buffers->ecccalc; + unsigned int max_bitflips = 0; + + /* Read the OOB area first */ + chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page); + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page); + + for (i = 0; i < chip->ecc.total; i++) + ecc_code[i] = chip->oob_poi[eccpos[i]]; + + for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { + int stat; + + chip->ecc.hwctl(mtd, NAND_ECC_READ); + chip->read_buf(mtd, p, eccsize); + chip->ecc.calculate(mtd, p, &ecc_calc[i]); + + stat = chip->ecc.correct(mtd, p, &ecc_code[i], NULL); + if (stat < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } + } + return max_bitflips; +} + +/** + * nand_read_page_syndrome - [REPLACEABLE] hardware ECC syndrome based page read + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: buffer to store read data + * @oob_required: caller requires OOB data read to chip->oob_poi + * @page: page number to read * - * The hw generator calculates the error syndrome automatically. Therefor - * we need a special oob layout and handling. + * The hw generator calculates the error syndrome automatically. Therefore we + * need a special oob layout and handling. */ static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip, - uint8_t *buf) + uint8_t *buf, int oob_required, int page) { int i, eccsize = chip->ecc.size; int eccbytes = chip->ecc.bytes; int eccsteps = chip->ecc.steps; uint8_t *p = buf; uint8_t *oob = chip->oob_poi; + unsigned int max_bitflips = 0; for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { int stat; @@ -870,10 +1386,12 @@ static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip, chip->read_buf(mtd, oob, eccbytes); stat = chip->ecc.correct(mtd, p, oob, NULL); - if (stat == -1) + if (stat < 0) { mtd->ecc_stats.failed++; - else + } else { mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } oob += eccbytes; @@ -888,34 +1406,33 @@ static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip, if (i) chip->read_buf(mtd, oob, i); - return 0; + return max_bitflips; } /** - * nand_transfer_oob - [Internal] Transfer oob to client buffer - * @chip: nand chip structure - * @oob: oob destination address - * @ops: oob ops structure + * nand_transfer_oob - [INTERN] Transfer oob to client buffer + * @chip: nand chip structure + * @oob: oob destination address + * @ops: oob ops structure + * @len: size of oob to transfer */ static uint8_t *nand_transfer_oob(struct nand_chip *chip, uint8_t *oob, - struct mtd_oob_ops *ops) + struct mtd_oob_ops *ops, size_t len) { - size_t len = ops->ooblen; + switch (ops->mode) { - switch(ops->mode) { - - case MTD_OOB_PLACE: - case MTD_OOB_RAW: + case MTD_OPS_PLACE_OOB: + case MTD_OPS_RAW: memcpy(oob, chip->oob_poi + ops->ooboffs, len); return oob + len; - case MTD_OOB_AUTO: { + case MTD_OPS_AUTO_OOB: { struct nand_oobfree *free = chip->ecc.layout->oobfree; uint32_t boffs = 0, roffs = ops->ooboffs; size_t bytes = 0; - for(; free->length && len; free++, len -= bytes) { - /* Read request not from offset 0 ? */ + for (; free->length && len; free++, len -= bytes) { + /* Read request not from offset 0? */ if (unlikely(roffs)) { if (roffs >= free->length) { roffs -= free->length; @@ -941,27 +1458,53 @@ static uint8_t *nand_transfer_oob(struct nand_chip *chip, uint8_t *oob, } /** - * nand_do_read_ops - [Internal] Read data with ECC + * nand_setup_read_retry - [INTERN] Set the READ RETRY mode + * @mtd: MTD device structure + * @retry_mode: the retry mode to use * - * @mtd: MTD device structure - * @from: offset to read from - * @ops: oob ops structure + * Some vendors supply a special command to shift the Vt threshold, to be used + * when there are too many bitflips in a page (i.e., ECC error). After setting + * a new threshold, the host should retry reading the page. + */ +static int nand_setup_read_retry(struct mtd_info *mtd, int retry_mode) +{ + struct nand_chip *chip = mtd->priv; + + pr_debug("setting READ RETRY mode %d\n", retry_mode); + + if (retry_mode >= chip->read_retries) + return -EINVAL; + + if (!chip->setup_read_retry) + return -EOPNOTSUPP; + + return chip->setup_read_retry(mtd, retry_mode); +} + +/** + * nand_do_read_ops - [INTERN] Read data with ECC + * @mtd: MTD device structure + * @from: offset to read from + * @ops: oob ops structure * * Internal function. Called with chip held. */ static int nand_do_read_ops(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) { - int chipnr, page, realpage, col, bytes, aligned; + int chipnr, page, realpage, col, bytes, aligned, oob_required; struct nand_chip *chip = mtd->priv; - struct mtd_ecc_stats stats; - int blkcheck = (1 << (chip->phys_erase_shift - chip->page_shift)) - 1; - int sndcmd = 1; int ret = 0; uint32_t readlen = ops->len; - uint8_t *bufpoi, *oob, *buf; + uint32_t oobreadlen = ops->ooblen; + uint32_t max_oobsize = ops->mode == MTD_OPS_AUTO_OOB ? + mtd->oobavail : mtd->oobsize; - stats = mtd->ecc_stats; + uint8_t *bufpoi, *oob, *buf; + int use_bufpoi; + unsigned int max_bitflips = 0; + int retry_mode = 0; + bool ecc_fail = false; chipnr = (int)(from >> chip->chip_shift); chip->select_chip(mtd, chipnr); @@ -970,69 +1513,131 @@ static int nand_do_read_ops(struct mtd_info *mtd, loff_t from, page = realpage & chip->pagemask; col = (int)(from & (mtd->writesize - 1)); - chip->oob_poi = chip->buffers.oobrbuf; buf = ops->datbuf; oob = ops->oobbuf; + oob_required = oob ? 1 : 0; + + while (1) { + unsigned int ecc_failures = mtd->ecc_stats.failed; - while(1) { bytes = min(mtd->writesize - col, readlen); aligned = (bytes == mtd->writesize); - /* Is the current page in the buffer ? */ + if (!aligned) + use_bufpoi = 1; + else if (chip->options & NAND_USE_BOUNCE_BUFFER) + use_bufpoi = !virt_addr_valid(buf); + else + use_bufpoi = 0; + + /* Is the current page in the buffer? */ if (realpage != chip->pagebuf || oob) { - bufpoi = aligned ? buf : chip->buffers.databuf; + bufpoi = use_bufpoi ? chip->buffers->databuf : buf; - if (likely(sndcmd)) { - chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page); - sndcmd = 0; - } + if (use_bufpoi && aligned) + pr_debug("%s: using read bounce buffer for buf@%p\n", + __func__, buf); - /* Now read the page into the buffer */ - ret = chip->ecc.read_page(mtd, chip, bufpoi); - if (ret < 0) +read_retry: + chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page); + + /* + * Now read the page into the buffer. Absent an error, + * the read methods return max bitflips per ecc step. + */ + if (unlikely(ops->mode == MTD_OPS_RAW)) + ret = chip->ecc.read_page_raw(mtd, chip, bufpoi, + oob_required, + page); + else if (!aligned && NAND_HAS_SUBPAGE_READ(chip) && + !oob) + ret = chip->ecc.read_subpage(mtd, chip, + col, bytes, bufpoi, + page); + else + ret = chip->ecc.read_page(mtd, chip, bufpoi, + oob_required, page); + if (ret < 0) { + if (use_bufpoi) + /* Invalidate page cache */ + chip->pagebuf = -1; break; + } + + max_bitflips = max_t(unsigned int, max_bitflips, ret); /* Transfer not aligned data */ - if (!aligned) { - chip->pagebuf = realpage; - memcpy(buf, chip->buffers.databuf + col, bytes); + if (use_bufpoi) { + if (!NAND_HAS_SUBPAGE_READ(chip) && !oob && + !(mtd->ecc_stats.failed - ecc_failures) && + (ops->mode != MTD_OPS_RAW)) { + chip->pagebuf = realpage; + chip->pagebuf_bitflips = ret; + } else { + /* Invalidate page cache */ + chip->pagebuf = -1; + } + memcpy(buf, chip->buffers->databuf + col, bytes); } - buf += bytes; - if (unlikely(oob)) { - /* Raw mode does data:oob:data:oob */ - if (ops->mode != MTD_OOB_RAW) - oob = nand_transfer_oob(chip, oob, ops); - else - buf = nand_transfer_oob(chip, buf, ops); + int toread = min(oobreadlen, max_oobsize); + + if (toread) { + oob = nand_transfer_oob(chip, + oob, ops, toread); + oobreadlen -= toread; + } } - if (!(chip->options & NAND_NO_READRDY)) { - /* - * Apply delay or wait for ready/busy pin. Do - * this before the AUTOINCR check, so no - * problems arise if a chip which does auto - * increment is marked as NOAUTOINCR by the - * board driver. - */ + if (chip->options & NAND_NEED_READRDY) { + /* Apply delay or wait for ready/busy pin */ if (!chip->dev_ready) udelay(chip->chip_delay); else nand_wait_ready(mtd); } + + if (mtd->ecc_stats.failed - ecc_failures) { + if (retry_mode + 1 < chip->read_retries) { + retry_mode++; + ret = nand_setup_read_retry(mtd, + retry_mode); + if (ret < 0) + break; + + /* Reset failures; retry */ + mtd->ecc_stats.failed = ecc_failures; + goto read_retry; + } else { + /* No more retry modes; real failure */ + ecc_fail = true; + } + } + + buf += bytes; } else { - memcpy(buf, chip->buffers.databuf + col, bytes); + memcpy(buf, chip->buffers->databuf + col, bytes); buf += bytes; + max_bitflips = max_t(unsigned int, max_bitflips, + chip->pagebuf_bitflips); } readlen -= bytes; + /* Reset to retry mode 0 */ + if (retry_mode) { + ret = nand_setup_read_retry(mtd, 0); + if (ret < 0) + break; + retry_mode = 0; + } + if (!readlen) break; - /* For subsequent reads align to page boundary. */ + /* For subsequent reads align to page boundary */ col = 0; /* Increment page address */ realpage++; @@ -1044,90 +1649,72 @@ static int nand_do_read_ops(struct mtd_info *mtd, loff_t from, chip->select_chip(mtd, -1); chip->select_chip(mtd, chipnr); } - - /* Check, if the chip supports auto page increment - * or if we have hit a block boundary. - */ - if (!NAND_CANAUTOINCR(chip) || !(page & blkcheck)) - sndcmd = 1; } + chip->select_chip(mtd, -1); ops->retlen = ops->len - (size_t) readlen; + if (oob) + ops->oobretlen = ops->ooblen - oobreadlen; - if (ret) + if (ret < 0) return ret; - if (mtd->ecc_stats.failed - stats.failed) + if (ecc_fail) return -EBADMSG; - return mtd->ecc_stats.corrected - stats.corrected ? -EUCLEAN : 0; + return max_bitflips; } /** - * nand_read - [MTD Interface] MTD compability function for nand_do_read_ecc - * @mtd: MTD device structure - * @from: offset to read from - * @len: number of bytes to read - * @retlen: pointer to variable to store the number of read bytes - * @buf: the databuffer to put data + * nand_read - [MTD Interface] MTD compatibility function for nand_do_read_ecc + * @mtd: MTD device structure + * @from: offset to read from + * @len: number of bytes to read + * @retlen: pointer to variable to store the number of read bytes + * @buf: the databuffer to put data * - * Get hold of the chip and call nand_do_read + * Get hold of the chip and call nand_do_read. */ static int nand_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, uint8_t *buf) { - struct nand_chip *chip = mtd->priv; + struct mtd_oob_ops ops; int ret; - /* Do not allow reads past end of device */ - if ((from + len) > mtd->size) - return -EINVAL; - if (!len) - return 0; - - nand_get_device(chip, mtd, FL_READING); - - chip->ops.len = len; - chip->ops.datbuf = buf; - chip->ops.oobbuf = NULL; - - ret = nand_do_read_ops(mtd, from, &chip->ops); - - *retlen = chip->ops.retlen; - + nand_get_device(mtd, FL_READING); + ops.len = len; + ops.datbuf = buf; + ops.oobbuf = NULL; + ops.mode = MTD_OPS_PLACE_OOB; + ret = nand_do_read_ops(mtd, from, &ops); + *retlen = ops.retlen; nand_release_device(mtd); - return ret; } /** - * nand_read_oob_std - [REPLACABLE] the most common OOB data read function - * @mtd: mtd info structure - * @chip: nand chip info structure - * @page: page number to read - * @sndcmd: flag whether to issue read command or not + * nand_read_oob_std - [REPLACEABLE] the most common OOB data read function + * @mtd: mtd info structure + * @chip: nand chip info structure + * @page: page number to read */ static int nand_read_oob_std(struct mtd_info *mtd, struct nand_chip *chip, - int page, int sndcmd) + int page) { - if (sndcmd) { - chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page); - sndcmd = 0; - } + chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page); chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); - return sndcmd; + return 0; } /** - * nand_read_oob_syndrome - [REPLACABLE] OOB data read function for HW ECC + * nand_read_oob_syndrome - [REPLACEABLE] OOB data read function for HW ECC * with syndromes - * @mtd: mtd info structure - * @chip: nand chip info structure - * @page: page number to read - * @sndcmd: flag whether to issue read command or not + * @mtd: mtd info structure + * @chip: nand chip info structure + * @page: page number to read */ static int nand_read_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip, - int page, int sndcmd) + int page) { uint8_t *buf = chip->oob_poi; int length = mtd->oobsize; @@ -1154,14 +1741,14 @@ static int nand_read_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip, if (length > 0) chip->read_buf(mtd, bufpoi, length); - return 1; + return 0; } /** - * nand_write_oob_std - [REPLACABLE] the most common OOB data write function - * @mtd: mtd info structure - * @chip: nand chip info structure - * @page: page number to write + * nand_write_oob_std - [REPLACEABLE] the most common OOB data write function + * @mtd: mtd info structure + * @chip: nand chip info structure + * @page: page number to write */ static int nand_write_oob_std(struct mtd_info *mtd, struct nand_chip *chip, int page) @@ -1181,11 +1768,11 @@ static int nand_write_oob_std(struct mtd_info *mtd, struct nand_chip *chip, } /** - * nand_write_oob_syndrome - [REPLACABLE] OOB data write function for HW ECC - * with syndrome - only for large page flash ! - * @mtd: mtd info structure - * @chip: nand chip info structure - * @page: page number to write + * nand_write_oob_syndrome - [REPLACEABLE] OOB data write function for HW ECC + * with syndrome - only for large page flash + * @mtd: mtd info structure + * @chip: nand chip info structure + * @page: page number to write */ static int nand_write_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip, int page) @@ -1240,24 +1827,48 @@ static int nand_write_oob_syndrome(struct mtd_info *mtd, } /** - * nand_do_read_oob - [Intern] NAND read out-of-band - * @mtd: MTD device structure - * @from: offset to read from - * @ops: oob operations description structure + * nand_do_read_oob - [INTERN] NAND read out-of-band + * @mtd: MTD device structure + * @from: offset to read from + * @ops: oob operations description structure * - * NAND read out-of-band data from the spare area + * NAND read out-of-band data from the spare area. */ static int nand_do_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) { - int page, realpage, chipnr, sndcmd = 1; + int page, realpage, chipnr; struct nand_chip *chip = mtd->priv; - int blkcheck = (1 << (chip->phys_erase_shift - chip->page_shift)) - 1; - int readlen = ops->len; + struct mtd_ecc_stats stats; + int readlen = ops->ooblen; + int len; uint8_t *buf = ops->oobbuf; + int ret = 0; - DEBUG(MTD_DEBUG_LEVEL3, "nand_read_oob: from = 0x%08Lx, len = %i\n", - (unsigned long long)from, readlen); + pr_debug("%s: from = 0x%08Lx, len = %i\n", + __func__, (unsigned long long)from, readlen); + + stats = mtd->ecc_stats; + + if (ops->mode == MTD_OPS_AUTO_OOB) + len = chip->ecc.layout->oobavail; + else + len = mtd->oobsize; + + if (unlikely(ops->ooboffs >= len)) { + pr_debug("%s: attempt to start read outside oob\n", + __func__); + return -EINVAL; + } + + /* Do not allow reads past end of device */ + if (unlikely(from >= mtd->size || + ops->ooboffs + readlen > ((mtd->size >> chip->page_shift) - + (from >> chip->page_shift)) * len)) { + pr_debug("%s: attempt to read beyond end of device\n", + __func__); + return -EINVAL; + } chipnr = (int)(from >> chip->chip_shift); chip->select_chip(mtd, chipnr); @@ -1266,26 +1877,27 @@ static int nand_do_read_oob(struct mtd_info *mtd, loff_t from, realpage = (int)(from >> chip->page_shift); page = realpage & chip->pagemask; - chip->oob_poi = chip->buffers.oobrbuf; + while (1) { + if (ops->mode == MTD_OPS_RAW) + ret = chip->ecc.read_oob_raw(mtd, chip, page); + else + ret = chip->ecc.read_oob(mtd, chip, page); + + if (ret < 0) + break; - while(1) { - sndcmd = chip->ecc.read_oob(mtd, chip, page, sndcmd); - buf = nand_transfer_oob(chip, buf, ops); + len = min(len, readlen); + buf = nand_transfer_oob(chip, buf, ops, len); - if (!(chip->options & NAND_NO_READRDY)) { - /* - * Apply delay or wait for ready/busy pin. Do this - * before the AUTOINCR check, so no problems arise if a - * chip which does auto increment is marked as - * NOAUTOINCR by the board driver. - */ + if (chip->options & NAND_NEED_READRDY) { + /* Apply delay or wait for ready/busy pin */ if (!chip->dev_ready) udelay(chip->chip_delay); else nand_wait_ready(mtd); } - readlen -= ops->ooblen; + readlen -= len; if (!readlen) break; @@ -1299,54 +1911,48 @@ static int nand_do_read_oob(struct mtd_info *mtd, loff_t from, chip->select_chip(mtd, -1); chip->select_chip(mtd, chipnr); } - - /* Check, if the chip supports auto page increment - * or if we have hit a block boundary. - */ - if (!NAND_CANAUTOINCR(chip) || !(page & blkcheck)) - sndcmd = 1; } + chip->select_chip(mtd, -1); - ops->retlen = ops->len; - return 0; + ops->oobretlen = ops->ooblen - readlen; + + if (ret < 0) + return ret; + + if (mtd->ecc_stats.failed - stats.failed) + return -EBADMSG; + + return mtd->ecc_stats.corrected - stats.corrected ? -EUCLEAN : 0; } /** * nand_read_oob - [MTD Interface] NAND read data and/or out-of-band - * @mtd: MTD device structure - * @from: offset to read from - * @ops: oob operation description structure + * @mtd: MTD device structure + * @from: offset to read from + * @ops: oob operation description structure * - * NAND read data and/or out-of-band data + * NAND read data and/or out-of-band data. */ static int nand_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) { - int (*read_page)(struct mtd_info *mtd, struct nand_chip *chip, - uint8_t *buf) = NULL; - struct nand_chip *chip = mtd->priv; int ret = -ENOTSUPP; ops->retlen = 0; /* Do not allow reads past end of device */ - if ((from + ops->len) > mtd->size) { - DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: " - "Attempt read beyond end of device\n"); + if (ops->datbuf && (from + ops->len) > mtd->size) { + pr_debug("%s: attempt to read beyond end of device\n", + __func__); return -EINVAL; } - nand_get_device(chip, mtd, FL_READING); + nand_get_device(mtd, FL_READING); - switch(ops->mode) { - case MTD_OOB_PLACE: - case MTD_OOB_AUTO: - break; - - case MTD_OOB_RAW: - /* Replace the read_page algorithm temporary */ - read_page = chip->ecc.read_page; - chip->ecc.read_page = nand_read_page_raw; + switch (ops->mode) { + case MTD_OPS_PLACE_OOB: + case MTD_OPS_AUTO_OOB: + case MTD_OPS_RAW: break; default: @@ -1358,68 +1964,116 @@ static int nand_read_oob(struct mtd_info *mtd, loff_t from, else ret = nand_do_read_ops(mtd, from, ops); - if (unlikely(ops->mode == MTD_OOB_RAW)) - chip->ecc.read_page = read_page; - out: +out: nand_release_device(mtd); return ret; } /** - * nand_write_page_raw - [Intern] raw page write function - * @mtd: mtd info structure - * @chip: nand chip info structure - * @buf: data buffer + * nand_write_page_raw - [INTERN] raw page write function + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: data buffer + * @oob_required: must write chip->oob_poi to OOB + * + * 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) +static int nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf, int oob_required) { chip->write_buf(mtd, buf, mtd->writesize); - chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); + if (oob_required) + chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); + + return 0; } /** - * nand_write_page_swecc - {REPLACABLE] software ecc based page write function - * @mtd: mtd info structure - * @chip: nand chip info structure - * @buf: data buffer + * nand_write_page_raw_syndrome - [INTERN] raw page write function + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: data buffer + * @oob_required: must write chip->oob_poi to OOB + * + * We need a special oob layout and handling even when ECC isn't checked. + */ +static int nand_write_page_raw_syndrome(struct mtd_info *mtd, + struct nand_chip *chip, + const uint8_t *buf, int oob_required) +{ + 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->write_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); + + return 0; +} +/** + * nand_write_page_swecc - [REPLACEABLE] software ECC based page write function + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: data buffer + * @oob_required: must write chip->oob_poi to OOB */ -static void nand_write_page_swecc(struct mtd_info *mtd, struct nand_chip *chip, - const uint8_t *buf) +static int nand_write_page_swecc(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf, int oob_required) { int i, eccsize = chip->ecc.size; int eccbytes = chip->ecc.bytes; int eccsteps = chip->ecc.steps; - uint8_t *ecc_calc = chip->buffers.ecccalc; + uint8_t *ecc_calc = chip->buffers->ecccalc; const uint8_t *p = buf; - int *eccpos = chip->ecc.layout->eccpos; + uint32_t *eccpos = chip->ecc.layout->eccpos; - /* Software ecc calculation */ + /* Software ECC calculation */ for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) chip->ecc.calculate(mtd, p, &ecc_calc[i]); for (i = 0; i < chip->ecc.total; i++) chip->oob_poi[eccpos[i]] = ecc_calc[i]; - nand_write_page_raw(mtd, chip, buf); + return chip->ecc.write_page_raw(mtd, chip, buf, 1); } /** - * nand_write_page_hwecc - {REPLACABLE] hardware ecc based page write function - * @mtd: mtd info structure - * @chip: nand chip info structure - * @buf: data buffer + * nand_write_page_hwecc - [REPLACEABLE] hardware ECC based page write function + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: data buffer + * @oob_required: must write chip->oob_poi to OOB */ -static void nand_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip, - const uint8_t *buf) +static int nand_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf, int oob_required) { int i, eccsize = chip->ecc.size; int eccbytes = chip->ecc.bytes; int eccsteps = chip->ecc.steps; - uint8_t *ecc_calc = chip->buffers.ecccalc; + uint8_t *ecc_calc = chip->buffers->ecccalc; const uint8_t *p = buf; - int *eccpos = chip->ecc.layout->eccpos; + uint32_t *eccpos = chip->ecc.layout->eccpos; for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { chip->ecc.hwctl(mtd, NAND_ECC_WRITE); @@ -1431,19 +2085,85 @@ static void nand_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip, chip->oob_poi[eccpos[i]] = ecc_calc[i]; chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); + + return 0; } + /** - * nand_write_page_syndrome - {REPLACABLE] hardware ecc syndrom based page write + * nand_write_subpage_hwecc - [REPLACABLE] hardware ECC based subpage write * @mtd: mtd info structure * @chip: nand chip info structure + * @offset: column address of subpage within the page + * @data_len: data length * @buf: data buffer + * @oob_required: must write chip->oob_poi to OOB + */ +static int nand_write_subpage_hwecc(struct mtd_info *mtd, + struct nand_chip *chip, uint32_t offset, + uint32_t data_len, const uint8_t *buf, + int oob_required) +{ + uint8_t *oob_buf = chip->oob_poi; + uint8_t *ecc_calc = chip->buffers->ecccalc; + int ecc_size = chip->ecc.size; + int ecc_bytes = chip->ecc.bytes; + int ecc_steps = chip->ecc.steps; + uint32_t *eccpos = chip->ecc.layout->eccpos; + uint32_t start_step = offset / ecc_size; + uint32_t end_step = (offset + data_len - 1) / ecc_size; + int oob_bytes = mtd->oobsize / ecc_steps; + int step, i; + + for (step = 0; step < ecc_steps; step++) { + /* configure controller for WRITE access */ + chip->ecc.hwctl(mtd, NAND_ECC_WRITE); + + /* write data (untouched subpages already masked by 0xFF) */ + chip->write_buf(mtd, buf, ecc_size); + + /* mask ECC of un-touched subpages by padding 0xFF */ + if ((step < start_step) || (step > end_step)) + memset(ecc_calc, 0xff, ecc_bytes); + else + chip->ecc.calculate(mtd, buf, ecc_calc); + + /* mask OOB of un-touched subpages by padding 0xFF */ + /* if oob_required, preserve OOB metadata of written subpage */ + if (!oob_required || (step < start_step) || (step > end_step)) + memset(oob_buf, 0xff, oob_bytes); + + buf += ecc_size; + ecc_calc += ecc_bytes; + oob_buf += oob_bytes; + } + + /* copy calculated ECC for whole page to chip->buffer->oob */ + /* this include masked-value(0xFF) for unwritten subpages */ + ecc_calc = chip->buffers->ecccalc; + for (i = 0; i < chip->ecc.total; i++) + chip->oob_poi[eccpos[i]] = ecc_calc[i]; + + /* write OOB buffer to NAND device */ + chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); + + return 0; +} + + +/** + * nand_write_page_syndrome - [REPLACEABLE] hardware ECC syndrome based page write + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: data buffer + * @oob_required: must write chip->oob_poi to OOB * - * The hw generator calculates the error syndrome automatically. Therefor - * we need a special oob layout and handling. + * The hw generator calculates the error syndrome automatically. Therefore we + * need a special oob layout and handling. */ -static void nand_write_page_syndrome(struct mtd_info *mtd, - struct nand_chip *chip, const uint8_t *buf) +static int nand_write_page_syndrome(struct mtd_info *mtd, + struct nand_chip *chip, + const uint8_t *buf, int oob_required) { int i, eccsize = chip->ecc.size; int eccbytes = chip->ecc.bytes; @@ -1475,38 +2195,61 @@ static void nand_write_page_syndrome(struct mtd_info *mtd, i = mtd->oobsize - (oob - chip->oob_poi); if (i) chip->write_buf(mtd, oob, i); + + return 0; } /** - * nand_write_page - [INTERNAL] write one page - * @mtd: MTD device structure - * @chip: NAND chip descriptor - * @buf: the data to write - * @page: page number to write - * @cached: cached programming + * nand_write_page - [REPLACEABLE] write one page + * @mtd: MTD device structure + * @chip: NAND chip descriptor + * @offset: address offset within the page + * @data_len: length of actual data to be written + * @buf: the data to write + * @oob_required: must write chip->oob_poi to OOB + * @page: page number to write + * @cached: cached programming + * @raw: use _raw version of write_page */ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip, - const uint8_t *buf, int page, int cached) + uint32_t offset, int data_len, const uint8_t *buf, + int oob_required, int page, int cached, int raw) { - int status; + int status, subpage; + + if (!(chip->options & NAND_NO_SUBPAGE_WRITE) && + chip->ecc.write_subpage) + subpage = offset || (data_len < mtd->writesize); + else + subpage = 0; chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page); - chip->ecc.write_page(mtd, chip, buf); + if (unlikely(raw)) + status = chip->ecc.write_page_raw(mtd, chip, buf, + oob_required); + else if (subpage) + status = chip->ecc.write_subpage(mtd, chip, offset, data_len, + buf, oob_required); + else + status = chip->ecc.write_page(mtd, chip, buf, oob_required); + + if (status < 0) + return status; /* - * Cached progamming disabled for now, Not sure if its worth the - * trouble. The speed gain is not very impressive. (2.3->2.6Mib/s) + * Cached progamming disabled for now. Not sure if it's worth the + * trouble. The speed gain is not very impressive. (2.3->2.6Mib/s). */ cached = 0; - if (!cached || !(chip->options & NAND_CACHEPRG)) { + if (!cached || !NAND_HAS_CACHEPROG(chip)) { chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); status = chip->waitfunc(mtd, chip); /* * See if operation failed and additional status checks are - * available + * available. */ if ((status & NAND_STATUS_FAIL) && (chip->errstat)) status = chip->errstat(mtd, chip, FL_WRITING, status, @@ -1519,41 +2262,41 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip, status = chip->waitfunc(mtd, chip); } -#ifdef CONFIG_MTD_NAND_VERIFY_WRITE - /* Send command to read back the data */ - chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page); - - if (chip->verify_buf(mtd, buf, mtd->writesize)) - return -EIO; -#endif return 0; } /** - * nand_fill_oob - [Internal] Transfer client buffer to oob - * @chip: nand chip structure - * @oob: oob data buffer - * @ops: oob ops structure + * nand_fill_oob - [INTERN] Transfer client buffer to oob + * @mtd: MTD device structure + * @oob: oob data buffer + * @len: oob data write length + * @ops: oob ops structure */ -static uint8_t *nand_fill_oob(struct nand_chip *chip, uint8_t *oob, - struct mtd_oob_ops *ops) +static uint8_t *nand_fill_oob(struct mtd_info *mtd, uint8_t *oob, size_t len, + struct mtd_oob_ops *ops) { - size_t len = ops->ooblen; + struct nand_chip *chip = mtd->priv; - switch(ops->mode) { + /* + * Initialise to all 0xFF, to avoid the possibility of left over OOB + * data from a previous OOB read. + */ + memset(chip->oob_poi, 0xff, mtd->oobsize); + + switch (ops->mode) { - case MTD_OOB_PLACE: - case MTD_OOB_RAW: + case MTD_OPS_PLACE_OOB: + case MTD_OPS_RAW: memcpy(chip->oob_poi + ops->ooboffs, oob, len); return oob + len; - case MTD_OOB_AUTO: { + case MTD_OPS_AUTO_OOB: { struct nand_oobfree *free = chip->ecc.layout->oobfree; uint32_t boffs = 0, woffs = ops->ooboffs; size_t bytes = 0; - for(; free->length && len; free++, len -= bytes) { - /* Write request not from offset 0 ? */ + for (; free->length && len; free++, len -= bytes) { + /* Write request not from offset 0? */ if (unlikely(woffs)) { if (woffs >= free->length) { woffs -= free->length; @@ -1578,45 +2321,53 @@ static uint8_t *nand_fill_oob(struct nand_chip *chip, uint8_t *oob, return NULL; } -#define NOTALIGNED(x) (x & (mtd->writesize-1)) != 0 +#define NOTALIGNED(x) ((x & (chip->subpagesize - 1)) != 0) /** - * nand_do_write_ops - [Internal] NAND write with ECC - * @mtd: MTD device structure - * @to: offset to write to - * @ops: oob operations description structure + * nand_do_write_ops - [INTERN] NAND write with ECC + * @mtd: MTD device structure + * @to: offset to write to + * @ops: oob operations description structure * - * NAND write with ECC + * NAND write with ECC. */ static int nand_do_write_ops(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops) { - int chipnr, realpage, page, blockmask; + int chipnr, realpage, page, blockmask, column; struct nand_chip *chip = mtd->priv; uint32_t writelen = ops->len; + + uint32_t oobwritelen = ops->ooblen; + uint32_t oobmaxlen = ops->mode == MTD_OPS_AUTO_OOB ? + mtd->oobavail : mtd->oobsize; + uint8_t *oob = ops->oobbuf; uint8_t *buf = ops->datbuf; - int bytes = mtd->writesize; int ret; + int oob_required = oob ? 1 : 0; ops->retlen = 0; + if (!writelen) + return 0; - /* reject writes, which are not page aligned */ + /* Reject writes, which are not page aligned */ if (NOTALIGNED(to) || NOTALIGNED(ops->len)) { - printk(KERN_NOTICE "nand_write: " - "Attempt to write not page aligned data\n"); + pr_notice("%s: attempt to write non page aligned data\n", + __func__); return -EINVAL; } - if (!writelen) - return 0; + column = to & (mtd->writesize - 1); chipnr = (int)(to >> chip->chip_shift); chip->select_chip(mtd, chipnr); /* Check, if it is write protected */ - if (nand_check_wp(mtd)) - return -EIO; + if (nand_check_wp(mtd)) { + ret = -EIO; + goto err_out; + } realpage = (int)(to >> chip->page_shift); page = realpage & chip->pagemask; @@ -1627,15 +2378,50 @@ static int nand_do_write_ops(struct mtd_info *mtd, loff_t to, (chip->pagebuf << chip->page_shift) < (to + ops->len)) chip->pagebuf = -1; - chip->oob_poi = chip->buffers.oobwbuf; + /* Don't allow multipage oob writes with offset */ + if (oob && ops->ooboffs && (ops->ooboffs + ops->ooblen > oobmaxlen)) { + ret = -EINVAL; + goto err_out; + } - while(1) { + while (1) { + int bytes = mtd->writesize; int cached = writelen > bytes && page != blockmask; + uint8_t *wbuf = buf; + int use_bufpoi; + int part_pagewr = (column || writelen < (mtd->writesize - 1)); + + if (part_pagewr) + use_bufpoi = 1; + else if (chip->options & NAND_USE_BOUNCE_BUFFER) + use_bufpoi = !virt_addr_valid(buf); + else + use_bufpoi = 0; + + /* Partial page write?, or need to use bounce buffer */ + if (use_bufpoi) { + pr_debug("%s: using write bounce buffer for buf@%p\n", + __func__, buf); + cached = 0; + if (part_pagewr) + bytes = min_t(int, bytes - column, writelen); + chip->pagebuf = -1; + memset(chip->buffers->databuf, 0xff, mtd->writesize); + memcpy(&chip->buffers->databuf[column], buf, bytes); + wbuf = chip->buffers->databuf; + } - if (unlikely(oob)) - oob = nand_fill_oob(chip, oob, ops); - - ret = nand_write_page(mtd, chip, buf, page, cached); + if (unlikely(oob)) { + size_t len = min(oobwritelen, oobmaxlen); + oob = nand_fill_oob(mtd, oob, len, ops); + oobwritelen -= len; + } else { + /* We still need to erase leftover OOB data */ + memset(chip->oob_poi, 0xff, mtd->oobsize); + } + ret = chip->write_page(mtd, chip, column, bytes, wbuf, + oob_required, page, cached, + (ops->mode == MTD_OPS_RAW)); if (ret) break; @@ -1643,6 +2429,7 @@ static int nand_do_write_ops(struct mtd_info *mtd, loff_t to, if (!writelen) break; + column = 0; buf += bytes; realpage++; @@ -1655,71 +2442,119 @@ static int nand_do_write_ops(struct mtd_info *mtd, loff_t to, } } + ops->retlen = ops->len - writelen; if (unlikely(oob)) - memset(chip->oob_poi, 0xff, mtd->oobsize); + ops->oobretlen = ops->ooblen; - ops->retlen = ops->len - writelen; +err_out: + chip->select_chip(mtd, -1); return ret; } /** - * nand_write - [MTD Interface] NAND write with ECC - * @mtd: MTD device structure - * @to: offset to write to - * @len: number of bytes to write - * @retlen: pointer to variable to store the number of written bytes - * @buf: the data to write + * panic_nand_write - [MTD Interface] NAND write with ECC + * @mtd: MTD device structure + * @to: offset to write to + * @len: number of bytes to write + * @retlen: pointer to variable to store the number of written bytes + * @buf: the data to write * - * NAND write with ECC + * NAND write with ECC. Used when performing writes in interrupt context, this + * may for example be called by mtdoops when writing an oops while in panic. */ -static int nand_write(struct mtd_info *mtd, loff_t to, size_t len, - size_t *retlen, const uint8_t *buf) +static int panic_nand_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const uint8_t *buf) { struct nand_chip *chip = mtd->priv; + struct mtd_oob_ops ops; int ret; - /* Do not allow reads past end of device */ - if ((to + len) > mtd->size) - return -EINVAL; - if (!len) - return 0; + /* Wait for the device to get ready */ + panic_nand_wait(mtd, chip, 400); - nand_get_device(chip, mtd, FL_WRITING); + /* Grab the device */ + panic_nand_get_device(chip, mtd, FL_WRITING); - chip->ops.len = len; - chip->ops.datbuf = (uint8_t *)buf; - chip->ops.oobbuf = NULL; + ops.len = len; + ops.datbuf = (uint8_t *)buf; + ops.oobbuf = NULL; + ops.mode = MTD_OPS_PLACE_OOB; - ret = nand_do_write_ops(mtd, to, &chip->ops); + ret = nand_do_write_ops(mtd, to, &ops); - *retlen = chip->ops.retlen; + *retlen = ops.retlen; + return ret; +} - nand_release_device(mtd); +/** + * nand_write - [MTD Interface] NAND write with ECC + * @mtd: MTD device structure + * @to: offset to write to + * @len: number of bytes to write + * @retlen: pointer to variable to store the number of written bytes + * @buf: the data to write + * + * NAND write with ECC. + */ +static int nand_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const uint8_t *buf) +{ + struct mtd_oob_ops ops; + int ret; + nand_get_device(mtd, FL_WRITING); + ops.len = len; + ops.datbuf = (uint8_t *)buf; + ops.oobbuf = NULL; + ops.mode = MTD_OPS_PLACE_OOB; + ret = nand_do_write_ops(mtd, to, &ops); + *retlen = ops.retlen; + nand_release_device(mtd); return ret; } /** * nand_do_write_oob - [MTD Interface] NAND write out-of-band - * @mtd: MTD device structure - * @to: offset to write to - * @ops: oob operation description structure + * @mtd: MTD device structure + * @to: offset to write to + * @ops: oob operation description structure * - * NAND write out-of-band + * NAND write out-of-band. */ static int nand_do_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops) { - int chipnr, page, status; + int chipnr, page, status, len; struct nand_chip *chip = mtd->priv; - DEBUG(MTD_DEBUG_LEVEL3, "nand_write_oob: to = 0x%08x, len = %i\n", - (unsigned int)to, (int)ops->len); + pr_debug("%s: to = 0x%08x, len = %i\n", + __func__, (unsigned int)to, (int)ops->ooblen); + + if (ops->mode == MTD_OPS_AUTO_OOB) + len = chip->ecc.layout->oobavail; + else + len = mtd->oobsize; /* Do not allow write past end of page */ - if ((ops->ooboffs + ops->len) > mtd->oobsize) { - DEBUG(MTD_DEBUG_LEVEL0, "nand_write_oob: " - "Attempt to write past end of page\n"); + if ((ops->ooboffs + ops->ooblen) > len) { + pr_debug("%s: attempt to write past end of page\n", + __func__); + return -EINVAL; + } + + if (unlikely(ops->ooboffs >= len)) { + pr_debug("%s: attempt to start write outside oob\n", + __func__); + return -EINVAL; + } + + /* Do not allow write past end of device */ + if (unlikely(to >= mtd->size || + ops->ooboffs + ops->ooblen > + ((mtd->size >> chip->page_shift) - + (to >> chip->page_shift)) * len)) { + pr_debug("%s: attempt to write beyond end of device\n", + __func__); return -EINVAL; } @@ -1738,61 +2573,58 @@ static int nand_do_write_oob(struct mtd_info *mtd, loff_t to, chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); /* Check, if it is write protected */ - if (nand_check_wp(mtd)) + if (nand_check_wp(mtd)) { + chip->select_chip(mtd, -1); return -EROFS; + } /* Invalidate the page cache, if we write to the cached page */ if (page == chip->pagebuf) chip->pagebuf = -1; - chip->oob_poi = chip->buffers.oobwbuf; - memset(chip->oob_poi, 0xff, mtd->oobsize); - nand_fill_oob(chip, ops->oobbuf, ops); - status = chip->ecc.write_oob(mtd, chip, page & chip->pagemask); - memset(chip->oob_poi, 0xff, mtd->oobsize); + nand_fill_oob(mtd, ops->oobbuf, ops->ooblen, ops); + + if (ops->mode == MTD_OPS_RAW) + status = chip->ecc.write_oob_raw(mtd, chip, page & chip->pagemask); + else + status = chip->ecc.write_oob(mtd, chip, page & chip->pagemask); + + chip->select_chip(mtd, -1); if (status) return status; - ops->retlen = ops->len; + ops->oobretlen = ops->ooblen; return 0; } /** * nand_write_oob - [MTD Interface] NAND write data and/or out-of-band - * @mtd: MTD device structure - * @to: offset to write to - * @ops: oob operation description structure + * @mtd: MTD device structure + * @to: offset to write to + * @ops: oob operation description structure */ static int nand_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops) { - void (*write_page)(struct mtd_info *mtd, struct nand_chip *chip, - const uint8_t *buf) = NULL; - struct nand_chip *chip = mtd->priv; int ret = -ENOTSUPP; ops->retlen = 0; /* Do not allow writes past end of device */ - if ((to + ops->len) > mtd->size) { - DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: " - "Attempt read beyond end of device\n"); + if (ops->datbuf && (to + ops->len) > mtd->size) { + pr_debug("%s: attempt to write beyond end of device\n", + __func__); return -EINVAL; } - nand_get_device(chip, mtd, FL_WRITING); + nand_get_device(mtd, FL_WRITING); - switch(ops->mode) { - case MTD_OOB_PLACE: - case MTD_OOB_AUTO: - break; - - case MTD_OOB_RAW: - /* Replace the write_page algorithm temporary */ - write_page = chip->ecc.write_page; - chip->ecc.write_page = nand_write_page_raw; + switch (ops->mode) { + case MTD_OPS_PLACE_OOB: + case MTD_OPS_AUTO_OOB: + case MTD_OPS_RAW: break; default: @@ -1804,103 +2636,64 @@ static int nand_write_oob(struct mtd_info *mtd, loff_t to, else ret = nand_do_write_ops(mtd, to, ops); - if (unlikely(ops->mode == MTD_OOB_RAW)) - chip->ecc.write_page = write_page; - out: +out: nand_release_device(mtd); return ret; } /** - * single_erease_cmd - [GENERIC] NAND standard block erase command function - * @mtd: MTD device structure - * @page: the page address of the block which will be erased + * single_erase - [GENERIC] NAND standard block erase command function + * @mtd: MTD device structure + * @page: the page address of the block which will be erased * - * Standard erase command for NAND chips + * Standard erase command for NAND chips. Returns NAND status. */ -static void single_erase_cmd(struct mtd_info *mtd, int page) +static int single_erase(struct mtd_info *mtd, int page) { struct nand_chip *chip = mtd->priv; /* Send commands to erase a block */ chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page); chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1); -} -/** - * multi_erease_cmd - [GENERIC] AND specific block erase command function - * @mtd: MTD device structure - * @page: the page address of the block which will be erased - * - * AND multi block erase command function - * Erase 4 consecutive blocks - */ -static void multi_erase_cmd(struct mtd_info *mtd, int page) -{ - struct nand_chip *chip = mtd->priv; - /* Send commands to erase a block */ - chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page++); - chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page++); - chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page++); - chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page); - chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1); + return chip->waitfunc(mtd, chip); } /** * nand_erase - [MTD Interface] erase block(s) - * @mtd: MTD device structure - * @instr: erase instruction + * @mtd: MTD device structure + * @instr: erase instruction * - * Erase one ore more blocks + * Erase one ore more blocks. */ static int nand_erase(struct mtd_info *mtd, struct erase_info *instr) { return nand_erase_nand(mtd, instr, 0); } -#define BBT_PAGE_MASK 0xffffff3f /** - * nand_erase_nand - [Internal] erase block(s) - * @mtd: MTD device structure - * @instr: erase instruction - * @allowbbt: allow erasing the bbt area + * nand_erase_nand - [INTERN] erase block(s) + * @mtd: MTD device structure + * @instr: erase instruction + * @allowbbt: allow erasing the bbt area * - * Erase one ore more blocks + * Erase one ore more blocks. */ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr, int allowbbt) { - int page, len, status, pages_per_block, ret, chipnr; + int page, status, pages_per_block, ret, chipnr; struct nand_chip *chip = mtd->priv; - int rewrite_bbt[NAND_MAX_CHIPS]={0}; - unsigned int bbt_masked_page = 0xffffffff; + loff_t len; - DEBUG(MTD_DEBUG_LEVEL3, "nand_erase: start = 0x%08x, len = %i\n", - (unsigned int)instr->addr, (unsigned int)instr->len); + pr_debug("%s: start = 0x%012llx, len = %llu\n", + __func__, (unsigned long long)instr->addr, + (unsigned long long)instr->len); - /* Start address must align on block boundary */ - if (instr->addr & ((1 << chip->phys_erase_shift) - 1)) { - DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: Unaligned address\n"); + if (check_offs_len(mtd, instr->addr, instr->len)) return -EINVAL; - } - - /* Length must align on block boundary */ - if (instr->len & ((1 << chip->phys_erase_shift) - 1)) { - DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: " - "Length not block aligned\n"); - return -EINVAL; - } - - /* Do not allow erase past end of device */ - if ((instr->len + instr->addr) > mtd->size) { - DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: " - "Erase past end of device\n"); - return -EINVAL; - } - - instr->fail_addr = 0xffffffff; /* Grab the lock and see if the device is available */ - nand_get_device(chip, mtd, FL_ERASING); + nand_get_device(mtd, FL_ERASING); /* Shift to get first page */ page = (int)(instr->addr >> chip->page_shift); @@ -1914,49 +2707,36 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr, /* Check, if it is write protected */ if (nand_check_wp(mtd)) { - DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: " - "Device is write protected!!!\n"); + pr_debug("%s: device is write protected!\n", + __func__); instr->state = MTD_ERASE_FAILED; goto erase_exit; } - /* - * If BBT requires refresh, set the BBT page mask to see if the BBT - * should be rewritten. Otherwise the mask is set to 0xffffffff which - * can not be matched. This is also done when the bbt is actually - * erased to avoid recusrsive updates - */ - if (chip->options & BBT_AUTO_REFRESH && !allowbbt) - bbt_masked_page = chip->bbt_td->pages[chipnr] & BBT_PAGE_MASK; - /* Loop through the pages */ len = instr->len; instr->state = MTD_ERASING; while (len) { - /* - * heck if we have a bad block, we do not erase bad blocks ! - */ + /* Check if we have a bad block, we do not erase bad blocks! */ if (nand_block_checkbad(mtd, ((loff_t) page) << chip->page_shift, 0, allowbbt)) { - printk(KERN_WARNING "nand_erase: attempt to erase a " - "bad block at page 0x%08x\n", page); + pr_warn("%s: attempt to erase a bad block at page 0x%08x\n", + __func__, page); instr->state = MTD_ERASE_FAILED; goto erase_exit; } /* * Invalidate the page cache, if we erase the block which - * contains the current cached page + * contains the current cached page. */ if (page <= chip->pagebuf && chip->pagebuf < (page + pages_per_block)) chip->pagebuf = -1; - chip->erase_cmd(mtd, page & chip->pagemask); - - status = chip->waitfunc(mtd, chip); + status = chip->erase(mtd, page & chip->pagemask); /* * See if operation failed and additional status checks are @@ -1968,23 +2748,16 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr, /* See if block erase succeeded */ if (status & NAND_STATUS_FAIL) { - DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: " - "Failed erase, page 0x%08x\n", page); + pr_debug("%s: failed erase, page 0x%08x\n", + __func__, page); instr->state = MTD_ERASE_FAILED; - instr->fail_addr = (page << chip->page_shift); + instr->fail_addr = + ((loff_t)page << chip->page_shift); goto erase_exit; } - /* - * If BBT requires refresh, set the BBT rewrite flag to the - * page being erased - */ - if (bbt_masked_page != 0xffffffff && - (page & BBT_PAGE_MASK) == bbt_masked_page) - rewrite_bbt[chipnr] = (page << chip->page_shift); - /* Increment page address and decrement length */ - len -= (1 << chip->phys_erase_shift); + len -= (1ULL << chip->phys_erase_shift); page += pages_per_block; /* Check, if we cross a chip boundary */ @@ -1992,45 +2765,21 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr, chipnr++; chip->select_chip(mtd, -1); chip->select_chip(mtd, chipnr); - - /* - * If BBT requires refresh and BBT-PERCHIP, set the BBT - * page mask to see if this BBT should be rewritten - */ - if (bbt_masked_page != 0xffffffff && - (chip->bbt_td->options & NAND_BBT_PERCHIP)) - bbt_masked_page = chip->bbt_td->pages[chipnr] & - BBT_PAGE_MASK; } } instr->state = MTD_ERASE_DONE; - erase_exit: +erase_exit: ret = instr->state == MTD_ERASE_DONE ? 0 : -EIO; - /* Do call back function */ - if (!ret) - mtd_erase_callback(instr); /* Deselect and wake up anyone waiting on the device */ + chip->select_chip(mtd, -1); nand_release_device(mtd); - /* - * If BBT requires refresh and erase was successful, rewrite any - * selected bad block tables - */ - if (bbt_masked_page == 0xffffffff || ret) - return ret; - - for (chipnr = 0; chipnr < chip->numchips; chipnr++) { - if (!rewrite_bbt[chipnr]) - continue; - /* update the BBT for chip */ - DEBUG(MTD_DEBUG_LEVEL0, "nand_erase_nand: nand_update_bbt " - "(%d:0x%0x 0x%0x)\n", chipnr, rewrite_bbt[chipnr], - chip->bbt_td->pages[chipnr]); - nand_update_bbt(mtd, rewrite_bbt[chipnr]); - } + /* Do call back function */ + if (!ret) + mtd_erase_callback(instr); /* Return more or less happy */ return ret; @@ -2038,70 +2787,116 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr, /** * nand_sync - [MTD Interface] sync - * @mtd: MTD device structure + * @mtd: MTD device structure * - * Sync is actually a wait for chip ready function + * Sync is actually a wait for chip ready function. */ static void nand_sync(struct mtd_info *mtd) { - struct nand_chip *chip = mtd->priv; - - DEBUG(MTD_DEBUG_LEVEL3, "nand_sync: called\n"); + pr_debug("%s: called\n", __func__); /* Grab the lock and see if the device is available */ - nand_get_device(chip, mtd, FL_SYNCING); + nand_get_device(mtd, FL_SYNCING); /* Release it and go back */ nand_release_device(mtd); } /** * nand_block_isbad - [MTD Interface] Check if block at offset is bad - * @mtd: MTD device structure - * @offs: offset relative to mtd start + * @mtd: MTD device structure + * @offs: offset relative to mtd start */ static int nand_block_isbad(struct mtd_info *mtd, loff_t offs) { - /* Check for invalid offset */ - if (offs > mtd->size) - return -EINVAL; - return nand_block_checkbad(mtd, offs, 1, 0); } /** * nand_block_markbad - [MTD Interface] Mark block at the given offset as bad - * @mtd: MTD device structure - * @ofs: offset relative to mtd start + * @mtd: MTD device structure + * @ofs: offset relative to mtd start */ static int nand_block_markbad(struct mtd_info *mtd, loff_t ofs) { - struct nand_chip *chip = mtd->priv; int ret; - if ((ret = nand_block_isbad(mtd, ofs))) { - /* If it was bad already, return success and do nothing. */ + ret = nand_block_isbad(mtd, ofs); + if (ret) { + /* If it was bad already, return success and do nothing */ if (ret > 0) return 0; return ret; } - return chip->block_markbad(mtd, ofs); + return nand_block_markbad_lowlevel(mtd, ofs); +} + +/** + * nand_onfi_set_features- [REPLACEABLE] set features for ONFI nand + * @mtd: MTD device structure + * @chip: nand chip info structure + * @addr: feature address. + * @subfeature_param: the subfeature parameters, a four bytes array. + */ +static int nand_onfi_set_features(struct mtd_info *mtd, struct nand_chip *chip, + int addr, uint8_t *subfeature_param) +{ + int status; + int i; + + if (!chip->onfi_version || + !(le16_to_cpu(chip->onfi_params.opt_cmd) + & ONFI_OPT_CMD_SET_GET_FEATURES)) + return -EINVAL; + + chip->cmdfunc(mtd, NAND_CMD_SET_FEATURES, addr, -1); + for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i) + chip->write_byte(mtd, subfeature_param[i]); + + status = chip->waitfunc(mtd, chip); + if (status & NAND_STATUS_FAIL) + return -EIO; + return 0; +} + +/** + * nand_onfi_get_features- [REPLACEABLE] get features for ONFI nand + * @mtd: MTD device structure + * @chip: nand chip info structure + * @addr: feature address. + * @subfeature_param: the subfeature parameters, a four bytes array. + */ +static int nand_onfi_get_features(struct mtd_info *mtd, struct nand_chip *chip, + int addr, uint8_t *subfeature_param) +{ + int i; + + if (!chip->onfi_version || + !(le16_to_cpu(chip->onfi_params.opt_cmd) + & ONFI_OPT_CMD_SET_GET_FEATURES)) + return -EINVAL; + + /* clear the sub feature parameters */ + memset(subfeature_param, 0, ONFI_SUBFEATURE_PARAM_LEN); + + chip->cmdfunc(mtd, NAND_CMD_GET_FEATURES, addr, -1); + for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i) + *subfeature_param++ = chip->read_byte(mtd); + return 0; } /** * nand_suspend - [MTD Interface] Suspend the NAND flash - * @mtd: MTD device structure + * @mtd: MTD device structure */ static int nand_suspend(struct mtd_info *mtd) { - struct nand_chip *chip = mtd->priv; - - return nand_get_device(chip, mtd, FL_PM_SUSPENDED); + return nand_get_device(mtd, FL_PM_SUSPENDED); } /** * nand_resume - [MTD Interface] Resume the NAND flash - * @mtd: MTD device structure + * @mtd: MTD device structure */ static void nand_resume(struct mtd_info *mtd) { @@ -2110,13 +2905,11 @@ static void nand_resume(struct mtd_info *mtd) if (chip->state == FL_PM_SUSPENDED) nand_release_device(mtd); else - printk(KERN_ERR "nand_resume() called for a chip which is not " - "in suspended state\n"); + pr_err("%s called for a chip which is not in suspended state\n", + __func__); } -/* - * Set default functions - */ +/* Set default functions */ static void nand_set_defaults(struct nand_chip *chip, int busw) { /* check for proper chip_delay setup, set 20us if not */ @@ -2133,7 +2926,15 @@ static void nand_set_defaults(struct nand_chip *chip, int busw) if (!chip->select_chip) chip->select_chip = nand_select_chip; - if (!chip->read_byte) + + /* set for ONFI nand */ + if (!chip->onfi_set_features) + chip->onfi_set_features = nand_onfi_set_features; + if (!chip->onfi_get_features) + chip->onfi_get_features = nand_onfi_get_features; + + /* If called twice, pointers that depend on busw may need to be reset */ + if (!chip->read_byte || chip->read_byte == nand_read_byte) chip->read_byte = busw ? nand_read_byte16 : nand_read_byte; if (!chip->read_word) chip->read_word = nand_read_word; @@ -2141,12 +2942,12 @@ static void nand_set_defaults(struct nand_chip *chip, int busw) chip->block_bad = nand_block_bad; if (!chip->block_markbad) chip->block_markbad = nand_default_block_markbad; - if (!chip->write_buf) + if (!chip->write_buf || chip->write_buf == nand_write_buf) chip->write_buf = busw ? nand_write_buf16 : nand_write_buf; - if (!chip->read_buf) + if (!chip->write_byte || chip->write_byte == nand_write_byte) + chip->write_byte = busw ? nand_write_byte16 : nand_write_byte; + if (!chip->read_buf || chip->read_buf == nand_read_buf) chip->read_buf = busw ? nand_read_buf16 : nand_read_buf; - if (!chip->verify_buf) - chip->verify_buf = busw ? nand_verify_buf16 : nand_verify_buf; if (!chip->scan_bbt) chip->scan_bbt = nand_default_bbt; @@ -2158,342 +2959,1175 @@ static void nand_set_defaults(struct nand_chip *chip, int busw) } +/* Sanitize ONFI strings so we can safely print them */ +static void sanitize_string(uint8_t *s, size_t len) +{ + ssize_t i; + + /* Null terminate */ + s[len - 1] = 0; + + /* Remove non printable chars */ + for (i = 0; i < len - 1; i++) { + if (s[i] < ' ' || s[i] > 127) + s[i] = '?'; + } + + /* Remove trailing spaces */ + strim(s); +} + +static u16 onfi_crc16(u16 crc, u8 const *p, size_t len) +{ + int i; + while (len--) { + crc ^= *p++ << 8; + for (i = 0; i < 8; i++) + crc = (crc << 1) ^ ((crc & 0x8000) ? 0x8005 : 0); + } + + return crc; +} + +/* Parse the Extended Parameter Page. */ +static int nand_flash_detect_ext_param_page(struct mtd_info *mtd, + struct nand_chip *chip, struct nand_onfi_params *p) +{ + struct onfi_ext_param_page *ep; + struct onfi_ext_section *s; + struct onfi_ext_ecc_info *ecc; + uint8_t *cursor; + int ret = -EINVAL; + int len; + int i; + + len = le16_to_cpu(p->ext_param_page_length) * 16; + ep = kmalloc(len, GFP_KERNEL); + if (!ep) + return -ENOMEM; + + /* Send our own NAND_CMD_PARAM. */ + chip->cmdfunc(mtd, NAND_CMD_PARAM, 0, -1); + + /* Use the Change Read Column command to skip the ONFI param pages. */ + chip->cmdfunc(mtd, NAND_CMD_RNDOUT, + sizeof(*p) * p->num_of_param_pages , -1); + + /* Read out the Extended Parameter Page. */ + chip->read_buf(mtd, (uint8_t *)ep, len); + if ((onfi_crc16(ONFI_CRC_BASE, ((uint8_t *)ep) + 2, len - 2) + != le16_to_cpu(ep->crc))) { + pr_debug("fail in the CRC.\n"); + goto ext_out; + } + + /* + * Check the signature. + * Do not strictly follow the ONFI spec, maybe changed in future. + */ + if (strncmp(ep->sig, "EPPS", 4)) { + pr_debug("The signature is invalid.\n"); + goto ext_out; + } + + /* find the ECC section. */ + cursor = (uint8_t *)(ep + 1); + for (i = 0; i < ONFI_EXT_SECTION_MAX; i++) { + s = ep->sections + i; + if (s->type == ONFI_SECTION_TYPE_2) + break; + cursor += s->length * 16; + } + if (i == ONFI_EXT_SECTION_MAX) { + pr_debug("We can not find the ECC section.\n"); + goto ext_out; + } + + /* get the info we want. */ + ecc = (struct onfi_ext_ecc_info *)cursor; + + if (!ecc->codeword_size) { + pr_debug("Invalid codeword size\n"); + goto ext_out; + } + + chip->ecc_strength_ds = ecc->ecc_bits; + chip->ecc_step_ds = 1 << ecc->codeword_size; + ret = 0; + +ext_out: + kfree(ep); + return ret; +} + +static int nand_setup_read_retry_micron(struct mtd_info *mtd, int retry_mode) +{ + struct nand_chip *chip = mtd->priv; + uint8_t feature[ONFI_SUBFEATURE_PARAM_LEN] = {retry_mode}; + + return chip->onfi_set_features(mtd, chip, ONFI_FEATURE_ADDR_READ_RETRY, + feature); +} + /* - * Get the flash and manufacturer id and lookup if the type is supported + * Configure chip properties from Micron vendor-specific ONFI table */ -static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd, - struct nand_chip *chip, - int busw, int *maf_id) +static void nand_onfi_detect_micron(struct nand_chip *chip, + struct nand_onfi_params *p) { - struct nand_flash_dev *type = NULL; - int i, dev_id, maf_idx; + struct nand_onfi_vendor_micron *micron = (void *)p->vendor; - /* Select the device */ - chip->select_chip(mtd, 0); + if (le16_to_cpu(p->vendor_revision) < 1) + return; - /* Send the command for reading device ID */ - chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); + chip->read_retries = micron->read_retry_options; + chip->setup_read_retry = nand_setup_read_retry_micron; +} - /* Read manufacturer and device IDs */ - *maf_id = chip->read_byte(mtd); - dev_id = chip->read_byte(mtd); +/* + * Check if the NAND chip is ONFI compliant, returns 1 if it is, 0 otherwise. + */ +static int nand_flash_detect_onfi(struct mtd_info *mtd, struct nand_chip *chip, + int *busw) +{ + struct nand_onfi_params *p = &chip->onfi_params; + int i, j; + int val; + + /* Try ONFI for unknown chip or LP */ + chip->cmdfunc(mtd, NAND_CMD_READID, 0x20, -1); + if (chip->read_byte(mtd) != 'O' || chip->read_byte(mtd) != 'N' || + chip->read_byte(mtd) != 'F' || chip->read_byte(mtd) != 'I') + return 0; - /* Lookup the flash id */ - for (i = 0; nand_flash_ids[i].name != NULL; i++) { - if (dev_id == nand_flash_ids[i].id) { - type = &nand_flash_ids[i]; + chip->cmdfunc(mtd, NAND_CMD_PARAM, 0, -1); + for (i = 0; i < 3; i++) { + for (j = 0; j < sizeof(*p); j++) + ((uint8_t *)p)[j] = chip->read_byte(mtd); + if (onfi_crc16(ONFI_CRC_BASE, (uint8_t *)p, 254) == + le16_to_cpu(p->crc)) { break; } } - if (!type) - return ERR_PTR(-ENODEV); + if (i == 3) { + pr_err("Could not find valid ONFI parameter page; aborting\n"); + return 0; + } + + /* Check version */ + val = le16_to_cpu(p->revision); + if (val & (1 << 5)) + chip->onfi_version = 23; + else if (val & (1 << 4)) + chip->onfi_version = 22; + else if (val & (1 << 3)) + chip->onfi_version = 21; + else if (val & (1 << 2)) + chip->onfi_version = 20; + else if (val & (1 << 1)) + chip->onfi_version = 10; + + if (!chip->onfi_version) { + pr_info("unsupported ONFI version: %d\n", val); + return 0; + } + sanitize_string(p->manufacturer, sizeof(p->manufacturer)); + sanitize_string(p->model, sizeof(p->model)); if (!mtd->name) - mtd->name = type->name; + mtd->name = p->model; + + mtd->writesize = le32_to_cpu(p->byte_per_page); + + /* + * pages_per_block and blocks_per_lun may not be a power-of-2 size + * (don't ask me who thought of this...). MTD assumes that these + * dimensions will be power-of-2, so just truncate the remaining area. + */ + mtd->erasesize = 1 << (fls(le32_to_cpu(p->pages_per_block)) - 1); + mtd->erasesize *= mtd->writesize; + + mtd->oobsize = le16_to_cpu(p->spare_bytes_per_page); + + /* See erasesize comment */ + chip->chipsize = 1 << (fls(le32_to_cpu(p->blocks_per_lun)) - 1); + chip->chipsize *= (uint64_t)mtd->erasesize * p->lun_count; + chip->bits_per_cell = p->bits_per_cell; + + if (onfi_feature(chip) & ONFI_FEATURE_16_BIT_BUS) + *busw = NAND_BUSWIDTH_16; + else + *busw = 0; + + if (p->ecc_bits != 0xff) { + chip->ecc_strength_ds = p->ecc_bits; + chip->ecc_step_ds = 512; + } else if (chip->onfi_version >= 21 && + (onfi_feature(chip) & ONFI_FEATURE_EXT_PARAM_PAGE)) { + + /* + * The nand_flash_detect_ext_param_page() uses the + * Change Read Column command which maybe not supported + * by the chip->cmdfunc. So try to update the chip->cmdfunc + * now. We do not replace user supplied command function. + */ + if (mtd->writesize > 512 && chip->cmdfunc == nand_command) + chip->cmdfunc = nand_command_lp; + + /* The Extended Parameter Page is supported since ONFI 2.1. */ + if (nand_flash_detect_ext_param_page(mtd, chip, p)) + pr_warn("Failed to detect ONFI extended param page\n"); + } else { + pr_warn("Could not retrieve ONFI ECC requirements\n"); + } + + if (p->jedec_id == NAND_MFR_MICRON) + nand_onfi_detect_micron(chip, p); + + return 1; +} + +/* + * Check if the NAND chip is JEDEC compliant, returns 1 if it is, 0 otherwise. + */ +static int nand_flash_detect_jedec(struct mtd_info *mtd, struct nand_chip *chip, + int *busw) +{ + struct nand_jedec_params *p = &chip->jedec_params; + struct jedec_ecc_info *ecc; + int val; + int i, j; + + /* Try JEDEC for unknown chip or LP */ + chip->cmdfunc(mtd, NAND_CMD_READID, 0x40, -1); + if (chip->read_byte(mtd) != 'J' || chip->read_byte(mtd) != 'E' || + chip->read_byte(mtd) != 'D' || chip->read_byte(mtd) != 'E' || + chip->read_byte(mtd) != 'C') + return 0; + + chip->cmdfunc(mtd, NAND_CMD_PARAM, 0x40, -1); + for (i = 0; i < 3; i++) { + for (j = 0; j < sizeof(*p); j++) + ((uint8_t *)p)[j] = chip->read_byte(mtd); + + if (onfi_crc16(ONFI_CRC_BASE, (uint8_t *)p, 510) == + le16_to_cpu(p->crc)) + break; + } + + if (i == 3) { + pr_err("Could not find valid JEDEC parameter page; aborting\n"); + return 0; + } + + /* Check version */ + val = le16_to_cpu(p->revision); + if (val & (1 << 2)) + chip->jedec_version = 10; + else if (val & (1 << 1)) + chip->jedec_version = 1; /* vendor specific version */ + + if (!chip->jedec_version) { + pr_info("unsupported JEDEC version: %d\n", val); + return 0; + } + + sanitize_string(p->manufacturer, sizeof(p->manufacturer)); + sanitize_string(p->model, sizeof(p->model)); + if (!mtd->name) + mtd->name = p->model; + + mtd->writesize = le32_to_cpu(p->byte_per_page); + + /* Please reference to the comment for nand_flash_detect_onfi. */ + mtd->erasesize = 1 << (fls(le32_to_cpu(p->pages_per_block)) - 1); + mtd->erasesize *= mtd->writesize; + + mtd->oobsize = le16_to_cpu(p->spare_bytes_per_page); + + /* Please reference to the comment for nand_flash_detect_onfi. */ + chip->chipsize = 1 << (fls(le32_to_cpu(p->blocks_per_lun)) - 1); + chip->chipsize *= (uint64_t)mtd->erasesize * p->lun_count; + chip->bits_per_cell = p->bits_per_cell; + + if (jedec_feature(chip) & JEDEC_FEATURE_16_BIT_BUS) + *busw = NAND_BUSWIDTH_16; + else + *busw = 0; + + /* ECC info */ + ecc = &p->ecc_info[0]; + + if (ecc->codeword_size >= 9) { + chip->ecc_strength_ds = ecc->ecc_bits; + chip->ecc_step_ds = 1 << ecc->codeword_size; + } else { + pr_warn("Invalid codeword size\n"); + } + + return 1; +} + +/* + * nand_id_has_period - Check if an ID string has a given wraparound period + * @id_data: the ID string + * @arrlen: the length of the @id_data array + * @period: the period of repitition + * + * Check if an ID string is repeated within a given sequence of bytes at + * specific repetition interval period (e.g., {0x20,0x01,0x7F,0x20} has a + * period of 3). This is a helper function for nand_id_len(). Returns non-zero + * if the repetition has a period of @period; otherwise, returns zero. + */ +static int nand_id_has_period(u8 *id_data, int arrlen, int period) +{ + int i, j; + for (i = 0; i < period; i++) + for (j = i + period; j < arrlen; j += period) + if (id_data[i] != id_data[j]) + return 0; + return 1; +} + +/* + * nand_id_len - Get the length of an ID string returned by CMD_READID + * @id_data: the ID string + * @arrlen: the length of the @id_data array + + * Returns the length of the ID string, according to known wraparound/trailing + * zero patterns. If no pattern exists, returns the length of the array. + */ +static int nand_id_len(u8 *id_data, int arrlen) +{ + int last_nonzero, period; + + /* Find last non-zero byte */ + for (last_nonzero = arrlen - 1; last_nonzero >= 0; last_nonzero--) + if (id_data[last_nonzero]) + break; + + /* All zeros */ + if (last_nonzero < 0) + return 0; + + /* Calculate wraparound period */ + for (period = 1; period < arrlen; period++) + if (nand_id_has_period(id_data, arrlen, period)) + break; + + /* There's a repeated pattern */ + if (period < arrlen) + return period; - chip->chipsize = type->chipsize << 20; + /* There are trailing zeros */ + if (last_nonzero < arrlen - 1) + return last_nonzero + 1; - /* Newer devices have all the information in additional id bytes */ - if (!type->pagesize) { - int extid; - /* The 3rd id byte contains non relevant data ATM */ - extid = chip->read_byte(mtd); - /* The 4th id byte is the important one */ - extid = chip->read_byte(mtd); + /* No pattern detected */ + return arrlen; +} + +/* Extract the bits of per cell from the 3rd byte of the extended ID */ +static int nand_get_bits_per_cell(u8 cellinfo) +{ + int bits; + + bits = cellinfo & NAND_CI_CELLTYPE_MSK; + bits >>= NAND_CI_CELLTYPE_SHIFT; + return bits + 1; +} + +/* + * Many new NAND share similar device ID codes, which represent the size of the + * chip. The rest of the parameters must be decoded according to generic or + * manufacturer-specific "extended ID" decoding patterns. + */ +static void nand_decode_ext_id(struct mtd_info *mtd, struct nand_chip *chip, + u8 id_data[8], int *busw) +{ + int extid, id_len; + /* The 3rd id byte holds MLC / multichip data */ + chip->bits_per_cell = nand_get_bits_per_cell(id_data[2]); + /* The 4th id byte is the important one */ + extid = id_data[3]; + + id_len = nand_id_len(id_data, 8); + + /* + * Field definitions are in the following datasheets: + * Old style (4,5 byte ID): Samsung K9GAG08U0M (p.32) + * New Samsung (6 byte ID): Samsung K9GAG08U0F (p.44) + * Hynix MLC (6 byte ID): Hynix H27UBG8T2B (p.22) + * + * Check for ID length, non-zero 6th byte, cell type, and Hynix/Samsung + * ID to decide what to do. + */ + if (id_len == 6 && id_data[0] == NAND_MFR_SAMSUNG && + !nand_is_slc(chip) && id_data[5] != 0x00) { + /* Calc pagesize */ + mtd->writesize = 2048 << (extid & 0x03); + extid >>= 2; + /* Calc oobsize */ + switch (((extid >> 2) & 0x04) | (extid & 0x03)) { + case 1: + mtd->oobsize = 128; + break; + case 2: + mtd->oobsize = 218; + break; + case 3: + mtd->oobsize = 400; + break; + case 4: + mtd->oobsize = 436; + break; + case 5: + mtd->oobsize = 512; + break; + case 6: + mtd->oobsize = 640; + break; + case 7: + default: /* Other cases are "reserved" (unknown) */ + mtd->oobsize = 1024; + break; + } + extid >>= 2; + /* Calc blocksize */ + mtd->erasesize = (128 * 1024) << + (((extid >> 1) & 0x04) | (extid & 0x03)); + *busw = 0; + } else if (id_len == 6 && id_data[0] == NAND_MFR_HYNIX && + !nand_is_slc(chip)) { + unsigned int tmp; + + /* Calc pagesize */ + mtd->writesize = 2048 << (extid & 0x03); + extid >>= 2; + /* Calc oobsize */ + switch (((extid >> 2) & 0x04) | (extid & 0x03)) { + case 0: + mtd->oobsize = 128; + break; + case 1: + mtd->oobsize = 224; + break; + case 2: + mtd->oobsize = 448; + break; + case 3: + mtd->oobsize = 64; + break; + case 4: + mtd->oobsize = 32; + break; + case 5: + mtd->oobsize = 16; + break; + default: + mtd->oobsize = 640; + break; + } + extid >>= 2; + /* Calc blocksize */ + tmp = ((extid >> 1) & 0x04) | (extid & 0x03); + if (tmp < 0x03) + mtd->erasesize = (128 * 1024) << tmp; + else if (tmp == 0x03) + mtd->erasesize = 768 * 1024; + else + mtd->erasesize = (64 * 1024) << tmp; + *busw = 0; + } else { /* Calc pagesize */ - mtd->writesize = 1024 << (extid & 0x3); + mtd->writesize = 1024 << (extid & 0x03); extid >>= 2; /* Calc oobsize */ - mtd->oobsize = (8 << (extid & 0x01)) * (mtd->writesize >> 9); + mtd->oobsize = (8 << (extid & 0x01)) * + (mtd->writesize >> 9); extid >>= 2; /* Calc blocksize. Blocksize is multiples of 64KiB */ mtd->erasesize = (64 * 1024) << (extid & 0x03); extid >>= 2; /* Get buswidth information */ - busw = (extid & 0x01) ? NAND_BUSWIDTH_16 : 0; + *busw = (extid & 0x01) ? NAND_BUSWIDTH_16 : 0; - } else { /* - * Old devices have chip data hardcoded in the device id table + * Toshiba 24nm raw SLC (i.e., not BENAND) have 32B OOB per + * 512B page. For Toshiba SLC, we decode the 5th/6th byte as + * follows: + * - ID byte 6, bits[2:0]: 100b -> 43nm, 101b -> 32nm, + * 110b -> 24nm + * - ID byte 5, bit[7]: 1 -> BENAND, 0 -> raw SLC */ - mtd->erasesize = type->erasesize; - mtd->writesize = type->pagesize; - mtd->oobsize = mtd->writesize / 32; - busw = type->options & NAND_BUSWIDTH_16; + if (id_len >= 6 && id_data[0] == NAND_MFR_TOSHIBA && + nand_is_slc(chip) && + (id_data[5] & 0x7) == 0x6 /* 24nm */ && + !(id_data[4] & 0x80) /* !BENAND */) { + mtd->oobsize = 32 * mtd->writesize >> 9; + } + } +} - /* Try to identify manufacturer */ - for (maf_idx = 0; nand_manuf_ids[maf_idx].id != 0x0; maf_idx++) { - if (nand_manuf_ids[maf_idx].id == *maf_id) - break; +/* + * Old devices have chip data hardcoded in the device ID table. nand_decode_id + * decodes a matching ID table entry and assigns the MTD size parameters for + * the chip. + */ +static void nand_decode_id(struct mtd_info *mtd, struct nand_chip *chip, + struct nand_flash_dev *type, u8 id_data[8], + int *busw) +{ + int maf_id = id_data[0]; + + mtd->erasesize = type->erasesize; + mtd->writesize = type->pagesize; + mtd->oobsize = mtd->writesize / 32; + *busw = type->options & NAND_BUSWIDTH_16; + + /* All legacy ID NAND are small-page, SLC */ + chip->bits_per_cell = 1; + + /* + * Check for Spansion/AMD ID + repeating 5th, 6th byte since + * some Spansion chips have erasesize that conflicts with size + * listed in nand_ids table. + * Data sheet (5 byte ID): Spansion S30ML-P ORNAND (p.39) + */ + if (maf_id == NAND_MFR_AMD && id_data[4] != 0x00 && id_data[5] == 0x00 + && id_data[6] == 0x00 && id_data[7] == 0x00 + && mtd->writesize == 512) { + mtd->erasesize = 128 * 1024; + mtd->erasesize <<= ((id_data[3] & 0x03) << 1); } +} + +/* + * Set the bad block marker/indicator (BBM/BBI) patterns according to some + * heuristic patterns using various detected parameters (e.g., manufacturer, + * page size, cell-type information). + */ +static void nand_decode_bbm_options(struct mtd_info *mtd, + struct nand_chip *chip, u8 id_data[8]) +{ + int maf_id = id_data[0]; + + /* Set the bad block position */ + if (mtd->writesize > 512 || (chip->options & NAND_BUSWIDTH_16)) + chip->badblockpos = NAND_LARGE_BADBLOCK_POS; + else + chip->badblockpos = NAND_SMALL_BADBLOCK_POS; /* - * Check, if buswidth is correct. Hardware drivers should set - * chip correct ! + * Bad block marker is stored in the last page of each block on Samsung + * and Hynix MLC devices; stored in first two pages of each block on + * Micron devices with 2KiB pages and on SLC Samsung, Hynix, Toshiba, + * AMD/Spansion, and Macronix. All others scan only the first page. */ - if (busw != (chip->options & NAND_BUSWIDTH_16)) { - printk(KERN_INFO "NAND device: Manufacturer ID:" - " 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id, - dev_id, nand_manuf_ids[maf_idx].name, mtd->name); - printk(KERN_WARNING "NAND bus width %d instead %d bit\n", - (chip->options & NAND_BUSWIDTH_16) ? 16 : 8, - busw ? 16 : 8); - return ERR_PTR(-EINVAL); + if (!nand_is_slc(chip) && + (maf_id == NAND_MFR_SAMSUNG || + maf_id == NAND_MFR_HYNIX)) + chip->bbt_options |= NAND_BBT_SCANLASTPAGE; + else if ((nand_is_slc(chip) && + (maf_id == NAND_MFR_SAMSUNG || + maf_id == NAND_MFR_HYNIX || + maf_id == NAND_MFR_TOSHIBA || + maf_id == NAND_MFR_AMD || + maf_id == NAND_MFR_MACRONIX)) || + (mtd->writesize == 2048 && + maf_id == NAND_MFR_MICRON)) + chip->bbt_options |= NAND_BBT_SCAN2NDPAGE; +} + +static inline bool is_full_id_nand(struct nand_flash_dev *type) +{ + return type->id_len; +} + +static bool find_full_id_nand(struct mtd_info *mtd, struct nand_chip *chip, + struct nand_flash_dev *type, u8 *id_data, int *busw) +{ + if (!strncmp(type->id, id_data, type->id_len)) { + mtd->writesize = type->pagesize; + mtd->erasesize = type->erasesize; + mtd->oobsize = type->oobsize; + + chip->bits_per_cell = nand_get_bits_per_cell(id_data[2]); + chip->chipsize = (uint64_t)type->chipsize << 20; + chip->options |= type->options; + chip->ecc_strength_ds = NAND_ECC_STRENGTH(type); + chip->ecc_step_ds = NAND_ECC_STEP(type); + + *busw = type->options & NAND_BUSWIDTH_16; + + if (!mtd->name) + mtd->name = type->name; + + return true; } + return false; +} - /* Calculate the address shift from the page size */ - chip->page_shift = ffs(mtd->writesize) - 1; - /* Convert chipsize to number of pages per chip -1. */ - chip->pagemask = (chip->chipsize >> chip->page_shift) - 1; +/* + * Get the flash and manufacturer id and lookup if the type is supported. + */ +static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd, + struct nand_chip *chip, + int *maf_id, int *dev_id, + struct nand_flash_dev *type) +{ + int busw; + int i, maf_idx; + u8 id_data[8]; - chip->bbt_erase_shift = chip->phys_erase_shift = - ffs(mtd->erasesize) - 1; - chip->chip_shift = ffs(chip->chipsize) - 1; + /* Select the device */ + chip->select_chip(mtd, 0); - /* Set the bad block position */ - chip->badblockpos = mtd->writesize > 512 ? - NAND_LARGE_BADBLOCK_POS : NAND_SMALL_BADBLOCK_POS; + /* + * Reset the chip, required by some chips (e.g. Micron MT29FxGxxxxx) + * after power-up. + */ + chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + + /* Send the command for reading device ID */ + chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); - /* Get chip options, preserve non chip based options */ - chip->options &= ~NAND_CHIPOPTIONS_MSK; - chip->options |= type->options & NAND_CHIPOPTIONS_MSK; + /* Read manufacturer and device IDs */ + *maf_id = chip->read_byte(mtd); + *dev_id = chip->read_byte(mtd); /* - * Set chip as a default. Board drivers can override it, if necessary + * Try again to make sure, as some systems the bus-hold or other + * interface concerns can cause random data which looks like a + * possibly credible NAND flash to appear. If the two results do + * not match, ignore the device completely. */ - chip->options |= NAND_NO_AUTOINCR; - /* Check if chip is a not a samsung device. Do not clear the - * options for chips which are not having an extended id. + chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); + + /* Read entire ID string */ + for (i = 0; i < 8; i++) + id_data[i] = chip->read_byte(mtd); + + if (id_data[0] != *maf_id || id_data[1] != *dev_id) { + pr_info("second ID read did not match %02x,%02x against %02x,%02x\n", + *maf_id, *dev_id, id_data[0], id_data[1]); + return ERR_PTR(-ENODEV); + } + + if (!type) + type = nand_flash_ids; + + for (; type->name != NULL; type++) { + if (is_full_id_nand(type)) { + if (find_full_id_nand(mtd, chip, type, id_data, &busw)) + goto ident_done; + } else if (*dev_id == type->dev_id) { + break; + } + } + + chip->onfi_version = 0; + if (!type->name || !type->pagesize) { + /* Check if the chip is ONFI compliant */ + if (nand_flash_detect_onfi(mtd, chip, &busw)) + goto ident_done; + + /* Check if the chip is JEDEC compliant */ + if (nand_flash_detect_jedec(mtd, chip, &busw)) + goto ident_done; + } + + if (!type->name) + return ERR_PTR(-ENODEV); + + if (!mtd->name) + mtd->name = type->name; + + chip->chipsize = (uint64_t)type->chipsize << 20; + + if (!type->pagesize && chip->init_size) { + /* Set the pagesize, oobsize, erasesize by the driver */ + busw = chip->init_size(mtd, chip, id_data); + } else if (!type->pagesize) { + /* Decode parameters from extended ID */ + nand_decode_ext_id(mtd, chip, id_data, &busw); + } else { + nand_decode_id(mtd, chip, type, id_data, &busw); + } + /* Get chip options */ + chip->options |= type->options; + + /* + * Check if chip is not a Samsung device. Do not clear the + * options for chips which do not have an extended id. */ if (*maf_id != NAND_MFR_SAMSUNG && !type->pagesize) chip->options &= ~NAND_SAMSUNG_LP_OPTIONS; +ident_done: - /* Check for AND chips with 4 page planes */ - if (chip->options & NAND_4PAGE_ARRAY) - chip->erase_cmd = multi_erase_cmd; - else - chip->erase_cmd = single_erase_cmd; + /* Try to identify manufacturer */ + for (maf_idx = 0; nand_manuf_ids[maf_idx].id != 0x0; maf_idx++) { + if (nand_manuf_ids[maf_idx].id == *maf_id) + break; + } + + if (chip->options & NAND_BUSWIDTH_AUTO) { + WARN_ON(chip->options & NAND_BUSWIDTH_16); + chip->options |= busw; + nand_set_defaults(chip, busw); + } else if (busw != (chip->options & NAND_BUSWIDTH_16)) { + /* + * Check, if buswidth is correct. Hardware drivers should set + * chip correct! + */ + pr_info("device found, Manufacturer ID: 0x%02x, Chip ID: 0x%02x\n", + *maf_id, *dev_id); + pr_info("%s %s\n", nand_manuf_ids[maf_idx].name, mtd->name); + pr_warn("bus width %d instead %d bit\n", + (chip->options & NAND_BUSWIDTH_16) ? 16 : 8, + busw ? 16 : 8); + return ERR_PTR(-EINVAL); + } + + nand_decode_bbm_options(mtd, chip, id_data); + + /* Calculate the address shift from the page size */ + chip->page_shift = ffs(mtd->writesize) - 1; + /* Convert chipsize to number of pages per chip -1 */ + chip->pagemask = (chip->chipsize >> chip->page_shift) - 1; + + chip->bbt_erase_shift = chip->phys_erase_shift = + ffs(mtd->erasesize) - 1; + if (chip->chipsize & 0xffffffff) + chip->chip_shift = ffs((unsigned)chip->chipsize) - 1; + else { + chip->chip_shift = ffs((unsigned)(chip->chipsize >> 32)); + chip->chip_shift += 32 - 1; + } + + chip->badblockbits = 8; + chip->erase = single_erase; - /* Do not replace user supplied command function ! */ + /* Do not replace user supplied command function! */ if (mtd->writesize > 512 && chip->cmdfunc == nand_command) chip->cmdfunc = nand_command_lp; - printk(KERN_INFO "NAND device: Manufacturer ID:" - " 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id, dev_id, - nand_manuf_ids[maf_idx].name, type->name); + pr_info("device found, Manufacturer ID: 0x%02x, Chip ID: 0x%02x\n", + *maf_id, *dev_id); + if (chip->onfi_version) + pr_info("%s %s\n", nand_manuf_ids[maf_idx].name, + chip->onfi_params.model); + else if (chip->jedec_version) + pr_info("%s %s\n", nand_manuf_ids[maf_idx].name, + chip->jedec_params.model); + else + pr_info("%s %s\n", nand_manuf_ids[maf_idx].name, + type->name); + + pr_info("%dMiB, %s, page size: %d, OOB size: %d\n", + (int)(chip->chipsize >> 20), nand_is_slc(chip) ? "SLC" : "MLC", + mtd->writesize, mtd->oobsize); return type; } -/* module_text_address() isn't exported, and it's mostly a pointless - test if this is a module _anyway_ -- they'd have to try _really_ hard - to call us from in-kernel code if the core NAND support is modular. */ -#ifdef MODULE -#define caller_is_module() (1) -#else -#define caller_is_module() \ - module_text_address((unsigned long)__builtin_return_address(0)) -#endif - /** - * nand_scan - [NAND Interface] Scan for the NAND device - * @mtd: MTD device structure - * @maxchips: Number of chips to scan for + * nand_scan_ident - [NAND Interface] Scan for the NAND device + * @mtd: MTD device structure + * @maxchips: number of chips to scan for + * @table: alternative NAND ID table * - * This fills out all the uninitialized function pointers - * with the defaults. - * The flash ID is read and the mtd/chip structures are - * filled with the appropriate values. - * The mtd->owner field must be set to the module of the caller + * This is the first phase of the normal nand_scan() function. It reads the + * flash ID and sets up MTD fields accordingly. * + * The mtd->owner field must be set to the module of the caller. */ -int nand_scan(struct mtd_info *mtd, int maxchips) +int nand_scan_ident(struct mtd_info *mtd, int maxchips, + struct nand_flash_dev *table) { - int i, busw, nand_maf_id; + int i, nand_maf_id, nand_dev_id; struct nand_chip *chip = mtd->priv; struct nand_flash_dev *type; - /* Many callers got this wrong, so check for it for a while... */ - if (!mtd->owner && caller_is_module()) { - printk(KERN_CRIT "nand_scan() called with NULL mtd->owner!\n"); - BUG(); - } - - /* Get buswidth to select the correct functions */ - busw = chip->options & NAND_BUSWIDTH_16; /* Set the default functions */ - nand_set_defaults(chip, busw); + nand_set_defaults(chip, chip->options & NAND_BUSWIDTH_16); /* Read the flash type */ - type = nand_get_flash_type(mtd, chip, busw, &nand_maf_id); + type = nand_get_flash_type(mtd, chip, &nand_maf_id, + &nand_dev_id, table); if (IS_ERR(type)) { - printk(KERN_WARNING "No NAND device found!!!\n"); + if (!(chip->options & NAND_SCAN_SILENT_NODEV)) + pr_warn("No NAND device found\n"); chip->select_chip(mtd, -1); return PTR_ERR(type); } + chip->select_chip(mtd, -1); + /* Check for a chip array */ for (i = 1; i < maxchips; i++) { chip->select_chip(mtd, i); + /* See comment in nand_get_flash_type for reset */ + chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); /* Send the command for reading device ID */ chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); /* Read manufacturer and device IDs */ if (nand_maf_id != chip->read_byte(mtd) || - type->id != chip->read_byte(mtd)) + nand_dev_id != chip->read_byte(mtd)) { + chip->select_chip(mtd, -1); break; + } + chip->select_chip(mtd, -1); } if (i > 1) - printk(KERN_INFO "%d NAND chips detected\n", i); + pr_info("%d chips detected\n", i); /* Store the number of chips and calc total size for mtd */ chip->numchips = i; mtd->size = i * chip->chipsize; - /* Preset the internal oob write buffer */ - memset(chip->buffers.oobwbuf, 0xff, mtd->oobsize); + return 0; +} +EXPORT_SYMBOL(nand_scan_ident); + +/* + * Check if the chip configuration meet the datasheet requirements. + + * If our configuration corrects A bits per B bytes and the minimum + * required correction level is X bits per Y bytes, then we must ensure + * both of the following are true: + * + * (1) A / B >= X / Y + * (2) A >= X + * + * Requirement (1) ensures we can correct for the required bitflip density. + * Requirement (2) ensures we can correct even when all bitflips are clumped + * in the same sector. + */ +static bool nand_ecc_strength_good(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct nand_ecc_ctrl *ecc = &chip->ecc; + int corr, ds_corr; + + if (ecc->size == 0 || chip->ecc_step_ds == 0) + /* Not enough information */ + return true; /* - * If no default placement scheme is given, select an appropriate one + * We get the number of corrected bits per page to compare + * the correction density. */ - if (!chip->ecc.layout) { + corr = (mtd->writesize * ecc->strength) / ecc->size; + ds_corr = (mtd->writesize * chip->ecc_strength_ds) / chip->ecc_step_ds; + + return corr >= ds_corr && ecc->strength >= chip->ecc_strength_ds; +} + +/** + * nand_scan_tail - [NAND Interface] Scan for the NAND device + * @mtd: MTD device structure + * + * This is the second phase of the normal nand_scan() function. It fills out + * all the uninitialized function pointers with the defaults and scans for a + * bad block table if appropriate. + */ +int nand_scan_tail(struct mtd_info *mtd) +{ + int i; + struct nand_chip *chip = mtd->priv; + struct nand_ecc_ctrl *ecc = &chip->ecc; + struct nand_buffers *nbuf; + + /* New bad blocks should be marked in OOB, flash-based BBT, or both */ + BUG_ON((chip->bbt_options & NAND_BBT_NO_OOB_BBM) && + !(chip->bbt_options & NAND_BBT_USE_FLASH)); + + if (!(chip->options & NAND_OWN_BUFFERS)) { + nbuf = kzalloc(sizeof(*nbuf) + mtd->writesize + + mtd->oobsize * 3, GFP_KERNEL); + if (!nbuf) + return -ENOMEM; + nbuf->ecccalc = (uint8_t *)(nbuf + 1); + nbuf->ecccode = nbuf->ecccalc + mtd->oobsize; + nbuf->databuf = nbuf->ecccode + mtd->oobsize; + + chip->buffers = nbuf; + } else { + if (!chip->buffers) + return -ENOMEM; + } + + /* Set the internal oob buffer location, just after the page data */ + chip->oob_poi = chip->buffers->databuf + mtd->writesize; + + /* + * If no default placement scheme is given, select an appropriate one. + */ + if (!ecc->layout && (ecc->mode != NAND_ECC_SOFT_BCH)) { switch (mtd->oobsize) { case 8: - chip->ecc.layout = &nand_oob_8; + ecc->layout = &nand_oob_8; break; case 16: - chip->ecc.layout = &nand_oob_16; + ecc->layout = &nand_oob_16; break; case 64: - chip->ecc.layout = &nand_oob_64; + ecc->layout = &nand_oob_64; + break; + case 128: + ecc->layout = &nand_oob_128; break; default: - printk(KERN_WARNING "No oob scheme defined for " - "oobsize %d\n", mtd->oobsize); + pr_warn("No oob scheme defined for oobsize %d\n", + mtd->oobsize); BUG(); } } + if (!chip->write_page) + chip->write_page = nand_write_page; + /* - * check ECC mode, default to software if 3byte/512byte hardware ECC is + * Check ECC mode, default to software if 3byte/512byte hardware ECC is * selected and we have 256 byte pagesize fallback to software ECC */ - switch (chip->ecc.mode) { + + switch (ecc->mode) { + case NAND_ECC_HW_OOB_FIRST: + /* Similar to NAND_ECC_HW, but a separate read_page handle */ + if (!ecc->calculate || !ecc->correct || !ecc->hwctl) { + pr_warn("No ECC functions supplied; " + "hardware ECC not possible\n"); + BUG(); + } + if (!ecc->read_page) + ecc->read_page = nand_read_page_hwecc_oob_first; + case NAND_ECC_HW: - /* Use standard hwecc read page function ? */ - if (!chip->ecc.read_page) - 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_oob) - chip->ecc.read_oob = nand_read_oob_std; - if (!chip->ecc.write_oob) - chip->ecc.write_oob = nand_write_oob_std; + /* Use standard hwecc read page function? */ + if (!ecc->read_page) + ecc->read_page = nand_read_page_hwecc; + if (!ecc->write_page) + ecc->write_page = nand_write_page_hwecc; + if (!ecc->read_page_raw) + ecc->read_page_raw = nand_read_page_raw; + if (!ecc->write_page_raw) + ecc->write_page_raw = nand_write_page_raw; + if (!ecc->read_oob) + ecc->read_oob = nand_read_oob_std; + if (!ecc->write_oob) + ecc->write_oob = nand_write_oob_std; + if (!ecc->read_subpage) + ecc->read_subpage = nand_read_subpage; + if (!ecc->write_subpage) + ecc->write_subpage = nand_write_subpage_hwecc; case NAND_ECC_HW_SYNDROME: - if (!chip->ecc.calculate || !chip->ecc.correct || - !chip->ecc.hwctl) { - printk(KERN_WARNING "No ECC functions supplied, " - "Hardware ECC not possible\n"); + if ((!ecc->calculate || !ecc->correct || !ecc->hwctl) && + (!ecc->read_page || + ecc->read_page == nand_read_page_hwecc || + !ecc->write_page || + ecc->write_page == nand_write_page_hwecc)) { + pr_warn("No ECC functions supplied; " + "hardware ECC not possible\n"); BUG(); } - /* Use standard syndrome read/write page function ? */ - if (!chip->ecc.read_page) - 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_oob) - chip->ecc.read_oob = nand_read_oob_syndrome; - if (!chip->ecc.write_oob) - chip->ecc.write_oob = nand_write_oob_syndrome; - - if (mtd->writesize >= chip->ecc.size) + /* Use standard syndrome read/write page function? */ + if (!ecc->read_page) + ecc->read_page = nand_read_page_syndrome; + if (!ecc->write_page) + ecc->write_page = nand_write_page_syndrome; + if (!ecc->read_page_raw) + ecc->read_page_raw = nand_read_page_raw_syndrome; + if (!ecc->write_page_raw) + ecc->write_page_raw = nand_write_page_raw_syndrome; + if (!ecc->read_oob) + ecc->read_oob = nand_read_oob_syndrome; + if (!ecc->write_oob) + ecc->write_oob = nand_write_oob_syndrome; + + if (mtd->writesize >= ecc->size) { + if (!ecc->strength) { + pr_warn("Driver must set ecc.strength when using hardware ECC\n"); + BUG(); + } break; - printk(KERN_WARNING "%d byte HW ECC not possible on " - "%d byte page size, fallback to SW ECC\n", - chip->ecc.size, mtd->writesize); - chip->ecc.mode = NAND_ECC_SOFT; + } + pr_warn("%d byte HW ECC not possible on " + "%d byte page size, fallback to SW ECC\n", + ecc->size, mtd->writesize); + ecc->mode = NAND_ECC_SOFT; case NAND_ECC_SOFT: - chip->ecc.calculate = nand_calculate_ecc; - chip->ecc.correct = nand_correct_data; - chip->ecc.read_page = nand_read_page_swecc; - chip->ecc.write_page = nand_write_page_swecc; - chip->ecc.read_oob = nand_read_oob_std; - chip->ecc.write_oob = nand_write_oob_std; - chip->ecc.size = 256; - chip->ecc.bytes = 3; + ecc->calculate = nand_calculate_ecc; + ecc->correct = nand_correct_data; + ecc->read_page = nand_read_page_swecc; + ecc->read_subpage = nand_read_subpage; + ecc->write_page = nand_write_page_swecc; + ecc->read_page_raw = nand_read_page_raw; + ecc->write_page_raw = nand_write_page_raw; + ecc->read_oob = nand_read_oob_std; + ecc->write_oob = nand_write_oob_std; + if (!ecc->size) + ecc->size = 256; + ecc->bytes = 3; + ecc->strength = 1; + break; + + case NAND_ECC_SOFT_BCH: + if (!mtd_nand_has_bch()) { + pr_warn("CONFIG_MTD_NAND_ECC_BCH not enabled\n"); + BUG(); + } + ecc->calculate = nand_bch_calculate_ecc; + ecc->correct = nand_bch_correct_data; + ecc->read_page = nand_read_page_swecc; + ecc->read_subpage = nand_read_subpage; + ecc->write_page = nand_write_page_swecc; + ecc->read_page_raw = nand_read_page_raw; + ecc->write_page_raw = nand_write_page_raw; + ecc->read_oob = nand_read_oob_std; + ecc->write_oob = nand_write_oob_std; + /* + * Board driver should supply ecc.size and ecc.bytes values to + * select how many bits are correctable; see nand_bch_init() + * for details. Otherwise, default to 4 bits for large page + * devices. + */ + if (!ecc->size && (mtd->oobsize >= 64)) { + ecc->size = 512; + ecc->bytes = 7; + } + ecc->priv = nand_bch_init(mtd, ecc->size, ecc->bytes, + &ecc->layout); + if (!ecc->priv) { + pr_warn("BCH ECC initialization failed!\n"); + BUG(); + } + ecc->strength = ecc->bytes * 8 / fls(8 * ecc->size); break; case NAND_ECC_NONE: - printk(KERN_WARNING "NAND_ECC_NONE selected by board driver. " - "This is not recommended !!\n"); - 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.write_oob = nand_write_oob_std; - chip->ecc.size = mtd->writesize; - chip->ecc.bytes = 0; + pr_warn("NAND_ECC_NONE selected by board driver. " + "This is not recommended!\n"); + ecc->read_page = nand_read_page_raw; + ecc->write_page = nand_write_page_raw; + ecc->read_oob = nand_read_oob_std; + ecc->read_page_raw = nand_read_page_raw; + ecc->write_page_raw = nand_write_page_raw; + ecc->write_oob = nand_write_oob_std; + ecc->size = mtd->writesize; + ecc->bytes = 0; + ecc->strength = 0; break; + default: - printk(KERN_WARNING "Invalid NAND_ECC_MODE %d\n", - chip->ecc.mode); + pr_warn("Invalid NAND_ECC_MODE %d\n", ecc->mode); BUG(); } + /* For many systems, the standard OOB write also works for raw */ + if (!ecc->read_oob_raw) + ecc->read_oob_raw = ecc->read_oob; + if (!ecc->write_oob_raw) + ecc->write_oob_raw = ecc->write_oob; + /* * The number of bytes available for a client to place data into - * the out of band area + * the out of band area. */ - chip->ecc.layout->oobavail = 0; - for (i = 0; chip->ecc.layout->oobfree[i].length; i++) - chip->ecc.layout->oobavail += - chip->ecc.layout->oobfree[i].length; + ecc->layout->oobavail = 0; + for (i = 0; ecc->layout->oobfree[i].length + && i < ARRAY_SIZE(ecc->layout->oobfree); i++) + ecc->layout->oobavail += ecc->layout->oobfree[i].length; + mtd->oobavail = ecc->layout->oobavail; + + /* ECC sanity check: warn if it's too weak */ + if (!nand_ecc_strength_good(mtd)) + pr_warn("WARNING: %s: the ECC used on your system is too weak compared to the one required by the NAND chip\n", + mtd->name); /* * Set the number of read / write steps for one page depending on ECC - * mode + * mode. */ - chip->ecc.steps = mtd->writesize / chip->ecc.size; - if(chip->ecc.steps * chip->ecc.size != mtd->writesize) { - printk(KERN_WARNING "Invalid ecc parameters\n"); + ecc->steps = mtd->writesize / ecc->size; + if (ecc->steps * ecc->size != mtd->writesize) { + pr_warn("Invalid ECC parameters\n"); BUG(); } - chip->ecc.total = chip->ecc.steps * chip->ecc.bytes; + ecc->total = ecc->steps * ecc->bytes; + + /* Allow subpage writes up to ecc.steps. Not possible for MLC flash */ + if (!(chip->options & NAND_NO_SUBPAGE_WRITE) && nand_is_slc(chip)) { + switch (ecc->steps) { + case 2: + mtd->subpage_sft = 1; + break; + case 4: + case 8: + case 16: + mtd->subpage_sft = 2; + break; + } + } + chip->subpagesize = mtd->writesize >> mtd->subpage_sft; /* Initialize state */ chip->state = FL_READY; - /* De-select the device */ - chip->select_chip(mtd, -1); - /* Invalidate the pagebuffer reference */ chip->pagebuf = -1; + /* Large page NAND with SOFT_ECC should support subpage reads */ + switch (ecc->mode) { + case NAND_ECC_SOFT: + case NAND_ECC_SOFT_BCH: + if (chip->page_shift > 9) + chip->options |= NAND_SUBPAGE_READ; + break; + + default: + break; + } + /* Fill in remaining MTD driver data */ - mtd->type = MTD_NANDFLASH; - mtd->flags = MTD_CAP_NANDFLASH; - mtd->ecctype = MTD_ECC_SW; - mtd->erase = nand_erase; - mtd->point = NULL; - mtd->unpoint = NULL; - mtd->read = nand_read; - mtd->write = nand_write; - mtd->read_oob = nand_read_oob; - mtd->write_oob = nand_write_oob; - mtd->sync = nand_sync; - mtd->lock = NULL; - mtd->unlock = NULL; - mtd->suspend = nand_suspend; - mtd->resume = nand_resume; - mtd->block_isbad = nand_block_isbad; - mtd->block_markbad = nand_block_markbad; - - /* propagate ecc.layout to mtd_info */ - mtd->ecclayout = chip->ecc.layout; + mtd->type = nand_is_slc(chip) ? MTD_NANDFLASH : MTD_MLCNANDFLASH; + mtd->flags = (chip->options & NAND_ROM) ? MTD_CAP_ROM : + MTD_CAP_NANDFLASH; + mtd->_erase = nand_erase; + mtd->_point = NULL; + mtd->_unpoint = NULL; + mtd->_read = nand_read; + mtd->_write = nand_write; + mtd->_panic_write = panic_nand_write; + mtd->_read_oob = nand_read_oob; + mtd->_write_oob = nand_write_oob; + mtd->_sync = nand_sync; + mtd->_lock = NULL; + mtd->_unlock = NULL; + mtd->_suspend = nand_suspend; + mtd->_resume = nand_resume; + mtd->_block_isbad = nand_block_isbad; + mtd->_block_markbad = nand_block_markbad; + mtd->writebufsize = mtd->writesize; + + /* propagate ecc info to mtd_info */ + mtd->ecclayout = ecc->layout; + mtd->ecc_strength = ecc->strength; + mtd->ecc_step_size = ecc->size; + /* + * Initialize bitflip_threshold to its default prior scan_bbt() call. + * scan_bbt() might invoke mtd_read(), thus bitflip_threshold must be + * properly set. + */ + if (!mtd->bitflip_threshold) + mtd->bitflip_threshold = mtd->ecc_strength; /* Check, if we should skip the bad block table scan */ if (chip->options & NAND_SKIP_BBTSCAN) @@ -2502,27 +4136,70 @@ int nand_scan(struct mtd_info *mtd, int maxchips) /* Build bad block table */ return chip->scan_bbt(mtd); } +EXPORT_SYMBOL(nand_scan_tail); + +/* + * is_module_text_address() isn't exported, and it's mostly a pointless + * test if this is a module _anyway_ -- they'd have to try _really_ hard + * to call us from in-kernel code if the core NAND support is modular. + */ +#ifdef MODULE +#define caller_is_module() (1) +#else +#define caller_is_module() \ + is_module_text_address((unsigned long)__builtin_return_address(0)) +#endif + +/** + * nand_scan - [NAND Interface] Scan for the NAND device + * @mtd: MTD device structure + * @maxchips: number of chips to scan for + * + * This fills out all the uninitialized function pointers with the defaults. + * The flash ID is read and the mtd/chip structures are filled with the + * appropriate values. The mtd->owner field must be set to the module of the + * caller. + */ +int nand_scan(struct mtd_info *mtd, int maxchips) +{ + int ret; + + /* Many callers got this wrong, so check for it for a while... */ + if (!mtd->owner && caller_is_module()) { + pr_crit("%s called with NULL mtd->owner!\n", __func__); + BUG(); + } + + ret = nand_scan_ident(mtd, maxchips, NULL); + if (!ret) + ret = nand_scan_tail(mtd); + return ret; +} +EXPORT_SYMBOL(nand_scan); /** * nand_release - [NAND Interface] Free resources held by the NAND device - * @mtd: MTD device structure -*/ + * @mtd: MTD device structure + */ void nand_release(struct mtd_info *mtd) { struct nand_chip *chip = mtd->priv; -#ifdef CONFIG_MTD_PARTITIONS - /* Deregister partitions */ - del_mtd_partitions(mtd); -#endif - /* Deregister the device */ - del_mtd_device(mtd); + if (chip->ecc.mode == NAND_ECC_SOFT_BCH) + nand_bch_free((struct nand_bch_control *)chip->ecc.priv); + + mtd_device_unregister(mtd); /* Free bad block table memory */ kfree(chip->bbt); -} + if (!(chip->options & NAND_OWN_BUFFERS)) + kfree(chip->buffers); -EXPORT_SYMBOL_GPL(nand_scan); + /* Free bad block descriptor memory */ + if (chip->badblock_pattern && chip->badblock_pattern->options + & NAND_BBT_DYNAMICSTRUCT) + kfree(chip->badblock_pattern); +} EXPORT_SYMBOL_GPL(nand_release); static int __init nand_base_init(void) @@ -2540,5 +4217,6 @@ module_init(nand_base_init); module_exit(nand_base_exit); MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Steven J. Hill <sjhill@realitydiluted.com>, Thomas Gleixner <tglx@linutronix.de>"); +MODULE_AUTHOR("Steven J. Hill <sjhill@realitydiluted.com>"); +MODULE_AUTHOR("Thomas Gleixner <tglx@linutronix.de>"); MODULE_DESCRIPTION("Generic NAND flash driver code"); diff --git a/drivers/mtd/nand/nand_bbt.c b/drivers/mtd/nand/nand_bbt.c index a612c4ea819..7f0c3b4c2a4 100644 --- a/drivers/mtd/nand/nand_bbt.c +++ b/drivers/mtd/nand/nand_bbt.c @@ -4,9 +4,7 @@ * Overview: * Bad block table support for the NAND driver * - * Copyright (C) 2004 Thomas Gleixner (tglx@linutronix.de) - * - * $Id: nand_bbt.c,v 1.36 2005/11/07 11:14:30 gleixner Exp $ + * Copyright © 2004 Thomas Gleixner (tglx@linutronix.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -15,28 +13,37 @@ * Description: * * When nand_scan_bbt is called, then it tries to find the bad block table - * depending on the options in the bbt descriptor(s). If a bbt is found - * then the contents are read and the memory based bbt is created. If a - * mirrored bbt is selected then the mirror is searched too and the - * versions are compared. If the mirror has a greater version number - * than the mirror bbt is used to build the memory based bbt. + * depending on the options in the BBT descriptor(s). If no flash based BBT + * (NAND_BBT_USE_FLASH) is specified then the device is scanned for factory + * marked good / bad blocks. This information is used to create a memory BBT. + * Once a new bad block is discovered then the "factory" information is updated + * on the device. + * If a flash based BBT is specified then the function first tries to find the + * BBT on flash. If a BBT is found then the contents are read and the memory + * based BBT is created. If a mirrored BBT is selected then the mirror is + * searched too and the versions are compared. If the mirror has a greater + * version number, then the mirror BBT is used to build the memory based BBT. * If the tables are not versioned, then we "or" the bad block information. - * If one of the bbt's is out of date or does not exist it is (re)created. - * If no bbt exists at all then the device is scanned for factory marked + * If one of the BBTs is out of date or does not exist it is (re)created. + * If no BBT exists at all then the device is scanned for factory marked * good / bad blocks and the bad block tables are created. * - * For manufacturer created bbts like the one found on M-SYS DOC devices - * the bbt is searched and read but never created + * For manufacturer created BBTs like the one found on M-SYS DOC devices + * the BBT is searched and read but never created * - * The autogenerated bad block table is located in the last good blocks + * The auto generated bad block table is located in the last good blocks * of the device. The table is mirrored, so it can be updated eventually. - * The table is marked in the oob area with an ident pattern and a version - * number which indicates which of both tables is more up to date. + * The table is marked in the OOB area with an ident pattern and a version + * number which indicates which of both tables is more up to date. If the NAND + * controller needs the complete OOB area for the ECC information then the + * option NAND_BBT_NO_OOB should be used (along with NAND_BBT_USE_FLASH, of + * course): it moves the ident pattern and the version byte into the data area + * and the OOB area will remain untouched. * * The table uses 2 bits per block - * 11b: block is good - * 00b: block is factory marked bad - * 01b, 10b: block is marked bad due to wear + * 11b: block is good + * 00b: block is factory marked bad + * 01b, 10b: block is marked bad due to wear * * The memory bad block table uses the following scheme: * 00b: block is good @@ -55,299 +62,377 @@ #include <linux/slab.h> #include <linux/types.h> #include <linux/mtd/mtd.h> +#include <linux/mtd/bbm.h> #include <linux/mtd/nand.h> #include <linux/mtd/nand_ecc.h> -#include <linux/mtd/compatmac.h> #include <linux/bitops.h> #include <linux/delay.h> #include <linux/vmalloc.h> +#include <linux/export.h> +#include <linux/string.h> + +#define BBT_BLOCK_GOOD 0x00 +#define BBT_BLOCK_WORN 0x01 +#define BBT_BLOCK_RESERVED 0x02 +#define BBT_BLOCK_FACTORY_BAD 0x03 + +#define BBT_ENTRY_MASK 0x03 +#define BBT_ENTRY_SHIFT 2 + +static int nand_update_bbt(struct mtd_info *mtd, loff_t offs); + +static inline uint8_t bbt_get_entry(struct nand_chip *chip, int block) +{ + uint8_t entry = chip->bbt[block >> BBT_ENTRY_SHIFT]; + entry >>= (block & BBT_ENTRY_MASK) * 2; + return entry & BBT_ENTRY_MASK; +} + +static inline void bbt_mark_entry(struct nand_chip *chip, int block, + uint8_t mark) +{ + uint8_t msk = (mark & BBT_ENTRY_MASK) << ((block & BBT_ENTRY_MASK) * 2); + chip->bbt[block >> BBT_ENTRY_SHIFT] |= msk; +} + +static int check_pattern_no_oob(uint8_t *buf, struct nand_bbt_descr *td) +{ + if (memcmp(buf, td->pattern, td->len)) + return -1; + return 0; +} /** * check_pattern - [GENERIC] check if a pattern is in the buffer - * @buf: the buffer to search - * @len: the length of buffer to search - * @paglen: the pagelength - * @td: search pattern descriptor - * - * Check for a pattern at the given place. Used to search bad block - * tables and good / bad block identifiers. - * If the SCAN_EMPTY option is set then check, if all bytes except the - * pattern area contain 0xff + * @buf: the buffer to search + * @len: the length of buffer to search + * @paglen: the pagelength + * @td: search pattern descriptor * -*/ + * Check for a pattern at the given place. Used to search bad block tables and + * good / bad block identifiers. + */ static int check_pattern(uint8_t *buf, int len, int paglen, struct nand_bbt_descr *td) { - int i, end = 0; - uint8_t *p = buf; - - end = paglen + td->offs; - if (td->options & NAND_BBT_SCANEMPTY) { - for (i = 0; i < end; i++) { - if (p[i] != 0xff) - return -1; - } - } - p += end; + if (td->options & NAND_BBT_NO_OOB) + return check_pattern_no_oob(buf, td); /* Compare the pattern */ - for (i = 0; i < td->len; i++) { - if (p[i] != td->pattern[i]) - return -1; - } + if (memcmp(buf + paglen + td->offs, td->pattern, td->len)) + return -1; - if (td->options & NAND_BBT_SCANEMPTY) { - p += td->len; - end += td->len; - for (i = end; i < len; i++) { - if (*p++ != 0xff) - return -1; - } - } return 0; } /** * check_short_pattern - [GENERIC] check if a pattern is in the buffer - * @buf: the buffer to search - * @td: search pattern descriptor - * - * Check for a pattern at the given place. Used to search bad block - * tables and good / bad block identifiers. Same as check_pattern, but - * no optional empty check + * @buf: the buffer to search + * @td: search pattern descriptor * -*/ + * Check for a pattern at the given place. Used to search bad block tables and + * good / bad block identifiers. Same as check_pattern, but no optional empty + * check. + */ static int check_short_pattern(uint8_t *buf, struct nand_bbt_descr *td) { - int i; - uint8_t *p = buf; - /* Compare the pattern */ - for (i = 0; i < td->len; i++) { - if (p[td->offs + i] != td->pattern[i]) - return -1; - } + if (memcmp(buf + td->offs, td->pattern, td->len)) + return -1; return 0; } /** + * add_marker_len - compute the length of the marker in data area + * @td: BBT descriptor used for computation + * + * The length will be 0 if the marker is located in OOB area. + */ +static u32 add_marker_len(struct nand_bbt_descr *td) +{ + u32 len; + + if (!(td->options & NAND_BBT_NO_OOB)) + return 0; + + len = td->len; + if (td->options & NAND_BBT_VERSION) + len++; + return len; +} + +/** * read_bbt - [GENERIC] Read the bad block table starting from page - * @mtd: MTD device structure - * @buf: temporary buffer - * @page: the starting page - * @num: the number of bbt descriptors to read - * @bits: number of bits per block - * @offs: offset in the memory table - * @reserved_block_code: Pattern to identify reserved blocks + * @mtd: MTD device structure + * @buf: temporary buffer + * @page: the starting page + * @num: the number of bbt descriptors to read + * @td: the bbt describtion table + * @offs: block number offset in the table * * Read the bad block table starting from page. - * */ static int read_bbt(struct mtd_info *mtd, uint8_t *buf, int page, int num, - int bits, int offs, int reserved_block_code) + struct nand_bbt_descr *td, int offs) { - int res, i, j, act = 0; + int res, ret = 0, i, j, act = 0; struct nand_chip *this = mtd->priv; size_t retlen, len, totlen; loff_t from; - uint8_t msk = (uint8_t) ((1 << bits) - 1); + int bits = td->options & NAND_BBT_NRBITS_MSK; + uint8_t msk = (uint8_t)((1 << bits) - 1); + u32 marker_len; + int reserved_block_code = td->reserved_block_code; totlen = (num * bits) >> 3; - from = ((loff_t) page) << this->page_shift; + marker_len = add_marker_len(td); + from = ((loff_t)page) << this->page_shift; while (totlen) { - len = min(totlen, (size_t) (1 << this->bbt_erase_shift)); - res = mtd->read(mtd, from, len, &retlen, buf); + len = min(totlen, (size_t)(1 << this->bbt_erase_shift)); + if (marker_len) { + /* + * In case the BBT marker is not in the OOB area it + * will be just in the first page. + */ + len -= marker_len; + from += marker_len; + marker_len = 0; + } + res = mtd_read(mtd, from, len, &retlen, buf); if (res < 0) { - if (retlen != len) { - printk(KERN_INFO "nand_bbt: Error reading bad block table\n"); + if (mtd_is_eccerr(res)) { + pr_info("nand_bbt: ECC error in BBT at " + "0x%012llx\n", from & ~mtd->writesize); + return res; + } else if (mtd_is_bitflip(res)) { + pr_info("nand_bbt: corrected error in BBT at " + "0x%012llx\n", from & ~mtd->writesize); + ret = res; + } else { + pr_info("nand_bbt: error reading BBT\n"); return res; } - printk(KERN_WARNING "nand_bbt: ECC error while reading bad block table\n"); } /* Analyse data */ for (i = 0; i < len; i++) { uint8_t dat = buf[i]; - for (j = 0; j < 8; j += bits, act += 2) { + for (j = 0; j < 8; j += bits, act++) { uint8_t tmp = (dat >> j) & msk; if (tmp == msk) continue; if (reserved_block_code && (tmp == reserved_block_code)) { - printk(KERN_DEBUG "nand_read_bbt: Reserved block at 0x%08x\n", - ((offs << 2) + (act >> 1)) << this->bbt_erase_shift); - this->bbt[offs + (act >> 3)] |= 0x2 << (act & 0x06); + pr_info("nand_read_bbt: reserved block at 0x%012llx\n", + (loff_t)(offs + act) << + this->bbt_erase_shift); + bbt_mark_entry(this, offs + act, + BBT_BLOCK_RESERVED); mtd->ecc_stats.bbtblocks++; continue; } - /* Leave it for now, if its matured we can move this - * message to MTD_DEBUG_LEVEL0 */ - printk(KERN_DEBUG "nand_read_bbt: Bad block at 0x%08x\n", - ((offs << 2) + (act >> 1)) << this->bbt_erase_shift); - /* Factory marked bad or worn out ? */ + /* + * Leave it for now, if it's matured we can + * move this message to pr_debug. + */ + pr_info("nand_read_bbt: bad block at 0x%012llx\n", + (loff_t)(offs + act) << + this->bbt_erase_shift); + /* Factory marked bad or worn out? */ if (tmp == 0) - this->bbt[offs + (act >> 3)] |= 0x3 << (act & 0x06); + bbt_mark_entry(this, offs + act, + BBT_BLOCK_FACTORY_BAD); else - this->bbt[offs + (act >> 3)] |= 0x1 << (act & 0x06); + bbt_mark_entry(this, offs + act, + BBT_BLOCK_WORN); mtd->ecc_stats.badblocks++; } } totlen -= len; from += len; } - return 0; + return ret; } /** * read_abs_bbt - [GENERIC] Read the bad block table starting at a given page - * @mtd: MTD device structure - * @buf: temporary buffer - * @td: descriptor for the bad block table - * @chip: read the table for a specific chip, -1 read all chips. - * Applies only if NAND_BBT_PERCHIP option is set + * @mtd: MTD device structure + * @buf: temporary buffer + * @td: descriptor for the bad block table + * @chip: read the table for a specific chip, -1 read all chips; applies only if + * NAND_BBT_PERCHIP option is set * - * Read the bad block table for all chips starting at a given page - * We assume that the bbt bits are in consecutive order. -*/ + * Read the bad block table for all chips starting at a given page. We assume + * that the bbt bits are in consecutive order. + */ static int read_abs_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *td, int chip) { struct nand_chip *this = mtd->priv; int res = 0, i; - int bits; - bits = td->options & NAND_BBT_NRBITS_MSK; if (td->options & NAND_BBT_PERCHIP) { int offs = 0; for (i = 0; i < this->numchips; i++) { if (chip == -1 || chip == i) - res = read_bbt (mtd, buf, td->pages[i], this->chipsize >> this->bbt_erase_shift, bits, offs, td->reserved_block_code); + res = read_bbt(mtd, buf, td->pages[i], + this->chipsize >> this->bbt_erase_shift, + td, offs); if (res) return res; - offs += this->chipsize >> (this->bbt_erase_shift + 2); + offs += this->chipsize >> this->bbt_erase_shift; } } else { - res = read_bbt (mtd, buf, td->pages[0], mtd->size >> this->bbt_erase_shift, bits, 0, td->reserved_block_code); + res = read_bbt(mtd, buf, td->pages[0], + mtd->size >> this->bbt_erase_shift, td, 0); if (res) return res; } return 0; } -/* - * Scan read raw data from flash +/* BBT marker is in the first page, no OOB */ +static int scan_read_data(struct mtd_info *mtd, uint8_t *buf, loff_t offs, + struct nand_bbt_descr *td) +{ + size_t retlen; + size_t len; + + len = td->len; + if (td->options & NAND_BBT_VERSION) + len++; + + return mtd_read(mtd, offs, len, &retlen, buf); +} + +/** + * scan_read_oob - [GENERIC] Scan data+OOB region to buffer + * @mtd: MTD device structure + * @buf: temporary buffer + * @offs: offset at which to scan + * @len: length of data region to read + * + * Scan read data from data+OOB. May traverse multiple pages, interleaving + * page,OOB,page,OOB,... in buf. Completes transfer and returns the "strongest" + * ECC condition (error or bitflip). May quit on the first (non-ECC) error. */ -static int scan_read_raw(struct mtd_info *mtd, uint8_t *buf, loff_t offs, +static int scan_read_oob(struct mtd_info *mtd, uint8_t *buf, loff_t offs, size_t len) { struct mtd_oob_ops ops; + int res, ret = 0; - ops.mode = MTD_OOB_RAW; + ops.mode = MTD_OPS_PLACE_OOB; ops.ooboffs = 0; ops.ooblen = mtd->oobsize; - ops.oobbuf = buf; - ops.datbuf = buf; - ops.len = len; - return mtd->read_oob(mtd, offs, &ops); + while (len > 0) { + ops.datbuf = buf; + ops.len = min(len, (size_t)mtd->writesize); + ops.oobbuf = buf + ops.len; + + res = mtd_read_oob(mtd, offs, &ops); + if (res) { + if (!mtd_is_bitflip_or_eccerr(res)) + return res; + else if (mtd_is_eccerr(res) || !ret) + ret = res; + } + + buf += mtd->oobsize + mtd->writesize; + len -= mtd->writesize; + offs += mtd->writesize; + } + return ret; } -/* - * Scan write data with oob to flash - */ +static int scan_read(struct mtd_info *mtd, uint8_t *buf, loff_t offs, + size_t len, struct nand_bbt_descr *td) +{ + if (td->options & NAND_BBT_NO_OOB) + return scan_read_data(mtd, buf, offs, td); + else + return scan_read_oob(mtd, buf, offs, len); +} + +/* Scan write data with oob to flash */ static int scan_write_bbt(struct mtd_info *mtd, loff_t offs, size_t len, uint8_t *buf, uint8_t *oob) { struct mtd_oob_ops ops; - ops.mode = MTD_OOB_PLACE; + ops.mode = MTD_OPS_PLACE_OOB; ops.ooboffs = 0; ops.ooblen = mtd->oobsize; ops.datbuf = buf; ops.oobbuf = oob; ops.len = len; - return mtd->write_oob(mtd, offs, &ops); + return mtd_write_oob(mtd, offs, &ops); +} + +static u32 bbt_get_ver_offs(struct mtd_info *mtd, struct nand_bbt_descr *td) +{ + u32 ver_offs = td->veroffs; + + if (!(td->options & NAND_BBT_NO_OOB)) + ver_offs += mtd->writesize; + return ver_offs; } /** * read_abs_bbts - [GENERIC] Read the bad block table(s) for all chips starting at a given page - * @mtd: MTD device structure - * @buf: temporary buffer - * @td: descriptor for the bad block table - * @md: descriptor for the bad block table mirror - * - * Read the bad block table(s) for all chips starting at a given page - * We assume that the bbt bits are in consecutive order. + * @mtd: MTD device structure + * @buf: temporary buffer + * @td: descriptor for the bad block table + * @md: descriptor for the bad block table mirror * -*/ -static int read_abs_bbts(struct mtd_info *mtd, uint8_t *buf, - struct nand_bbt_descr *td, struct nand_bbt_descr *md) + * Read the bad block table(s) for all chips starting at a given page. We + * assume that the bbt bits are in consecutive order. + */ +static void read_abs_bbts(struct mtd_info *mtd, uint8_t *buf, + struct nand_bbt_descr *td, struct nand_bbt_descr *md) { struct nand_chip *this = mtd->priv; /* Read the primary version, if available */ if (td->options & NAND_BBT_VERSION) { - scan_read_raw(mtd, buf, td->pages[0] << this->page_shift, - mtd->writesize); - td->version[0] = buf[mtd->writesize + td->veroffs]; - printk(KERN_DEBUG "Bad block table at page %d, version 0x%02X\n", - td->pages[0], td->version[0]); + scan_read(mtd, buf, (loff_t)td->pages[0] << this->page_shift, + mtd->writesize, td); + td->version[0] = buf[bbt_get_ver_offs(mtd, td)]; + pr_info("Bad block table at page %d, version 0x%02X\n", + td->pages[0], td->version[0]); } /* Read the mirror version, if available */ if (md && (md->options & NAND_BBT_VERSION)) { - scan_read_raw(mtd, buf, md->pages[0] << this->page_shift, - mtd->writesize); - md->version[0] = buf[mtd->writesize + md->veroffs]; - printk(KERN_DEBUG "Bad block table at page %d, version 0x%02X\n", - md->pages[0], md->version[0]); - } - return 1; -} - -/* - * Scan a given block full - */ -static int scan_block_full(struct mtd_info *mtd, struct nand_bbt_descr *bd, - loff_t offs, uint8_t *buf, size_t readlen, - int scanlen, int len) -{ - int ret, j; - - ret = scan_read_raw(mtd, buf, offs, readlen); - if (ret) - return ret; - - for (j = 0; j < len; j++, buf += scanlen) { - if (check_pattern(buf, scanlen, mtd->writesize, bd)) - return 1; + scan_read(mtd, buf, (loff_t)md->pages[0] << this->page_shift, + mtd->writesize, md); + md->version[0] = buf[bbt_get_ver_offs(mtd, md)]; + pr_info("Bad block table at page %d, version 0x%02X\n", + md->pages[0], md->version[0]); } - return 0; } -/* - * Scan a given block partially - */ +/* Scan a given block partially */ static int scan_block_fast(struct mtd_info *mtd, struct nand_bbt_descr *bd, - loff_t offs, uint8_t *buf, int len) + loff_t offs, uint8_t *buf, int numpages) { struct mtd_oob_ops ops; int j, ret; - ops.len = mtd->oobsize; ops.ooblen = mtd->oobsize; ops.oobbuf = buf; ops.ooboffs = 0; ops.datbuf = NULL; - ops.mode = MTD_OOB_PLACE; + ops.mode = MTD_OPS_PLACE_OOB; - for (j = 0; j < len; j++) { + for (j = 0; j < numpages; j++) { /* - * Read the full oob until read_oob is fixed to - * handle single byte reads for 16 bit - * buswidth + * Read the full oob until read_oob is fixed to handle single + * byte reads for 16 bit buswidth. */ - ret = mtd->read_oob(mtd, offs, &ops); - if (ret) + ret = mtd_read_oob(mtd, offs, &ops); + /* Ignore ECC errors when checking for BBM */ + if (ret && !mtd_is_bitflip_or_eccerr(ret)) return ret; if (check_short_pattern(buf, bd)) @@ -360,83 +445,65 @@ static int scan_block_fast(struct mtd_info *mtd, struct nand_bbt_descr *bd, /** * create_bbt - [GENERIC] Create a bad block table by scanning the device - * @mtd: MTD device structure - * @buf: temporary buffer - * @bd: descriptor for the good/bad block search pattern - * @chip: create the table for a specific chip, -1 read all chips. - * Applies only if NAND_BBT_PERCHIP option is set + * @mtd: MTD device structure + * @buf: temporary buffer + * @bd: descriptor for the good/bad block search pattern + * @chip: create the table for a specific chip, -1 read all chips; applies only + * if NAND_BBT_PERCHIP option is set * - * Create a bad block table by scanning the device - * for the given good/bad block identify pattern + * Create a bad block table by scanning the device for the given good/bad block + * identify pattern. */ static int create_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *bd, int chip) { struct nand_chip *this = mtd->priv; - int i, numblocks, len, scanlen; + int i, numblocks, numpages; int startblock; loff_t from; - size_t readlen; - - printk(KERN_INFO "Scanning device for bad blocks\n"); - if (bd->options & NAND_BBT_SCANALLPAGES) - len = 1 << (this->bbt_erase_shift - this->page_shift); - else { - if (bd->options & NAND_BBT_SCAN2NDPAGE) - len = 2; - else - len = 1; - } + pr_info("Scanning device for bad blocks\n"); - if (!(bd->options & NAND_BBT_SCANEMPTY)) { - /* We need only read few bytes from the OOB area */ - scanlen = 0; - readlen = bd->len; - } else { - /* Full page content should be read */ - scanlen = mtd->writesize + mtd->oobsize; - readlen = len * mtd->writesize; - } + if (bd->options & NAND_BBT_SCAN2NDPAGE) + numpages = 2; + else + numpages = 1; if (chip == -1) { - /* Note that numblocks is 2 * (real numblocks) here, see i+=2 - * below as it makes shifting and masking less painful */ - numblocks = mtd->size >> (this->bbt_erase_shift - 1); + numblocks = mtd->size >> this->bbt_erase_shift; startblock = 0; from = 0; } else { if (chip >= this->numchips) { - printk(KERN_WARNING "create_bbt(): chipnr (%d) > available chips (%d)\n", + pr_warn("create_bbt(): chipnr (%d) > available chips (%d)\n", chip + 1, this->numchips); return -EINVAL; } - numblocks = this->chipsize >> (this->bbt_erase_shift - 1); + numblocks = this->chipsize >> this->bbt_erase_shift; startblock = chip * numblocks; numblocks += startblock; - from = startblock << (this->bbt_erase_shift - 1); + from = (loff_t)startblock << this->bbt_erase_shift; } - for (i = startblock; i < numblocks;) { + if (this->bbt_options & NAND_BBT_SCANLASTPAGE) + from += mtd->erasesize - (mtd->writesize * numpages); + + for (i = startblock; i < numblocks; i++) { int ret; - if (bd->options & NAND_BBT_SCANALLPAGES) - ret = scan_block_full(mtd, bd, from, buf, readlen, - scanlen, len); - else - ret = scan_block_fast(mtd, bd, from, buf, len); + BUG_ON(bd->options & NAND_BBT_NO_OOB); + ret = scan_block_fast(mtd, bd, from, buf, numpages); if (ret < 0) return ret; if (ret) { - this->bbt[i >> 3] |= 0x03 << (i & 0x6); - printk(KERN_WARNING "Bad eraseblock %d at 0x%08x\n", - i >> 1, (unsigned int)from); + bbt_mark_entry(this, i, BBT_BLOCK_FACTORY_BAD); + pr_warn("Bad eraseblock %d at 0x%012llx\n", + i, (unsigned long long)from); mtd->ecc_stats.badblocks++; } - i += 2; from += (1 << this->bbt_erase_shift); } return 0; @@ -444,31 +511,29 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf, /** * search_bbt - [GENERIC] scan the device for a specific bad block table - * @mtd: MTD device structure - * @buf: temporary buffer - * @td: descriptor for the bad block table + * @mtd: MTD device structure + * @buf: temporary buffer + * @td: descriptor for the bad block table * - * Read the bad block table by searching for a given ident pattern. - * Search is preformed either from the beginning up or from the end of - * the device downwards. The search starts always at the start of a - * block. - * If the option NAND_BBT_PERCHIP is given, each chip is searched - * for a bbt, which contains the bad block information of this chip. - * This is necessary to provide support for certain DOC devices. + * Read the bad block table by searching for a given ident pattern. Search is + * preformed either from the beginning up or from the end of the device + * downwards. The search starts always at the start of a block. If the option + * NAND_BBT_PERCHIP is given, each chip is searched for a bbt, which contains + * the bad block information of this chip. This is necessary to provide support + * for certain DOC devices. * - * The bbt ident pattern resides in the oob area of the first page - * in a block. + * The bbt ident pattern resides in the oob area of the first page in a block. */ static int search_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *td) { struct nand_chip *this = mtd->priv; int i, chips; - int bits, startblock, block, dir; + int startblock, block, dir; int scanlen = mtd->writesize + mtd->oobsize; int bbtblocks; int blocktopage = this->bbt_erase_shift - this->page_shift; - /* Search direction top -> down ? */ + /* Search direction top -> down? */ if (td->options & NAND_BBT_LASTBLOCK) { startblock = (mtd->size >> this->bbt_erase_shift) - 1; dir = -1; @@ -477,7 +542,7 @@ static int search_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr dir = 1; } - /* Do we have a bbt per chip ? */ + /* Do we have a bbt per chip? */ if (td->options & NAND_BBT_PERCHIP) { chips = this->numchips; bbtblocks = this->chipsize >> this->bbt_erase_shift; @@ -487,9 +552,6 @@ static int search_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr bbtblocks = mtd->size >> this->bbt_erase_shift; } - /* Number of bits for each erase block in the bbt */ - bits = td->options & NAND_BBT_NRBITS_MSK; - for (i = 0; i < chips; i++) { /* Reset version information */ td->version[i] = 0; @@ -498,14 +560,15 @@ static int search_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr for (block = 0; block < td->maxblocks; block++) { int actblock = startblock + dir * block; - loff_t offs = actblock << this->bbt_erase_shift; + loff_t offs = (loff_t)actblock << this->bbt_erase_shift; /* Read first page */ - scan_read_raw(mtd, buf, offs, mtd->writesize); + scan_read(mtd, buf, offs, mtd->writesize, td); if (!check_pattern(buf, scanlen, mtd->writesize, td)) { td->pages[i] = actblock << blocktopage; if (td->options & NAND_BBT_VERSION) { - td->version[i] = buf[mtd->writesize + td->veroffs]; + offs = bbt_get_ver_offs(mtd, td); + td->version[i] = buf[offs]; } break; } @@ -515,24 +578,26 @@ static int search_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr /* Check, if we found a bbt for each requested chip */ for (i = 0; i < chips; i++) { if (td->pages[i] == -1) - printk(KERN_WARNING "Bad block table not found for chip %d\n", i); + pr_warn("Bad block table not found for chip %d\n", i); else - printk(KERN_DEBUG "Bad block table found at page %d, version 0x%02X\n", td->pages[i], - td->version[i]); + pr_info("Bad block table found at page %d, version " + "0x%02X\n", td->pages[i], td->version[i]); } return 0; } /** * search_read_bbts - [GENERIC] scan the device for bad block table(s) - * @mtd: MTD device structure - * @buf: temporary buffer - * @td: descriptor for the bad block table - * @md: descriptor for the bad block table mirror + * @mtd: MTD device structure + * @buf: temporary buffer + * @td: descriptor for the bad block table + * @md: descriptor for the bad block table mirror * - * Search and read the bad block table(s) -*/ -static int search_read_bbts(struct mtd_info *mtd, uint8_t * buf, struct nand_bbt_descr *td, struct nand_bbt_descr *md) + * Search and read the bad block table(s). + */ +static void search_read_bbts(struct mtd_info *mtd, uint8_t *buf, + struct nand_bbt_descr *td, + struct nand_bbt_descr *md) { /* Search the primary table */ search_bbt(mtd, buf, td); @@ -540,32 +605,27 @@ static int search_read_bbts(struct mtd_info *mtd, uint8_t * buf, struct nand_bbt /* Search the mirror table */ if (md) search_bbt(mtd, buf, md); - - /* Force result check */ - return 1; } /** * write_bbt - [GENERIC] (Re)write the bad block table + * @mtd: MTD device structure + * @buf: temporary buffer + * @td: descriptor for the bad block table + * @md: descriptor for the bad block table mirror + * @chipsel: selector for a specific chip, -1 for all * - * @mtd: MTD device structure - * @buf: temporary buffer - * @td: descriptor for the bad block table - * @md: descriptor for the bad block table mirror - * @chipsel: selector for a specific chip, -1 for all - * - * (Re)write the bad block table - * -*/ + * (Re)write the bad block table. + */ static int write_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *td, struct nand_bbt_descr *md, int chipsel) { struct nand_chip *this = mtd->priv; struct erase_info einfo; - int i, j, res, chip = 0; + int i, res, chip = 0; int bits, startblock, dir, page, offs, numblocks, sft, sftmsk; - int nrchips, bbtoffs, pageoffs, ooboffs; + int nrchips, pageoffs, ooboffs; uint8_t msk[4]; uint8_t rcode = td->reserved_block_code; size_t retlen, len = 0; @@ -575,14 +635,14 @@ static int write_bbt(struct mtd_info *mtd, uint8_t *buf, ops.ooblen = mtd->oobsize; ops.ooboffs = 0; ops.datbuf = NULL; - ops.mode = MTD_OOB_PLACE; + ops.mode = MTD_OPS_PLACE_OOB; if (!rcode) rcode = 0xff; - /* Write bad block table per chip rather than per device ? */ + /* Write bad block table per chip rather than per device? */ if (td->options & NAND_BBT_PERCHIP) { numblocks = (int)(this->chipsize >> this->bbt_erase_shift); - /* Full device write or specific chip ? */ + /* Full device write or specific chip? */ if (chipsel == -1) { nrchips = this->numchips; } else { @@ -596,8 +656,8 @@ static int write_bbt(struct mtd_info *mtd, uint8_t *buf, /* Loop through the chips */ for (; chip < nrchips; chip++) { - - /* There was already a version of the table, reuse the page + /* + * There was already a version of the table, reuse the page * This applies for absolute placement too, as we have the * page nr. in td->pages. */ @@ -606,8 +666,10 @@ static int write_bbt(struct mtd_info *mtd, uint8_t *buf, goto write; } - /* Automatic placement of the bad block table */ - /* Search direction top -> down ? */ + /* + * Automatic placement of the bad block table. Search direction + * top -> down? + */ if (td->options & NAND_BBT_LASTBLOCK) { startblock = numblocks * (chip + 1) - 1; dir = -1; @@ -619,10 +681,9 @@ static int write_bbt(struct mtd_info *mtd, uint8_t *buf, for (i = 0; i < td->maxblocks; i++) { int block = startblock + dir * i; /* Check, if the block is bad */ - switch ((this->bbt[block >> 2] >> - (2 * (block & 0x03))) & 0x03) { - case 0x01: - case 0x03: + switch (bbt_get_entry(this, block)) { + case BBT_BLOCK_WORN: + case BBT_BLOCK_FACTORY_BAD: continue; } page = block << @@ -631,7 +692,7 @@ static int write_bbt(struct mtd_info *mtd, uint8_t *buf, if (!md || md->pages[chip] != page) goto write; } - printk(KERN_ERR "No space left to write bad block table\n"); + pr_err("No space left to write bad block table\n"); return -ENOSPC; write: @@ -654,47 +715,57 @@ static int write_bbt(struct mtd_info *mtd, uint8_t *buf, default: return -EINVAL; } - bbtoffs = chip * (numblocks >> 2); - - to = ((loff_t) page) << this->page_shift; + to = ((loff_t)page) << this->page_shift; - /* Must we save the block contents ? */ + /* Must we save the block contents? */ if (td->options & NAND_BBT_SAVECONTENT) { /* Make it block aligned */ - to &= ~((loff_t) ((1 << this->bbt_erase_shift) - 1)); + to &= ~((loff_t)((1 << this->bbt_erase_shift) - 1)); len = 1 << this->bbt_erase_shift; - res = mtd->read(mtd, to, len, &retlen, buf); + res = mtd_read(mtd, to, len, &retlen, buf); if (res < 0) { if (retlen != len) { - printk(KERN_INFO "nand_bbt: Error " - "reading block for writing " - "the bad block table\n"); + pr_info("nand_bbt: error reading block " + "for writing the bad block table\n"); return res; } - printk(KERN_WARNING "nand_bbt: ECC error " - "while reading block for writing " - "bad block table\n"); + pr_warn("nand_bbt: ECC error while reading " + "block for writing bad block table\n"); } /* Read oob data */ - ops.len = (len >> this->page_shift) * mtd->oobsize; + ops.ooblen = (len >> this->page_shift) * mtd->oobsize; ops.oobbuf = &buf[len]; - res = mtd->read_oob(mtd, to + mtd->writesize, &ops); - if (res < 0 || ops.retlen != ops.len) + res = mtd_read_oob(mtd, to + mtd->writesize, &ops); + if (res < 0 || ops.oobretlen != ops.ooblen) goto outerr; /* Calc the byte offset in the buffer */ pageoffs = page - (int)(to >> this->page_shift); offs = pageoffs << this->page_shift; /* Preset the bbt area with 0xff */ - memset(&buf[offs], 0xff, (size_t) (numblocks >> sft)); + memset(&buf[offs], 0xff, (size_t)(numblocks >> sft)); ooboffs = len + (pageoffs * mtd->oobsize); + } else if (td->options & NAND_BBT_NO_OOB) { + ooboffs = 0; + offs = td->len; + /* The version byte */ + if (td->options & NAND_BBT_VERSION) + offs++; + /* Calc length */ + len = (size_t)(numblocks >> sft); + len += offs; + /* Make it page aligned! */ + len = ALIGN(len, mtd->writesize); + /* Preset the buffer with 0xff */ + memset(buf, 0xff, len); + /* Pattern is located at the begin of first page */ + memcpy(buf, td->pattern, td->len); } else { /* Calc length */ - len = (size_t) (numblocks >> sft); - /* Make it page aligned ! */ - len = (len + (mtd->writesize - 1)) & - ~(mtd->writesize - 1); + len = (size_t)(numblocks >> sft); + /* Make it page aligned! */ + len = ALIGN(len, mtd->writesize); /* Preset the buffer with 0xff */ memset(buf, 0xff, len + (len >> this->page_shift)* mtd->oobsize); @@ -707,33 +778,31 @@ static int write_bbt(struct mtd_info *mtd, uint8_t *buf, if (td->options & NAND_BBT_VERSION) buf[ooboffs + td->veroffs] = td->version[chip]; - /* walk through the memory table */ - for (i = 0; i < numblocks;) { + /* Walk through the memory table */ + for (i = 0; i < numblocks; i++) { uint8_t dat; - dat = this->bbt[bbtoffs + (i >> 2)]; - for (j = 0; j < 4; j++, i++) { - int sftcnt = (i << (3 - sft)) & sftmsk; - /* Do not store the reserved bbt blocks ! */ - buf[offs + (i >> sft)] &= - ~(msk[dat & 0x03] << sftcnt); - dat >>= 2; - } + int sftcnt = (i << (3 - sft)) & sftmsk; + dat = bbt_get_entry(this, chip * numblocks + i); + /* Do not store the reserved bbt blocks! */ + buf[offs + (i >> sft)] &= ~(msk[dat] << sftcnt); } memset(&einfo, 0, sizeof(einfo)); einfo.mtd = mtd; - einfo.addr = (unsigned long)to; + einfo.addr = to; einfo.len = 1 << this->bbt_erase_shift; res = nand_erase_nand(mtd, &einfo, 1); if (res < 0) goto outerr; - res = scan_write_bbt(mtd, to, len, buf, &buf[len]); + res = scan_write_bbt(mtd, to, len, buf, + td->options & NAND_BBT_NO_OOB ? NULL : + &buf[len]); if (res < 0) goto outerr; - printk(KERN_DEBUG "Bad block table written to 0x%08x, version " - "0x%02X\n", (unsigned int)to, td->version[chip]); + pr_info("Bad block table written to 0x%012llx, version 0x%02X\n", + (unsigned long long)to, td->version[chip]); /* Mark it as used */ td->pages[chip] = page; @@ -741,48 +810,45 @@ static int write_bbt(struct mtd_info *mtd, uint8_t *buf, return 0; outerr: - printk(KERN_WARNING - "nand_bbt: Error while writing bad block table %d\n", res); + pr_warn("nand_bbt: error while writing bad block table %d\n", res); return res; } /** * nand_memory_bbt - [GENERIC] create a memory based bad block table - * @mtd: MTD device structure - * @bd: descriptor for the good/bad block search pattern + * @mtd: MTD device structure + * @bd: descriptor for the good/bad block search pattern * - * The function creates a memory based bbt by scanning the device - * for manufacturer / software marked good / bad blocks -*/ + * The function creates a memory based bbt by scanning the device for + * manufacturer / software marked good / bad blocks. + */ static inline int nand_memory_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd) { struct nand_chip *this = mtd->priv; - bd->options &= ~NAND_BBT_SCANEMPTY; - return create_bbt(mtd, this->buffers.databuf, bd, -1); + return create_bbt(mtd, this->buffers->databuf, bd, -1); } /** * check_create - [GENERIC] create and write bbt(s) if necessary - * @mtd: MTD device structure - * @buf: temporary buffer - * @bd: descriptor for the good/bad block search pattern + * @mtd: MTD device structure + * @buf: temporary buffer + * @bd: descriptor for the good/bad block search pattern * - * The function checks the results of the previous call to read_bbt - * and creates / updates the bbt(s) if necessary - * Creation is necessary if no bbt was found for the chip/device - * Update is necessary if one of the tables is missing or the - * version nr. of one table is less than the other -*/ + * The function checks the results of the previous call to read_bbt and creates + * / updates the bbt(s) if necessary. Creation is necessary if no bbt was found + * for the chip/device. Update is necessary if one of the tables is missing or + * the version nr. of one table is less than the other. + */ static int check_create(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *bd) { - int i, chips, writeops, chipsel, res; + int i, chips, writeops, create, chipsel, res, res2; struct nand_chip *this = mtd->priv; struct nand_bbt_descr *td = this->bbt_td; struct nand_bbt_descr *md = this->bbt_md; struct nand_bbt_descr *rd, *rd2; - /* Do we have a bbt per chip ? */ + /* Do we have a bbt per chip? */ if (td->options & NAND_BBT_PERCHIP) chips = this->numchips; else @@ -790,85 +856,98 @@ static int check_create(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_desc for (i = 0; i < chips; i++) { writeops = 0; + create = 0; rd = NULL; rd2 = NULL; - /* Per chip or per device ? */ + res = res2 = 0; + /* Per chip or per device? */ chipsel = (td->options & NAND_BBT_PERCHIP) ? i : -1; - /* Mirrored table avilable ? */ + /* Mirrored table available? */ if (md) { if (td->pages[i] == -1 && md->pages[i] == -1) { + create = 1; writeops = 0x03; - goto create; - } - - if (td->pages[i] == -1) { + } else if (td->pages[i] == -1) { rd = md; - td->version[i] = md->version[i]; - writeops = 1; - goto writecheck; - } - - if (md->pages[i] == -1) { + writeops = 0x01; + } else if (md->pages[i] == -1) { rd = td; - md->version[i] = td->version[i]; - writeops = 2; - goto writecheck; - } - - if (td->version[i] == md->version[i]) { + writeops = 0x02; + } else if (td->version[i] == md->version[i]) { rd = td; if (!(td->options & NAND_BBT_VERSION)) rd2 = md; - goto writecheck; - } - - if (((int8_t) (td->version[i] - md->version[i])) > 0) { + } else if (((int8_t)(td->version[i] - md->version[i])) > 0) { rd = td; - md->version[i] = td->version[i]; - writeops = 2; + writeops = 0x02; } else { rd = md; - td->version[i] = md->version[i]; - writeops = 1; + writeops = 0x01; } - - goto writecheck; - } else { if (td->pages[i] == -1) { + create = 1; writeops = 0x01; - goto create; + } else { + rd = td; } - rd = td; - goto writecheck; } - create: - /* Create the bad block table by scanning the device ? */ - if (!(td->options & NAND_BBT_CREATE)) - continue; - /* Create the table in memory by scanning the chip(s) */ - create_bbt(mtd, buf, bd, chipsel); - - td->version[i] = 1; - if (md) - md->version[i] = 1; - writecheck: - /* read back first ? */ - if (rd) - read_abs_bbt(mtd, buf, rd, chipsel); - /* If they weren't versioned, read both. */ - if (rd2) - read_abs_bbt(mtd, buf, rd2, chipsel); - - /* Write the bad block table to the device ? */ + if (create) { + /* Create the bad block table by scanning the device? */ + if (!(td->options & NAND_BBT_CREATE)) + continue; + + /* Create the table in memory by scanning the chip(s) */ + if (!(this->bbt_options & NAND_BBT_CREATE_EMPTY)) + create_bbt(mtd, buf, bd, chipsel); + + td->version[i] = 1; + if (md) + md->version[i] = 1; + } + + /* Read back first? */ + if (rd) { + res = read_abs_bbt(mtd, buf, rd, chipsel); + if (mtd_is_eccerr(res)) { + /* Mark table as invalid */ + rd->pages[i] = -1; + rd->version[i] = 0; + i--; + continue; + } + } + /* If they weren't versioned, read both */ + if (rd2) { + res2 = read_abs_bbt(mtd, buf, rd2, chipsel); + if (mtd_is_eccerr(res2)) { + /* Mark table as invalid */ + rd2->pages[i] = -1; + rd2->version[i] = 0; + i--; + continue; + } + } + + /* Scrub the flash table(s)? */ + if (mtd_is_bitflip(res) || mtd_is_bitflip(res2)) + writeops = 0x03; + + /* Update version numbers before writing */ + if (md) { + td->version[i] = max(td->version[i], md->version[i]); + md->version[i] = td->version[i]; + } + + /* Write the bad block table to the device? */ if ((writeops & 0x01) && (td->options & NAND_BBT_WRITE)) { res = write_bbt(mtd, buf, td, md, chipsel); if (res < 0) return res; } - /* Write the mirror bad block table to the device ? */ + /* Write the mirror bad block table to the device? */ if ((writeops & 0x02) && md && (md->options & NAND_BBT_WRITE)) { res = write_bbt(mtd, buf, md, td, chipsel); if (res < 0) @@ -880,20 +959,19 @@ static int check_create(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_desc /** * mark_bbt_regions - [GENERIC] mark the bad block table regions - * @mtd: MTD device structure - * @td: bad block table descriptor + * @mtd: MTD device structure + * @td: bad block table descriptor * - * The bad block table regions are marked as "bad" to prevent - * accidental erasures / writes. The regions are identified by - * the mark 0x02. -*/ + * The bad block table regions are marked as "bad" to prevent accidental + * erasures / writes. The regions are identified by the mark 0x02. + */ static void mark_bbt_region(struct mtd_info *mtd, struct nand_bbt_descr *td) { struct nand_chip *this = mtd->priv; int i, j, chips, block, nrblocks, update; - uint8_t oldval, newval; + uint8_t oldval; - /* Do we have a bbt per chip ? */ + /* Do we have a bbt per chip? */ if (td->options & NAND_BBT_PERCHIP) { chips = this->numchips; nrblocks = (int)(this->chipsize >> this->bbt_erase_shift); @@ -908,12 +986,12 @@ static void mark_bbt_region(struct mtd_info *mtd, struct nand_bbt_descr *td) if (td->pages[i] == -1) continue; block = td->pages[i] >> (this->bbt_erase_shift - this->page_shift); - block <<= 1; - oldval = this->bbt[(block >> 3)]; - newval = oldval | (0x2 << (block & 0x06)); - this->bbt[(block >> 3)] = newval; - if ((oldval != newval) && td->reserved_block_code) - nand_update_bbt(mtd, block << (this->bbt_erase_shift - 1)); + oldval = bbt_get_entry(this, block); + bbt_mark_entry(this, block, BBT_BLOCK_RESERVED); + if ((oldval != BBT_BLOCK_RESERVED) && + td->reserved_block_code) + nand_update_bbt(mtd, (loff_t)block << + this->bbt_erase_shift); continue; } update = 0; @@ -921,37 +999,84 @@ static void mark_bbt_region(struct mtd_info *mtd, struct nand_bbt_descr *td) block = ((i + 1) * nrblocks) - td->maxblocks; else block = i * nrblocks; - block <<= 1; for (j = 0; j < td->maxblocks; j++) { - oldval = this->bbt[(block >> 3)]; - newval = oldval | (0x2 << (block & 0x06)); - this->bbt[(block >> 3)] = newval; - if (oldval != newval) + oldval = bbt_get_entry(this, block); + bbt_mark_entry(this, block, BBT_BLOCK_RESERVED); + if (oldval != BBT_BLOCK_RESERVED) update = 1; - block += 2; + block++; } - /* If we want reserved blocks to be recorded to flash, and some - new ones have been marked, then we need to update the stored - bbts. This should only happen once. */ + /* + * If we want reserved blocks to be recorded to flash, and some + * new ones have been marked, then we need to update the stored + * bbts. This should only happen once. + */ if (update && td->reserved_block_code) - nand_update_bbt(mtd, (block - 2) << (this->bbt_erase_shift - 1)); + nand_update_bbt(mtd, (loff_t)(block - 1) << + this->bbt_erase_shift); } } /** - * nand_scan_bbt - [NAND Interface] scan, find, read and maybe create bad block table(s) - * @mtd: MTD device structure - * @bd: descriptor for the good/bad block search pattern + * verify_bbt_descr - verify the bad block description + * @mtd: MTD device structure + * @bd: the table to verify * - * The function checks, if a bad block table(s) is/are already - * available. If not it scans the device for manufacturer - * marked good / bad blocks and writes the bad block table(s) to - * the selected place. + * This functions performs a few sanity checks on the bad block description + * table. + */ +static void verify_bbt_descr(struct mtd_info *mtd, struct nand_bbt_descr *bd) +{ + struct nand_chip *this = mtd->priv; + u32 pattern_len; + u32 bits; + u32 table_size; + + if (!bd) + return; + + pattern_len = bd->len; + bits = bd->options & NAND_BBT_NRBITS_MSK; + + BUG_ON((this->bbt_options & NAND_BBT_NO_OOB) && + !(this->bbt_options & NAND_BBT_USE_FLASH)); + BUG_ON(!bits); + + if (bd->options & NAND_BBT_VERSION) + pattern_len++; + + if (bd->options & NAND_BBT_NO_OOB) { + BUG_ON(!(this->bbt_options & NAND_BBT_USE_FLASH)); + BUG_ON(!(this->bbt_options & NAND_BBT_NO_OOB)); + BUG_ON(bd->offs); + if (bd->options & NAND_BBT_VERSION) + BUG_ON(bd->veroffs != bd->len); + BUG_ON(bd->options & NAND_BBT_SAVECONTENT); + } + + if (bd->options & NAND_BBT_PERCHIP) + table_size = this->chipsize >> this->bbt_erase_shift; + else + table_size = mtd->size >> this->bbt_erase_shift; + table_size >>= 3; + table_size *= bits; + if (bd->options & NAND_BBT_NO_OOB) + table_size += pattern_len; + BUG_ON(table_size > (1 << this->bbt_erase_shift)); +} + +/** + * nand_scan_bbt - [NAND Interface] scan, find, read and maybe create bad block table(s) + * @mtd: MTD device structure + * @bd: descriptor for the good/bad block search pattern * - * The bad block table memory is allocated here. It must be freed - * by calling the nand_free_bbt function. + * The function checks, if a bad block table(s) is/are already available. If + * not it scans the device for manufacturer marked good / bad blocks and writes + * the bad block table(s) to the selected place. * -*/ + * The bad block table memory is allocated here. It must be freed by calling + * the nand_free_bbt function. + */ int nand_scan_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd) { struct nand_chip *this = mtd->priv; @@ -961,48 +1086,48 @@ int nand_scan_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd) struct nand_bbt_descr *md = this->bbt_md; len = mtd->size >> (this->bbt_erase_shift + 2); - /* Allocate memory (2bit per block) */ - this->bbt = kmalloc(len, GFP_KERNEL); - if (!this->bbt) { - printk(KERN_ERR "nand_scan_bbt: Out of memory\n"); + /* + * Allocate memory (2bit per block) and clear the memory bad block + * table. + */ + this->bbt = kzalloc(len, GFP_KERNEL); + if (!this->bbt) return -ENOMEM; - } - /* Clear the memory bad block table */ - memset(this->bbt, 0x00, len); - /* If no primary table decriptor is given, scan the device - * to build a memory based bad block table + /* + * If no primary table decriptor is given, scan the device to build a + * memory based bad block table. */ if (!td) { if ((res = nand_memory_bbt(mtd, bd))) { - printk(KERN_ERR "nand_bbt: Can't scan flash and build the RAM-based BBT\n"); + pr_err("nand_bbt: can't scan flash and build the RAM-based BBT\n"); kfree(this->bbt); this->bbt = NULL; } return res; } + verify_bbt_descr(mtd, td); + verify_bbt_descr(mtd, md); /* Allocate a temporary buffer for one eraseblock incl. oob */ len = (1 << this->bbt_erase_shift); len += (len >> this->page_shift) * mtd->oobsize; buf = vmalloc(len); if (!buf) { - printk(KERN_ERR "nand_bbt: Out of memory\n"); kfree(this->bbt); this->bbt = NULL; return -ENOMEM; } - /* Is the bbt at a given page ? */ + /* Is the bbt at a given page? */ if (td->options & NAND_BBT_ABSPAGE) { - res = read_abs_bbts(mtd, buf, td, md); + read_abs_bbts(mtd, buf, td, md); } else { /* Search the bad block table using a pattern in oob */ - res = search_read_bbts(mtd, buf, td, md); + search_read_bbts(mtd, buf, td, md); } - if (res) - res = check_create(mtd, buf, bd); + res = check_create(mtd, buf, bd); /* Prevent the bbt regions from erasing / writing */ mark_bbt_region(mtd, td); @@ -1014,16 +1139,16 @@ int nand_scan_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd) } /** - * nand_update_bbt - [NAND Interface] update bad block table(s) - * @mtd: MTD device structure - * @offs: the offset of the newly marked block + * nand_update_bbt - update bad block table(s) + * @mtd: MTD device structure + * @offs: the offset of the newly marked block * - * The function updates the bad block table(s) -*/ -int nand_update_bbt(struct mtd_info *mtd, loff_t offs) + * The function updates the bad block table(s). + */ +static int nand_update_bbt(struct mtd_info *mtd, loff_t offs) { struct nand_chip *this = mtd->priv; - int len, res = 0, writeops = 0; + int len, res = 0; int chip, chipsel; uint8_t *buf; struct nand_bbt_descr *td = this->bbt_td; @@ -1032,19 +1157,14 @@ int nand_update_bbt(struct mtd_info *mtd, loff_t offs) if (!this->bbt || !td) return -EINVAL; - len = mtd->size >> (this->bbt_erase_shift + 2); /* Allocate a temporary buffer for one eraseblock incl. oob */ len = (1 << this->bbt_erase_shift); len += (len >> this->page_shift) * mtd->oobsize; buf = kmalloc(len, GFP_KERNEL); - if (!buf) { - printk(KERN_ERR "nand_update_bbt: Out of memory\n"); + if (!buf) return -ENOMEM; - } - writeops = md != NULL ? 0x03 : 0x01; - - /* Do we have a bbt per chip ? */ + /* Do we have a bbt per chip? */ if (td->options & NAND_BBT_PERCHIP) { chip = (int)(offs >> this->chip_shift); chipsel = chip; @@ -1057,14 +1177,14 @@ int nand_update_bbt(struct mtd_info *mtd, loff_t offs) if (md) md->version[chip]++; - /* Write the bad block table to the device ? */ - if ((writeops & 0x01) && (td->options & NAND_BBT_WRITE)) { + /* Write the bad block table to the device? */ + if (td->options & NAND_BBT_WRITE) { res = write_bbt(mtd, buf, td, md, chipsel); if (res < 0) goto out; } - /* Write the mirror bad block table to the device ? */ - if ((writeops & 0x02) && md && (md->options & NAND_BBT_WRITE)) { + /* Write the mirror bad block table to the device? */ + if (md && (md->options & NAND_BBT_WRITE)) { res = write_bbt(mtd, buf, md, td, chipsel); } @@ -1073,49 +1193,13 @@ int nand_update_bbt(struct mtd_info *mtd, loff_t offs) return res; } -/* Define some generic bad / good block scan pattern which are used - * while scanning a device for factory marked good / bad blocks. */ +/* + * Define some generic bad / good block scan pattern which are used + * while scanning a device for factory marked good / bad blocks. + */ static uint8_t scan_ff_pattern[] = { 0xff, 0xff }; -static struct nand_bbt_descr smallpage_memorybased = { - .options = NAND_BBT_SCAN2NDPAGE, - .offs = 5, - .len = 1, - .pattern = scan_ff_pattern -}; - -static struct nand_bbt_descr largepage_memorybased = { - .options = 0, - .offs = 0, - .len = 2, - .pattern = scan_ff_pattern -}; - -static struct nand_bbt_descr smallpage_flashbased = { - .options = NAND_BBT_SCAN2NDPAGE, - .offs = 5, - .len = 1, - .pattern = scan_ff_pattern -}; - -static struct nand_bbt_descr largepage_flashbased = { - .options = NAND_BBT_SCAN2NDPAGE, - .offs = 0, - .len = 2, - .pattern = scan_ff_pattern -}; - -static uint8_t scan_agand_pattern[] = { 0x1C, 0x71, 0xC7, 0x1C, 0x71, 0xC7 }; - -static struct nand_bbt_descr agand_flashbased = { - .options = NAND_BBT_SCANEMPTY | NAND_BBT_SCANALLPAGES, - .offs = 0x20, - .len = 6, - .pattern = scan_agand_pattern -}; - -/* Generic flash bbt decriptors -*/ +/* Generic flash bbt descriptors */ static uint8_t bbt_pattern[] = {'B', 'b', 't', '0' }; static uint8_t mirror_pattern[] = {'1', 't', 'b', 'B' }; @@ -1125,7 +1209,7 @@ static struct nand_bbt_descr bbt_main_descr = { .offs = 8, .len = 4, .veroffs = 12, - .maxblocks = 4, + .maxblocks = NAND_BBT_SCAN_MAXBLOCKS, .pattern = bbt_pattern }; @@ -1135,90 +1219,146 @@ static struct nand_bbt_descr bbt_mirror_descr = { .offs = 8, .len = 4, .veroffs = 12, - .maxblocks = 4, + .maxblocks = NAND_BBT_SCAN_MAXBLOCKS, + .pattern = mirror_pattern +}; + +static struct nand_bbt_descr bbt_main_no_oob_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP + | NAND_BBT_NO_OOB, + .len = 4, + .veroffs = 4, + .maxblocks = NAND_BBT_SCAN_MAXBLOCKS, + .pattern = bbt_pattern +}; + +static struct nand_bbt_descr bbt_mirror_no_oob_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP + | NAND_BBT_NO_OOB, + .len = 4, + .veroffs = 4, + .maxblocks = NAND_BBT_SCAN_MAXBLOCKS, .pattern = mirror_pattern }; +#define BADBLOCK_SCAN_MASK (~NAND_BBT_NO_OOB) /** - * nand_default_bbt - [NAND Interface] Select a default bad block table for the device - * @mtd: MTD device structure + * nand_create_badblock_pattern - [INTERN] Creates a BBT descriptor structure + * @this: NAND chip to create descriptor for * - * This function selects the default bad block table - * support for the device and calls the nand_scan_bbt function + * This function allocates and initializes a nand_bbt_descr for BBM detection + * based on the properties of @this. The new descriptor is stored in + * this->badblock_pattern. Thus, this->badblock_pattern should be NULL when + * passed to this function. + */ +static int nand_create_badblock_pattern(struct nand_chip *this) +{ + struct nand_bbt_descr *bd; + if (this->badblock_pattern) { + pr_warn("Bad block pattern already allocated; not replacing\n"); + return -EINVAL; + } + bd = kzalloc(sizeof(*bd), GFP_KERNEL); + if (!bd) + return -ENOMEM; + bd->options = this->bbt_options & BADBLOCK_SCAN_MASK; + bd->offs = this->badblockpos; + bd->len = (this->options & NAND_BUSWIDTH_16) ? 2 : 1; + bd->pattern = scan_ff_pattern; + bd->options |= NAND_BBT_DYNAMICSTRUCT; + this->badblock_pattern = bd; + return 0; +} + +/** + * nand_default_bbt - [NAND Interface] Select a default bad block table for the device + * @mtd: MTD device structure * -*/ + * This function selects the default bad block table support for the device and + * calls the nand_scan_bbt function. + */ int nand_default_bbt(struct mtd_info *mtd) { struct nand_chip *this = mtd->priv; + int ret; - /* Default for AG-AND. We must use a flash based - * bad block table as the devices have factory marked - * _good_ blocks. Erasing those blocks leads to loss - * of the good / bad information, so we _must_ store - * this information in a good / bad table during - * startup - */ - if (this->options & NAND_IS_AND) { - /* Use the default pattern descriptors */ - if (!this->bbt_td) { - this->bbt_td = &bbt_main_descr; - this->bbt_md = &bbt_mirror_descr; - } - this->options |= NAND_USE_FLASH_BBT; - return nand_scan_bbt(mtd, &agand_flashbased); - } - - /* Is a flash based bad block table requested ? */ - if (this->options & NAND_USE_FLASH_BBT) { + /* Is a flash based bad block table requested? */ + if (this->bbt_options & NAND_BBT_USE_FLASH) { /* Use the default pattern descriptors */ if (!this->bbt_td) { - this->bbt_td = &bbt_main_descr; - this->bbt_md = &bbt_mirror_descr; - } - if (!this->badblock_pattern) { - this->badblock_pattern = (mtd->writesize > 512) ? &largepage_flashbased : &smallpage_flashbased; + if (this->bbt_options & NAND_BBT_NO_OOB) { + this->bbt_td = &bbt_main_no_oob_descr; + this->bbt_md = &bbt_mirror_no_oob_descr; + } else { + this->bbt_td = &bbt_main_descr; + this->bbt_md = &bbt_mirror_descr; + } } } else { this->bbt_td = NULL; this->bbt_md = NULL; - if (!this->badblock_pattern) { - this->badblock_pattern = (mtd->writesize > 512) ? - &largepage_memorybased : &smallpage_memorybased; - } } + + if (!this->badblock_pattern) { + ret = nand_create_badblock_pattern(this); + if (ret) + return ret; + } + return nand_scan_bbt(mtd, this->badblock_pattern); } /** * nand_isbad_bbt - [NAND Interface] Check if a block is bad - * @mtd: MTD device structure - * @offs: offset in the device - * @allowbbt: allow access to bad block table region - * -*/ + * @mtd: MTD device structure + * @offs: offset in the device + * @allowbbt: allow access to bad block table region + */ int nand_isbad_bbt(struct mtd_info *mtd, loff_t offs, int allowbbt) { struct nand_chip *this = mtd->priv; - int block; - uint8_t res; + int block, res; - /* Get block number * 2 */ - block = (int)(offs >> (this->bbt_erase_shift - 1)); - res = (this->bbt[block >> 3] >> (block & 0x06)) & 0x03; + block = (int)(offs >> this->bbt_erase_shift); + res = bbt_get_entry(this, block); - DEBUG(MTD_DEBUG_LEVEL2, "nand_isbad_bbt(): bbt info for offs 0x%08x: (block %d) 0x%02x\n", - (unsigned int)offs, block >> 1, res); + pr_debug("nand_isbad_bbt(): bbt info for offs 0x%08x: " + "(block %d) 0x%02x\n", + (unsigned int)offs, block, res); - switch ((int)res) { - case 0x00: + switch (res) { + case BBT_BLOCK_GOOD: return 0; - case 0x01: + case BBT_BLOCK_WORN: return 1; - case 0x02: + case BBT_BLOCK_RESERVED: return allowbbt ? 0 : 1; } return 1; } +/** + * nand_markbad_bbt - [NAND Interface] Mark a block bad in the BBT + * @mtd: MTD device structure + * @offs: offset of the bad block + */ +int nand_markbad_bbt(struct mtd_info *mtd, loff_t offs) +{ + struct nand_chip *this = mtd->priv; + int block, ret = 0; + + block = (int)(offs >> this->bbt_erase_shift); + + /* Mark bad block in memory */ + bbt_mark_entry(this, block, BBT_BLOCK_WORN); + + /* Update flash-based bad block table */ + if (this->bbt_options & NAND_BBT_USE_FLASH) + ret = nand_update_bbt(mtd, offs); + + return ret; +} + EXPORT_SYMBOL(nand_scan_bbt); -EXPORT_SYMBOL(nand_default_bbt); diff --git a/drivers/mtd/nand/nand_bch.c b/drivers/mtd/nand/nand_bch.c new file mode 100644 index 00000000000..3803e0bba23 --- /dev/null +++ b/drivers/mtd/nand/nand_bch.c @@ -0,0 +1,243 @@ +/* + * This file provides ECC correction for more than 1 bit per block of data, + * using binary BCH codes. It relies on the generic BCH library lib/bch.c. + * + * Copyright © 2011 Ivan Djelic <ivan.djelic@parrot.com> + * + * This file 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 or (at your option) any + * later version. + * + * This file 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 file; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/bitops.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/nand_bch.h> +#include <linux/bch.h> + +/** + * struct nand_bch_control - private NAND BCH control structure + * @bch: BCH control structure + * @ecclayout: private ecc layout for this BCH configuration + * @errloc: error location array + * @eccmask: XOR ecc mask, allows erased pages to be decoded as valid + */ +struct nand_bch_control { + struct bch_control *bch; + struct nand_ecclayout ecclayout; + unsigned int *errloc; + unsigned char *eccmask; +}; + +/** + * nand_bch_calculate_ecc - [NAND Interface] Calculate ECC for data block + * @mtd: MTD block structure + * @buf: input buffer with raw data + * @code: output buffer with ECC + */ +int nand_bch_calculate_ecc(struct mtd_info *mtd, const unsigned char *buf, + unsigned char *code) +{ + const struct nand_chip *chip = mtd->priv; + struct nand_bch_control *nbc = chip->ecc.priv; + unsigned int i; + + memset(code, 0, chip->ecc.bytes); + encode_bch(nbc->bch, buf, chip->ecc.size, code); + + /* apply mask so that an erased page is a valid codeword */ + for (i = 0; i < chip->ecc.bytes; i++) + code[i] ^= nbc->eccmask[i]; + + return 0; +} +EXPORT_SYMBOL(nand_bch_calculate_ecc); + +/** + * nand_bch_correct_data - [NAND Interface] Detect and correct bit error(s) + * @mtd: MTD block structure + * @buf: raw data read from the chip + * @read_ecc: ECC from the chip + * @calc_ecc: the ECC calculated from raw data + * + * Detect and correct bit errors for a data byte block + */ +int nand_bch_correct_data(struct mtd_info *mtd, unsigned char *buf, + unsigned char *read_ecc, unsigned char *calc_ecc) +{ + const struct nand_chip *chip = mtd->priv; + struct nand_bch_control *nbc = chip->ecc.priv; + unsigned int *errloc = nbc->errloc; + int i, count; + + count = decode_bch(nbc->bch, NULL, chip->ecc.size, read_ecc, calc_ecc, + NULL, errloc); + if (count > 0) { + for (i = 0; i < count; i++) { + if (errloc[i] < (chip->ecc.size*8)) + /* error is located in data, correct it */ + buf[errloc[i] >> 3] ^= (1 << (errloc[i] & 7)); + /* else error in ecc, no action needed */ + + pr_debug("%s: corrected bitflip %u\n", __func__, + errloc[i]); + } + } else if (count < 0) { + printk(KERN_ERR "ecc unrecoverable error\n"); + count = -1; + } + return count; +} +EXPORT_SYMBOL(nand_bch_correct_data); + +/** + * nand_bch_init - [NAND Interface] Initialize NAND BCH error correction + * @mtd: MTD block structure + * @eccsize: ecc block size in bytes + * @eccbytes: ecc length in bytes + * @ecclayout: output default layout + * + * Returns: + * a pointer to a new NAND BCH control structure, or NULL upon failure + * + * Initialize NAND BCH error correction. Parameters @eccsize and @eccbytes + * are used to compute BCH parameters m (Galois field order) and t (error + * correction capability). @eccbytes should be equal to the number of bytes + * required to store m*t bits, where m is such that 2^m-1 > @eccsize*8. + * + * Example: to configure 4 bit correction per 512 bytes, you should pass + * @eccsize = 512 (thus, m=13 is the smallest integer such that 2^m-1 > 512*8) + * @eccbytes = 7 (7 bytes are required to store m*t = 13*4 = 52 bits) + */ +struct nand_bch_control * +nand_bch_init(struct mtd_info *mtd, unsigned int eccsize, unsigned int eccbytes, + struct nand_ecclayout **ecclayout) +{ + unsigned int m, t, eccsteps, i; + struct nand_ecclayout *layout; + struct nand_bch_control *nbc = NULL; + unsigned char *erased_page; + + if (!eccsize || !eccbytes) { + printk(KERN_WARNING "ecc parameters not supplied\n"); + goto fail; + } + + m = fls(1+8*eccsize); + t = (eccbytes*8)/m; + + nbc = kzalloc(sizeof(*nbc), GFP_KERNEL); + if (!nbc) + goto fail; + + nbc->bch = init_bch(m, t, 0); + if (!nbc->bch) + goto fail; + + /* verify that eccbytes has the expected value */ + if (nbc->bch->ecc_bytes != eccbytes) { + printk(KERN_WARNING "invalid eccbytes %u, should be %u\n", + eccbytes, nbc->bch->ecc_bytes); + goto fail; + } + + eccsteps = mtd->writesize/eccsize; + + /* if no ecc placement scheme was provided, build one */ + if (!*ecclayout) { + + /* handle large page devices only */ + if (mtd->oobsize < 64) { + printk(KERN_WARNING "must provide an oob scheme for " + "oobsize %d\n", mtd->oobsize); + goto fail; + } + + layout = &nbc->ecclayout; + layout->eccbytes = eccsteps*eccbytes; + + /* reserve 2 bytes for bad block marker */ + if (layout->eccbytes+2 > mtd->oobsize) { + printk(KERN_WARNING "no suitable oob scheme available " + "for oobsize %d eccbytes %u\n", mtd->oobsize, + eccbytes); + goto fail; + } + /* put ecc bytes at oob tail */ + for (i = 0; i < layout->eccbytes; i++) + layout->eccpos[i] = mtd->oobsize-layout->eccbytes+i; + + layout->oobfree[0].offset = 2; + layout->oobfree[0].length = mtd->oobsize-2-layout->eccbytes; + + *ecclayout = layout; + } + + /* sanity checks */ + if (8*(eccsize+eccbytes) >= (1 << m)) { + printk(KERN_WARNING "eccsize %u is too large\n", eccsize); + goto fail; + } + if ((*ecclayout)->eccbytes != (eccsteps*eccbytes)) { + printk(KERN_WARNING "invalid ecc layout\n"); + goto fail; + } + + nbc->eccmask = kmalloc(eccbytes, GFP_KERNEL); + nbc->errloc = kmalloc(t*sizeof(*nbc->errloc), GFP_KERNEL); + if (!nbc->eccmask || !nbc->errloc) + goto fail; + /* + * compute and store the inverted ecc of an erased ecc block + */ + erased_page = kmalloc(eccsize, GFP_KERNEL); + if (!erased_page) + goto fail; + + memset(erased_page, 0xff, eccsize); + memset(nbc->eccmask, 0, eccbytes); + encode_bch(nbc->bch, erased_page, eccsize, nbc->eccmask); + kfree(erased_page); + + for (i = 0; i < eccbytes; i++) + nbc->eccmask[i] ^= 0xff; + + return nbc; +fail: + nand_bch_free(nbc); + return NULL; +} +EXPORT_SYMBOL(nand_bch_init); + +/** + * nand_bch_free - [NAND Interface] Release NAND BCH ECC resources + * @nbc: NAND BCH control structure + */ +void nand_bch_free(struct nand_bch_control *nbc) +{ + if (nbc) { + free_bch(nbc->bch); + kfree(nbc->errloc); + kfree(nbc->eccmask); + kfree(nbc); + } +} +EXPORT_SYMBOL(nand_bch_free); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ivan Djelic <ivan.djelic@parrot.com>"); +MODULE_DESCRIPTION("NAND software BCH ECC support"); diff --git a/drivers/mtd/nand/nand_ecc.c b/drivers/mtd/nand/nand_ecc.c index dd438ca47d9..97c4c0216c9 100644 --- a/drivers/mtd/nand/nand_ecc.c +++ b/drivers/mtd/nand/nand_ecc.c @@ -1,15 +1,18 @@ /* - * This file contains an ECC algorithm from Toshiba that detects and - * corrects 1 bit errors in a 256 byte block of data. + * This file contains an ECC algorithm that detects and corrects 1 bit + * errors in a 256 byte block of data. * * drivers/mtd/nand/nand_ecc.c * - * Copyright (C) 2000-2004 Steven J. Hill (sjhill@realitydiluted.com) - * Toshiba America Electronics Components, Inc. + * Copyright © 2008 Koninklijke Philips Electronics NV. + * Author: Frans Meulenbroeks * - * Copyright (C) 2006 Thomas Gleixner <tglx@linutronix.de> + * Completely replaces the previous ECC implementation which was written by: + * Steven J. Hill (sjhill@realitydiluted.com) + * Thomas Gleixner (tglx@linutronix.de) * - * $Id: nand_ecc.c,v 1.15 2005/11/07 11:14:30 gleixner Exp $ + * Information on how this algorithm works and how it was developed + * can be found in Documentation/mtd/nand_ecc.txt * * This file is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -25,174 +28,506 @@ * with this file; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * - * As a special exception, if other files instantiate templates or use - * macros or inline functions from these files, or you compile these - * files and link them with other works to produce a work based on these - * files, these files do not by themselves cause the resulting work to be - * covered by the GNU General Public License. However the source code for - * these files must still be made available in accordance with section (3) - * of the GNU General Public License. - * - * This exception does not invalidate any other reasons why a work based on - * this file might be covered by the GNU General Public License. */ +/* + * The STANDALONE macro is useful when running the code outside the kernel + * e.g. when running the code in a testbed or a benchmark program. + * When STANDALONE is used, the module related macros are commented out + * as well as the linux include files. + * Instead a private definition of mtd_info is given to satisfy the compiler + * (the code does not use mtd_info, so the code does not care) + */ +#ifndef STANDALONE #include <linux/types.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> #include <linux/mtd/nand_ecc.h> +#include <asm/byteorder.h> +#else +#include <stdint.h> +struct mtd_info; +#define EXPORT_SYMBOL(x) /* x */ + +#define MODULE_LICENSE(x) /* x */ +#define MODULE_AUTHOR(x) /* x */ +#define MODULE_DESCRIPTION(x) /* x */ + +#define pr_err printf +#endif /* - * Pre-calculated 256-way 1 byte column parity + * invparity is a 256 byte table that contains the odd parity + * for each byte. So if the number of bits in a byte is even, + * the array element is 1, and when the number of bits is odd + * the array eleemnt is 0. */ -static const u_char nand_ecc_precalc_table[] = { - 0x00, 0x55, 0x56, 0x03, 0x59, 0x0c, 0x0f, 0x5a, 0x5a, 0x0f, 0x0c, 0x59, 0x03, 0x56, 0x55, 0x00, - 0x65, 0x30, 0x33, 0x66, 0x3c, 0x69, 0x6a, 0x3f, 0x3f, 0x6a, 0x69, 0x3c, 0x66, 0x33, 0x30, 0x65, - 0x66, 0x33, 0x30, 0x65, 0x3f, 0x6a, 0x69, 0x3c, 0x3c, 0x69, 0x6a, 0x3f, 0x65, 0x30, 0x33, 0x66, - 0x03, 0x56, 0x55, 0x00, 0x5a, 0x0f, 0x0c, 0x59, 0x59, 0x0c, 0x0f, 0x5a, 0x00, 0x55, 0x56, 0x03, - 0x69, 0x3c, 0x3f, 0x6a, 0x30, 0x65, 0x66, 0x33, 0x33, 0x66, 0x65, 0x30, 0x6a, 0x3f, 0x3c, 0x69, - 0x0c, 0x59, 0x5a, 0x0f, 0x55, 0x00, 0x03, 0x56, 0x56, 0x03, 0x00, 0x55, 0x0f, 0x5a, 0x59, 0x0c, - 0x0f, 0x5a, 0x59, 0x0c, 0x56, 0x03, 0x00, 0x55, 0x55, 0x00, 0x03, 0x56, 0x0c, 0x59, 0x5a, 0x0f, - 0x6a, 0x3f, 0x3c, 0x69, 0x33, 0x66, 0x65, 0x30, 0x30, 0x65, 0x66, 0x33, 0x69, 0x3c, 0x3f, 0x6a, - 0x6a, 0x3f, 0x3c, 0x69, 0x33, 0x66, 0x65, 0x30, 0x30, 0x65, 0x66, 0x33, 0x69, 0x3c, 0x3f, 0x6a, - 0x0f, 0x5a, 0x59, 0x0c, 0x56, 0x03, 0x00, 0x55, 0x55, 0x00, 0x03, 0x56, 0x0c, 0x59, 0x5a, 0x0f, - 0x0c, 0x59, 0x5a, 0x0f, 0x55, 0x00, 0x03, 0x56, 0x56, 0x03, 0x00, 0x55, 0x0f, 0x5a, 0x59, 0x0c, - 0x69, 0x3c, 0x3f, 0x6a, 0x30, 0x65, 0x66, 0x33, 0x33, 0x66, 0x65, 0x30, 0x6a, 0x3f, 0x3c, 0x69, - 0x03, 0x56, 0x55, 0x00, 0x5a, 0x0f, 0x0c, 0x59, 0x59, 0x0c, 0x0f, 0x5a, 0x00, 0x55, 0x56, 0x03, - 0x66, 0x33, 0x30, 0x65, 0x3f, 0x6a, 0x69, 0x3c, 0x3c, 0x69, 0x6a, 0x3f, 0x65, 0x30, 0x33, 0x66, - 0x65, 0x30, 0x33, 0x66, 0x3c, 0x69, 0x6a, 0x3f, 0x3f, 0x6a, 0x69, 0x3c, 0x66, 0x33, 0x30, 0x65, - 0x00, 0x55, 0x56, 0x03, 0x59, 0x0c, 0x0f, 0x5a, 0x5a, 0x0f, 0x0c, 0x59, 0x03, 0x56, 0x55, 0x00 +static const char invparity[256] = { + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1 +}; + +/* + * bitsperbyte contains the number of bits per byte + * this is only used for testing and repairing parity + * (a precalculated value slightly improves performance) + */ +static const char bitsperbyte[256] = { + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, +}; + +/* + * addressbits is a lookup table to filter out the bits from the xor-ed + * ECC data that identify the faulty location. + * this is only used for repairing parity + * see the comments in nand_correct_data for more details + */ +static const char addressbits[256] = { + 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, + 0x02, 0x02, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, + 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, + 0x02, 0x02, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, + 0x04, 0x04, 0x05, 0x05, 0x04, 0x04, 0x05, 0x05, + 0x06, 0x06, 0x07, 0x07, 0x06, 0x06, 0x07, 0x07, + 0x04, 0x04, 0x05, 0x05, 0x04, 0x04, 0x05, 0x05, + 0x06, 0x06, 0x07, 0x07, 0x06, 0x06, 0x07, 0x07, + 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, + 0x02, 0x02, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, + 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, + 0x02, 0x02, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, + 0x04, 0x04, 0x05, 0x05, 0x04, 0x04, 0x05, 0x05, + 0x06, 0x06, 0x07, 0x07, 0x06, 0x06, 0x07, 0x07, + 0x04, 0x04, 0x05, 0x05, 0x04, 0x04, 0x05, 0x05, + 0x06, 0x06, 0x07, 0x07, 0x06, 0x06, 0x07, 0x07, + 0x08, 0x08, 0x09, 0x09, 0x08, 0x08, 0x09, 0x09, + 0x0a, 0x0a, 0x0b, 0x0b, 0x0a, 0x0a, 0x0b, 0x0b, + 0x08, 0x08, 0x09, 0x09, 0x08, 0x08, 0x09, 0x09, + 0x0a, 0x0a, 0x0b, 0x0b, 0x0a, 0x0a, 0x0b, 0x0b, + 0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0d, 0x0d, + 0x0e, 0x0e, 0x0f, 0x0f, 0x0e, 0x0e, 0x0f, 0x0f, + 0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0d, 0x0d, + 0x0e, 0x0e, 0x0f, 0x0f, 0x0e, 0x0e, 0x0f, 0x0f, + 0x08, 0x08, 0x09, 0x09, 0x08, 0x08, 0x09, 0x09, + 0x0a, 0x0a, 0x0b, 0x0b, 0x0a, 0x0a, 0x0b, 0x0b, + 0x08, 0x08, 0x09, 0x09, 0x08, 0x08, 0x09, 0x09, + 0x0a, 0x0a, 0x0b, 0x0b, 0x0a, 0x0a, 0x0b, 0x0b, + 0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0d, 0x0d, + 0x0e, 0x0e, 0x0f, 0x0f, 0x0e, 0x0e, 0x0f, 0x0f, + 0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0d, 0x0d, + 0x0e, 0x0e, 0x0f, 0x0f, 0x0e, 0x0e, 0x0f, 0x0f }; /** - * nand_calculate_ecc - [NAND Interface] Calculate 3-byte ECC for 256-byte block - * @mtd: MTD block structure - * @dat: raw data - * @ecc_code: buffer for ECC + * __nand_calculate_ecc - [NAND Interface] Calculate 3-byte ECC for 256/512-byte + * block + * @buf: input buffer with raw data + * @eccsize: data bytes per ECC step (256 or 512) + * @code: output buffer with ECC */ -int nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, - u_char *ecc_code) +void __nand_calculate_ecc(const unsigned char *buf, unsigned int eccsize, + unsigned char *code) { - uint8_t idx, reg1, reg2, reg3, tmp1, tmp2; int i; + const uint32_t *bp = (uint32_t *)buf; + /* 256 or 512 bytes/ecc */ + const uint32_t eccsize_mult = eccsize >> 8; + uint32_t cur; /* current value in buffer */ + /* rp0..rp15..rp17 are the various accumulated parities (per byte) */ + uint32_t rp0, rp1, rp2, rp3, rp4, rp5, rp6, rp7; + uint32_t rp8, rp9, rp10, rp11, rp12, rp13, rp14, rp15, rp16; + uint32_t uninitialized_var(rp17); /* to make compiler happy */ + uint32_t par; /* the cumulative parity for all data */ + uint32_t tmppar; /* the cumulative parity for this iteration; + for rp12, rp14 and rp16 at the end of the + loop */ + + par = 0; + rp4 = 0; + rp6 = 0; + rp8 = 0; + rp10 = 0; + rp12 = 0; + rp14 = 0; + rp16 = 0; + + /* + * The loop is unrolled a number of times; + * This avoids if statements to decide on which rp value to update + * Also we process the data by longwords. + * Note: passing unaligned data might give a performance penalty. + * It is assumed that the buffers are aligned. + * tmppar is the cumulative sum of this iteration. + * needed for calculating rp12, rp14, rp16 and par + * also used as a performance improvement for rp6, rp8 and rp10 + */ + for (i = 0; i < eccsize_mult << 2; i++) { + cur = *bp++; + tmppar = cur; + rp4 ^= cur; + cur = *bp++; + tmppar ^= cur; + rp6 ^= tmppar; + cur = *bp++; + tmppar ^= cur; + rp4 ^= cur; + cur = *bp++; + tmppar ^= cur; + rp8 ^= tmppar; - /* Initialize variables */ - reg1 = reg2 = reg3 = 0; + cur = *bp++; + tmppar ^= cur; + rp4 ^= cur; + rp6 ^= cur; + cur = *bp++; + tmppar ^= cur; + rp6 ^= cur; + cur = *bp++; + tmppar ^= cur; + rp4 ^= cur; + cur = *bp++; + tmppar ^= cur; + rp10 ^= tmppar; - /* Build up column parity */ - for(i = 0; i < 256; i++) { - /* Get CP0 - CP5 from table */ - idx = nand_ecc_precalc_table[*dat++]; - reg1 ^= (idx & 0x3f); + cur = *bp++; + tmppar ^= cur; + rp4 ^= cur; + rp6 ^= cur; + rp8 ^= cur; + cur = *bp++; + tmppar ^= cur; + rp6 ^= cur; + rp8 ^= cur; + cur = *bp++; + tmppar ^= cur; + rp4 ^= cur; + rp8 ^= cur; + cur = *bp++; + tmppar ^= cur; + rp8 ^= cur; + + cur = *bp++; + tmppar ^= cur; + rp4 ^= cur; + rp6 ^= cur; + cur = *bp++; + tmppar ^= cur; + rp6 ^= cur; + cur = *bp++; + tmppar ^= cur; + rp4 ^= cur; + cur = *bp++; + tmppar ^= cur; + + par ^= tmppar; + if ((i & 0x1) == 0) + rp12 ^= tmppar; + if ((i & 0x2) == 0) + rp14 ^= tmppar; + if (eccsize_mult == 2 && (i & 0x4) == 0) + rp16 ^= tmppar; + } - /* All bit XOR = 1 ? */ - if (idx & 0x40) { - reg3 ^= (uint8_t) i; - reg2 ^= ~((uint8_t) i); - } + /* + * handle the fact that we use longword operations + * we'll bring rp4..rp14..rp16 back to single byte entities by + * shifting and xoring first fold the upper and lower 16 bits, + * then the upper and lower 8 bits. + */ + rp4 ^= (rp4 >> 16); + rp4 ^= (rp4 >> 8); + rp4 &= 0xff; + rp6 ^= (rp6 >> 16); + rp6 ^= (rp6 >> 8); + rp6 &= 0xff; + rp8 ^= (rp8 >> 16); + rp8 ^= (rp8 >> 8); + rp8 &= 0xff; + rp10 ^= (rp10 >> 16); + rp10 ^= (rp10 >> 8); + rp10 &= 0xff; + rp12 ^= (rp12 >> 16); + rp12 ^= (rp12 >> 8); + rp12 &= 0xff; + rp14 ^= (rp14 >> 16); + rp14 ^= (rp14 >> 8); + rp14 &= 0xff; + if (eccsize_mult == 2) { + rp16 ^= (rp16 >> 16); + rp16 ^= (rp16 >> 8); + rp16 &= 0xff; } - /* Create non-inverted ECC code from line parity */ - tmp1 = (reg3 & 0x80) >> 0; /* B7 -> B7 */ - tmp1 |= (reg2 & 0x80) >> 1; /* B7 -> B6 */ - tmp1 |= (reg3 & 0x40) >> 1; /* B6 -> B5 */ - tmp1 |= (reg2 & 0x40) >> 2; /* B6 -> B4 */ - tmp1 |= (reg3 & 0x20) >> 2; /* B5 -> B3 */ - tmp1 |= (reg2 & 0x20) >> 3; /* B5 -> B2 */ - tmp1 |= (reg3 & 0x10) >> 3; /* B4 -> B1 */ - tmp1 |= (reg2 & 0x10) >> 4; /* B4 -> B0 */ - - tmp2 = (reg3 & 0x08) << 4; /* B3 -> B7 */ - tmp2 |= (reg2 & 0x08) << 3; /* B3 -> B6 */ - tmp2 |= (reg3 & 0x04) << 3; /* B2 -> B5 */ - tmp2 |= (reg2 & 0x04) << 2; /* B2 -> B4 */ - tmp2 |= (reg3 & 0x02) << 2; /* B1 -> B3 */ - tmp2 |= (reg2 & 0x02) << 1; /* B1 -> B2 */ - tmp2 |= (reg3 & 0x01) << 1; /* B0 -> B1 */ - tmp2 |= (reg2 & 0x01) << 0; /* B7 -> B0 */ - - /* Calculate final ECC code */ -#ifdef CONFIG_NAND_ECC_SMC - ecc_code[0] = ~tmp2; - ecc_code[1] = ~tmp1; + /* + * we also need to calculate the row parity for rp0..rp3 + * This is present in par, because par is now + * rp3 rp3 rp2 rp2 in little endian and + * rp2 rp2 rp3 rp3 in big endian + * as well as + * rp1 rp0 rp1 rp0 in little endian and + * rp0 rp1 rp0 rp1 in big endian + * First calculate rp2 and rp3 + */ +#ifdef __BIG_ENDIAN + rp2 = (par >> 16); + rp2 ^= (rp2 >> 8); + rp2 &= 0xff; + rp3 = par & 0xffff; + rp3 ^= (rp3 >> 8); + rp3 &= 0xff; #else - ecc_code[0] = ~tmp1; - ecc_code[1] = ~tmp2; + rp3 = (par >> 16); + rp3 ^= (rp3 >> 8); + rp3 &= 0xff; + rp2 = par & 0xffff; + rp2 ^= (rp2 >> 8); + rp2 &= 0xff; #endif - ecc_code[2] = ((~reg1) << 2) | 0x03; - return 0; + /* reduce par to 16 bits then calculate rp1 and rp0 */ + par ^= (par >> 16); +#ifdef __BIG_ENDIAN + rp0 = (par >> 8) & 0xff; + rp1 = (par & 0xff); +#else + rp1 = (par >> 8) & 0xff; + rp0 = (par & 0xff); +#endif + + /* finally reduce par to 8 bits */ + par ^= (par >> 8); + par &= 0xff; + + /* + * and calculate rp5..rp15..rp17 + * note that par = rp4 ^ rp5 and due to the commutative property + * of the ^ operator we can say: + * rp5 = (par ^ rp4); + * The & 0xff seems superfluous, but benchmarking learned that + * leaving it out gives slightly worse results. No idea why, probably + * it has to do with the way the pipeline in pentium is organized. + */ + rp5 = (par ^ rp4) & 0xff; + rp7 = (par ^ rp6) & 0xff; + rp9 = (par ^ rp8) & 0xff; + rp11 = (par ^ rp10) & 0xff; + rp13 = (par ^ rp12) & 0xff; + rp15 = (par ^ rp14) & 0xff; + if (eccsize_mult == 2) + rp17 = (par ^ rp16) & 0xff; + + /* + * Finally calculate the ECC bits. + * Again here it might seem that there are performance optimisations + * possible, but benchmarks showed that on the system this is developed + * the code below is the fastest + */ +#ifdef CONFIG_MTD_NAND_ECC_SMC + code[0] = + (invparity[rp7] << 7) | + (invparity[rp6] << 6) | + (invparity[rp5] << 5) | + (invparity[rp4] << 4) | + (invparity[rp3] << 3) | + (invparity[rp2] << 2) | + (invparity[rp1] << 1) | + (invparity[rp0]); + code[1] = + (invparity[rp15] << 7) | + (invparity[rp14] << 6) | + (invparity[rp13] << 5) | + (invparity[rp12] << 4) | + (invparity[rp11] << 3) | + (invparity[rp10] << 2) | + (invparity[rp9] << 1) | + (invparity[rp8]); +#else + code[1] = + (invparity[rp7] << 7) | + (invparity[rp6] << 6) | + (invparity[rp5] << 5) | + (invparity[rp4] << 4) | + (invparity[rp3] << 3) | + (invparity[rp2] << 2) | + (invparity[rp1] << 1) | + (invparity[rp0]); + code[0] = + (invparity[rp15] << 7) | + (invparity[rp14] << 6) | + (invparity[rp13] << 5) | + (invparity[rp12] << 4) | + (invparity[rp11] << 3) | + (invparity[rp10] << 2) | + (invparity[rp9] << 1) | + (invparity[rp8]); +#endif + if (eccsize_mult == 1) + code[2] = + (invparity[par & 0xf0] << 7) | + (invparity[par & 0x0f] << 6) | + (invparity[par & 0xcc] << 5) | + (invparity[par & 0x33] << 4) | + (invparity[par & 0xaa] << 3) | + (invparity[par & 0x55] << 2) | + 3; + else + code[2] = + (invparity[par & 0xf0] << 7) | + (invparity[par & 0x0f] << 6) | + (invparity[par & 0xcc] << 5) | + (invparity[par & 0x33] << 4) | + (invparity[par & 0xaa] << 3) | + (invparity[par & 0x55] << 2) | + (invparity[rp17] << 1) | + (invparity[rp16] << 0); } -EXPORT_SYMBOL(nand_calculate_ecc); +EXPORT_SYMBOL(__nand_calculate_ecc); -static inline int countbits(uint32_t byte) +/** + * nand_calculate_ecc - [NAND Interface] Calculate 3-byte ECC for 256/512-byte + * block + * @mtd: MTD block structure + * @buf: input buffer with raw data + * @code: output buffer with ECC + */ +int nand_calculate_ecc(struct mtd_info *mtd, const unsigned char *buf, + unsigned char *code) { - int res = 0; + __nand_calculate_ecc(buf, + ((struct nand_chip *)mtd->priv)->ecc.size, code); - for (;byte; byte >>= 1) - res += byte & 0x01; - return res; + return 0; } +EXPORT_SYMBOL(nand_calculate_ecc); /** - * nand_correct_data - [NAND Interface] Detect and correct bit error(s) - * @mtd: MTD block structure - * @dat: raw data read from the chip + * __nand_correct_data - [NAND Interface] Detect and correct bit error(s) + * @buf: raw data read from the chip * @read_ecc: ECC from the chip * @calc_ecc: the ECC calculated from raw data + * @eccsize: data bytes per ECC step (256 or 512) * - * Detect and correct a 1 bit error for 256 byte block + * Detect and correct a 1 bit error for eccsize byte block */ -int nand_correct_data(struct mtd_info *mtd, u_char *dat, - u_char *read_ecc, u_char *calc_ecc) +int __nand_correct_data(unsigned char *buf, + unsigned char *read_ecc, unsigned char *calc_ecc, + unsigned int eccsize) { - uint8_t s0, s1, s2; + unsigned char b0, b1, b2, bit_addr; + unsigned int byte_addr; + /* 256 or 512 bytes/ecc */ + const uint32_t eccsize_mult = eccsize >> 8; -#ifdef CONFIG_NAND_ECC_SMC - s0 = calc_ecc[0] ^ read_ecc[0]; - s1 = calc_ecc[1] ^ read_ecc[1]; - s2 = calc_ecc[2] ^ read_ecc[2]; + /* + * b0 to b2 indicate which bit is faulty (if any) + * we might need the xor result more than once, + * so keep them in a local var + */ +#ifdef CONFIG_MTD_NAND_ECC_SMC + b0 = read_ecc[0] ^ calc_ecc[0]; + b1 = read_ecc[1] ^ calc_ecc[1]; #else - s1 = calc_ecc[0] ^ read_ecc[0]; - s0 = calc_ecc[1] ^ read_ecc[1]; - s2 = calc_ecc[2] ^ read_ecc[2]; + b0 = read_ecc[1] ^ calc_ecc[1]; + b1 = read_ecc[0] ^ calc_ecc[0]; #endif - if ((s0 | s1 | s2) == 0) - return 0; - - /* Check for a single bit error */ - if( ((s0 ^ (s0 >> 1)) & 0x55) == 0x55 && - ((s1 ^ (s1 >> 1)) & 0x55) == 0x55 && - ((s2 ^ (s2 >> 1)) & 0x54) == 0x54) { - - uint32_t byteoffs, bitnum; - - byteoffs = (s1 << 0) & 0x80; - byteoffs |= (s1 << 1) & 0x40; - byteoffs |= (s1 << 2) & 0x20; - byteoffs |= (s1 << 3) & 0x10; + b2 = read_ecc[2] ^ calc_ecc[2]; - byteoffs |= (s0 >> 4) & 0x08; - byteoffs |= (s0 >> 3) & 0x04; - byteoffs |= (s0 >> 2) & 0x02; - byteoffs |= (s0 >> 1) & 0x01; + /* check if there are any bitfaults */ - bitnum = (s2 >> 5) & 0x04; - bitnum |= (s2 >> 4) & 0x02; - bitnum |= (s2 >> 3) & 0x01; + /* repeated if statements are slightly more efficient than switch ... */ + /* ordered in order of likelihood */ - dat[byteoffs] ^= (1 << bitnum); + if ((b0 | b1 | b2) == 0) + return 0; /* no error */ + if ((((b0 ^ (b0 >> 1)) & 0x55) == 0x55) && + (((b1 ^ (b1 >> 1)) & 0x55) == 0x55) && + ((eccsize_mult == 1 && ((b2 ^ (b2 >> 1)) & 0x54) == 0x54) || + (eccsize_mult == 2 && ((b2 ^ (b2 >> 1)) & 0x55) == 0x55))) { + /* single bit error */ + /* + * rp17/rp15/13/11/9/7/5/3/1 indicate which byte is the faulty + * byte, cp 5/3/1 indicate the faulty bit. + * A lookup table (called addressbits) is used to filter + * the bits from the byte they are in. + * A marginal optimisation is possible by having three + * different lookup tables. + * One as we have now (for b0), one for b2 + * (that would avoid the >> 1), and one for b1 (with all values + * << 4). However it was felt that introducing two more tables + * hardly justify the gain. + * + * The b2 shift is there to get rid of the lowest two bits. + * We could also do addressbits[b2] >> 1 but for the + * performance it does not make any difference + */ + if (eccsize_mult == 1) + byte_addr = (addressbits[b1] << 4) + addressbits[b0]; + else + byte_addr = (addressbits[b2 & 0x3] << 8) + + (addressbits[b1] << 4) + addressbits[b0]; + bit_addr = addressbits[b2 >> 2]; + /* flip the bit */ + buf[byte_addr] ^= (1 << bit_addr); return 1; - } - if(countbits(s0 | ((uint32_t)s1 << 8) | ((uint32_t)s2 <<16)) == 1) - return 1; + } + /* count nr of bits; use table lookup, faster than calculating it */ + if ((bitsperbyte[b0] + bitsperbyte[b1] + bitsperbyte[b2]) == 1) + return 1; /* error in ECC data; no action needed */ + pr_err("%s: uncorrectable ECC error\n", __func__); return -1; } +EXPORT_SYMBOL(__nand_correct_data); + +/** + * nand_correct_data - [NAND Interface] Detect and correct bit error(s) + * @mtd: MTD block structure + * @buf: raw data read from the chip + * @read_ecc: ECC from the chip + * @calc_ecc: the ECC calculated from raw data + * + * Detect and correct a 1 bit error for 256/512 byte block + */ +int nand_correct_data(struct mtd_info *mtd, unsigned char *buf, + unsigned char *read_ecc, unsigned char *calc_ecc) +{ + return __nand_correct_data(buf, read_ecc, calc_ecc, + ((struct nand_chip *)mtd->priv)->ecc.size); +} EXPORT_SYMBOL(nand_correct_data); MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Steven J. Hill <sjhill@realitydiluted.com>"); +MODULE_AUTHOR("Frans Meulenbroeks <fransmeulenbroeks@gmail.com>"); MODULE_DESCRIPTION("Generic NAND ECC support"); diff --git a/drivers/mtd/nand/nand_ids.c b/drivers/mtd/nand/nand_ids.c index 2e2cdf2fc91..3d7c89fc103 100644 --- a/drivers/mtd/nand/nand_ids.c +++ b/drivers/mtd/nand/nand_ids.c @@ -3,8 +3,6 @@ * * Copyright (C) 2002 Thomas Gleixner (tglx@linutronix.de) * - * $Id: nand_ids.c,v 1.16 2005/11/07 11:14:31 gleixner Exp $ - * * 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. @@ -12,123 +10,156 @@ */ #include <linux/module.h> #include <linux/mtd/nand.h> +#include <linux/sizes.h> + +#define LP_OPTIONS NAND_SAMSUNG_LP_OPTIONS +#define LP_OPTIONS16 (LP_OPTIONS | NAND_BUSWIDTH_16) + +#define SP_OPTIONS NAND_NEED_READRDY +#define SP_OPTIONS16 (SP_OPTIONS | NAND_BUSWIDTH_16) + /* -* Chip ID list -* -* Name. ID code, pagesize, chipsize in MegaByte, eraseblock size, -* options -* -* Pagesize; 0, 256, 512 -* 0 get this information from the extended chip ID -+ 256 256 Byte page size -* 512 512 Byte page size -*/ + * The chip ID list: + * name, device ID, page size, chip size in MiB, eraseblock size, options + * + * If page size and eraseblock size are 0, the sizes are taken from the + * extended chip ID. + */ struct nand_flash_dev nand_flash_ids[] = { - {"NAND 1MiB 5V 8-bit", 0x6e, 256, 1, 0x1000, 0}, - {"NAND 2MiB 5V 8-bit", 0x64, 256, 2, 0x1000, 0}, - {"NAND 4MiB 5V 8-bit", 0x6b, 512, 4, 0x2000, 0}, - {"NAND 1MiB 3,3V 8-bit", 0xe8, 256, 1, 0x1000, 0}, - {"NAND 1MiB 3,3V 8-bit", 0xec, 256, 1, 0x1000, 0}, - {"NAND 2MiB 3,3V 8-bit", 0xea, 256, 2, 0x1000, 0}, - {"NAND 4MiB 3,3V 8-bit", 0xd5, 512, 4, 0x2000, 0}, - {"NAND 4MiB 3,3V 8-bit", 0xe3, 512, 4, 0x2000, 0}, - {"NAND 4MiB 3,3V 8-bit", 0xe5, 512, 4, 0x2000, 0}, - {"NAND 8MiB 3,3V 8-bit", 0xd6, 512, 8, 0x2000, 0}, - - {"NAND 8MiB 1,8V 8-bit", 0x39, 512, 8, 0x2000, 0}, - {"NAND 8MiB 3,3V 8-bit", 0xe6, 512, 8, 0x2000, 0}, - {"NAND 8MiB 1,8V 16-bit", 0x49, 512, 8, 0x2000, NAND_BUSWIDTH_16}, - {"NAND 8MiB 3,3V 16-bit", 0x59, 512, 8, 0x2000, NAND_BUSWIDTH_16}, - - {"NAND 16MiB 1,8V 8-bit", 0x33, 512, 16, 0x4000, 0}, - {"NAND 16MiB 3,3V 8-bit", 0x73, 512, 16, 0x4000, 0}, - {"NAND 16MiB 1,8V 16-bit", 0x43, 512, 16, 0x4000, NAND_BUSWIDTH_16}, - {"NAND 16MiB 3,3V 16-bit", 0x53, 512, 16, 0x4000, NAND_BUSWIDTH_16}, - - {"NAND 32MiB 1,8V 8-bit", 0x35, 512, 32, 0x4000, 0}, - {"NAND 32MiB 3,3V 8-bit", 0x75, 512, 32, 0x4000, 0}, - {"NAND 32MiB 1,8V 16-bit", 0x45, 512, 32, 0x4000, NAND_BUSWIDTH_16}, - {"NAND 32MiB 3,3V 16-bit", 0x55, 512, 32, 0x4000, NAND_BUSWIDTH_16}, - - {"NAND 64MiB 1,8V 8-bit", 0x36, 512, 64, 0x4000, 0}, - {"NAND 64MiB 3,3V 8-bit", 0x76, 512, 64, 0x4000, 0}, - {"NAND 64MiB 1,8V 16-bit", 0x46, 512, 64, 0x4000, NAND_BUSWIDTH_16}, - {"NAND 64MiB 3,3V 16-bit", 0x56, 512, 64, 0x4000, NAND_BUSWIDTH_16}, - - {"NAND 128MiB 1,8V 8-bit", 0x78, 512, 128, 0x4000, 0}, - {"NAND 128MiB 1,8V 8-bit", 0x39, 512, 128, 0x4000, 0}, - {"NAND 128MiB 3,3V 8-bit", 0x79, 512, 128, 0x4000, 0}, - {"NAND 128MiB 1,8V 16-bit", 0x72, 512, 128, 0x4000, NAND_BUSWIDTH_16}, - {"NAND 128MiB 1,8V 16-bit", 0x49, 512, 128, 0x4000, NAND_BUSWIDTH_16}, - {"NAND 128MiB 3,3V 16-bit", 0x74, 512, 128, 0x4000, NAND_BUSWIDTH_16}, - {"NAND 128MiB 3,3V 16-bit", 0x59, 512, 128, 0x4000, NAND_BUSWIDTH_16}, - - {"NAND 256MiB 3,3V 8-bit", 0x71, 512, 256, 0x4000, 0}, + /* + * Some incompatible NAND chips share device ID's and so must be + * listed by full ID. We list them first so that we can easily identify + * the most specific match. + */ + {"TC58NVG2S0F 4G 3.3V 8-bit", + { .id = {0x98, 0xdc, 0x90, 0x26, 0x76, 0x15, 0x01, 0x08} }, + SZ_4K, SZ_512, SZ_256K, 0, 8, 224, NAND_ECC_INFO(4, SZ_512) }, + {"TC58NVG3S0F 8G 3.3V 8-bit", + { .id = {0x98, 0xd3, 0x90, 0x26, 0x76, 0x15, 0x02, 0x08} }, + SZ_4K, SZ_1K, SZ_256K, 0, 8, 232, NAND_ECC_INFO(4, SZ_512) }, + {"TC58NVG5D2 32G 3.3V 8-bit", + { .id = {0x98, 0xd7, 0x94, 0x32, 0x76, 0x56, 0x09, 0x00} }, + SZ_8K, SZ_4K, SZ_1M, 0, 8, 640, NAND_ECC_INFO(40, SZ_1K) }, + {"TC58NVG6D2 64G 3.3V 8-bit", + { .id = {0x98, 0xde, 0x94, 0x82, 0x76, 0x56, 0x04, 0x20} }, + SZ_8K, SZ_8K, SZ_2M, 0, 8, 640, NAND_ECC_INFO(40, SZ_1K) }, + {"SDTNRGAMA 64G 3.3V 8-bit", + { .id = {0x45, 0xde, 0x94, 0x93, 0x76, 0x50} }, + SZ_16K, SZ_8K, SZ_4M, 0, 6, 1280, NAND_ECC_INFO(40, SZ_1K) }, + + LEGACY_ID_NAND("NAND 4MiB 5V 8-bit", 0x6B, 4, SZ_8K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 4MiB 3,3V 8-bit", 0xE3, 4, SZ_8K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 4MiB 3,3V 8-bit", 0xE5, 4, SZ_8K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 8MiB 3,3V 8-bit", 0xD6, 8, SZ_8K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 8MiB 3,3V 8-bit", 0xE6, 8, SZ_8K, SP_OPTIONS), + + LEGACY_ID_NAND("NAND 16MiB 1,8V 8-bit", 0x33, 16, SZ_16K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 16MiB 3,3V 8-bit", 0x73, 16, SZ_16K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 16MiB 1,8V 16-bit", 0x43, 16, SZ_16K, SP_OPTIONS16), + LEGACY_ID_NAND("NAND 16MiB 3,3V 16-bit", 0x53, 16, SZ_16K, SP_OPTIONS16), + + LEGACY_ID_NAND("NAND 32MiB 1,8V 8-bit", 0x35, 32, SZ_16K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 32MiB 3,3V 8-bit", 0x75, 32, SZ_16K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 32MiB 1,8V 16-bit", 0x45, 32, SZ_16K, SP_OPTIONS16), + LEGACY_ID_NAND("NAND 32MiB 3,3V 16-bit", 0x55, 32, SZ_16K, SP_OPTIONS16), + + LEGACY_ID_NAND("NAND 64MiB 1,8V 8-bit", 0x36, 64, SZ_16K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 64MiB 3,3V 8-bit", 0x76, 64, SZ_16K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 64MiB 1,8V 16-bit", 0x46, 64, SZ_16K, SP_OPTIONS16), + LEGACY_ID_NAND("NAND 64MiB 3,3V 16-bit", 0x56, 64, SZ_16K, SP_OPTIONS16), + + LEGACY_ID_NAND("NAND 128MiB 1,8V 8-bit", 0x78, 128, SZ_16K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 128MiB 1,8V 8-bit", 0x39, 128, SZ_16K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 128MiB 3,3V 8-bit", 0x79, 128, SZ_16K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 128MiB 1,8V 16-bit", 0x72, 128, SZ_16K, SP_OPTIONS16), + LEGACY_ID_NAND("NAND 128MiB 1,8V 16-bit", 0x49, 128, SZ_16K, SP_OPTIONS16), + LEGACY_ID_NAND("NAND 128MiB 3,3V 16-bit", 0x74, 128, SZ_16K, SP_OPTIONS16), + LEGACY_ID_NAND("NAND 128MiB 3,3V 16-bit", 0x59, 128, SZ_16K, SP_OPTIONS16), + + LEGACY_ID_NAND("NAND 256MiB 3,3V 8-bit", 0x71, 256, SZ_16K, SP_OPTIONS), /* - * These are the new chips with large page size. The pagesize and the - * erasesize is determined from the extended id bytes + * These are the new chips with large page size. Their page size and + * eraseblock size are determined from the extended ID bytes. */ -#define LP_OPTIONS (NAND_SAMSUNG_LP_OPTIONS | NAND_NO_READRDY | NAND_NO_AUTOINCR) -#define LP_OPTIONS16 (LP_OPTIONS | NAND_BUSWIDTH_16) - /*512 Megabit */ - {"NAND 64MiB 1,8V 8-bit", 0xA2, 0, 64, 0, LP_OPTIONS}, - {"NAND 64MiB 3,3V 8-bit", 0xF2, 0, 64, 0, LP_OPTIONS}, - {"NAND 64MiB 1,8V 16-bit", 0xB2, 0, 64, 0, LP_OPTIONS16}, - {"NAND 64MiB 3,3V 16-bit", 0xC2, 0, 64, 0, LP_OPTIONS16}, + /* 512 Megabit */ + EXTENDED_ID_NAND("NAND 64MiB 1,8V 8-bit", 0xA2, 64, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 64MiB 1,8V 8-bit", 0xA0, 64, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 64MiB 3,3V 8-bit", 0xF2, 64, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 64MiB 3,3V 8-bit", 0xD0, 64, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 64MiB 3,3V 8-bit", 0xF0, 64, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 64MiB 1,8V 16-bit", 0xB2, 64, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 64MiB 1,8V 16-bit", 0xB0, 64, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 64MiB 3,3V 16-bit", 0xC2, 64, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 64MiB 3,3V 16-bit", 0xC0, 64, LP_OPTIONS16), /* 1 Gigabit */ - {"NAND 128MiB 1,8V 8-bit", 0xA1, 0, 128, 0, LP_OPTIONS}, - {"NAND 128MiB 3,3V 8-bit", 0xF1, 0, 128, 0, LP_OPTIONS}, - {"NAND 128MiB 1,8V 16-bit", 0xB1, 0, 128, 0, LP_OPTIONS16}, - {"NAND 128MiB 3,3V 16-bit", 0xC1, 0, 128, 0, LP_OPTIONS16}, + EXTENDED_ID_NAND("NAND 128MiB 1,8V 8-bit", 0xA1, 128, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 128MiB 3,3V 8-bit", 0xF1, 128, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 128MiB 3,3V 8-bit", 0xD1, 128, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 128MiB 1,8V 16-bit", 0xB1, 128, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 128MiB 3,3V 16-bit", 0xC1, 128, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 128MiB 1,8V 16-bit", 0xAD, 128, LP_OPTIONS16), /* 2 Gigabit */ - {"NAND 256MiB 1,8V 8-bit", 0xAA, 0, 256, 0, LP_OPTIONS}, - {"NAND 256MiB 3,3V 8-bit", 0xDA, 0, 256, 0, LP_OPTIONS}, - {"NAND 256MiB 1,8V 16-bit", 0xBA, 0, 256, 0, LP_OPTIONS16}, - {"NAND 256MiB 3,3V 16-bit", 0xCA, 0, 256, 0, LP_OPTIONS16}, + EXTENDED_ID_NAND("NAND 256MiB 1,8V 8-bit", 0xAA, 256, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 256MiB 3,3V 8-bit", 0xDA, 256, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 256MiB 1,8V 16-bit", 0xBA, 256, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 256MiB 3,3V 16-bit", 0xCA, 256, LP_OPTIONS16), /* 4 Gigabit */ - {"NAND 512MiB 1,8V 8-bit", 0xAC, 0, 512, 0, LP_OPTIONS}, - {"NAND 512MiB 3,3V 8-bit", 0xDC, 0, 512, 0, LP_OPTIONS}, - {"NAND 512MiB 1,8V 16-bit", 0xBC, 0, 512, 0, LP_OPTIONS16}, - {"NAND 512MiB 3,3V 16-bit", 0xCC, 0, 512, 0, LP_OPTIONS16}, + EXTENDED_ID_NAND("NAND 512MiB 1,8V 8-bit", 0xAC, 512, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 512MiB 3,3V 8-bit", 0xDC, 512, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 512MiB 1,8V 16-bit", 0xBC, 512, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 512MiB 3,3V 16-bit", 0xCC, 512, LP_OPTIONS16), /* 8 Gigabit */ - {"NAND 1GiB 1,8V 8-bit", 0xA3, 0, 1024, 0, LP_OPTIONS}, - {"NAND 1GiB 3,3V 8-bit", 0xD3, 0, 1024, 0, LP_OPTIONS}, - {"NAND 1GiB 1,8V 16-bit", 0xB3, 0, 1024, 0, LP_OPTIONS16}, - {"NAND 1GiB 3,3V 16-bit", 0xC3, 0, 1024, 0, LP_OPTIONS16}, + EXTENDED_ID_NAND("NAND 1GiB 1,8V 8-bit", 0xA3, 1024, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 1GiB 3,3V 8-bit", 0xD3, 1024, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 1GiB 1,8V 16-bit", 0xB3, 1024, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 1GiB 3,3V 16-bit", 0xC3, 1024, LP_OPTIONS16), /* 16 Gigabit */ - {"NAND 2GiB 1,8V 8-bit", 0xA5, 0, 2048, 0, LP_OPTIONS}, - {"NAND 2GiB 3,3V 8-bit", 0xD5, 0, 2048, 0, LP_OPTIONS}, - {"NAND 2GiB 1,8V 16-bit", 0xB5, 0, 2048, 0, LP_OPTIONS16}, - {"NAND 2GiB 3,3V 16-bit", 0xC5, 0, 2048, 0, LP_OPTIONS16}, - - /* - * Renesas AND 1 Gigabit. Those chips do not support extended id and - * have a strange page/block layout ! The chosen minimum erasesize is - * 4 * 2 * 2048 = 16384 Byte, as those chips have an array of 4 page - * planes 1 block = 2 pages, but due to plane arrangement the blocks - * 0-3 consists of page 0 + 4,1 + 5, 2 + 6, 3 + 7 Anyway JFFS2 would - * increase the eraseblock size so we chose a combined one which can be - * erased in one go There are more speed improvements for reads and - * writes possible, but not implemented now - */ - {"AND 128MiB 3,3V 8-bit", 0x01, 2048, 128, 0x4000, - NAND_IS_AND | NAND_NO_AUTOINCR |NAND_NO_READRDY | NAND_4PAGE_ARRAY | - BBT_AUTO_REFRESH - }, - - {NULL,} + EXTENDED_ID_NAND("NAND 2GiB 1,8V 8-bit", 0xA5, 2048, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 2GiB 3,3V 8-bit", 0xD5, 2048, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 2GiB 1,8V 16-bit", 0xB5, 2048, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 2GiB 3,3V 16-bit", 0xC5, 2048, LP_OPTIONS16), + + /* 32 Gigabit */ + EXTENDED_ID_NAND("NAND 4GiB 1,8V 8-bit", 0xA7, 4096, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 4GiB 3,3V 8-bit", 0xD7, 4096, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 4GiB 1,8V 16-bit", 0xB7, 4096, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 4GiB 3,3V 16-bit", 0xC7, 4096, LP_OPTIONS16), + + /* 64 Gigabit */ + EXTENDED_ID_NAND("NAND 8GiB 1,8V 8-bit", 0xAE, 8192, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 8GiB 3,3V 8-bit", 0xDE, 8192, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 8GiB 1,8V 16-bit", 0xBE, 8192, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 8GiB 3,3V 16-bit", 0xCE, 8192, LP_OPTIONS16), + + /* 128 Gigabit */ + EXTENDED_ID_NAND("NAND 16GiB 1,8V 8-bit", 0x1A, 16384, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 16GiB 3,3V 8-bit", 0x3A, 16384, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 16GiB 1,8V 16-bit", 0x2A, 16384, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 16GiB 3,3V 16-bit", 0x4A, 16384, LP_OPTIONS16), + + /* 256 Gigabit */ + EXTENDED_ID_NAND("NAND 32GiB 1,8V 8-bit", 0x1C, 32768, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 32GiB 3,3V 8-bit", 0x3C, 32768, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 32GiB 1,8V 16-bit", 0x2C, 32768, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 32GiB 3,3V 16-bit", 0x4C, 32768, LP_OPTIONS16), + + /* 512 Gigabit */ + EXTENDED_ID_NAND("NAND 64GiB 1,8V 8-bit", 0x1E, 65536, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 64GiB 3,3V 8-bit", 0x3E, 65536, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 64GiB 1,8V 16-bit", 0x2E, 65536, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 64GiB 3,3V 16-bit", 0x4E, 65536, LP_OPTIONS16), + + {NULL} }; -/* -* Manufacturer ID list -*/ +/* Manufacturer IDs */ struct nand_manufacturers nand_manuf_ids[] = { {NAND_MFR_TOSHIBA, "Toshiba"}, {NAND_MFR_SAMSUNG, "Samsung"}, @@ -137,6 +168,12 @@ struct nand_manufacturers nand_manuf_ids[] = { {NAND_MFR_RENESAS, "Renesas"}, {NAND_MFR_STMICRO, "ST Micro"}, {NAND_MFR_HYNIX, "Hynix"}, + {NAND_MFR_MICRON, "Micron"}, + {NAND_MFR_AMD, "AMD/Spansion"}, + {NAND_MFR_MACRONIX, "Macronix"}, + {NAND_MFR_EON, "Eon"}, + {NAND_MFR_SANDISK, "SanDisk"}, + {NAND_MFR_INTEL, "Intel"}, {0x0, "Unknown"} }; diff --git a/drivers/mtd/nand/nandsim.c b/drivers/mtd/nand/nandsim.c index 545ff252d81..4f0d83648e5 100644 --- a/drivers/mtd/nand/nandsim.c +++ b/drivers/mtd/nand/nandsim.c @@ -21,8 +21,6 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA - * - * $Id: nandsim.c,v 1.8 2005/03/19 15:33:56 dedekind Exp $ */ #include <linux/init.h> @@ -30,17 +28,22 @@ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/vmalloc.h> +#include <linux/math64.h> #include <linux/slab.h> #include <linux/errno.h> #include <linux/string.h> #include <linux/mtd/mtd.h> #include <linux/mtd/nand.h> +#include <linux/mtd/nand_bch.h> #include <linux/mtd/partitions.h> #include <linux/delay.h> -#ifdef CONFIG_NS_ABS_POS -#include <asm/io.h> -#endif - +#include <linux/list.h> +#include <linux/random.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/pagemap.h> +#include <linux/seq_file.h> +#include <linux/debugfs.h> /* Default simulator parameters values */ #if !defined(CONFIG_NANDSIM_FIRST_ID_BYTE) || \ @@ -80,6 +83,9 @@ #ifndef CONFIG_NANDSIM_DBG #define CONFIG_NANDSIM_DBG 0 #endif +#ifndef CONFIG_NANDSIM_MAX_PARTS +#define CONFIG_NANDSIM_MAX_PARTS 32 +#endif static uint first_id_byte = CONFIG_NANDSIM_FIRST_ID_BYTE; static uint second_id_byte = CONFIG_NANDSIM_SECOND_ID_BYTE; @@ -94,6 +100,17 @@ static uint bus_width = CONFIG_NANDSIM_BUS_WIDTH; static uint do_delays = CONFIG_NANDSIM_DO_DELAYS; static uint log = CONFIG_NANDSIM_LOG; static uint dbg = CONFIG_NANDSIM_DBG; +static unsigned long parts[CONFIG_NANDSIM_MAX_PARTS]; +static unsigned int parts_num; +static char *badblocks = NULL; +static char *weakblocks = NULL; +static char *weakpages = NULL; +static unsigned int bitflips = 0; +static char *gravepages = NULL; +static unsigned int overridesize = 0; +static char *cache_file = NULL; +static unsigned int bbt; +static unsigned int bch; module_param(first_id_byte, uint, 0400); module_param(second_id_byte, uint, 0400); @@ -108,23 +125,53 @@ module_param(bus_width, uint, 0400); module_param(do_delays, uint, 0400); module_param(log, uint, 0400); module_param(dbg, uint, 0400); - -MODULE_PARM_DESC(first_id_byte, "The fist byte returned by NAND Flash 'read ID' command (manufaturer ID)"); +module_param_array(parts, ulong, &parts_num, 0400); +module_param(badblocks, charp, 0400); +module_param(weakblocks, charp, 0400); +module_param(weakpages, charp, 0400); +module_param(bitflips, uint, 0400); +module_param(gravepages, charp, 0400); +module_param(overridesize, uint, 0400); +module_param(cache_file, charp, 0400); +module_param(bbt, uint, 0400); +module_param(bch, uint, 0400); + +MODULE_PARM_DESC(first_id_byte, "The first byte returned by NAND Flash 'read ID' command (manufacturer ID)"); MODULE_PARM_DESC(second_id_byte, "The second byte returned by NAND Flash 'read ID' command (chip ID)"); MODULE_PARM_DESC(third_id_byte, "The third byte returned by NAND Flash 'read ID' command"); MODULE_PARM_DESC(fourth_id_byte, "The fourth byte returned by NAND Flash 'read ID' command"); -MODULE_PARM_DESC(access_delay, "Initial page access delay (microiseconds)"); +MODULE_PARM_DESC(access_delay, "Initial page access delay (microseconds)"); MODULE_PARM_DESC(programm_delay, "Page programm delay (microseconds"); MODULE_PARM_DESC(erase_delay, "Sector erase delay (milliseconds)"); -MODULE_PARM_DESC(output_cycle, "Word output (from flash) time (nanodeconds)"); -MODULE_PARM_DESC(input_cycle, "Word input (to flash) time (nanodeconds)"); +MODULE_PARM_DESC(output_cycle, "Word output (from flash) time (nanoseconds)"); +MODULE_PARM_DESC(input_cycle, "Word input (to flash) time (nanoseconds)"); MODULE_PARM_DESC(bus_width, "Chip's bus width (8- or 16-bit)"); MODULE_PARM_DESC(do_delays, "Simulate NAND delays using busy-waits if not zero"); MODULE_PARM_DESC(log, "Perform logging if not zero"); MODULE_PARM_DESC(dbg, "Output debug information if not zero"); +MODULE_PARM_DESC(parts, "Partition sizes (in erase blocks) separated by commas"); +/* Page and erase block positions for the following parameters are independent of any partitions */ +MODULE_PARM_DESC(badblocks, "Erase blocks that are initially marked bad, separated by commas"); +MODULE_PARM_DESC(weakblocks, "Weak erase blocks [: remaining erase cycles (defaults to 3)]" + " separated by commas e.g. 113:2 means eb 113" + " can be erased only twice before failing"); +MODULE_PARM_DESC(weakpages, "Weak pages [: maximum writes (defaults to 3)]" + " separated by commas e.g. 1401:2 means page 1401" + " can be written only twice before failing"); +MODULE_PARM_DESC(bitflips, "Maximum number of random bit flips per page (zero by default)"); +MODULE_PARM_DESC(gravepages, "Pages that lose data [: maximum reads (defaults to 3)]" + " separated by commas e.g. 1401:2 means page 1401" + " can be read only twice before failing"); +MODULE_PARM_DESC(overridesize, "Specifies the NAND Flash size overriding the ID bytes. " + "The size is specified in erase blocks and as the exponent of a power of two" + " e.g. 5 means a size of 32 erase blocks"); +MODULE_PARM_DESC(cache_file, "File to use to cache nand pages instead of memory"); +MODULE_PARM_DESC(bbt, "0 OOB, 1 BBT with marker in OOB, 2 BBT with marker in data area"); +MODULE_PARM_DESC(bch, "Enable BCH ecc and set how many bits should " + "be correctable in 512-byte blocks"); /* The largest possible page size */ -#define NS_LARGEST_PAGE_SIZE 2048 +#define NS_LARGEST_PAGE_SIZE 4096 /* The prefix for simulator output */ #define NS_OUTPUT_PREFIX "[nandsim]" @@ -135,9 +182,11 @@ MODULE_PARM_DESC(dbg, "Output debug information if not zero"); #define NS_DBG(args...) \ do { if (dbg) printk(KERN_DEBUG NS_OUTPUT_PREFIX " debug: " args); } while(0) #define NS_WARN(args...) \ - do { printk(KERN_WARNING NS_OUTPUT_PREFIX " warnig: " args); } while(0) + do { printk(KERN_WARNING NS_OUTPUT_PREFIX " warning: " args); } while(0) #define NS_ERR(args...) \ - do { printk(KERN_ERR NS_OUTPUT_PREFIX " errorr: " args); } while(0) + do { printk(KERN_ERR NS_OUTPUT_PREFIX " error: " args); } while(0) +#define NS_INFO(args...) \ + do { printk(KERN_INFO NS_OUTPUT_PREFIX " " args); } while(0) /* Busy-wait delay macros (microseconds, milliseconds) */ #define NS_UDELAY(us) \ @@ -156,7 +205,7 @@ MODULE_PARM_DESC(dbg, "Output debug information if not zero"); /* Calculate the page offset in flash RAM image by (row, column) address */ #define NS_RAW_OFFSET(ns) \ - (((ns)->regs.row << (ns)->geom.pgshift) + ((ns)->regs.row * (ns)->geom.oobsz) + (ns)->regs.column) + (((ns)->regs.row * (ns)->geom.pgszoob) + (ns)->regs.column) /* Calculate the OOB offset in flash RAM image by (row, column) address */ #define NS_RAW_OFFSET_OOB(ns) (NS_RAW_OFFSET(ns) + ns->geom.pgsz) @@ -164,25 +213,27 @@ MODULE_PARM_DESC(dbg, "Output debug information if not zero"); /* After a command is input, the simulator goes to one of the following states */ #define STATE_CMD_READ0 0x00000001 /* read data from the beginning of page */ #define STATE_CMD_READ1 0x00000002 /* read data from the second half of page */ -#define STATE_CMD_READSTART 0x00000003 /* read data second command (large page devices) */ -#define STATE_CMD_PAGEPROG 0x00000004 /* start page programm */ +#define STATE_CMD_READSTART 0x00000003 /* read data second command (large page devices) */ +#define STATE_CMD_PAGEPROG 0x00000004 /* start page program */ #define STATE_CMD_READOOB 0x00000005 /* read OOB area */ #define STATE_CMD_ERASE1 0x00000006 /* sector erase first command */ #define STATE_CMD_STATUS 0x00000007 /* read status */ -#define STATE_CMD_STATUS_M 0x00000008 /* read multi-plane status (isn't implemented) */ -#define STATE_CMD_SEQIN 0x00000009 /* sequential data imput */ +#define STATE_CMD_SEQIN 0x00000009 /* sequential data input */ #define STATE_CMD_READID 0x0000000A /* read ID */ #define STATE_CMD_ERASE2 0x0000000B /* sector erase second command */ #define STATE_CMD_RESET 0x0000000C /* reset */ +#define STATE_CMD_RNDOUT 0x0000000D /* random output command */ +#define STATE_CMD_RNDOUTSTART 0x0000000E /* random output start command */ #define STATE_CMD_MASK 0x0000000F /* command states mask */ -/* After an addres is input, the simulator goes to one of these states */ +/* After an address is input, the simulator goes to one of these states */ #define STATE_ADDR_PAGE 0x00000010 /* full (row, column) address is accepted */ #define STATE_ADDR_SEC 0x00000020 /* sector address was accepted */ -#define STATE_ADDR_ZERO 0x00000030 /* one byte zero address was accepted */ -#define STATE_ADDR_MASK 0x00000030 /* address states mask */ +#define STATE_ADDR_COLUMN 0x00000030 /* column address was accepted */ +#define STATE_ADDR_ZERO 0x00000040 /* one byte zero address was accepted */ +#define STATE_ADDR_MASK 0x00000070 /* address states mask */ -/* Durind data input/output the simulator is in these states */ +/* During data input/output the simulator is in these states */ #define STATE_DATAIN 0x00000100 /* waiting for data input */ #define STATE_DATAIN_MASK 0x00000100 /* data input states mask */ @@ -200,41 +251,57 @@ MODULE_PARM_DESC(dbg, "Output debug information if not zero"); /* Simulator's actions bit masks */ #define ACTION_CPY 0x00100000 /* copy page/OOB to the internal buffer */ -#define ACTION_PRGPAGE 0x00200000 /* programm the internal buffer to flash */ +#define ACTION_PRGPAGE 0x00200000 /* program the internal buffer to flash */ #define ACTION_SECERASE 0x00300000 /* erase sector */ #define ACTION_ZEROOFF 0x00400000 /* don't add any offset to address */ #define ACTION_HALFOFF 0x00500000 /* add to address half of page */ #define ACTION_OOBOFF 0x00600000 /* add to address OOB offset */ #define ACTION_MASK 0x00700000 /* action mask */ -#define NS_OPER_NUM 12 /* Number of operations supported by the simulator */ +#define NS_OPER_NUM 13 /* Number of operations supported by the simulator */ #define NS_OPER_STATES 6 /* Maximum number of states in operation */ #define OPT_ANY 0xFFFFFFFF /* any chip supports this operation */ -#define OPT_PAGE256 0x00000001 /* 256-byte page chips */ #define OPT_PAGE512 0x00000002 /* 512-byte page chips */ #define OPT_PAGE2048 0x00000008 /* 2048-byte page chips */ #define OPT_SMARTMEDIA 0x00000010 /* SmartMedia technology chips */ -#define OPT_AUTOINCR 0x00000020 /* page number auto inctimentation is possible */ #define OPT_PAGE512_8BIT 0x00000040 /* 512-byte page chips with 8-bit bus width */ -#define OPT_LARGEPAGE (OPT_PAGE2048) /* 2048-byte page chips */ -#define OPT_SMALLPAGE (OPT_PAGE256 | OPT_PAGE512) /* 256 and 512-byte page chips */ +#define OPT_PAGE4096 0x00000080 /* 4096-byte page chips */ +#define OPT_LARGEPAGE (OPT_PAGE2048 | OPT_PAGE4096) /* 2048 & 4096-byte page chips */ +#define OPT_SMALLPAGE (OPT_PAGE512) /* 512-byte page chips */ -/* Remove action bits ftom state */ +/* Remove action bits from state */ #define NS_STATE(x) ((x) & ~ACTION_MASK) /* * Maximum previous states which need to be saved. Currently saving is - * only needed for page programm operation with preceeded read command + * only needed for page program operation with preceded read command * (which is only valid for 512-byte pages). */ #define NS_MAX_PREVSTATES 1 +/* Maximum page cache pages needed to read or write a NAND page to the cache_file */ +#define NS_MAX_HELD_PAGES 16 + +struct nandsim_debug_info { + struct dentry *dfs_root; + struct dentry *dfs_wear_report; +}; + +/* + * A union to represent flash memory contents and flash buffer. + */ +union ns_mem { + u_char *byte; /* for byte access */ + uint16_t *word; /* for 16-bit word access */ +}; + /* * The structure which describes all the internal simulator data. */ struct nandsim { - struct mtd_partition part; + struct mtd_partition partitions[CONFIG_NANDSIM_MAX_PARTS]; + unsigned int nbparts; uint busw; /* flash chip bus width (8 or 16) */ u_char ids[4]; /* chip's ID bytes */ @@ -247,39 +314,35 @@ struct nandsim { uint16_t npstates; /* number of previous states saved */ uint16_t stateidx; /* current state index */ - /* The simulated NAND flash image */ - union flash_media { - u_char *byte; - uint16_t *word; - } mem; + /* The simulated NAND flash pages array */ + union ns_mem *pages; + + /* Slab allocator for nand pages */ + struct kmem_cache *nand_pages_slab; /* Internal buffer of page + OOB size bytes */ - union internal_buffer { - u_char *byte; /* for byte access */ - uint16_t *word; /* for 16-bit word access */ - } buf; + union ns_mem buf; /* NAND flash "geometry" */ - struct nandsin_geometry { - uint32_t totsz; /* total flash size, bytes */ + struct { + uint64_t totsz; /* total flash size, bytes */ uint32_t secsz; /* flash sector (erase block) size, bytes */ uint pgsz; /* NAND flash page size, bytes */ uint oobsz; /* page OOB area size, bytes */ - uint32_t totszoob; /* total flash size including OOB, bytes */ + uint64_t totszoob; /* total flash size including OOB, bytes */ uint pgszoob; /* page size including OOB , bytes*/ uint secszoob; /* sector size including OOB, bytes */ uint pgnum; /* total number of pages */ uint pgsec; /* number of pages per sector */ uint secshift; /* bits number in sector size */ uint pgshift; /* bits number in page size */ - uint oobshift; /* bits number in OOB size */ uint pgaddrbytes; /* bytes per page address */ uint secaddrbytes; /* bytes per sector address */ uint idbytes; /* the number ID bytes that this chip outputs */ } geom; /* NAND flash internal registers */ - struct nandsim_regs { + struct { unsigned command; /* the command register */ u_char status; /* the status register */ uint row; /* the page number */ @@ -290,12 +353,21 @@ struct nandsim { } regs; /* NAND flash lines state */ - struct ns_lines_status { + struct { int ce; /* chip Enable */ int cle; /* command Latch Enable */ int ale; /* address Latch Enable */ int wp; /* write Protect */ } lines; + + /* Fields needed when using a cache file */ + struct file *cfile; /* Open file */ + unsigned long *pages_written; /* Which pages have been written */ + void *file_buf; + struct page *held_pages[NS_MAX_HELD_PAGES]; + int held_cnt; + + struct nandsim_debug_info dbg; }; /* @@ -315,47 +387,287 @@ static struct nandsim_operations { /* Read OOB */ {OPT_SMALLPAGE, {STATE_CMD_READOOB | ACTION_OOBOFF, STATE_ADDR_PAGE | ACTION_CPY, STATE_DATAOUT, STATE_READY}}, - /* Programm page starting from the beginning */ + /* Program page starting from the beginning */ {OPT_ANY, {STATE_CMD_SEQIN, STATE_ADDR_PAGE, STATE_DATAIN, STATE_CMD_PAGEPROG | ACTION_PRGPAGE, STATE_READY}}, - /* Programm page starting from the beginning */ + /* Program page starting from the beginning */ {OPT_SMALLPAGE, {STATE_CMD_READ0, STATE_CMD_SEQIN | ACTION_ZEROOFF, STATE_ADDR_PAGE, STATE_DATAIN, STATE_CMD_PAGEPROG | ACTION_PRGPAGE, STATE_READY}}, - /* Programm page starting from the second half */ + /* Program page starting from the second half */ {OPT_PAGE512, {STATE_CMD_READ1, STATE_CMD_SEQIN | ACTION_HALFOFF, STATE_ADDR_PAGE, STATE_DATAIN, STATE_CMD_PAGEPROG | ACTION_PRGPAGE, STATE_READY}}, - /* Programm OOB */ + /* Program OOB */ {OPT_SMALLPAGE, {STATE_CMD_READOOB, STATE_CMD_SEQIN | ACTION_OOBOFF, STATE_ADDR_PAGE, STATE_DATAIN, STATE_CMD_PAGEPROG | ACTION_PRGPAGE, STATE_READY}}, /* Erase sector */ {OPT_ANY, {STATE_CMD_ERASE1, STATE_ADDR_SEC, STATE_CMD_ERASE2 | ACTION_SECERASE, STATE_READY}}, /* Read status */ {OPT_ANY, {STATE_CMD_STATUS, STATE_DATAOUT_STATUS, STATE_READY}}, - /* Read multi-plane status */ - {OPT_SMARTMEDIA, {STATE_CMD_STATUS_M, STATE_DATAOUT_STATUS_M, STATE_READY}}, /* Read ID */ {OPT_ANY, {STATE_CMD_READID, STATE_ADDR_ZERO, STATE_DATAOUT_ID, STATE_READY}}, /* Large page devices read page */ {OPT_LARGEPAGE, {STATE_CMD_READ0, STATE_ADDR_PAGE, STATE_CMD_READSTART | ACTION_CPY, - STATE_DATAOUT, STATE_READY}} + STATE_DATAOUT, STATE_READY}}, + /* Large page devices random page read */ + {OPT_LARGEPAGE, {STATE_CMD_RNDOUT, STATE_ADDR_COLUMN, STATE_CMD_RNDOUTSTART | ACTION_CPY, + STATE_DATAOUT, STATE_READY}}, +}; + +struct weak_block { + struct list_head list; + unsigned int erase_block_no; + unsigned int max_erases; + unsigned int erases_done; +}; + +static LIST_HEAD(weak_blocks); + +struct weak_page { + struct list_head list; + unsigned int page_no; + unsigned int max_writes; + unsigned int writes_done; +}; + +static LIST_HEAD(weak_pages); + +struct grave_page { + struct list_head list; + unsigned int page_no; + unsigned int max_reads; + unsigned int reads_done; }; +static LIST_HEAD(grave_pages); + +static unsigned long *erase_block_wear = NULL; +static unsigned int wear_eb_count = 0; +static unsigned long total_wear = 0; + /* MTD structure for NAND controller */ static struct mtd_info *nsmtd; -static u_char ns_verify_buf[NS_LARGEST_PAGE_SIZE]; +static int nandsim_debugfs_show(struct seq_file *m, void *private) +{ + unsigned long wmin = -1, wmax = 0, avg; + unsigned long deciles[10], decile_max[10], tot = 0; + unsigned int i; + + /* Calc wear stats */ + for (i = 0; i < wear_eb_count; ++i) { + unsigned long wear = erase_block_wear[i]; + if (wear < wmin) + wmin = wear; + if (wear > wmax) + wmax = wear; + tot += wear; + } + + for (i = 0; i < 9; ++i) { + deciles[i] = 0; + decile_max[i] = (wmax * (i + 1) + 5) / 10; + } + deciles[9] = 0; + decile_max[9] = wmax; + for (i = 0; i < wear_eb_count; ++i) { + int d; + unsigned long wear = erase_block_wear[i]; + for (d = 0; d < 10; ++d) + if (wear <= decile_max[d]) { + deciles[d] += 1; + break; + } + } + avg = tot / wear_eb_count; + + /* Output wear report */ + seq_printf(m, "Total numbers of erases: %lu\n", tot); + seq_printf(m, "Number of erase blocks: %u\n", wear_eb_count); + seq_printf(m, "Average number of erases: %lu\n", avg); + seq_printf(m, "Maximum number of erases: %lu\n", wmax); + seq_printf(m, "Minimum number of erases: %lu\n", wmin); + for (i = 0; i < 10; ++i) { + unsigned long from = (i ? decile_max[i - 1] + 1 : 0); + if (from > decile_max[i]) + continue; + seq_printf(m, "Number of ebs with erase counts from %lu to %lu : %lu\n", + from, + decile_max[i], + deciles[i]); + } + + return 0; +} + +static int nandsim_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, nandsim_debugfs_show, inode->i_private); +} + +static const struct file_operations dfs_fops = { + .open = nandsim_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/** + * nandsim_debugfs_create - initialize debugfs + * @dev: nandsim device description object + * + * This function creates all debugfs files for UBI device @ubi. Returns zero in + * case of success and a negative error code in case of failure. + */ +static int nandsim_debugfs_create(struct nandsim *dev) +{ + struct nandsim_debug_info *dbg = &dev->dbg; + struct dentry *dent; + int err; + + if (!IS_ENABLED(CONFIG_DEBUG_FS)) + return 0; + + dent = debugfs_create_dir("nandsim", NULL); + if (IS_ERR_OR_NULL(dent)) { + int err = dent ? -ENODEV : PTR_ERR(dent); + + NS_ERR("cannot create \"nandsim\" debugfs directory, err %d\n", + err); + return err; + } + dbg->dfs_root = dent; + + dent = debugfs_create_file("wear_report", S_IRUSR, + dbg->dfs_root, dev, &dfs_fops); + if (IS_ERR_OR_NULL(dent)) + goto out_remove; + dbg->dfs_wear_report = dent; + + return 0; + +out_remove: + debugfs_remove_recursive(dbg->dfs_root); + err = dent ? PTR_ERR(dent) : -ENODEV; + return err; +} + +/** + * nandsim_debugfs_remove - destroy all debugfs files + */ +static void nandsim_debugfs_remove(struct nandsim *ns) +{ + if (IS_ENABLED(CONFIG_DEBUG_FS)) + debugfs_remove_recursive(ns->dbg.dfs_root); +} + +/* + * Allocate array of page pointers, create slab allocation for an array + * and initialize the array by NULL pointers. + * + * RETURNS: 0 if success, -ENOMEM if memory alloc fails. + */ +static int alloc_device(struct nandsim *ns) +{ + struct file *cfile; + int i, err; + + if (cache_file) { + cfile = filp_open(cache_file, O_CREAT | O_RDWR | O_LARGEFILE, 0600); + if (IS_ERR(cfile)) + return PTR_ERR(cfile); + if (!(cfile->f_mode & FMODE_CAN_READ)) { + NS_ERR("alloc_device: cache file not readable\n"); + err = -EINVAL; + goto err_close; + } + if (!(cfile->f_mode & FMODE_CAN_WRITE)) { + NS_ERR("alloc_device: cache file not writeable\n"); + err = -EINVAL; + goto err_close; + } + ns->pages_written = vzalloc(BITS_TO_LONGS(ns->geom.pgnum) * + sizeof(unsigned long)); + if (!ns->pages_written) { + NS_ERR("alloc_device: unable to allocate pages written array\n"); + err = -ENOMEM; + goto err_close; + } + ns->file_buf = kmalloc(ns->geom.pgszoob, GFP_KERNEL); + if (!ns->file_buf) { + NS_ERR("alloc_device: unable to allocate file buf\n"); + err = -ENOMEM; + goto err_free; + } + ns->cfile = cfile; + return 0; + } + + ns->pages = vmalloc(ns->geom.pgnum * sizeof(union ns_mem)); + if (!ns->pages) { + NS_ERR("alloc_device: unable to allocate page array\n"); + return -ENOMEM; + } + for (i = 0; i < ns->geom.pgnum; i++) { + ns->pages[i].byte = NULL; + } + ns->nand_pages_slab = kmem_cache_create("nandsim", + ns->geom.pgszoob, 0, 0, NULL); + if (!ns->nand_pages_slab) { + NS_ERR("cache_create: unable to create kmem_cache\n"); + return -ENOMEM; + } + + return 0; + +err_free: + vfree(ns->pages_written); +err_close: + filp_close(cfile, NULL); + return err; +} + +/* + * Free any allocated pages, and free the array of page pointers. + */ +static void free_device(struct nandsim *ns) +{ + int i; + + if (ns->cfile) { + kfree(ns->file_buf); + vfree(ns->pages_written); + filp_close(ns->cfile, NULL); + return; + } + + if (ns->pages) { + for (i = 0; i < ns->geom.pgnum; i++) { + if (ns->pages[i].byte) + kmem_cache_free(ns->nand_pages_slab, + ns->pages[i].byte); + } + kmem_cache_destroy(ns->nand_pages_slab); + vfree(ns->pages); + } +} + +static char *get_partition_name(int i) +{ + return kasprintf(GFP_KERNEL, "NAND simulator partition %d", i); +} /* * Initialize the nandsim structure. * * RETURNS: 0 if success, -ERRNO if failure. */ -static int -init_nandsim(struct mtd_info *mtd) +static int init_nandsim(struct mtd_info *mtd) { - struct nand_chip *chip = (struct nand_chip *)mtd->priv; - struct nandsim *ns = (struct nandsim *)(chip->priv); - int i; + struct nand_chip *chip = mtd->priv; + struct nandsim *ns = chip->priv; + int i, ret = 0; + uint64_t remains; + uint64_t next_offset; if (NS_IS_INITIALIZED(ns)) { NS_ERR("init_nandsim: nandsim is already initialized\n"); @@ -372,31 +684,29 @@ init_nandsim(struct mtd_info *mtd) ns->geom.oobsz = mtd->oobsize; ns->geom.secsz = mtd->erasesize; ns->geom.pgszoob = ns->geom.pgsz + ns->geom.oobsz; - ns->geom.pgnum = ns->geom.totsz / ns->geom.pgsz; - ns->geom.totszoob = ns->geom.totsz + ns->geom.pgnum * ns->geom.oobsz; + ns->geom.pgnum = div_u64(ns->geom.totsz, ns->geom.pgsz); + ns->geom.totszoob = ns->geom.totsz + (uint64_t)ns->geom.pgnum * ns->geom.oobsz; ns->geom.secshift = ffs(ns->geom.secsz) - 1; ns->geom.pgshift = chip->page_shift; - ns->geom.oobshift = ffs(ns->geom.oobsz) - 1; ns->geom.pgsec = ns->geom.secsz / ns->geom.pgsz; ns->geom.secszoob = ns->geom.secsz + ns->geom.oobsz * ns->geom.pgsec; ns->options = 0; - if (ns->geom.pgsz == 256) { - ns->options |= OPT_PAGE256; - } - else if (ns->geom.pgsz == 512) { - ns->options |= (OPT_PAGE512 | OPT_AUTOINCR); + if (ns->geom.pgsz == 512) { + ns->options |= OPT_PAGE512; if (ns->busw == 8) ns->options |= OPT_PAGE512_8BIT; } else if (ns->geom.pgsz == 2048) { ns->options |= OPT_PAGE2048; + } else if (ns->geom.pgsz == 4096) { + ns->options |= OPT_PAGE4096; } else { NS_ERR("init_nandsim: unknown page size %u\n", ns->geom.pgsz); return -EIO; } if (ns->options & OPT_SMALLPAGE) { - if (ns->geom.totsz < (64 << 20)) { + if (ns->geom.totsz <= (32 << 20)) { ns->geom.pgaddrbytes = 3; ns->geom.secaddrbytes = 2; } else { @@ -405,7 +715,7 @@ init_nandsim(struct mtd_info *mtd) } } else { if (ns->geom.totsz <= (128 << 20)) { - ns->geom.pgaddrbytes = 5; + ns->geom.pgaddrbytes = 4; ns->geom.secaddrbytes = 2; } else { ns->geom.pgaddrbytes = 5; @@ -413,18 +723,46 @@ init_nandsim(struct mtd_info *mtd) } } - /* Detect how many ID bytes the NAND chip outputs */ - for (i = 0; nand_flash_ids[i].name != NULL; i++) { - if (second_id_byte != nand_flash_ids[i].id) - continue; - if (!(nand_flash_ids[i].options & NAND_NO_AUTOINCR)) - ns->options |= OPT_AUTOINCR; + /* Fill the partition_info structure */ + if (parts_num > ARRAY_SIZE(ns->partitions)) { + NS_ERR("too many partitions.\n"); + ret = -EINVAL; + goto error; + } + remains = ns->geom.totsz; + next_offset = 0; + for (i = 0; i < parts_num; ++i) { + uint64_t part_sz = (uint64_t)parts[i] * ns->geom.secsz; + + if (!part_sz || part_sz > remains) { + NS_ERR("bad partition size.\n"); + ret = -EINVAL; + goto error; + } + ns->partitions[i].name = get_partition_name(i); + ns->partitions[i].offset = next_offset; + ns->partitions[i].size = part_sz; + next_offset += ns->partitions[i].size; + remains -= ns->partitions[i].size; + } + ns->nbparts = parts_num; + if (remains) { + if (parts_num + 1 > ARRAY_SIZE(ns->partitions)) { + NS_ERR("too many partitions.\n"); + ret = -EINVAL; + goto error; + } + ns->partitions[i].name = get_partition_name(i); + ns->partitions[i].offset = next_offset; + ns->partitions[i].size = remains; + ns->nbparts += 1; } if (ns->busw == 16) NS_WARN("16-bit flashes support wasn't tested\n"); - printk("flash size: %u MiB\n", ns->geom.totsz >> 20); + printk("flash size: %llu MiB\n", + (unsigned long long)ns->geom.totsz >> 20); printk("page size: %u bytes\n", ns->geom.pgsz); printk("OOB area size: %u bytes\n", ns->geom.oobsz); printk("sector size: %u KiB\n", ns->geom.secsz >> 10); @@ -433,78 +771,282 @@ init_nandsim(struct mtd_info *mtd) printk("bus width: %u\n", ns->busw); printk("bits in sector size: %u\n", ns->geom.secshift); printk("bits in page size: %u\n", ns->geom.pgshift); - printk("bits in OOB size: %u\n", ns->geom.oobshift); - printk("flash size with OOB: %u KiB\n", ns->geom.totszoob >> 10); + printk("bits in OOB size: %u\n", ffs(ns->geom.oobsz) - 1); + printk("flash size with OOB: %llu KiB\n", + (unsigned long long)ns->geom.totszoob >> 10); printk("page address bytes: %u\n", ns->geom.pgaddrbytes); printk("sector address bytes: %u\n", ns->geom.secaddrbytes); printk("options: %#x\n", ns->options); - /* Map / allocate and initialize the flash image */ -#ifdef CONFIG_NS_ABS_POS - ns->mem.byte = ioremap(CONFIG_NS_ABS_POS, ns->geom.totszoob); - if (!ns->mem.byte) { - NS_ERR("init_nandsim: failed to map the NAND flash image at address %p\n", - (void *)CONFIG_NS_ABS_POS); - return -ENOMEM; - } -#else - ns->mem.byte = vmalloc(ns->geom.totszoob); - if (!ns->mem.byte) { - NS_ERR("init_nandsim: unable to allocate %u bytes for flash image\n", - ns->geom.totszoob); - return -ENOMEM; - } - memset(ns->mem.byte, 0xFF, ns->geom.totszoob); -#endif + if ((ret = alloc_device(ns)) != 0) + goto error; /* Allocate / initialize the internal buffer */ ns->buf.byte = kmalloc(ns->geom.pgszoob, GFP_KERNEL); if (!ns->buf.byte) { NS_ERR("init_nandsim: unable to allocate %u bytes for the internal buffer\n", ns->geom.pgszoob); + ret = -ENOMEM; goto error; } memset(ns->buf.byte, 0xFF, ns->geom.pgszoob); - /* Fill the partition_info structure */ - ns->part.name = "NAND simulator partition"; - ns->part.offset = 0; - ns->part.size = ns->geom.totsz; - return 0; error: -#ifdef CONFIG_NS_ABS_POS - iounmap(ns->mem.byte); -#else - vfree(ns->mem.byte); -#endif + free_device(ns); - return -ENOMEM; + return ret; } /* * Free the nandsim structure. */ -static void -free_nandsim(struct nandsim *ns) +static void free_nandsim(struct nandsim *ns) { kfree(ns->buf.byte); - -#ifdef CONFIG_NS_ABS_POS - iounmap(ns->mem.byte); -#else - vfree(ns->mem.byte); -#endif + free_device(ns); return; } +static int parse_badblocks(struct nandsim *ns, struct mtd_info *mtd) +{ + char *w; + int zero_ok; + unsigned int erase_block_no; + loff_t offset; + + if (!badblocks) + return 0; + w = badblocks; + do { + zero_ok = (*w == '0' ? 1 : 0); + erase_block_no = simple_strtoul(w, &w, 0); + if (!zero_ok && !erase_block_no) { + NS_ERR("invalid badblocks.\n"); + return -EINVAL; + } + offset = erase_block_no * ns->geom.secsz; + if (mtd_block_markbad(mtd, offset)) { + NS_ERR("invalid badblocks.\n"); + return -EINVAL; + } + if (*w == ',') + w += 1; + } while (*w); + return 0; +} + +static int parse_weakblocks(void) +{ + char *w; + int zero_ok; + unsigned int erase_block_no; + unsigned int max_erases; + struct weak_block *wb; + + if (!weakblocks) + return 0; + w = weakblocks; + do { + zero_ok = (*w == '0' ? 1 : 0); + erase_block_no = simple_strtoul(w, &w, 0); + if (!zero_ok && !erase_block_no) { + NS_ERR("invalid weakblocks.\n"); + return -EINVAL; + } + max_erases = 3; + if (*w == ':') { + w += 1; + max_erases = simple_strtoul(w, &w, 0); + } + if (*w == ',') + w += 1; + wb = kzalloc(sizeof(*wb), GFP_KERNEL); + if (!wb) { + NS_ERR("unable to allocate memory.\n"); + return -ENOMEM; + } + wb->erase_block_no = erase_block_no; + wb->max_erases = max_erases; + list_add(&wb->list, &weak_blocks); + } while (*w); + return 0; +} + +static int erase_error(unsigned int erase_block_no) +{ + struct weak_block *wb; + + list_for_each_entry(wb, &weak_blocks, list) + if (wb->erase_block_no == erase_block_no) { + if (wb->erases_done >= wb->max_erases) + return 1; + wb->erases_done += 1; + return 0; + } + return 0; +} + +static int parse_weakpages(void) +{ + char *w; + int zero_ok; + unsigned int page_no; + unsigned int max_writes; + struct weak_page *wp; + + if (!weakpages) + return 0; + w = weakpages; + do { + zero_ok = (*w == '0' ? 1 : 0); + page_no = simple_strtoul(w, &w, 0); + if (!zero_ok && !page_no) { + NS_ERR("invalid weakpagess.\n"); + return -EINVAL; + } + max_writes = 3; + if (*w == ':') { + w += 1; + max_writes = simple_strtoul(w, &w, 0); + } + if (*w == ',') + w += 1; + wp = kzalloc(sizeof(*wp), GFP_KERNEL); + if (!wp) { + NS_ERR("unable to allocate memory.\n"); + return -ENOMEM; + } + wp->page_no = page_no; + wp->max_writes = max_writes; + list_add(&wp->list, &weak_pages); + } while (*w); + return 0; +} + +static int write_error(unsigned int page_no) +{ + struct weak_page *wp; + + list_for_each_entry(wp, &weak_pages, list) + if (wp->page_no == page_no) { + if (wp->writes_done >= wp->max_writes) + return 1; + wp->writes_done += 1; + return 0; + } + return 0; +} + +static int parse_gravepages(void) +{ + char *g; + int zero_ok; + unsigned int page_no; + unsigned int max_reads; + struct grave_page *gp; + + if (!gravepages) + return 0; + g = gravepages; + do { + zero_ok = (*g == '0' ? 1 : 0); + page_no = simple_strtoul(g, &g, 0); + if (!zero_ok && !page_no) { + NS_ERR("invalid gravepagess.\n"); + return -EINVAL; + } + max_reads = 3; + if (*g == ':') { + g += 1; + max_reads = simple_strtoul(g, &g, 0); + } + if (*g == ',') + g += 1; + gp = kzalloc(sizeof(*gp), GFP_KERNEL); + if (!gp) { + NS_ERR("unable to allocate memory.\n"); + return -ENOMEM; + } + gp->page_no = page_no; + gp->max_reads = max_reads; + list_add(&gp->list, &grave_pages); + } while (*g); + return 0; +} + +static int read_error(unsigned int page_no) +{ + struct grave_page *gp; + + list_for_each_entry(gp, &grave_pages, list) + if (gp->page_no == page_no) { + if (gp->reads_done >= gp->max_reads) + return 1; + gp->reads_done += 1; + return 0; + } + return 0; +} + +static void free_lists(void) +{ + struct list_head *pos, *n; + list_for_each_safe(pos, n, &weak_blocks) { + list_del(pos); + kfree(list_entry(pos, struct weak_block, list)); + } + list_for_each_safe(pos, n, &weak_pages) { + list_del(pos); + kfree(list_entry(pos, struct weak_page, list)); + } + list_for_each_safe(pos, n, &grave_pages) { + list_del(pos); + kfree(list_entry(pos, struct grave_page, list)); + } + kfree(erase_block_wear); +} + +static int setup_wear_reporting(struct mtd_info *mtd) +{ + size_t mem; + + wear_eb_count = div_u64(mtd->size, mtd->erasesize); + mem = wear_eb_count * sizeof(unsigned long); + if (mem / sizeof(unsigned long) != wear_eb_count) { + NS_ERR("Too many erase blocks for wear reporting\n"); + return -ENOMEM; + } + erase_block_wear = kzalloc(mem, GFP_KERNEL); + if (!erase_block_wear) { + NS_ERR("Too many erase blocks for wear reporting\n"); + return -ENOMEM; + } + return 0; +} + +static void update_wear(unsigned int erase_block_no) +{ + if (!erase_block_wear) + return; + total_wear += 1; + /* + * TODO: Notify this through a debugfs entry, + * instead of showing an error message. + */ + if (total_wear == 0) + NS_ERR("Erase counter total overflow\n"); + erase_block_wear[erase_block_no] += 1; + if (erase_block_wear[erase_block_no] == 0) + NS_ERR("Erase counter overflow for erase block %u\n", erase_block_no); +} + /* * Returns the string representation of 'state' state. */ -static char * -get_state_name(uint32_t state) +static char *get_state_name(uint32_t state) { switch (NS_STATE(state)) { case STATE_CMD_READ0: @@ -521,8 +1063,6 @@ get_state_name(uint32_t state) return "STATE_CMD_ERASE1"; case STATE_CMD_STATUS: return "STATE_CMD_STATUS"; - case STATE_CMD_STATUS_M: - return "STATE_CMD_STATUS_M"; case STATE_CMD_SEQIN: return "STATE_CMD_SEQIN"; case STATE_CMD_READID: @@ -531,12 +1071,18 @@ get_state_name(uint32_t state) return "STATE_CMD_ERASE2"; case STATE_CMD_RESET: return "STATE_CMD_RESET"; + case STATE_CMD_RNDOUT: + return "STATE_CMD_RNDOUT"; + case STATE_CMD_RNDOUTSTART: + return "STATE_CMD_RNDOUTSTART"; case STATE_ADDR_PAGE: return "STATE_ADDR_PAGE"; case STATE_ADDR_SEC: return "STATE_ADDR_SEC"; case STATE_ADDR_ZERO: return "STATE_ADDR_ZERO"; + case STATE_ADDR_COLUMN: + return "STATE_ADDR_COLUMN"; case STATE_DATAIN: return "STATE_DATAIN"; case STATE_DATAOUT: @@ -562,12 +1108,12 @@ get_state_name(uint32_t state) * * RETURNS: 1 if wrong command, 0 if right. */ -static int -check_command(int cmd) +static int check_command(int cmd) { switch (cmd) { case NAND_CMD_READ0: + case NAND_CMD_READ1: case NAND_CMD_READSTART: case NAND_CMD_PAGEPROG: case NAND_CMD_READOOB: @@ -577,10 +1123,10 @@ check_command(int cmd) case NAND_CMD_READID: case NAND_CMD_ERASE2: case NAND_CMD_RESET: - case NAND_CMD_READ1: + case NAND_CMD_RNDOUT: + case NAND_CMD_RNDOUTSTART: return 0; - case NAND_CMD_STATUS_MULTI: default: return 1; } @@ -589,8 +1135,7 @@ check_command(int cmd) /* * Returns state after command is accepted by command number. */ -static uint32_t -get_state_by_command(unsigned command) +static uint32_t get_state_by_command(unsigned command) { switch (command) { case NAND_CMD_READ0: @@ -607,8 +1152,6 @@ get_state_by_command(unsigned command) return STATE_CMD_ERASE1; case NAND_CMD_STATUS: return STATE_CMD_STATUS; - case NAND_CMD_STATUS_MULTI: - return STATE_CMD_STATUS_M; case NAND_CMD_SEQIN: return STATE_CMD_SEQIN; case NAND_CMD_READID: @@ -617,6 +1160,10 @@ get_state_by_command(unsigned command) return STATE_CMD_ERASE2; case NAND_CMD_RESET: return STATE_CMD_RESET; + case NAND_CMD_RNDOUT: + return STATE_CMD_RNDOUT; + case NAND_CMD_RNDOUTSTART: + return STATE_CMD_RNDOUTSTART; } NS_ERR("get_state_by_command: unknown command, BUG\n"); @@ -626,8 +1173,7 @@ get_state_by_command(unsigned command) /* * Move an address byte to the correspondent internal register. */ -static inline void -accept_addr_byte(struct nandsim *ns, u_char bt) +static inline void accept_addr_byte(struct nandsim *ns, u_char bt) { uint byte = (uint)bt; @@ -645,8 +1191,7 @@ accept_addr_byte(struct nandsim *ns, u_char bt) /* * Switch to STATE_READY state. */ -static inline void -switch_to_ready_state(struct nandsim *ns, u_char status) +static inline void switch_to_ready_state(struct nandsim *ns, u_char status) { NS_DBG("switch_to_ready_state: switch to %s state\n", get_state_name(STATE_READY)); @@ -668,9 +1213,9 @@ switch_to_ready_state(struct nandsim *ns, u_char status) * of supported operations. * * Operation can be unknown because of the following. - * 1. New command was accepted and this is the firs call to find the + * 1. New command was accepted and this is the first call to find the * correspondent states chain. In this case ns->npstates = 0; - * 2. There is several operations which begin with the same command(s) + * 2. There are several operations which begin with the same command(s) * (for example program from the second half and read from the * second half operations both begin with the READ1 command). In this * case the ns->pstates[] array contains previous states. @@ -683,7 +1228,7 @@ switch_to_ready_state(struct nandsim *ns, u_char status) * ns->ops, ns->state, ns->nxstate are initialized, ns->npstate is * zeroed). * - * If there are several maches, the current state is pushed to the + * If there are several matches, the current state is pushed to the * ns->pstates. * * The operation can be unknown only while commands are input to the chip. @@ -692,10 +1237,10 @@ switch_to_ready_state(struct nandsim *ns, u_char status) * operation is searched using the following pattern: * ns->pstates[0], ... ns->pstates[ns->npstates], <address input> * - * It is supposed that this pattern must either match one operation on + * It is supposed that this pattern must either match one operation or * none. There can't be ambiguity in that case. * - * If no matches found, the functions does the following: + * If no matches found, the function does the following: * 1. if there are saved states present, try to ignore them and search * again only using the last command. If nothing was found, switch * to the STATE_READY state. @@ -705,8 +1250,7 @@ switch_to_ready_state(struct nandsim *ns, u_char status) * -1 - several matches. * 0 - operation is found. */ -static int -find_operation(struct nandsim *ns, uint32_t flag) +static int find_operation(struct nandsim *ns, uint32_t flag) { int opsfound = 0; int i, j, idx = 0; @@ -790,16 +1334,288 @@ find_operation(struct nandsim *ns, uint32_t flag) return -1; } +static void put_pages(struct nandsim *ns) +{ + int i; + + for (i = 0; i < ns->held_cnt; i++) + page_cache_release(ns->held_pages[i]); +} + +/* Get page cache pages in advance to provide NOFS memory allocation */ +static int get_pages(struct nandsim *ns, struct file *file, size_t count, loff_t pos) +{ + pgoff_t index, start_index, end_index; + struct page *page; + struct address_space *mapping = file->f_mapping; + + start_index = pos >> PAGE_CACHE_SHIFT; + end_index = (pos + count - 1) >> PAGE_CACHE_SHIFT; + if (end_index - start_index + 1 > NS_MAX_HELD_PAGES) + return -EINVAL; + ns->held_cnt = 0; + for (index = start_index; index <= end_index; index++) { + page = find_get_page(mapping, index); + if (page == NULL) { + page = find_or_create_page(mapping, index, GFP_NOFS); + if (page == NULL) { + write_inode_now(mapping->host, 1); + page = find_or_create_page(mapping, index, GFP_NOFS); + } + if (page == NULL) { + put_pages(ns); + return -ENOMEM; + } + unlock_page(page); + } + ns->held_pages[ns->held_cnt++] = page; + } + return 0; +} + +static int set_memalloc(void) +{ + if (current->flags & PF_MEMALLOC) + return 0; + current->flags |= PF_MEMALLOC; + return 1; +} + +static void clear_memalloc(int memalloc) +{ + if (memalloc) + current->flags &= ~PF_MEMALLOC; +} + +static ssize_t read_file(struct nandsim *ns, struct file *file, void *buf, size_t count, loff_t pos) +{ + ssize_t tx; + int err, memalloc; + + err = get_pages(ns, file, count, pos); + if (err) + return err; + memalloc = set_memalloc(); + tx = kernel_read(file, pos, buf, count); + clear_memalloc(memalloc); + put_pages(ns); + return tx; +} + +static ssize_t write_file(struct nandsim *ns, struct file *file, void *buf, size_t count, loff_t pos) +{ + ssize_t tx; + int err, memalloc; + + err = get_pages(ns, file, count, pos); + if (err) + return err; + memalloc = set_memalloc(); + tx = kernel_write(file, buf, count, pos); + clear_memalloc(memalloc); + put_pages(ns); + return tx; +} + +/* + * Returns a pointer to the current page. + */ +static inline union ns_mem *NS_GET_PAGE(struct nandsim *ns) +{ + return &(ns->pages[ns->regs.row]); +} + +/* + * Retuns a pointer to the current byte, within the current page. + */ +static inline u_char *NS_PAGE_BYTE_OFF(struct nandsim *ns) +{ + return NS_GET_PAGE(ns)->byte + ns->regs.column + ns->regs.off; +} + +static int do_read_error(struct nandsim *ns, int num) +{ + unsigned int page_no = ns->regs.row; + + if (read_error(page_no)) { + prandom_bytes(ns->buf.byte, num); + NS_WARN("simulating read error in page %u\n", page_no); + return 1; + } + return 0; +} + +static void do_bit_flips(struct nandsim *ns, int num) +{ + if (bitflips && prandom_u32() < (1 << 22)) { + int flips = 1; + if (bitflips > 1) + flips = (prandom_u32() % (int) bitflips) + 1; + while (flips--) { + int pos = prandom_u32() % (num * 8); + ns->buf.byte[pos / 8] ^= (1 << (pos % 8)); + NS_WARN("read_page: flipping bit %d in page %d " + "reading from %d ecc: corrected=%u failed=%u\n", + pos, ns->regs.row, ns->regs.column + ns->regs.off, + nsmtd->ecc_stats.corrected, nsmtd->ecc_stats.failed); + } + } +} + +/* + * Fill the NAND buffer with data read from the specified page. + */ +static void read_page(struct nandsim *ns, int num) +{ + union ns_mem *mypage; + + if (ns->cfile) { + if (!test_bit(ns->regs.row, ns->pages_written)) { + NS_DBG("read_page: page %d not written\n", ns->regs.row); + memset(ns->buf.byte, 0xFF, num); + } else { + loff_t pos; + ssize_t tx; + + NS_DBG("read_page: page %d written, reading from %d\n", + ns->regs.row, ns->regs.column + ns->regs.off); + if (do_read_error(ns, num)) + return; + pos = (loff_t)NS_RAW_OFFSET(ns) + ns->regs.off; + tx = read_file(ns, ns->cfile, ns->buf.byte, num, pos); + if (tx != num) { + NS_ERR("read_page: read error for page %d ret %ld\n", ns->regs.row, (long)tx); + return; + } + do_bit_flips(ns, num); + } + return; + } + + mypage = NS_GET_PAGE(ns); + if (mypage->byte == NULL) { + NS_DBG("read_page: page %d not allocated\n", ns->regs.row); + memset(ns->buf.byte, 0xFF, num); + } else { + NS_DBG("read_page: page %d allocated, reading from %d\n", + ns->regs.row, ns->regs.column + ns->regs.off); + if (do_read_error(ns, num)) + return; + memcpy(ns->buf.byte, NS_PAGE_BYTE_OFF(ns), num); + do_bit_flips(ns, num); + } +} + +/* + * Erase all pages in the specified sector. + */ +static void erase_sector(struct nandsim *ns) +{ + union ns_mem *mypage; + int i; + + if (ns->cfile) { + for (i = 0; i < ns->geom.pgsec; i++) + if (__test_and_clear_bit(ns->regs.row + i, + ns->pages_written)) { + NS_DBG("erase_sector: freeing page %d\n", ns->regs.row + i); + } + return; + } + + mypage = NS_GET_PAGE(ns); + for (i = 0; i < ns->geom.pgsec; i++) { + if (mypage->byte != NULL) { + NS_DBG("erase_sector: freeing page %d\n", ns->regs.row+i); + kmem_cache_free(ns->nand_pages_slab, mypage->byte); + mypage->byte = NULL; + } + mypage++; + } +} + +/* + * Program the specified page with the contents from the NAND buffer. + */ +static int prog_page(struct nandsim *ns, int num) +{ + int i; + union ns_mem *mypage; + u_char *pg_off; + + if (ns->cfile) { + loff_t off; + ssize_t tx; + int all; + + NS_DBG("prog_page: writing page %d\n", ns->regs.row); + pg_off = ns->file_buf + ns->regs.column + ns->regs.off; + off = (loff_t)NS_RAW_OFFSET(ns) + ns->regs.off; + if (!test_bit(ns->regs.row, ns->pages_written)) { + all = 1; + memset(ns->file_buf, 0xff, ns->geom.pgszoob); + } else { + all = 0; + tx = read_file(ns, ns->cfile, pg_off, num, off); + if (tx != num) { + NS_ERR("prog_page: read error for page %d ret %ld\n", ns->regs.row, (long)tx); + return -1; + } + } + for (i = 0; i < num; i++) + pg_off[i] &= ns->buf.byte[i]; + if (all) { + loff_t pos = (loff_t)ns->regs.row * ns->geom.pgszoob; + tx = write_file(ns, ns->cfile, ns->file_buf, ns->geom.pgszoob, pos); + if (tx != ns->geom.pgszoob) { + NS_ERR("prog_page: write error for page %d ret %ld\n", ns->regs.row, (long)tx); + return -1; + } + __set_bit(ns->regs.row, ns->pages_written); + } else { + tx = write_file(ns, ns->cfile, pg_off, num, off); + if (tx != num) { + NS_ERR("prog_page: write error for page %d ret %ld\n", ns->regs.row, (long)tx); + return -1; + } + } + return 0; + } + + mypage = NS_GET_PAGE(ns); + if (mypage->byte == NULL) { + NS_DBG("prog_page: allocating page %d\n", ns->regs.row); + /* + * We allocate memory with GFP_NOFS because a flash FS may + * utilize this. If it is holding an FS lock, then gets here, + * then kernel memory alloc runs writeback which goes to the FS + * again and deadlocks. This was seen in practice. + */ + mypage->byte = kmem_cache_alloc(ns->nand_pages_slab, GFP_NOFS); + if (mypage->byte == NULL) { + NS_ERR("prog_page: error allocating memory for page %d\n", ns->regs.row); + return -1; + } + memset(mypage->byte, 0xFF, ns->geom.pgszoob); + } + + pg_off = NS_PAGE_BYTE_OFF(ns); + for (i = 0; i < num; i++) + pg_off[i] &= ns->buf.byte[i]; + + return 0; +} + /* * If state has any action bit, perform this action. * * RETURNS: 0 if success, -1 if error. */ -static int -do_state_action(struct nandsim *ns, uint32_t action) +static int do_state_action(struct nandsim *ns, uint32_t action) { - int i, num; + int num; int busdiv = ns->busw == 8 ? 1 : 2; + unsigned int erase_block_no, page_no; action &= ACTION_MASK; @@ -822,7 +1638,7 @@ do_state_action(struct nandsim *ns, uint32_t action) break; } num = ns->geom.pgszoob - ns->regs.off - ns->regs.column; - memcpy(ns->buf.byte, ns->mem.byte + NS_RAW_OFFSET(ns) + ns->regs.off, num); + read_page(ns, num); NS_DBG("do_state_action: (ACTION_CPY:) copy %d bytes to int buf, raw offset %d\n", num, NS_RAW_OFFSET(ns) + ns->regs.off); @@ -859,19 +1675,29 @@ do_state_action(struct nandsim *ns, uint32_t action) 8 * (ns->geom.pgaddrbytes - ns->geom.secaddrbytes)) | ns->regs.column; ns->regs.column = 0; + erase_block_no = ns->regs.row >> (ns->geom.secshift - ns->geom.pgshift); + NS_DBG("do_state_action: erase sector at address %#x, off = %d\n", ns->regs.row, NS_RAW_OFFSET(ns)); - NS_LOG("erase sector %d\n", ns->regs.row >> (ns->geom.secshift - ns->geom.pgshift)); + NS_LOG("erase sector %u\n", erase_block_no); - memset(ns->mem.byte + NS_RAW_OFFSET(ns), 0xFF, ns->geom.secszoob); + erase_sector(ns); NS_MDELAY(erase_delay); + if (erase_block_wear) + update_wear(erase_block_no); + + if (erase_error(erase_block_no)) { + NS_WARN("simulating erase failure in erase block %u\n", erase_block_no); + return -1; + } + break; case ACTION_PRGPAGE: /* - * Programm page - move internal buffer data to the page. + * Program page - move internal buffer data to the page. */ if (ns->lines.wp) { @@ -886,8 +1712,10 @@ do_state_action(struct nandsim *ns, uint32_t action) return -1; } - for (i = 0; i < num; i++) - ns->mem.byte[NS_RAW_OFFSET(ns) + ns->regs.off + i] &= ns->buf.byte[i]; + if (prog_page(ns, num) == -1) + return -1; + + page_no = ns->regs.row; NS_DBG("do_state_action: copy %d bytes from int buf to (%#x, %#x), raw off = %d\n", num, ns->regs.row, ns->regs.column, NS_RAW_OFFSET(ns) + ns->regs.off); @@ -896,6 +1724,11 @@ do_state_action(struct nandsim *ns, uint32_t action) NS_UDELAY(programm_delay); NS_UDELAY(output_cycle * ns->geom.pgsz / 1000 / busdiv); + if (write_error(page_no)) { + NS_WARN("simulating write failure in page %u\n", page_no); + return -1; + } + break; case ACTION_ZEROOFF: @@ -928,8 +1761,7 @@ do_state_action(struct nandsim *ns, uint32_t action) /* * Switch simulator's state. */ -static void -switch_state(struct nandsim *ns) +static void switch_state(struct nandsim *ns) { if (ns->op) { /* @@ -1057,6 +1889,11 @@ switch_state(struct nandsim *ns) ns->regs.num = 1; break; + case STATE_ADDR_COLUMN: + /* Column address is always 2 bytes */ + ns->regs.num = ns->geom.pgaddrbytes - ns->geom.secaddrbytes; + break; + default: NS_ERR("switch_state: BUG! unknown address state\n"); } @@ -1070,10 +1907,9 @@ switch_state(struct nandsim *ns) } } -static u_char -ns_nand_read_byte(struct mtd_info *mtd) +static u_char ns_nand_read_byte(struct mtd_info *mtd) { - struct nandsim *ns = (struct nandsim *)((struct nand_chip *)mtd->priv)->priv; + struct nandsim *ns = ((struct nand_chip *)mtd->priv)->priv; u_char outb = 0x00; /* Sanity and correctness checks */ @@ -1125,29 +1961,16 @@ ns_nand_read_byte(struct mtd_info *mtd) if (ns->regs.count == ns->regs.num) { NS_DBG("read_byte: all bytes were read\n"); - /* - * The OPT_AUTOINCR allows to read next conseqitive pages without - * new read operation cycle. - */ - if ((ns->options & OPT_AUTOINCR) && NS_STATE(ns->state) == STATE_DATAOUT) { - ns->regs.count = 0; - if (ns->regs.row + 1 < ns->geom.pgnum) - ns->regs.row += 1; - NS_DBG("read_byte: switch to the next page (%#x)\n", ns->regs.row); - do_state_action(ns, ACTION_CPY); - } - else if (NS_STATE(ns->nxstate) == STATE_READY) + if (NS_STATE(ns->nxstate) == STATE_READY) switch_state(ns); - } return outb; } -static void -ns_nand_write_byte(struct mtd_info *mtd, u_char byte) +static void ns_nand_write_byte(struct mtd_info *mtd, u_char byte) { - struct nandsim *ns = (struct nandsim *)((struct nand_chip *)mtd->priv)->priv; + struct nandsim *ns = ((struct nand_chip *)mtd->priv)->priv; /* Sanity and correctness checks */ if (!ns->lines.ce) { @@ -1170,34 +1993,38 @@ ns_nand_write_byte(struct mtd_info *mtd, u_char byte) return; } - /* - * Chip might still be in STATE_DATAOUT - * (if OPT_AUTOINCR feature is supported), STATE_DATAOUT_STATUS or - * STATE_DATAOUT_STATUS_M state. If so, switch state. - */ + /* Check that the command byte is correct */ + if (check_command(byte)) { + NS_ERR("write_byte: unknown command %#x\n", (uint)byte); + return; + } + if (NS_STATE(ns->state) == STATE_DATAOUT_STATUS || NS_STATE(ns->state) == STATE_DATAOUT_STATUS_M - || ((ns->options & OPT_AUTOINCR) && NS_STATE(ns->state) == STATE_DATAOUT)) + || NS_STATE(ns->state) == STATE_DATAOUT) { + int row = ns->regs.row; + switch_state(ns); + if (byte == NAND_CMD_RNDOUT) + ns->regs.row = row; + } /* Check if chip is expecting command */ if (NS_STATE(ns->nxstate) != STATE_UNKNOWN && !(ns->nxstate & STATE_CMD_MASK)) { - /* - * We are in situation when something else (not command) - * was expected but command was input. In this case ignore - * previous command(s)/state(s) and accept the last one. - */ - NS_WARN("write_byte: command (%#x) wasn't expected, expected state is %s, " - "ignore previous states\n", (uint)byte, get_state_name(ns->nxstate)); + /* Do not warn if only 2 id bytes are read */ + if (!(ns->regs.command == NAND_CMD_READID && + NS_STATE(ns->state) == STATE_DATAOUT_ID && ns->regs.count == 2)) { + /* + * We are in situation when something else (not command) + * was expected but command was input. In this case ignore + * previous command(s)/state(s) and accept the last one. + */ + NS_WARN("write_byte: command (%#x) wasn't expected, expected state is %s, " + "ignore previous states\n", (uint)byte, get_state_name(ns->nxstate)); + } switch_to_ready_state(ns, NS_STATUS_FAILED(ns)); } - /* Check that the command byte is correct */ - if (check_command(byte)) { - NS_ERR("write_byte: unknown command %#x\n", (uint)byte); - return; - } - NS_DBG("command byte corresponding to %s state accepted\n", get_state_name(get_state_by_command(byte))); ns->regs.command = byte; @@ -1308,15 +2135,13 @@ static void ns_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int bitmask) ns_nand_write_byte(mtd, cmd); } -static int -ns_device_ready(struct mtd_info *mtd) +static int ns_device_ready(struct mtd_info *mtd) { NS_DBG("device_ready\n"); return 1; } -static uint16_t -ns_nand_read_word(struct mtd_info *mtd) +static uint16_t ns_nand_read_word(struct mtd_info *mtd) { struct nand_chip *chip = (struct nand_chip *)mtd->priv; @@ -1325,10 +2150,9 @@ ns_nand_read_word(struct mtd_info *mtd) return chip->read_byte(mtd) | (chip->read_byte(mtd) << 8); } -static void -ns_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len) +static void ns_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len) { - struct nandsim *ns = (struct nandsim *)((struct nand_chip *)mtd->priv)->priv; + struct nandsim *ns = ((struct nand_chip *)mtd->priv)->priv; /* Check that chip is expecting data input */ if (!(ns->state & STATE_DATAIN_MASK)) { @@ -1353,10 +2177,9 @@ ns_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len) } } -static void -ns_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len) +static void ns_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len) { - struct nandsim *ns = (struct nandsim *)((struct nand_chip *)mtd->priv)->priv; + struct nandsim *ns = ((struct nand_chip *)mtd->priv)->priv; /* Sanity and correctness checks */ if (!ns->lines.ce) { @@ -1393,34 +2216,13 @@ ns_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len) ns->regs.count += len; if (ns->regs.count == ns->regs.num) { - if ((ns->options & OPT_AUTOINCR) && NS_STATE(ns->state) == STATE_DATAOUT) { - ns->regs.count = 0; - if (ns->regs.row + 1 < ns->geom.pgnum) - ns->regs.row += 1; - NS_DBG("read_buf: switch to the next page (%#x)\n", ns->regs.row); - do_state_action(ns, ACTION_CPY); - } - else if (NS_STATE(ns->nxstate) == STATE_READY) + if (NS_STATE(ns->nxstate) == STATE_READY) switch_state(ns); } return; } -static int -ns_nand_verify_buf(struct mtd_info *mtd, const u_char *buf, int len) -{ - ns_nand_read_buf(mtd, (u_char *)&ns_verify_buf[0], len); - - if (!memcmp(buf, &ns_verify_buf[0], len)) { - NS_DBG("verify_buf: the buffer is OK\n"); - return 0; - } else { - NS_DBG("verify_buf: the buffer is wrong\n"); - return -EFAULT; - } -} - /* * Module initialization function */ @@ -1428,7 +2230,7 @@ static int __init ns_init_module(void) { struct nand_chip *chip; struct nandsim *nand; - int retval = -ENOMEM; + int retval = -ENOMEM, i; if (bus_width != 8 && bus_width != 16) { NS_ERR("wrong bus width (%d), use only 8 or 16\n", bus_width); @@ -1436,14 +2238,12 @@ static int __init ns_init_module(void) } /* Allocate and initialize mtd_info, nand_chip and nandsim structures */ - nsmtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip) + nsmtd = kzalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip) + sizeof(struct nandsim), GFP_KERNEL); if (!nsmtd) { NS_ERR("unable to allocate core structures.\n"); return -ENOMEM; } - memset(nsmtd, 0, sizeof(struct mtd_info) + sizeof(struct nand_chip) + - sizeof(struct nandsim)); chip = (struct nand_chip *)(nsmtd + 1); nsmtd->priv = (void *)chip; nand = (struct nandsim *)(chip + 1); @@ -1457,11 +2257,24 @@ static int __init ns_init_module(void) chip->dev_ready = ns_device_ready; chip->write_buf = ns_nand_write_buf; chip->read_buf = ns_nand_read_buf; - chip->verify_buf = ns_nand_verify_buf; chip->read_word = ns_nand_read_word; chip->ecc.mode = NAND_ECC_SOFT; + /* The NAND_SKIP_BBTSCAN option is necessary for 'overridesize' */ + /* and 'badblocks' parameters to work */ chip->options |= NAND_SKIP_BBTSCAN; + switch (bbt) { + case 2: + chip->bbt_options |= NAND_BBT_NO_OOB; + case 1: + chip->bbt_options |= NAND_BBT_USE_FLASH; + case 0: + break; + default: + NS_ERR("bbt has to be 0..2\n"); + retval = -EINVAL; + goto error; + } /* * Perform minimum nandsim structure initialization to handle * the initial ID read command correctly @@ -1472,7 +2285,7 @@ static int __init ns_init_module(void) nand->geom.idbytes = 2; nand->regs.status = NS_STATUS_OK(nand); nand->nxstate = STATE_UNKNOWN; - nand->options |= OPT_PAGE256; /* temporary value */ + nand->options |= OPT_PAGE512; /* temporary value */ nand->ids[0] = first_id_byte; nand->ids[1] = second_id_byte; nand->ids[2] = third_id_byte; @@ -1484,30 +2297,103 @@ static int __init ns_init_module(void) nsmtd->owner = THIS_MODULE; - if ((retval = nand_scan(nsmtd, 1)) != 0) { - NS_ERR("can't register NAND Simulator\n"); + if ((retval = parse_weakblocks()) != 0) + goto error; + + if ((retval = parse_weakpages()) != 0) + goto error; + + if ((retval = parse_gravepages()) != 0) + goto error; + + retval = nand_scan_ident(nsmtd, 1, NULL); + if (retval) { + NS_ERR("cannot scan NAND Simulator device\n"); if (retval > 0) retval = -ENXIO; goto error; } - if ((retval = init_nandsim(nsmtd)) != 0) { - NS_ERR("scan_bbt: can't initialize the nandsim structure\n"); - goto error; + if (bch) { + unsigned int eccsteps, eccbytes; + if (!mtd_nand_has_bch()) { + NS_ERR("BCH ECC support is disabled\n"); + retval = -EINVAL; + goto error; + } + /* use 512-byte ecc blocks */ + eccsteps = nsmtd->writesize/512; + eccbytes = (bch*13+7)/8; + /* do not bother supporting small page devices */ + if ((nsmtd->oobsize < 64) || !eccsteps) { + NS_ERR("bch not available on small page devices\n"); + retval = -EINVAL; + goto error; + } + if ((eccbytes*eccsteps+2) > nsmtd->oobsize) { + NS_ERR("invalid bch value %u\n", bch); + retval = -EINVAL; + goto error; + } + chip->ecc.mode = NAND_ECC_SOFT_BCH; + chip->ecc.size = 512; + chip->ecc.bytes = eccbytes; + NS_INFO("using %u-bit/%u bytes BCH ECC\n", bch, chip->ecc.size); } - if ((retval = nand_default_bbt(nsmtd)) != 0) { - free_nandsim(nand); + retval = nand_scan_tail(nsmtd); + if (retval) { + NS_ERR("can't register NAND Simulator\n"); + if (retval > 0) + retval = -ENXIO; goto error; } - /* Register NAND as one big partition */ - add_mtd_partitions(nsmtd, &nand->part, 1); + if (overridesize) { + uint64_t new_size = (uint64_t)nsmtd->erasesize << overridesize; + if (new_size >> overridesize != nsmtd->erasesize) { + NS_ERR("overridesize is too big\n"); + retval = -EINVAL; + goto err_exit; + } + /* N.B. This relies on nand_scan not doing anything with the size before we change it */ + nsmtd->size = new_size; + chip->chipsize = new_size; + chip->chip_shift = ffs(nsmtd->erasesize) + overridesize - 1; + chip->pagemask = (chip->chipsize >> chip->page_shift) - 1; + } + + if ((retval = setup_wear_reporting(nsmtd)) != 0) + goto err_exit; + + if ((retval = nandsim_debugfs_create(nand)) != 0) + goto err_exit; + + if ((retval = init_nandsim(nsmtd)) != 0) + goto err_exit; + + if ((retval = chip->scan_bbt(nsmtd)) != 0) + goto err_exit; + + if ((retval = parse_badblocks(nand, nsmtd)) != 0) + goto err_exit; + + /* Register NAND partitions */ + retval = mtd_device_register(nsmtd, &nand->partitions[0], + nand->nbparts); + if (retval != 0) + goto err_exit; return 0; +err_exit: + free_nandsim(nand); + nand_release(nsmtd); + for (i = 0;i < ARRAY_SIZE(nand->partitions); ++i) + kfree(nand->partitions[i].name); error: kfree(nsmtd); + free_lists(); return retval; } @@ -1519,11 +2405,16 @@ module_init(ns_init_module); */ static void __exit ns_cleanup_module(void) { - struct nandsim *ns = (struct nandsim *)(((struct nand_chip *)nsmtd->priv)->priv); + struct nandsim *ns = ((struct nand_chip *)nsmtd->priv)->priv; + int i; + nandsim_debugfs_remove(ns); free_nandsim(ns); /* Free nandsim private resources */ - nand_release(nsmtd); /* Unregisterd drived */ + nand_release(nsmtd); /* Unregister driver */ + for (i = 0;i < ARRAY_SIZE(ns->partitions); ++i) + kfree(ns->partitions[i].name); kfree(nsmtd); /* Free other structures */ + free_lists(); } module_exit(ns_cleanup_module); @@ -1531,4 +2422,3 @@ module_exit(ns_cleanup_module); MODULE_LICENSE ("GPL"); MODULE_AUTHOR ("Artem B. Bityuckiy"); MODULE_DESCRIPTION ("The NAND flash simulator"); - diff --git a/drivers/mtd/nand/ndfc.c b/drivers/mtd/nand/ndfc.c index 039c759cfbf..69eaba690a9 100644 --- a/drivers/mtd/nand/ndfc.c +++ b/drivers/mtd/nand/ndfc.c @@ -2,12 +2,20 @@ * drivers/mtd/ndfc.c * * Overview: - * Platform independend driver for NDFC (NanD Flash Controller) + * Platform independent driver for NDFC (NanD Flash Controller) * integrated into EP440 cores * + * Ported to an OF platform driver by Sean MacLennan + * + * The NDFC supports multiple chips, but this driver only supports a + * single chip since I do not have access to any boards with + * multiple chips. + * * Author: Thomas Gleixner * * Copyright 2006 IBM + * Copyright 2008 PIKA Technologies + * Sean MacLennan <smaclennan@pikatech.com> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -20,48 +28,44 @@ #include <linux/mtd/nand_ecc.h> #include <linux/mtd/partitions.h> #include <linux/mtd/ndfc.h> +#include <linux/slab.h> #include <linux/mtd/mtd.h> -#include <linux/platform_device.h> - +#include <linux/of_address.h> +#include <linux/of_platform.h> #include <asm/io.h> -#include <asm/ibm44x.h> -struct ndfc_nand_mtd { - struct mtd_info mtd; - struct nand_chip chip; - struct platform_nand_chip *pl_chip; -}; - -static struct ndfc_nand_mtd ndfc_mtd[NDFC_MAX_BANKS]; +#define NDFC_MAX_CS 4 struct ndfc_controller { - void __iomem *ndfcbase; - struct nand_hw_control ndfc_control; - atomic_t childs_active; + struct platform_device *ofdev; + void __iomem *ndfcbase; + struct mtd_info mtd; + struct nand_chip chip; + int chip_select; + struct nand_hw_control ndfc_control; }; -static struct ndfc_controller ndfc_ctrl; +static struct ndfc_controller ndfc_ctrl[NDFC_MAX_CS]; static void ndfc_select_chip(struct mtd_info *mtd, int chip) { uint32_t ccr; - struct ndfc_controller *ndfc = &ndfc_ctrl; - struct nand_chip *nandchip = mtd->priv; - struct ndfc_nand_mtd *nandmtd = nandchip->priv; - struct platform_nand_chip *pchip = nandmtd->pl_chip; + struct nand_chip *nchip = mtd->priv; + struct ndfc_controller *ndfc = nchip->priv; - ccr = __raw_readl(ndfc->ndfcbase + NDFC_CCR); + ccr = in_be32(ndfc->ndfcbase + NDFC_CCR); if (chip >= 0) { ccr &= ~NDFC_CCR_BS_MASK; - ccr |= NDFC_CCR_BS(chip + pchip->chip_offset); + ccr |= NDFC_CCR_BS(chip + ndfc->chip_select); } else ccr |= NDFC_CCR_RESET_CE; - writel(ccr, ndfc->ndfcbase + NDFC_CCR); + out_be32(ndfc->ndfcbase + NDFC_CCR, ccr); } static void ndfc_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl) { - struct ndfc_controller *ndfc = &ndfc_ctrl; + struct nand_chip *chip = mtd->priv; + struct ndfc_controller *ndfc = chip->priv; if (cmd == NAND_CMD_NONE) return; @@ -74,31 +78,35 @@ static void ndfc_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl) static int ndfc_ready(struct mtd_info *mtd) { - struct ndfc_controller *ndfc = &ndfc_ctrl; + struct nand_chip *chip = mtd->priv; + struct ndfc_controller *ndfc = chip->priv; - return __raw_readl(ndfc->ndfcbase + NDFC_STAT) & NDFC_STAT_IS_READY; + return in_be32(ndfc->ndfcbase + NDFC_STAT) & NDFC_STAT_IS_READY; } static void ndfc_enable_hwecc(struct mtd_info *mtd, int mode) { uint32_t ccr; - struct ndfc_controller *ndfc = &ndfc_ctrl; + struct nand_chip *chip = mtd->priv; + struct ndfc_controller *ndfc = chip->priv; - ccr = __raw_readl(ndfc->ndfcbase + NDFC_CCR); + ccr = in_be32(ndfc->ndfcbase + NDFC_CCR); ccr |= NDFC_CCR_RESET_ECC; - __raw_writel(ccr, ndfc->ndfcbase + NDFC_CCR); + out_be32(ndfc->ndfcbase + NDFC_CCR, ccr); wmb(); } static int ndfc_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code) { - struct ndfc_controller *ndfc = &ndfc_ctrl; + struct nand_chip *chip = mtd->priv; + struct ndfc_controller *ndfc = chip->priv; uint32_t ecc; uint8_t *p = (uint8_t *)&ecc; wmb(); - ecc = __raw_readl(ndfc->ndfcbase + NDFC_ECC); + ecc = in_be32(ndfc->ndfcbase + NDFC_ECC); + /* The NDFC uses Smart Media (SMC) bytes order */ ecc_code[0] = p[1]; ecc_code[1] = p[2]; ecc_code[2] = p[3]; @@ -115,40 +123,34 @@ static int ndfc_calculate_ecc(struct mtd_info *mtd, */ static void ndfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) { - struct ndfc_controller *ndfc = &ndfc_ctrl; + struct nand_chip *chip = mtd->priv; + struct ndfc_controller *ndfc = chip->priv; uint32_t *p = (uint32_t *) buf; for(;len > 0; len -= 4) - *p++ = __raw_readl(ndfc->ndfcbase + NDFC_DATA); + *p++ = in_be32(ndfc->ndfcbase + NDFC_DATA); } static void ndfc_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) { - struct ndfc_controller *ndfc = &ndfc_ctrl; + struct nand_chip *chip = mtd->priv; + struct ndfc_controller *ndfc = chip->priv; uint32_t *p = (uint32_t *) buf; for(;len > 0; len -= 4) - __raw_writel(*p++, ndfc->ndfcbase + NDFC_DATA); -} - -static int ndfc_verify_buf(struct mtd_info *mtd, const uint8_t *buf, int len) -{ - struct ndfc_controller *ndfc = &ndfc_ctrl; - uint32_t *p = (uint32_t *) buf; - - for(;len > 0; len -= 4) - if (*p++ != __raw_readl(ndfc->ndfcbase + NDFC_DATA)) - return -EFAULT; - return 0; + out_be32(ndfc->ndfcbase + NDFC_DATA, *p++); } /* * Initialize chip structure */ -static void ndfc_chip_init(struct ndfc_nand_mtd *mtd) +static int ndfc_chip_init(struct ndfc_controller *ndfc, + struct device_node *node) { - struct ndfc_controller *ndfc = &ndfc_ctrl; - struct nand_chip *chip = &mtd->chip; + struct device_node *flash_np; + struct nand_chip *chip = &ndfc->chip; + struct mtd_part_parser_data ppdata; + int ret; chip->IO_ADDR_R = ndfc->ndfcbase + NDFC_DATA; chip->IO_ADDR_W = ndfc->ndfcbase + NDFC_DATA; @@ -156,156 +158,133 @@ static void ndfc_chip_init(struct ndfc_nand_mtd *mtd) chip->dev_ready = ndfc_ready; chip->select_chip = ndfc_select_chip; chip->chip_delay = 50; - chip->priv = mtd; - chip->options = mtd->pl_chip->options; chip->controller = &ndfc->ndfc_control; chip->read_buf = ndfc_read_buf; chip->write_buf = ndfc_write_buf; - chip->verify_buf = ndfc_verify_buf; chip->ecc.correct = nand_correct_data; chip->ecc.hwctl = ndfc_enable_hwecc; chip->ecc.calculate = ndfc_calculate_ecc; chip->ecc.mode = NAND_ECC_HW; chip->ecc.size = 256; chip->ecc.bytes = 3; - chip->ecclayout = chip->ecc.layout = mtd->pl_chip->ecclayout; - mtd->mtd.priv = chip; - mtd->mtd.owner = THIS_MODULE; -} - -static int ndfc_chip_probe(struct platform_device *pdev) -{ - struct platform_nand_chip *nc = pdev->dev.platform_data; - struct ndfc_chip_settings *settings = nc->priv; - struct ndfc_controller *ndfc = &ndfc_ctrl; - struct ndfc_nand_mtd *nandmtd; - - if (nc->chip_offset >= NDFC_MAX_BANKS || nc->nr_chips > NDFC_MAX_BANKS) - return -EINVAL; - - /* Set the bank settings */ - __raw_writel(settings->bank_settings, - ndfc->ndfcbase + NDFC_BCFG0 + (nc->chip_offset << 2)); - - nandmtd = &ndfc_mtd[pdev->id]; - if (nandmtd->pl_chip) - return -EBUSY; + chip->ecc.strength = 1; + chip->priv = ndfc; - nandmtd->pl_chip = nc; - ndfc_chip_init(nandmtd); + ndfc->mtd.priv = chip; + ndfc->mtd.owner = THIS_MODULE; - /* Scan for chips */ - if (nand_scan(&nandmtd->mtd, nc->nr_chips)) { - nandmtd->pl_chip = NULL; + flash_np = of_get_next_child(node, NULL); + if (!flash_np) return -ENODEV; + + ppdata.of_node = flash_np; + ndfc->mtd.name = kasprintf(GFP_KERNEL, "%s.%s", + dev_name(&ndfc->ofdev->dev), flash_np->name); + if (!ndfc->mtd.name) { + ret = -ENOMEM; + goto err; } -#ifdef CONFIG_MTD_PARTITIONS - printk("Number of partitions %d\n", nc->nr_partitions); - if (nc->nr_partitions) { - /* Add the full device, so complete dumps can be made */ - add_mtd_device(&nandmtd->mtd); - add_mtd_partitions(&nandmtd->mtd, nc->partitions, - nc->nr_partitions); + ret = nand_scan(&ndfc->mtd, 1); + if (ret) + goto err; - } else -#else - add_mtd_device(&nandmtd->mtd); -#endif + ret = mtd_device_parse_register(&ndfc->mtd, NULL, &ppdata, NULL, 0); - atomic_inc(&ndfc->childs_active); - return 0; +err: + of_node_put(flash_np); + if (ret) + kfree(ndfc->mtd.name); + return ret; } -static int ndfc_chip_remove(struct platform_device *pdev) +static int ndfc_probe(struct platform_device *ofdev) { - return 0; -} + struct ndfc_controller *ndfc; + const __be32 *reg; + u32 ccr; + int err, len, cs; + + /* Read the reg property to get the chip select */ + reg = of_get_property(ofdev->dev.of_node, "reg", &len); + if (reg == NULL || len != 12) { + dev_err(&ofdev->dev, "unable read reg property (%d)\n", len); + return -ENOENT; + } -static int ndfc_nand_probe(struct platform_device *pdev) -{ - struct platform_nand_ctrl *nc = pdev->dev.platform_data; - struct ndfc_controller_settings *settings = nc->priv; - struct resource *res = pdev->resource; - struct ndfc_controller *ndfc = &ndfc_ctrl; - unsigned long long phys = settings->ndfc_erpn | res->start; + cs = be32_to_cpu(reg[0]); + if (cs >= NDFC_MAX_CS) { + dev_err(&ofdev->dev, "invalid CS number (%d)\n", cs); + return -EINVAL; + } - ndfc->ndfcbase = ioremap64(phys, res->end - res->start + 1); + ndfc = &ndfc_ctrl[cs]; + ndfc->chip_select = cs; + + spin_lock_init(&ndfc->ndfc_control.lock); + init_waitqueue_head(&ndfc->ndfc_control.wq); + ndfc->ofdev = ofdev; + dev_set_drvdata(&ofdev->dev, ndfc); + + ndfc->ndfcbase = of_iomap(ofdev->dev.of_node, 0); if (!ndfc->ndfcbase) { - printk(KERN_ERR "NDFC: ioremap failed\n"); + dev_err(&ofdev->dev, "failed to get memory\n"); return -EIO; } - __raw_writel(settings->ccr_settings, ndfc->ndfcbase + NDFC_CCR); + ccr = NDFC_CCR_BS(ndfc->chip_select); - spin_lock_init(&ndfc->ndfc_control.lock); - init_waitqueue_head(&ndfc->ndfc_control.wq); + /* It is ok if ccr does not exist - just default to 0 */ + reg = of_get_property(ofdev->dev.of_node, "ccr", NULL); + if (reg) + ccr |= be32_to_cpup(reg); - platform_set_drvdata(pdev, ndfc); + out_be32(ndfc->ndfcbase + NDFC_CCR, ccr); - printk("NDFC NAND Driver initialized. Chip-Rev: 0x%08x\n", - __raw_readl(ndfc->ndfcbase + NDFC_REVID)); + /* Set the bank settings if given */ + reg = of_get_property(ofdev->dev.of_node, "bank-settings", NULL); + if (reg) { + int offset = NDFC_BCFG0 + (ndfc->chip_select << 2); + out_be32(ndfc->ndfcbase + offset, be32_to_cpup(reg)); + } + + err = ndfc_chip_init(ndfc, ofdev->dev.of_node); + if (err) { + iounmap(ndfc->ndfcbase); + return err; + } return 0; } -static int ndfc_nand_remove(struct platform_device *pdev) +static int ndfc_remove(struct platform_device *ofdev) { - struct ndfc_controller *ndfc = platform_get_drvdata(pdev); + struct ndfc_controller *ndfc = dev_get_drvdata(&ofdev->dev); - if (atomic_read(&ndfc->childs_active)) - return -EBUSY; + nand_release(&ndfc->mtd); + kfree(ndfc->mtd.name); - if (ndfc) { - platform_set_drvdata(pdev, NULL); - iounmap(ndfc_ctrl.ndfcbase); - ndfc_ctrl.ndfcbase = NULL; - } return 0; } -/* driver device registration */ - -static struct platform_driver ndfc_chip_driver = { - .probe = ndfc_chip_probe, - .remove = ndfc_chip_remove, - .driver = { - .name = "ndfc-chip", - .owner = THIS_MODULE, - }, +static const struct of_device_id ndfc_match[] = { + { .compatible = "ibm,ndfc", }, + {} }; +MODULE_DEVICE_TABLE(of, ndfc_match); -static struct platform_driver ndfc_nand_driver = { - .probe = ndfc_nand_probe, - .remove = ndfc_nand_remove, - .driver = { - .name = "ndfc-nand", - .owner = THIS_MODULE, +static struct platform_driver ndfc_driver = { + .driver = { + .name = "ndfc", + .owner = THIS_MODULE, + .of_match_table = ndfc_match, }, + .probe = ndfc_probe, + .remove = ndfc_remove, }; -static int __init ndfc_nand_init(void) -{ - int ret; - - spin_lock_init(&ndfc_ctrl.ndfc_control.lock); - init_waitqueue_head(&ndfc_ctrl.ndfc_control.wq); - - ret = platform_driver_register(&ndfc_nand_driver); - if (!ret) - ret = platform_driver_register(&ndfc_chip_driver); - return ret; -} - -static void __exit ndfc_nand_exit(void) -{ - platform_driver_unregister(&ndfc_chip_driver); - platform_driver_unregister(&ndfc_nand_driver); -} - -module_init(ndfc_nand_init); -module_exit(ndfc_nand_exit); +module_platform_driver(ndfc_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Thomas Gleixner <tglx@linutronix.de>"); -MODULE_DESCRIPTION("Platform driver for NDFC"); +MODULE_DESCRIPTION("OF Platform driver for NDFC"); diff --git a/drivers/mtd/nand/nuc900_nand.c b/drivers/mtd/nand/nuc900_nand.c new file mode 100644 index 00000000000..e8a5fffd6ab --- /dev/null +++ b/drivers/mtd/nand/nuc900_nand.c @@ -0,0 +1,312 @@ +/* + * Copyright © 2009 Nuvoton technology corporation. + * + * Wan ZongShun <mcuos.com@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation;version 2 of the License. + * + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/err.h> + +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> + +#define REG_FMICSR 0x00 +#define REG_SMCSR 0xa0 +#define REG_SMISR 0xac +#define REG_SMCMD 0xb0 +#define REG_SMADDR 0xb4 +#define REG_SMDATA 0xb8 + +#define RESET_FMI 0x01 +#define NAND_EN 0x08 +#define READYBUSY (0x01 << 18) + +#define SWRST 0x01 +#define PSIZE (0x01 << 3) +#define DMARWEN (0x03 << 1) +#define BUSWID (0x01 << 4) +#define ECC4EN (0x01 << 5) +#define WP (0x01 << 24) +#define NANDCS (0x01 << 25) +#define ENDADDR (0x01 << 31) + +#define read_data_reg(dev) \ + __raw_readl((dev)->reg + REG_SMDATA) + +#define write_data_reg(dev, val) \ + __raw_writel((val), (dev)->reg + REG_SMDATA) + +#define write_cmd_reg(dev, val) \ + __raw_writel((val), (dev)->reg + REG_SMCMD) + +#define write_addr_reg(dev, val) \ + __raw_writel((val), (dev)->reg + REG_SMADDR) + +struct nuc900_nand { + struct mtd_info mtd; + struct nand_chip chip; + void __iomem *reg; + struct clk *clk; + spinlock_t lock; +}; + +static const struct mtd_partition partitions[] = { + { + .name = "NAND FS 0", + .offset = 0, + .size = 8 * 1024 * 1024 + }, + { + .name = "NAND FS 1", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL + } +}; + +static unsigned char nuc900_nand_read_byte(struct mtd_info *mtd) +{ + unsigned char ret; + struct nuc900_nand *nand; + + nand = container_of(mtd, struct nuc900_nand, mtd); + + ret = (unsigned char)read_data_reg(nand); + + return ret; +} + +static void nuc900_nand_read_buf(struct mtd_info *mtd, + unsigned char *buf, int len) +{ + int i; + struct nuc900_nand *nand; + + nand = container_of(mtd, struct nuc900_nand, mtd); + + for (i = 0; i < len; i++) + buf[i] = (unsigned char)read_data_reg(nand); +} + +static void nuc900_nand_write_buf(struct mtd_info *mtd, + const unsigned char *buf, int len) +{ + int i; + struct nuc900_nand *nand; + + nand = container_of(mtd, struct nuc900_nand, mtd); + + for (i = 0; i < len; i++) + write_data_reg(nand, buf[i]); +} + +static int nuc900_check_rb(struct nuc900_nand *nand) +{ + unsigned int val; + spin_lock(&nand->lock); + val = __raw_readl(REG_SMISR); + val &= READYBUSY; + spin_unlock(&nand->lock); + + return val; +} + +static int nuc900_nand_devready(struct mtd_info *mtd) +{ + struct nuc900_nand *nand; + int ready; + + nand = container_of(mtd, struct nuc900_nand, mtd); + + ready = (nuc900_check_rb(nand)) ? 1 : 0; + return ready; +} + +static void nuc900_nand_command_lp(struct mtd_info *mtd, unsigned int command, + int column, int page_addr) +{ + register struct nand_chip *chip = mtd->priv; + struct nuc900_nand *nand; + + nand = container_of(mtd, struct nuc900_nand, mtd); + + if (command == NAND_CMD_READOOB) { + column += mtd->writesize; + command = NAND_CMD_READ0; + } + + write_cmd_reg(nand, command & 0xff); + + if (column != -1 || page_addr != -1) { + + if (column != -1) { + if (chip->options & NAND_BUSWIDTH_16 && + !nand_opcode_8bits(command)) + column >>= 1; + write_addr_reg(nand, column); + write_addr_reg(nand, column >> 8 | ENDADDR); + } + if (page_addr != -1) { + write_addr_reg(nand, page_addr); + + if (chip->chipsize > (128 << 20)) { + write_addr_reg(nand, page_addr >> 8); + write_addr_reg(nand, page_addr >> 16 | ENDADDR); + } else { + write_addr_reg(nand, page_addr >> 8 | ENDADDR); + } + } + } + + switch (command) { + case NAND_CMD_CACHEDPROG: + case NAND_CMD_PAGEPROG: + case NAND_CMD_ERASE1: + case NAND_CMD_ERASE2: + case NAND_CMD_SEQIN: + case NAND_CMD_RNDIN: + case NAND_CMD_STATUS: + return; + + case NAND_CMD_RESET: + if (chip->dev_ready) + break; + udelay(chip->chip_delay); + + write_cmd_reg(nand, NAND_CMD_STATUS); + write_cmd_reg(nand, command); + + while (!nuc900_check_rb(nand)) + ; + + return; + + case NAND_CMD_RNDOUT: + write_cmd_reg(nand, NAND_CMD_RNDOUTSTART); + return; + + case NAND_CMD_READ0: + + write_cmd_reg(nand, NAND_CMD_READSTART); + default: + + if (!chip->dev_ready) { + udelay(chip->chip_delay); + return; + } + } + + /* Apply this short delay always to ensure that we do wait tWB in + * any case on any machine. */ + ndelay(100); + + while (!chip->dev_ready(mtd)) + ; +} + + +static void nuc900_nand_enable(struct nuc900_nand *nand) +{ + unsigned int val; + spin_lock(&nand->lock); + __raw_writel(RESET_FMI, (nand->reg + REG_FMICSR)); + + val = __raw_readl(nand->reg + REG_FMICSR); + + if (!(val & NAND_EN)) + __raw_writel(val | NAND_EN, nand->reg + REG_FMICSR); + + val = __raw_readl(nand->reg + REG_SMCSR); + + val &= ~(SWRST|PSIZE|DMARWEN|BUSWID|ECC4EN|NANDCS); + val |= WP; + + __raw_writel(val, nand->reg + REG_SMCSR); + + spin_unlock(&nand->lock); +} + +static int nuc900_nand_probe(struct platform_device *pdev) +{ + struct nuc900_nand *nuc900_nand; + struct nand_chip *chip; + struct resource *res; + + nuc900_nand = devm_kzalloc(&pdev->dev, sizeof(struct nuc900_nand), + GFP_KERNEL); + if (!nuc900_nand) + return -ENOMEM; + chip = &(nuc900_nand->chip); + + nuc900_nand->mtd.priv = chip; + nuc900_nand->mtd.owner = THIS_MODULE; + spin_lock_init(&nuc900_nand->lock); + + nuc900_nand->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(nuc900_nand->clk)) + return -ENOENT; + clk_enable(nuc900_nand->clk); + + chip->cmdfunc = nuc900_nand_command_lp; + chip->dev_ready = nuc900_nand_devready; + chip->read_byte = nuc900_nand_read_byte; + chip->write_buf = nuc900_nand_write_buf; + chip->read_buf = nuc900_nand_read_buf; + chip->chip_delay = 50; + chip->options = 0; + chip->ecc.mode = NAND_ECC_SOFT; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + nuc900_nand->reg = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(nuc900_nand->reg)) + return PTR_ERR(nuc900_nand->reg); + + nuc900_nand_enable(nuc900_nand); + + if (nand_scan(&(nuc900_nand->mtd), 1)) + return -ENXIO; + + mtd_device_register(&(nuc900_nand->mtd), partitions, + ARRAY_SIZE(partitions)); + + platform_set_drvdata(pdev, nuc900_nand); + + return 0; +} + +static int nuc900_nand_remove(struct platform_device *pdev) +{ + struct nuc900_nand *nuc900_nand = platform_get_drvdata(pdev); + + nand_release(&nuc900_nand->mtd); + clk_disable(nuc900_nand->clk); + + return 0; +} + +static struct platform_driver nuc900_nand_driver = { + .probe = nuc900_nand_probe, + .remove = nuc900_nand_remove, + .driver = { + .name = "nuc900-fmi", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(nuc900_nand_driver); + +MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>"); +MODULE_DESCRIPTION("w90p910/NUC9xx nand driver!"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:nuc900-fmi"); diff --git a/drivers/mtd/nand/omap2.c b/drivers/mtd/nand/omap2.c new file mode 100644 index 00000000000..f0ed92e210a --- /dev/null +++ b/drivers/mtd/nand/omap2.c @@ -0,0 +1,2078 @@ +/* + * Copyright © 2004 Texas Instruments, Jian Zhang <jzhang@ti.com> + * Copyright © 2004 Micron Technology Inc. + * Copyright © 2004 David Brownell + * + * 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/platform_device.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/sched.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/omap-dma.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_device.h> + +#include <linux/mtd/nand_bch.h> +#include <linux/platform_data/elm.h> + +#include <linux/platform_data/mtd-nand-omap2.h> + +#define DRIVER_NAME "omap2-nand" +#define OMAP_NAND_TIMEOUT_MS 5000 + +#define NAND_Ecc_P1e (1 << 0) +#define NAND_Ecc_P2e (1 << 1) +#define NAND_Ecc_P4e (1 << 2) +#define NAND_Ecc_P8e (1 << 3) +#define NAND_Ecc_P16e (1 << 4) +#define NAND_Ecc_P32e (1 << 5) +#define NAND_Ecc_P64e (1 << 6) +#define NAND_Ecc_P128e (1 << 7) +#define NAND_Ecc_P256e (1 << 8) +#define NAND_Ecc_P512e (1 << 9) +#define NAND_Ecc_P1024e (1 << 10) +#define NAND_Ecc_P2048e (1 << 11) + +#define NAND_Ecc_P1o (1 << 16) +#define NAND_Ecc_P2o (1 << 17) +#define NAND_Ecc_P4o (1 << 18) +#define NAND_Ecc_P8o (1 << 19) +#define NAND_Ecc_P16o (1 << 20) +#define NAND_Ecc_P32o (1 << 21) +#define NAND_Ecc_P64o (1 << 22) +#define NAND_Ecc_P128o (1 << 23) +#define NAND_Ecc_P256o (1 << 24) +#define NAND_Ecc_P512o (1 << 25) +#define NAND_Ecc_P1024o (1 << 26) +#define NAND_Ecc_P2048o (1 << 27) + +#define TF(value) (value ? 1 : 0) + +#define P2048e(a) (TF(a & NAND_Ecc_P2048e) << 0) +#define P2048o(a) (TF(a & NAND_Ecc_P2048o) << 1) +#define P1e(a) (TF(a & NAND_Ecc_P1e) << 2) +#define P1o(a) (TF(a & NAND_Ecc_P1o) << 3) +#define P2e(a) (TF(a & NAND_Ecc_P2e) << 4) +#define P2o(a) (TF(a & NAND_Ecc_P2o) << 5) +#define P4e(a) (TF(a & NAND_Ecc_P4e) << 6) +#define P4o(a) (TF(a & NAND_Ecc_P4o) << 7) + +#define P8e(a) (TF(a & NAND_Ecc_P8e) << 0) +#define P8o(a) (TF(a & NAND_Ecc_P8o) << 1) +#define P16e(a) (TF(a & NAND_Ecc_P16e) << 2) +#define P16o(a) (TF(a & NAND_Ecc_P16o) << 3) +#define P32e(a) (TF(a & NAND_Ecc_P32e) << 4) +#define P32o(a) (TF(a & NAND_Ecc_P32o) << 5) +#define P64e(a) (TF(a & NAND_Ecc_P64e) << 6) +#define P64o(a) (TF(a & NAND_Ecc_P64o) << 7) + +#define P128e(a) (TF(a & NAND_Ecc_P128e) << 0) +#define P128o(a) (TF(a & NAND_Ecc_P128o) << 1) +#define P256e(a) (TF(a & NAND_Ecc_P256e) << 2) +#define P256o(a) (TF(a & NAND_Ecc_P256o) << 3) +#define P512e(a) (TF(a & NAND_Ecc_P512e) << 4) +#define P512o(a) (TF(a & NAND_Ecc_P512o) << 5) +#define P1024e(a) (TF(a & NAND_Ecc_P1024e) << 6) +#define P1024o(a) (TF(a & NAND_Ecc_P1024o) << 7) + +#define P8e_s(a) (TF(a & NAND_Ecc_P8e) << 0) +#define P8o_s(a) (TF(a & NAND_Ecc_P8o) << 1) +#define P16e_s(a) (TF(a & NAND_Ecc_P16e) << 2) +#define P16o_s(a) (TF(a & NAND_Ecc_P16o) << 3) +#define P1e_s(a) (TF(a & NAND_Ecc_P1e) << 4) +#define P1o_s(a) (TF(a & NAND_Ecc_P1o) << 5) +#define P2e_s(a) (TF(a & NAND_Ecc_P2e) << 6) +#define P2o_s(a) (TF(a & NAND_Ecc_P2o) << 7) + +#define P4e_s(a) (TF(a & NAND_Ecc_P4e) << 0) +#define P4o_s(a) (TF(a & NAND_Ecc_P4o) << 1) + +#define PREFETCH_CONFIG1_CS_SHIFT 24 +#define ECC_CONFIG_CS_SHIFT 1 +#define CS_MASK 0x7 +#define ENABLE_PREFETCH (0x1 << 7) +#define DMA_MPU_MODE_SHIFT 2 +#define ECCSIZE0_SHIFT 12 +#define ECCSIZE1_SHIFT 22 +#define ECC1RESULTSIZE 0x1 +#define ECCCLEAR 0x100 +#define ECC1 0x1 +#define PREFETCH_FIFOTHRESHOLD_MAX 0x40 +#define PREFETCH_FIFOTHRESHOLD(val) ((val) << 8) +#define PREFETCH_STATUS_COUNT(val) (val & 0x00003fff) +#define PREFETCH_STATUS_FIFO_CNT(val) ((val >> 24) & 0x7F) +#define STATUS_BUFF_EMPTY 0x00000001 + +#define OMAP24XX_DMA_GPMC 4 + +#define SECTOR_BYTES 512 +/* 4 bit padding to make byte aligned, 56 = 52 + 4 */ +#define BCH4_BIT_PAD 4 + +/* GPMC ecc engine settings for read */ +#define BCH_WRAPMODE_1 1 /* BCH wrap mode 1 */ +#define BCH8R_ECC_SIZE0 0x1a /* ecc_size0 = 26 */ +#define BCH8R_ECC_SIZE1 0x2 /* ecc_size1 = 2 */ +#define BCH4R_ECC_SIZE0 0xd /* ecc_size0 = 13 */ +#define BCH4R_ECC_SIZE1 0x3 /* ecc_size1 = 3 */ + +/* GPMC ecc engine settings for write */ +#define BCH_WRAPMODE_6 6 /* BCH wrap mode 6 */ +#define BCH_ECC_SIZE0 0x0 /* ecc_size0 = 0, no oob protection */ +#define BCH_ECC_SIZE1 0x20 /* ecc_size1 = 32 */ + +#define BADBLOCK_MARKER_LENGTH 2 + +#ifdef CONFIG_MTD_NAND_OMAP_BCH +static u_char bch16_vector[] = {0xf5, 0x24, 0x1c, 0xd0, 0x61, 0xb3, 0xf1, 0x55, + 0x2e, 0x2c, 0x86, 0xa3, 0xed, 0x36, 0x1b, 0x78, + 0x48, 0x76, 0xa9, 0x3b, 0x97, 0xd1, 0x7a, 0x93, + 0x07, 0x0e}; +static u_char bch8_vector[] = {0xf3, 0xdb, 0x14, 0x16, 0x8b, 0xd2, 0xbe, 0xcc, + 0xac, 0x6b, 0xff, 0x99, 0x7b}; +static u_char bch4_vector[] = {0x00, 0x6b, 0x31, 0xdd, 0x41, 0xbc, 0x10}; +#endif + +/* oob info generated runtime depending on ecc algorithm and layout selected */ +static struct nand_ecclayout omap_oobinfo; + +struct omap_nand_info { + struct nand_hw_control controller; + struct omap_nand_platform_data *pdata; + struct mtd_info mtd; + struct nand_chip nand; + struct platform_device *pdev; + + int gpmc_cs; + unsigned long phys_base; + enum omap_ecc ecc_opt; + struct completion comp; + struct dma_chan *dma; + int gpmc_irq_fifo; + int gpmc_irq_count; + enum { + OMAP_NAND_IO_READ = 0, /* read */ + OMAP_NAND_IO_WRITE, /* write */ + } iomode; + u_char *buf; + int buf_len; + struct gpmc_nand_regs reg; + /* fields specific for BCHx_HW ECC scheme */ + struct device *elm_dev; + struct device_node *of_node; +}; + +/** + * omap_prefetch_enable - configures and starts prefetch transfer + * @cs: cs (chip select) number + * @fifo_th: fifo threshold to be used for read/ write + * @dma_mode: dma mode enable (1) or disable (0) + * @u32_count: number of bytes to be transferred + * @is_write: prefetch read(0) or write post(1) mode + */ +static int omap_prefetch_enable(int cs, int fifo_th, int dma_mode, + unsigned int u32_count, int is_write, struct omap_nand_info *info) +{ + u32 val; + + if (fifo_th > PREFETCH_FIFOTHRESHOLD_MAX) + return -1; + + if (readl(info->reg.gpmc_prefetch_control)) + return -EBUSY; + + /* Set the amount of bytes to be prefetched */ + writel(u32_count, info->reg.gpmc_prefetch_config2); + + /* Set dma/mpu mode, the prefetch read / post write and + * enable the engine. Set which cs is has requested for. + */ + val = ((cs << PREFETCH_CONFIG1_CS_SHIFT) | + PREFETCH_FIFOTHRESHOLD(fifo_th) | ENABLE_PREFETCH | + (dma_mode << DMA_MPU_MODE_SHIFT) | (0x1 & is_write)); + writel(val, info->reg.gpmc_prefetch_config1); + + /* Start the prefetch engine */ + writel(0x1, info->reg.gpmc_prefetch_control); + + return 0; +} + +/** + * omap_prefetch_reset - disables and stops the prefetch engine + */ +static int omap_prefetch_reset(int cs, struct omap_nand_info *info) +{ + u32 config1; + + /* check if the same module/cs is trying to reset */ + config1 = readl(info->reg.gpmc_prefetch_config1); + if (((config1 >> PREFETCH_CONFIG1_CS_SHIFT) & CS_MASK) != cs) + return -EINVAL; + + /* Stop the PFPW engine */ + writel(0x0, info->reg.gpmc_prefetch_control); + + /* Reset/disable the PFPW engine */ + writel(0x0, info->reg.gpmc_prefetch_config1); + + return 0; +} + +/** + * omap_hwcontrol - hardware specific access to control-lines + * @mtd: MTD device structure + * @cmd: command to device + * @ctrl: + * NAND_NCE: bit 0 -> don't care + * NAND_CLE: bit 1 -> Command Latch + * NAND_ALE: bit 2 -> Address Latch + * + * NOTE: boards may use different bits for these!! + */ +static void omap_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl) +{ + struct omap_nand_info *info = container_of(mtd, + struct omap_nand_info, mtd); + + if (cmd != NAND_CMD_NONE) { + if (ctrl & NAND_CLE) + writeb(cmd, info->reg.gpmc_nand_command); + + else if (ctrl & NAND_ALE) + writeb(cmd, info->reg.gpmc_nand_address); + + else /* NAND_NCE */ + writeb(cmd, info->reg.gpmc_nand_data); + } +} + +/** + * omap_read_buf8 - read data from NAND controller into buffer + * @mtd: MTD device structure + * @buf: buffer to store date + * @len: number of bytes to read + */ +static void omap_read_buf8(struct mtd_info *mtd, u_char *buf, int len) +{ + struct nand_chip *nand = mtd->priv; + + ioread8_rep(nand->IO_ADDR_R, buf, len); +} + +/** + * omap_write_buf8 - write buffer to NAND controller + * @mtd: MTD device structure + * @buf: data buffer + * @len: number of bytes to write + */ +static void omap_write_buf8(struct mtd_info *mtd, const u_char *buf, int len) +{ + struct omap_nand_info *info = container_of(mtd, + struct omap_nand_info, mtd); + u_char *p = (u_char *)buf; + u32 status = 0; + + while (len--) { + iowrite8(*p++, info->nand.IO_ADDR_W); + /* wait until buffer is available for write */ + do { + status = readl(info->reg.gpmc_status) & + STATUS_BUFF_EMPTY; + } while (!status); + } +} + +/** + * omap_read_buf16 - read data from NAND controller into buffer + * @mtd: MTD device structure + * @buf: buffer to store date + * @len: number of bytes to read + */ +static void omap_read_buf16(struct mtd_info *mtd, u_char *buf, int len) +{ + struct nand_chip *nand = mtd->priv; + + ioread16_rep(nand->IO_ADDR_R, buf, len / 2); +} + +/** + * omap_write_buf16 - write buffer to NAND controller + * @mtd: MTD device structure + * @buf: data buffer + * @len: number of bytes to write + */ +static void omap_write_buf16(struct mtd_info *mtd, const u_char * buf, int len) +{ + struct omap_nand_info *info = container_of(mtd, + struct omap_nand_info, mtd); + u16 *p = (u16 *) buf; + u32 status = 0; + /* FIXME try bursts of writesw() or DMA ... */ + len >>= 1; + + while (len--) { + iowrite16(*p++, info->nand.IO_ADDR_W); + /* wait until buffer is available for write */ + do { + status = readl(info->reg.gpmc_status) & + STATUS_BUFF_EMPTY; + } while (!status); + } +} + +/** + * omap_read_buf_pref - read data from NAND controller into buffer + * @mtd: MTD device structure + * @buf: buffer to store date + * @len: number of bytes to read + */ +static void omap_read_buf_pref(struct mtd_info *mtd, u_char *buf, int len) +{ + struct omap_nand_info *info = container_of(mtd, + struct omap_nand_info, mtd); + uint32_t r_count = 0; + int ret = 0; + u32 *p = (u32 *)buf; + + /* take care of subpage reads */ + if (len % 4) { + if (info->nand.options & NAND_BUSWIDTH_16) + omap_read_buf16(mtd, buf, len % 4); + else + omap_read_buf8(mtd, buf, len % 4); + p = (u32 *) (buf + len % 4); + len -= len % 4; + } + + /* configure and start prefetch transfer */ + ret = omap_prefetch_enable(info->gpmc_cs, + PREFETCH_FIFOTHRESHOLD_MAX, 0x0, len, 0x0, info); + if (ret) { + /* PFPW engine is busy, use cpu copy method */ + if (info->nand.options & NAND_BUSWIDTH_16) + omap_read_buf16(mtd, (u_char *)p, len); + else + omap_read_buf8(mtd, (u_char *)p, len); + } else { + do { + r_count = readl(info->reg.gpmc_prefetch_status); + r_count = PREFETCH_STATUS_FIFO_CNT(r_count); + r_count = r_count >> 2; + ioread32_rep(info->nand.IO_ADDR_R, p, r_count); + p += r_count; + len -= r_count << 2; + } while (len); + /* disable and stop the PFPW engine */ + omap_prefetch_reset(info->gpmc_cs, info); + } +} + +/** + * omap_write_buf_pref - write buffer to NAND controller + * @mtd: MTD device structure + * @buf: data buffer + * @len: number of bytes to write + */ +static void omap_write_buf_pref(struct mtd_info *mtd, + const u_char *buf, int len) +{ + struct omap_nand_info *info = container_of(mtd, + struct omap_nand_info, mtd); + uint32_t w_count = 0; + int i = 0, ret = 0; + u16 *p = (u16 *)buf; + unsigned long tim, limit; + u32 val; + + /* take care of subpage writes */ + if (len % 2 != 0) { + writeb(*buf, info->nand.IO_ADDR_W); + p = (u16 *)(buf + 1); + len--; + } + + /* configure and start prefetch transfer */ + ret = omap_prefetch_enable(info->gpmc_cs, + PREFETCH_FIFOTHRESHOLD_MAX, 0x0, len, 0x1, info); + if (ret) { + /* PFPW engine is busy, use cpu copy method */ + if (info->nand.options & NAND_BUSWIDTH_16) + omap_write_buf16(mtd, (u_char *)p, len); + else + omap_write_buf8(mtd, (u_char *)p, len); + } else { + while (len) { + w_count = readl(info->reg.gpmc_prefetch_status); + w_count = PREFETCH_STATUS_FIFO_CNT(w_count); + w_count = w_count >> 1; + for (i = 0; (i < w_count) && len; i++, len -= 2) + iowrite16(*p++, info->nand.IO_ADDR_W); + } + /* wait for data to flushed-out before reset the prefetch */ + tim = 0; + limit = (loops_per_jiffy * + msecs_to_jiffies(OMAP_NAND_TIMEOUT_MS)); + do { + cpu_relax(); + val = readl(info->reg.gpmc_prefetch_status); + val = PREFETCH_STATUS_COUNT(val); + } while (val && (tim++ < limit)); + + /* disable and stop the PFPW engine */ + omap_prefetch_reset(info->gpmc_cs, info); + } +} + +/* + * omap_nand_dma_callback: callback on the completion of dma transfer + * @data: pointer to completion data structure + */ +static void omap_nand_dma_callback(void *data) +{ + complete((struct completion *) data); +} + +/* + * omap_nand_dma_transfer: configure and start dma transfer + * @mtd: MTD device structure + * @addr: virtual address in RAM of source/destination + * @len: number of data bytes to be transferred + * @is_write: flag for read/write operation + */ +static inline int omap_nand_dma_transfer(struct mtd_info *mtd, void *addr, + unsigned int len, int is_write) +{ + struct omap_nand_info *info = container_of(mtd, + struct omap_nand_info, mtd); + struct dma_async_tx_descriptor *tx; + enum dma_data_direction dir = is_write ? DMA_TO_DEVICE : + DMA_FROM_DEVICE; + struct scatterlist sg; + unsigned long tim, limit; + unsigned n; + int ret; + u32 val; + + if (addr >= high_memory) { + struct page *p1; + + if (((size_t)addr & PAGE_MASK) != + ((size_t)(addr + len - 1) & PAGE_MASK)) + goto out_copy; + p1 = vmalloc_to_page(addr); + if (!p1) + goto out_copy; + addr = page_address(p1) + ((size_t)addr & ~PAGE_MASK); + } + + sg_init_one(&sg, addr, len); + n = dma_map_sg(info->dma->device->dev, &sg, 1, dir); + if (n == 0) { + dev_err(&info->pdev->dev, + "Couldn't DMA map a %d byte buffer\n", len); + goto out_copy; + } + + tx = dmaengine_prep_slave_sg(info->dma, &sg, n, + is_write ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!tx) + goto out_copy_unmap; + + tx->callback = omap_nand_dma_callback; + tx->callback_param = &info->comp; + dmaengine_submit(tx); + + /* configure and start prefetch transfer */ + ret = omap_prefetch_enable(info->gpmc_cs, + PREFETCH_FIFOTHRESHOLD_MAX, 0x1, len, is_write, info); + if (ret) + /* PFPW engine is busy, use cpu copy method */ + goto out_copy_unmap; + + init_completion(&info->comp); + dma_async_issue_pending(info->dma); + + /* setup and start DMA using dma_addr */ + wait_for_completion(&info->comp); + tim = 0; + limit = (loops_per_jiffy * msecs_to_jiffies(OMAP_NAND_TIMEOUT_MS)); + + do { + cpu_relax(); + val = readl(info->reg.gpmc_prefetch_status); + val = PREFETCH_STATUS_COUNT(val); + } while (val && (tim++ < limit)); + + /* disable and stop the PFPW engine */ + omap_prefetch_reset(info->gpmc_cs, info); + + dma_unmap_sg(info->dma->device->dev, &sg, 1, dir); + return 0; + +out_copy_unmap: + dma_unmap_sg(info->dma->device->dev, &sg, 1, dir); +out_copy: + if (info->nand.options & NAND_BUSWIDTH_16) + is_write == 0 ? omap_read_buf16(mtd, (u_char *) addr, len) + : omap_write_buf16(mtd, (u_char *) addr, len); + else + is_write == 0 ? omap_read_buf8(mtd, (u_char *) addr, len) + : omap_write_buf8(mtd, (u_char *) addr, len); + return 0; +} + +/** + * omap_read_buf_dma_pref - read data from NAND controller into buffer + * @mtd: MTD device structure + * @buf: buffer to store date + * @len: number of bytes to read + */ +static void omap_read_buf_dma_pref(struct mtd_info *mtd, u_char *buf, int len) +{ + if (len <= mtd->oobsize) + omap_read_buf_pref(mtd, buf, len); + else + /* start transfer in DMA mode */ + omap_nand_dma_transfer(mtd, buf, len, 0x0); +} + +/** + * omap_write_buf_dma_pref - write buffer to NAND controller + * @mtd: MTD device structure + * @buf: data buffer + * @len: number of bytes to write + */ +static void omap_write_buf_dma_pref(struct mtd_info *mtd, + const u_char *buf, int len) +{ + if (len <= mtd->oobsize) + omap_write_buf_pref(mtd, buf, len); + else + /* start transfer in DMA mode */ + omap_nand_dma_transfer(mtd, (u_char *) buf, len, 0x1); +} + +/* + * omap_nand_irq - GPMC irq handler + * @this_irq: gpmc irq number + * @dev: omap_nand_info structure pointer is passed here + */ +static irqreturn_t omap_nand_irq(int this_irq, void *dev) +{ + struct omap_nand_info *info = (struct omap_nand_info *) dev; + u32 bytes; + + bytes = readl(info->reg.gpmc_prefetch_status); + bytes = PREFETCH_STATUS_FIFO_CNT(bytes); + bytes = bytes & 0xFFFC; /* io in multiple of 4 bytes */ + if (info->iomode == OMAP_NAND_IO_WRITE) { /* checks for write io */ + if (this_irq == info->gpmc_irq_count) + goto done; + + if (info->buf_len && (info->buf_len < bytes)) + bytes = info->buf_len; + else if (!info->buf_len) + bytes = 0; + iowrite32_rep(info->nand.IO_ADDR_W, + (u32 *)info->buf, bytes >> 2); + info->buf = info->buf + bytes; + info->buf_len -= bytes; + + } else { + ioread32_rep(info->nand.IO_ADDR_R, + (u32 *)info->buf, bytes >> 2); + info->buf = info->buf + bytes; + + if (this_irq == info->gpmc_irq_count) + goto done; + } + + return IRQ_HANDLED; + +done: + complete(&info->comp); + + disable_irq_nosync(info->gpmc_irq_fifo); + disable_irq_nosync(info->gpmc_irq_count); + + return IRQ_HANDLED; +} + +/* + * omap_read_buf_irq_pref - read data from NAND controller into buffer + * @mtd: MTD device structure + * @buf: buffer to store date + * @len: number of bytes to read + */ +static void omap_read_buf_irq_pref(struct mtd_info *mtd, u_char *buf, int len) +{ + struct omap_nand_info *info = container_of(mtd, + struct omap_nand_info, mtd); + int ret = 0; + + if (len <= mtd->oobsize) { + omap_read_buf_pref(mtd, buf, len); + return; + } + + info->iomode = OMAP_NAND_IO_READ; + info->buf = buf; + init_completion(&info->comp); + + /* configure and start prefetch transfer */ + ret = omap_prefetch_enable(info->gpmc_cs, + PREFETCH_FIFOTHRESHOLD_MAX/2, 0x0, len, 0x0, info); + if (ret) + /* PFPW engine is busy, use cpu copy method */ + goto out_copy; + + info->buf_len = len; + + enable_irq(info->gpmc_irq_count); + enable_irq(info->gpmc_irq_fifo); + + /* waiting for read to complete */ + wait_for_completion(&info->comp); + + /* disable and stop the PFPW engine */ + omap_prefetch_reset(info->gpmc_cs, info); + return; + +out_copy: + if (info->nand.options & NAND_BUSWIDTH_16) + omap_read_buf16(mtd, buf, len); + else + omap_read_buf8(mtd, buf, len); +} + +/* + * omap_write_buf_irq_pref - write buffer to NAND controller + * @mtd: MTD device structure + * @buf: data buffer + * @len: number of bytes to write + */ +static void omap_write_buf_irq_pref(struct mtd_info *mtd, + const u_char *buf, int len) +{ + struct omap_nand_info *info = container_of(mtd, + struct omap_nand_info, mtd); + int ret = 0; + unsigned long tim, limit; + u32 val; + + if (len <= mtd->oobsize) { + omap_write_buf_pref(mtd, buf, len); + return; + } + + info->iomode = OMAP_NAND_IO_WRITE; + info->buf = (u_char *) buf; + init_completion(&info->comp); + + /* configure and start prefetch transfer : size=24 */ + ret = omap_prefetch_enable(info->gpmc_cs, + (PREFETCH_FIFOTHRESHOLD_MAX * 3) / 8, 0x0, len, 0x1, info); + if (ret) + /* PFPW engine is busy, use cpu copy method */ + goto out_copy; + + info->buf_len = len; + + enable_irq(info->gpmc_irq_count); + enable_irq(info->gpmc_irq_fifo); + + /* waiting for write to complete */ + wait_for_completion(&info->comp); + + /* wait for data to flushed-out before reset the prefetch */ + tim = 0; + limit = (loops_per_jiffy * msecs_to_jiffies(OMAP_NAND_TIMEOUT_MS)); + do { + val = readl(info->reg.gpmc_prefetch_status); + val = PREFETCH_STATUS_COUNT(val); + cpu_relax(); + } while (val && (tim++ < limit)); + + /* disable and stop the PFPW engine */ + omap_prefetch_reset(info->gpmc_cs, info); + return; + +out_copy: + if (info->nand.options & NAND_BUSWIDTH_16) + omap_write_buf16(mtd, buf, len); + else + omap_write_buf8(mtd, buf, len); +} + +/** + * gen_true_ecc - This function will generate true ECC value + * @ecc_buf: buffer to store ecc code + * + * This generated true ECC value can be used when correcting + * data read from NAND flash memory core + */ +static void gen_true_ecc(u8 *ecc_buf) +{ + u32 tmp = ecc_buf[0] | (ecc_buf[1] << 16) | + ((ecc_buf[2] & 0xF0) << 20) | ((ecc_buf[2] & 0x0F) << 8); + + ecc_buf[0] = ~(P64o(tmp) | P64e(tmp) | P32o(tmp) | P32e(tmp) | + P16o(tmp) | P16e(tmp) | P8o(tmp) | P8e(tmp)); + ecc_buf[1] = ~(P1024o(tmp) | P1024e(tmp) | P512o(tmp) | P512e(tmp) | + P256o(tmp) | P256e(tmp) | P128o(tmp) | P128e(tmp)); + ecc_buf[2] = ~(P4o(tmp) | P4e(tmp) | P2o(tmp) | P2e(tmp) | P1o(tmp) | + P1e(tmp) | P2048o(tmp) | P2048e(tmp)); +} + +/** + * omap_compare_ecc - Detect (2 bits) and correct (1 bit) error in data + * @ecc_data1: ecc code from nand spare area + * @ecc_data2: ecc code from hardware register obtained from hardware ecc + * @page_data: page data + * + * This function compares two ECC's and indicates if there is an error. + * If the error can be corrected it will be corrected to the buffer. + * If there is no error, %0 is returned. If there is an error but it + * was corrected, %1 is returned. Otherwise, %-1 is returned. + */ +static int omap_compare_ecc(u8 *ecc_data1, /* read from NAND memory */ + u8 *ecc_data2, /* read from register */ + u8 *page_data) +{ + uint i; + u8 tmp0_bit[8], tmp1_bit[8], tmp2_bit[8]; + u8 comp0_bit[8], comp1_bit[8], comp2_bit[8]; + u8 ecc_bit[24]; + u8 ecc_sum = 0; + u8 find_bit = 0; + uint find_byte = 0; + int isEccFF; + + isEccFF = ((*(u32 *)ecc_data1 & 0xFFFFFF) == 0xFFFFFF); + + gen_true_ecc(ecc_data1); + gen_true_ecc(ecc_data2); + + for (i = 0; i <= 2; i++) { + *(ecc_data1 + i) = ~(*(ecc_data1 + i)); + *(ecc_data2 + i) = ~(*(ecc_data2 + i)); + } + + for (i = 0; i < 8; i++) { + tmp0_bit[i] = *ecc_data1 % 2; + *ecc_data1 = *ecc_data1 / 2; + } + + for (i = 0; i < 8; i++) { + tmp1_bit[i] = *(ecc_data1 + 1) % 2; + *(ecc_data1 + 1) = *(ecc_data1 + 1) / 2; + } + + for (i = 0; i < 8; i++) { + tmp2_bit[i] = *(ecc_data1 + 2) % 2; + *(ecc_data1 + 2) = *(ecc_data1 + 2) / 2; + } + + for (i = 0; i < 8; i++) { + comp0_bit[i] = *ecc_data2 % 2; + *ecc_data2 = *ecc_data2 / 2; + } + + for (i = 0; i < 8; i++) { + comp1_bit[i] = *(ecc_data2 + 1) % 2; + *(ecc_data2 + 1) = *(ecc_data2 + 1) / 2; + } + + for (i = 0; i < 8; i++) { + comp2_bit[i] = *(ecc_data2 + 2) % 2; + *(ecc_data2 + 2) = *(ecc_data2 + 2) / 2; + } + + for (i = 0; i < 6; i++) + ecc_bit[i] = tmp2_bit[i + 2] ^ comp2_bit[i + 2]; + + for (i = 0; i < 8; i++) + ecc_bit[i + 6] = tmp0_bit[i] ^ comp0_bit[i]; + + for (i = 0; i < 8; i++) + ecc_bit[i + 14] = tmp1_bit[i] ^ comp1_bit[i]; + + ecc_bit[22] = tmp2_bit[0] ^ comp2_bit[0]; + ecc_bit[23] = tmp2_bit[1] ^ comp2_bit[1]; + + for (i = 0; i < 24; i++) + ecc_sum += ecc_bit[i]; + + switch (ecc_sum) { + case 0: + /* Not reached because this function is not called if + * ECC values are equal + */ + return 0; + + case 1: + /* Uncorrectable error */ + pr_debug("ECC UNCORRECTED_ERROR 1\n"); + return -1; + + case 11: + /* UN-Correctable error */ + pr_debug("ECC UNCORRECTED_ERROR B\n"); + return -1; + + case 12: + /* Correctable error */ + find_byte = (ecc_bit[23] << 8) + + (ecc_bit[21] << 7) + + (ecc_bit[19] << 6) + + (ecc_bit[17] << 5) + + (ecc_bit[15] << 4) + + (ecc_bit[13] << 3) + + (ecc_bit[11] << 2) + + (ecc_bit[9] << 1) + + ecc_bit[7]; + + find_bit = (ecc_bit[5] << 2) + (ecc_bit[3] << 1) + ecc_bit[1]; + + pr_debug("Correcting single bit ECC error at offset: " + "%d, bit: %d\n", find_byte, find_bit); + + page_data[find_byte] ^= (1 << find_bit); + + return 1; + default: + if (isEccFF) { + if (ecc_data2[0] == 0 && + ecc_data2[1] == 0 && + ecc_data2[2] == 0) + return 0; + } + pr_debug("UNCORRECTED_ERROR default\n"); + return -1; + } +} + +/** + * omap_correct_data - Compares the ECC read with HW generated ECC + * @mtd: MTD device structure + * @dat: page data + * @read_ecc: ecc read from nand flash + * @calc_ecc: ecc read from HW ECC registers + * + * Compares the ecc read from nand spare area with ECC registers values + * and if ECC's mismatched, it will call 'omap_compare_ecc' for error + * detection and correction. If there are no errors, %0 is returned. If + * there were errors and all of the errors were corrected, the number of + * corrected errors is returned. If uncorrectable errors exist, %-1 is + * returned. + */ +static int omap_correct_data(struct mtd_info *mtd, u_char *dat, + u_char *read_ecc, u_char *calc_ecc) +{ + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + int blockCnt = 0, i = 0, ret = 0; + int stat = 0; + + /* Ex NAND_ECC_HW12_2048 */ + if ((info->nand.ecc.mode == NAND_ECC_HW) && + (info->nand.ecc.size == 2048)) + blockCnt = 4; + else + blockCnt = 1; + + for (i = 0; i < blockCnt; i++) { + if (memcmp(read_ecc, calc_ecc, 3) != 0) { + ret = omap_compare_ecc(read_ecc, calc_ecc, dat); + if (ret < 0) + return ret; + /* keep track of the number of corrected errors */ + stat += ret; + } + read_ecc += 3; + calc_ecc += 3; + dat += 512; + } + return stat; +} + +/** + * omap_calcuate_ecc - Generate non-inverted ECC bytes. + * @mtd: MTD device structure + * @dat: The pointer to data on which ecc is computed + * @ecc_code: The ecc_code buffer + * + * Using noninverted ECC can be considered ugly since writing a blank + * page ie. padding will clear the ECC bytes. This is no problem as long + * nobody is trying to write data on the seemingly unused page. Reading + * an erased page will produce an ECC mismatch between generated and read + * ECC bytes that has to be dealt with separately. + */ +static int omap_calculate_ecc(struct mtd_info *mtd, const u_char *dat, + u_char *ecc_code) +{ + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + u32 val; + + val = readl(info->reg.gpmc_ecc_config); + if (((val >> ECC_CONFIG_CS_SHIFT) & ~CS_MASK) != info->gpmc_cs) + return -EINVAL; + + /* read ecc result */ + val = readl(info->reg.gpmc_ecc1_result); + *ecc_code++ = val; /* P128e, ..., P1e */ + *ecc_code++ = val >> 16; /* P128o, ..., P1o */ + /* P2048o, P1024o, P512o, P256o, P2048e, P1024e, P512e, P256e */ + *ecc_code++ = ((val >> 8) & 0x0f) | ((val >> 20) & 0xf0); + + return 0; +} + +/** + * omap_enable_hwecc - This function enables the hardware ecc functionality + * @mtd: MTD device structure + * @mode: Read/Write mode + */ +static void omap_enable_hwecc(struct mtd_info *mtd, int mode) +{ + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + struct nand_chip *chip = mtd->priv; + unsigned int dev_width = (chip->options & NAND_BUSWIDTH_16) ? 1 : 0; + u32 val; + + /* clear ecc and enable bits */ + val = ECCCLEAR | ECC1; + writel(val, info->reg.gpmc_ecc_control); + + /* program ecc and result sizes */ + val = ((((info->nand.ecc.size >> 1) - 1) << ECCSIZE1_SHIFT) | + ECC1RESULTSIZE); + writel(val, info->reg.gpmc_ecc_size_config); + + switch (mode) { + case NAND_ECC_READ: + case NAND_ECC_WRITE: + writel(ECCCLEAR | ECC1, info->reg.gpmc_ecc_control); + break; + case NAND_ECC_READSYN: + writel(ECCCLEAR, info->reg.gpmc_ecc_control); + break; + default: + dev_info(&info->pdev->dev, + "error: unrecognized Mode[%d]!\n", mode); + break; + } + + /* (ECC 16 or 8 bit col) | ( CS ) | ECC Enable */ + val = (dev_width << 7) | (info->gpmc_cs << 1) | (0x1); + writel(val, info->reg.gpmc_ecc_config); +} + +/** + * omap_wait - wait until the command is done + * @mtd: MTD device structure + * @chip: NAND Chip structure + * + * Wait function is called during Program and erase operations and + * the way it is called from MTD layer, we should wait till the NAND + * chip is ready after the programming/erase operation has completed. + * + * Erase can take up to 400ms and program up to 20ms according to + * general NAND and SmartMedia specs + */ +static int omap_wait(struct mtd_info *mtd, struct nand_chip *chip) +{ + struct nand_chip *this = mtd->priv; + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + unsigned long timeo = jiffies; + int status, state = this->state; + + if (state == FL_ERASING) + timeo += msecs_to_jiffies(400); + else + timeo += msecs_to_jiffies(20); + + writeb(NAND_CMD_STATUS & 0xFF, info->reg.gpmc_nand_command); + while (time_before(jiffies, timeo)) { + status = readb(info->reg.gpmc_nand_data); + if (status & NAND_STATUS_READY) + break; + cond_resched(); + } + + status = readb(info->reg.gpmc_nand_data); + return status; +} + +/** + * omap_dev_ready - calls the platform specific dev_ready function + * @mtd: MTD device structure + */ +static int omap_dev_ready(struct mtd_info *mtd) +{ + unsigned int val = 0; + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + + val = readl(info->reg.gpmc_status); + + if ((val & 0x100) == 0x100) { + return 1; + } else { + return 0; + } +} + +/** + * omap_enable_hwecc_bch - Program GPMC to perform BCH ECC calculation + * @mtd: MTD device structure + * @mode: Read/Write mode + * + * When using BCH, sector size is hardcoded to 512 bytes. + * Using wrapping mode 6 both for reading and writing if ELM module not uses + * for error correction. + * On writing, + * eccsize0 = 0 (no additional protected byte in spare area) + * eccsize1 = 32 (skip 32 nibbles = 16 bytes per sector in spare area) + */ +static void __maybe_unused omap_enable_hwecc_bch(struct mtd_info *mtd, int mode) +{ + unsigned int bch_type; + unsigned int dev_width, nsectors; + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + enum omap_ecc ecc_opt = info->ecc_opt; + struct nand_chip *chip = mtd->priv; + u32 val, wr_mode; + unsigned int ecc_size1, ecc_size0; + + /* GPMC configurations for calculating ECC */ + switch (ecc_opt) { + case OMAP_ECC_BCH4_CODE_HW_DETECTION_SW: + bch_type = 0; + nsectors = 1; + if (mode == NAND_ECC_READ) { + wr_mode = BCH_WRAPMODE_6; + ecc_size0 = BCH_ECC_SIZE0; + ecc_size1 = BCH_ECC_SIZE1; + } else { + wr_mode = BCH_WRAPMODE_6; + ecc_size0 = BCH_ECC_SIZE0; + ecc_size1 = BCH_ECC_SIZE1; + } + break; + case OMAP_ECC_BCH4_CODE_HW: + bch_type = 0; + nsectors = chip->ecc.steps; + if (mode == NAND_ECC_READ) { + wr_mode = BCH_WRAPMODE_1; + ecc_size0 = BCH4R_ECC_SIZE0; + ecc_size1 = BCH4R_ECC_SIZE1; + } else { + wr_mode = BCH_WRAPMODE_6; + ecc_size0 = BCH_ECC_SIZE0; + ecc_size1 = BCH_ECC_SIZE1; + } + break; + case OMAP_ECC_BCH8_CODE_HW_DETECTION_SW: + bch_type = 1; + nsectors = 1; + if (mode == NAND_ECC_READ) { + wr_mode = BCH_WRAPMODE_6; + ecc_size0 = BCH_ECC_SIZE0; + ecc_size1 = BCH_ECC_SIZE1; + } else { + wr_mode = BCH_WRAPMODE_6; + ecc_size0 = BCH_ECC_SIZE0; + ecc_size1 = BCH_ECC_SIZE1; + } + break; + case OMAP_ECC_BCH8_CODE_HW: + bch_type = 1; + nsectors = chip->ecc.steps; + if (mode == NAND_ECC_READ) { + wr_mode = BCH_WRAPMODE_1; + ecc_size0 = BCH8R_ECC_SIZE0; + ecc_size1 = BCH8R_ECC_SIZE1; + } else { + wr_mode = BCH_WRAPMODE_6; + ecc_size0 = BCH_ECC_SIZE0; + ecc_size1 = BCH_ECC_SIZE1; + } + break; + case OMAP_ECC_BCH16_CODE_HW: + bch_type = 0x2; + nsectors = chip->ecc.steps; + if (mode == NAND_ECC_READ) { + wr_mode = 0x01; + ecc_size0 = 52; /* ECC bits in nibbles per sector */ + ecc_size1 = 0; /* non-ECC bits in nibbles per sector */ + } else { + wr_mode = 0x01; + ecc_size0 = 0; /* extra bits in nibbles per sector */ + ecc_size1 = 52; /* OOB bits in nibbles per sector */ + } + break; + default: + return; + } + + writel(ECC1, info->reg.gpmc_ecc_control); + + /* Configure ecc size for BCH */ + val = (ecc_size1 << ECCSIZE1_SHIFT) | (ecc_size0 << ECCSIZE0_SHIFT); + writel(val, info->reg.gpmc_ecc_size_config); + + dev_width = (chip->options & NAND_BUSWIDTH_16) ? 1 : 0; + + /* BCH configuration */ + val = ((1 << 16) | /* enable BCH */ + (bch_type << 12) | /* BCH4/BCH8/BCH16 */ + (wr_mode << 8) | /* wrap mode */ + (dev_width << 7) | /* bus width */ + (((nsectors-1) & 0x7) << 4) | /* number of sectors */ + (info->gpmc_cs << 1) | /* ECC CS */ + (0x1)); /* enable ECC */ + + writel(val, info->reg.gpmc_ecc_config); + + /* Clear ecc and enable bits */ + writel(ECCCLEAR | ECC1, info->reg.gpmc_ecc_control); +} + +static u8 bch4_polynomial[] = {0x28, 0x13, 0xcc, 0x39, 0x96, 0xac, 0x7f}; +static u8 bch8_polynomial[] = {0xef, 0x51, 0x2e, 0x09, 0xed, 0x93, 0x9a, 0xc2, + 0x97, 0x79, 0xe5, 0x24, 0xb5}; + +/** + * omap_calculate_ecc_bch - Generate bytes of ECC bytes + * @mtd: MTD device structure + * @dat: The pointer to data on which ecc is computed + * @ecc_code: The ecc_code buffer + * + * Support calculating of BCH4/8 ecc vectors for the page + */ +static int __maybe_unused omap_calculate_ecc_bch(struct mtd_info *mtd, + const u_char *dat, u_char *ecc_calc) +{ + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + int eccbytes = info->nand.ecc.bytes; + struct gpmc_nand_regs *gpmc_regs = &info->reg; + u8 *ecc_code; + unsigned long nsectors, bch_val1, bch_val2, bch_val3, bch_val4; + u32 val; + int i, j; + + nsectors = ((readl(info->reg.gpmc_ecc_config) >> 4) & 0x7) + 1; + for (i = 0; i < nsectors; i++) { + ecc_code = ecc_calc; + switch (info->ecc_opt) { + case OMAP_ECC_BCH8_CODE_HW_DETECTION_SW: + case OMAP_ECC_BCH8_CODE_HW: + bch_val1 = readl(gpmc_regs->gpmc_bch_result0[i]); + bch_val2 = readl(gpmc_regs->gpmc_bch_result1[i]); + bch_val3 = readl(gpmc_regs->gpmc_bch_result2[i]); + bch_val4 = readl(gpmc_regs->gpmc_bch_result3[i]); + *ecc_code++ = (bch_val4 & 0xFF); + *ecc_code++ = ((bch_val3 >> 24) & 0xFF); + *ecc_code++ = ((bch_val3 >> 16) & 0xFF); + *ecc_code++ = ((bch_val3 >> 8) & 0xFF); + *ecc_code++ = (bch_val3 & 0xFF); + *ecc_code++ = ((bch_val2 >> 24) & 0xFF); + *ecc_code++ = ((bch_val2 >> 16) & 0xFF); + *ecc_code++ = ((bch_val2 >> 8) & 0xFF); + *ecc_code++ = (bch_val2 & 0xFF); + *ecc_code++ = ((bch_val1 >> 24) & 0xFF); + *ecc_code++ = ((bch_val1 >> 16) & 0xFF); + *ecc_code++ = ((bch_val1 >> 8) & 0xFF); + *ecc_code++ = (bch_val1 & 0xFF); + break; + case OMAP_ECC_BCH4_CODE_HW_DETECTION_SW: + case OMAP_ECC_BCH4_CODE_HW: + bch_val1 = readl(gpmc_regs->gpmc_bch_result0[i]); + bch_val2 = readl(gpmc_regs->gpmc_bch_result1[i]); + *ecc_code++ = ((bch_val2 >> 12) & 0xFF); + *ecc_code++ = ((bch_val2 >> 4) & 0xFF); + *ecc_code++ = ((bch_val2 & 0xF) << 4) | + ((bch_val1 >> 28) & 0xF); + *ecc_code++ = ((bch_val1 >> 20) & 0xFF); + *ecc_code++ = ((bch_val1 >> 12) & 0xFF); + *ecc_code++ = ((bch_val1 >> 4) & 0xFF); + *ecc_code++ = ((bch_val1 & 0xF) << 4); + break; + case OMAP_ECC_BCH16_CODE_HW: + val = readl(gpmc_regs->gpmc_bch_result6[i]); + ecc_code[0] = ((val >> 8) & 0xFF); + ecc_code[1] = ((val >> 0) & 0xFF); + val = readl(gpmc_regs->gpmc_bch_result5[i]); + ecc_code[2] = ((val >> 24) & 0xFF); + ecc_code[3] = ((val >> 16) & 0xFF); + ecc_code[4] = ((val >> 8) & 0xFF); + ecc_code[5] = ((val >> 0) & 0xFF); + val = readl(gpmc_regs->gpmc_bch_result4[i]); + ecc_code[6] = ((val >> 24) & 0xFF); + ecc_code[7] = ((val >> 16) & 0xFF); + ecc_code[8] = ((val >> 8) & 0xFF); + ecc_code[9] = ((val >> 0) & 0xFF); + val = readl(gpmc_regs->gpmc_bch_result3[i]); + ecc_code[10] = ((val >> 24) & 0xFF); + ecc_code[11] = ((val >> 16) & 0xFF); + ecc_code[12] = ((val >> 8) & 0xFF); + ecc_code[13] = ((val >> 0) & 0xFF); + val = readl(gpmc_regs->gpmc_bch_result2[i]); + ecc_code[14] = ((val >> 24) & 0xFF); + ecc_code[15] = ((val >> 16) & 0xFF); + ecc_code[16] = ((val >> 8) & 0xFF); + ecc_code[17] = ((val >> 0) & 0xFF); + val = readl(gpmc_regs->gpmc_bch_result1[i]); + ecc_code[18] = ((val >> 24) & 0xFF); + ecc_code[19] = ((val >> 16) & 0xFF); + ecc_code[20] = ((val >> 8) & 0xFF); + ecc_code[21] = ((val >> 0) & 0xFF); + val = readl(gpmc_regs->gpmc_bch_result0[i]); + ecc_code[22] = ((val >> 24) & 0xFF); + ecc_code[23] = ((val >> 16) & 0xFF); + ecc_code[24] = ((val >> 8) & 0xFF); + ecc_code[25] = ((val >> 0) & 0xFF); + break; + default: + return -EINVAL; + } + + /* ECC scheme specific syndrome customizations */ + switch (info->ecc_opt) { + case OMAP_ECC_BCH4_CODE_HW_DETECTION_SW: + /* Add constant polynomial to remainder, so that + * ECC of blank pages results in 0x0 on reading back */ + for (j = 0; j < eccbytes; j++) + ecc_calc[j] ^= bch4_polynomial[j]; + break; + case OMAP_ECC_BCH4_CODE_HW: + /* Set 8th ECC byte as 0x0 for ROM compatibility */ + ecc_calc[eccbytes - 1] = 0x0; + break; + case OMAP_ECC_BCH8_CODE_HW_DETECTION_SW: + /* Add constant polynomial to remainder, so that + * ECC of blank pages results in 0x0 on reading back */ + for (j = 0; j < eccbytes; j++) + ecc_calc[j] ^= bch8_polynomial[j]; + break; + case OMAP_ECC_BCH8_CODE_HW: + /* Set 14th ECC byte as 0x0 for ROM compatibility */ + ecc_calc[eccbytes - 1] = 0x0; + break; + case OMAP_ECC_BCH16_CODE_HW: + break; + default: + return -EINVAL; + } + + ecc_calc += eccbytes; + } + + return 0; +} + +#ifdef CONFIG_MTD_NAND_OMAP_BCH +/** + * erased_sector_bitflips - count bit flips + * @data: data sector buffer + * @oob: oob buffer + * @info: omap_nand_info + * + * Check the bit flips in erased page falls below correctable level. + * If falls below, report the page as erased with correctable bit + * flip, else report as uncorrectable page. + */ +static int erased_sector_bitflips(u_char *data, u_char *oob, + struct omap_nand_info *info) +{ + int flip_bits = 0, i; + + for (i = 0; i < info->nand.ecc.size; i++) { + flip_bits += hweight8(~data[i]); + if (flip_bits > info->nand.ecc.strength) + return 0; + } + + for (i = 0; i < info->nand.ecc.bytes - 1; i++) { + flip_bits += hweight8(~oob[i]); + if (flip_bits > info->nand.ecc.strength) + return 0; + } + + /* + * Bit flips falls in correctable level. + * Fill data area with 0xFF + */ + if (flip_bits) { + memset(data, 0xFF, info->nand.ecc.size); + memset(oob, 0xFF, info->nand.ecc.bytes); + } + + return flip_bits; +} + +/** + * omap_elm_correct_data - corrects page data area in case error reported + * @mtd: MTD device structure + * @data: page data + * @read_ecc: ecc read from nand flash + * @calc_ecc: ecc read from HW ECC registers + * + * Calculated ecc vector reported as zero in case of non-error pages. + * In case of non-zero ecc vector, first filter out erased-pages, and + * then process data via ELM to detect bit-flips. + */ +static int omap_elm_correct_data(struct mtd_info *mtd, u_char *data, + u_char *read_ecc, u_char *calc_ecc) +{ + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + struct nand_ecc_ctrl *ecc = &info->nand.ecc; + int eccsteps = info->nand.ecc.steps; + int i , j, stat = 0; + int eccflag, actual_eccbytes; + struct elm_errorvec err_vec[ERROR_VECTOR_MAX]; + u_char *ecc_vec = calc_ecc; + u_char *spare_ecc = read_ecc; + u_char *erased_ecc_vec; + u_char *buf; + int bitflip_count; + bool is_error_reported = false; + u32 bit_pos, byte_pos, error_max, pos; + int err; + + switch (info->ecc_opt) { + case OMAP_ECC_BCH4_CODE_HW: + /* omit 7th ECC byte reserved for ROM code compatibility */ + actual_eccbytes = ecc->bytes - 1; + erased_ecc_vec = bch4_vector; + break; + case OMAP_ECC_BCH8_CODE_HW: + /* omit 14th ECC byte reserved for ROM code compatibility */ + actual_eccbytes = ecc->bytes - 1; + erased_ecc_vec = bch8_vector; + break; + case OMAP_ECC_BCH16_CODE_HW: + actual_eccbytes = ecc->bytes; + erased_ecc_vec = bch16_vector; + break; + default: + pr_err("invalid driver configuration\n"); + return -EINVAL; + } + + /* Initialize elm error vector to zero */ + memset(err_vec, 0, sizeof(err_vec)); + + for (i = 0; i < eccsteps ; i++) { + eccflag = 0; /* initialize eccflag */ + + /* + * Check any error reported, + * In case of error, non zero ecc reported. + */ + for (j = 0; j < actual_eccbytes; j++) { + if (calc_ecc[j] != 0) { + eccflag = 1; /* non zero ecc, error present */ + break; + } + } + + if (eccflag == 1) { + if (memcmp(calc_ecc, erased_ecc_vec, + actual_eccbytes) == 0) { + /* + * calc_ecc[] matches pattern for ECC(all 0xff) + * so this is definitely an erased-page + */ + } else { + buf = &data[info->nand.ecc.size * i]; + /* + * count number of 0-bits in read_buf. + * This check can be removed once a similar + * check is introduced in generic NAND driver + */ + bitflip_count = erased_sector_bitflips( + buf, read_ecc, info); + if (bitflip_count) { + /* + * number of 0-bits within ECC limits + * So this may be an erased-page + */ + stat += bitflip_count; + } else { + /* + * Too many 0-bits. It may be a + * - programmed-page, OR + * - erased-page with many bit-flips + * So this page requires check by ELM + */ + err_vec[i].error_reported = true; + is_error_reported = true; + } + } + } + + /* Update the ecc vector */ + calc_ecc += ecc->bytes; + read_ecc += ecc->bytes; + } + + /* Check if any error reported */ + if (!is_error_reported) + return stat; + + /* Decode BCH error using ELM module */ + elm_decode_bch_error_page(info->elm_dev, ecc_vec, err_vec); + + err = 0; + for (i = 0; i < eccsteps; i++) { + if (err_vec[i].error_uncorrectable) { + pr_err("nand: uncorrectable bit-flips found\n"); + err = -EBADMSG; + } else if (err_vec[i].error_reported) { + for (j = 0; j < err_vec[i].error_count; j++) { + switch (info->ecc_opt) { + case OMAP_ECC_BCH4_CODE_HW: + /* Add 4 bits to take care of padding */ + pos = err_vec[i].error_loc[j] + + BCH4_BIT_PAD; + break; + case OMAP_ECC_BCH8_CODE_HW: + case OMAP_ECC_BCH16_CODE_HW: + pos = err_vec[i].error_loc[j]; + break; + default: + return -EINVAL; + } + error_max = (ecc->size + actual_eccbytes) * 8; + /* Calculate bit position of error */ + bit_pos = pos % 8; + + /* Calculate byte position of error */ + byte_pos = (error_max - pos - 1) / 8; + + if (pos < error_max) { + if (byte_pos < 512) { + pr_debug("bitflip@dat[%d]=%x\n", + byte_pos, data[byte_pos]); + data[byte_pos] ^= 1 << bit_pos; + } else { + pr_debug("bitflip@oob[%d]=%x\n", + (byte_pos - 512), + spare_ecc[byte_pos - 512]); + spare_ecc[byte_pos - 512] ^= + 1 << bit_pos; + } + } else { + pr_err("invalid bit-flip @ %d:%d\n", + byte_pos, bit_pos); + err = -EBADMSG; + } + } + } + + /* Update number of correctable errors */ + stat += err_vec[i].error_count; + + /* Update page data with sector size */ + data += ecc->size; + spare_ecc += ecc->bytes; + } + + return (err) ? err : stat; +} + +/** + * omap_write_page_bch - BCH ecc based write page function for entire page + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: data buffer + * @oob_required: must write chip->oob_poi to OOB + * + * Custom write page method evolved to support multi sector writing in one shot + */ +static int omap_write_page_bch(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf, int oob_required) +{ + int i; + uint8_t *ecc_calc = chip->buffers->ecccalc; + uint32_t *eccpos = chip->ecc.layout->eccpos; + + /* Enable GPMC ecc engine */ + chip->ecc.hwctl(mtd, NAND_ECC_WRITE); + + /* Write data */ + chip->write_buf(mtd, buf, mtd->writesize); + + /* Update ecc vector from GPMC result registers */ + chip->ecc.calculate(mtd, buf, &ecc_calc[0]); + + for (i = 0; i < chip->ecc.total; i++) + chip->oob_poi[eccpos[i]] = ecc_calc[i]; + + /* Write ecc vector to OOB area */ + chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); + return 0; +} + +/** + * omap_read_page_bch - BCH ecc based page read function for entire page + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: buffer to store read data + * @oob_required: caller requires OOB data read to chip->oob_poi + * @page: page number to read + * + * For BCH ecc scheme, GPMC used for syndrome calculation and ELM module + * used for error correction. + * Custom method evolved to support ELM error correction & multi sector + * reading. On reading page data area is read along with OOB data with + * ecc engine enabled. ecc vector updated after read of OOB data. + * For non error pages ecc vector reported as zero. + */ +static int omap_read_page_bch(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf, int oob_required, int page) +{ + uint8_t *ecc_calc = chip->buffers->ecccalc; + uint8_t *ecc_code = chip->buffers->ecccode; + uint32_t *eccpos = chip->ecc.layout->eccpos; + uint8_t *oob = &chip->oob_poi[eccpos[0]]; + uint32_t oob_pos = mtd->writesize + chip->ecc.layout->eccpos[0]; + int stat; + unsigned int max_bitflips = 0; + + /* Enable GPMC ecc engine */ + chip->ecc.hwctl(mtd, NAND_ECC_READ); + + /* Read data */ + chip->read_buf(mtd, buf, mtd->writesize); + + /* Read oob bytes */ + chip->cmdfunc(mtd, NAND_CMD_RNDOUT, oob_pos, -1); + chip->read_buf(mtd, oob, chip->ecc.total); + + /* Calculate ecc bytes */ + chip->ecc.calculate(mtd, buf, ecc_calc); + + memcpy(ecc_code, &chip->oob_poi[eccpos[0]], chip->ecc.total); + + stat = chip->ecc.correct(mtd, buf, ecc_code, ecc_calc); + + if (stat < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } + + return max_bitflips; +} + +/** + * is_elm_present - checks for presence of ELM module by scanning DT nodes + * @omap_nand_info: NAND device structure containing platform data + * @bch_type: 0x0=BCH4, 0x1=BCH8, 0x2=BCH16 + */ +static int is_elm_present(struct omap_nand_info *info, + struct device_node *elm_node, enum bch_ecc bch_type) +{ + struct platform_device *pdev; + struct nand_ecc_ctrl *ecc = &info->nand.ecc; + int err; + /* check whether elm-id is passed via DT */ + if (!elm_node) { + pr_err("nand: error: ELM DT node not found\n"); + return -ENODEV; + } + pdev = of_find_device_by_node(elm_node); + /* check whether ELM device is registered */ + if (!pdev) { + pr_err("nand: error: ELM device not found\n"); + return -ENODEV; + } + /* ELM module available, now configure it */ + info->elm_dev = &pdev->dev; + err = elm_config(info->elm_dev, bch_type, + (info->mtd.writesize / ecc->size), ecc->size, ecc->bytes); + + return err; +} +#endif /* CONFIG_MTD_NAND_ECC_BCH */ + +static int omap_nand_probe(struct platform_device *pdev) +{ + struct omap_nand_info *info; + struct omap_nand_platform_data *pdata; + struct mtd_info *mtd; + struct nand_chip *nand_chip; + struct nand_ecclayout *ecclayout; + int err; + int i; + dma_cap_mask_t mask; + unsigned sig; + unsigned oob_index; + struct resource *res; + struct mtd_part_parser_data ppdata = {}; + + pdata = dev_get_platdata(&pdev->dev); + if (pdata == NULL) { + dev_err(&pdev->dev, "platform data missing\n"); + return -ENODEV; + } + + info = devm_kzalloc(&pdev->dev, sizeof(struct omap_nand_info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + platform_set_drvdata(pdev, info); + + spin_lock_init(&info->controller.lock); + init_waitqueue_head(&info->controller.wq); + + info->pdev = pdev; + info->gpmc_cs = pdata->cs; + info->reg = pdata->reg; + info->of_node = pdata->of_node; + info->ecc_opt = pdata->ecc_opt; + mtd = &info->mtd; + mtd->priv = &info->nand; + mtd->name = dev_name(&pdev->dev); + mtd->owner = THIS_MODULE; + nand_chip = &info->nand; + nand_chip->ecc.priv = NULL; + nand_chip->options |= NAND_SKIP_BBTSCAN; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + nand_chip->IO_ADDR_R = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(nand_chip->IO_ADDR_R)) + return PTR_ERR(nand_chip->IO_ADDR_R); + + info->phys_base = res->start; + + nand_chip->controller = &info->controller; + + nand_chip->IO_ADDR_W = nand_chip->IO_ADDR_R; + nand_chip->cmd_ctrl = omap_hwcontrol; + + /* + * If RDY/BSY line is connected to OMAP then use the omap ready + * function and the generic nand_wait function which reads the status + * register after monitoring the RDY/BSY line. Otherwise use a standard + * chip delay which is slightly more than tR (AC Timing) of the NAND + * device and read status register until you get a failure or success + */ + if (pdata->dev_ready) { + nand_chip->dev_ready = omap_dev_ready; + nand_chip->chip_delay = 0; + } else { + nand_chip->waitfunc = omap_wait; + nand_chip->chip_delay = 50; + } + + /* scan NAND device connected to chip controller */ + nand_chip->options |= pdata->devsize & NAND_BUSWIDTH_16; + if (nand_scan_ident(mtd, 1, NULL)) { + pr_err("nand device scan failed, may be bus-width mismatch\n"); + err = -ENXIO; + goto return_error; + } + + /* check for small page devices */ + if ((mtd->oobsize < 64) && (pdata->ecc_opt != OMAP_ECC_HAM1_CODE_HW)) { + pr_err("small page devices are not supported\n"); + err = -EINVAL; + goto return_error; + } + + /* re-populate low-level callbacks based on xfer modes */ + switch (pdata->xfer_type) { + case NAND_OMAP_PREFETCH_POLLED: + nand_chip->read_buf = omap_read_buf_pref; + nand_chip->write_buf = omap_write_buf_pref; + break; + + case NAND_OMAP_POLLED: + /* Use nand_base defaults for {read,write}_buf */ + break; + + case NAND_OMAP_PREFETCH_DMA: + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + sig = OMAP24XX_DMA_GPMC; + info->dma = dma_request_channel(mask, omap_dma_filter_fn, &sig); + if (!info->dma) { + dev_err(&pdev->dev, "DMA engine request failed\n"); + err = -ENXIO; + goto return_error; + } else { + struct dma_slave_config cfg; + + memset(&cfg, 0, sizeof(cfg)); + cfg.src_addr = info->phys_base; + cfg.dst_addr = info->phys_base; + cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + cfg.src_maxburst = 16; + cfg.dst_maxburst = 16; + err = dmaengine_slave_config(info->dma, &cfg); + if (err) { + dev_err(&pdev->dev, "DMA engine slave config failed: %d\n", + err); + goto return_error; + } + nand_chip->read_buf = omap_read_buf_dma_pref; + nand_chip->write_buf = omap_write_buf_dma_pref; + } + break; + + case NAND_OMAP_PREFETCH_IRQ: + info->gpmc_irq_fifo = platform_get_irq(pdev, 0); + if (info->gpmc_irq_fifo <= 0) { + dev_err(&pdev->dev, "error getting fifo irq\n"); + err = -ENODEV; + goto return_error; + } + err = devm_request_irq(&pdev->dev, info->gpmc_irq_fifo, + omap_nand_irq, IRQF_SHARED, + "gpmc-nand-fifo", info); + if (err) { + dev_err(&pdev->dev, "requesting irq(%d) error:%d", + info->gpmc_irq_fifo, err); + info->gpmc_irq_fifo = 0; + goto return_error; + } + + info->gpmc_irq_count = platform_get_irq(pdev, 1); + if (info->gpmc_irq_count <= 0) { + dev_err(&pdev->dev, "error getting count irq\n"); + err = -ENODEV; + goto return_error; + } + err = devm_request_irq(&pdev->dev, info->gpmc_irq_count, + omap_nand_irq, IRQF_SHARED, + "gpmc-nand-count", info); + if (err) { + dev_err(&pdev->dev, "requesting irq(%d) error:%d", + info->gpmc_irq_count, err); + info->gpmc_irq_count = 0; + goto return_error; + } + + nand_chip->read_buf = omap_read_buf_irq_pref; + nand_chip->write_buf = omap_write_buf_irq_pref; + + break; + + default: + dev_err(&pdev->dev, + "xfer_type(%d) not supported!\n", pdata->xfer_type); + err = -EINVAL; + goto return_error; + } + + /* populate MTD interface based on ECC scheme */ + nand_chip->ecc.layout = &omap_oobinfo; + ecclayout = &omap_oobinfo; + switch (info->ecc_opt) { + case OMAP_ECC_HAM1_CODE_HW: + pr_info("nand: using OMAP_ECC_HAM1_CODE_HW\n"); + nand_chip->ecc.mode = NAND_ECC_HW; + nand_chip->ecc.bytes = 3; + nand_chip->ecc.size = 512; + nand_chip->ecc.strength = 1; + nand_chip->ecc.calculate = omap_calculate_ecc; + nand_chip->ecc.hwctl = omap_enable_hwecc; + nand_chip->ecc.correct = omap_correct_data; + /* define ECC layout */ + ecclayout->eccbytes = nand_chip->ecc.bytes * + (mtd->writesize / + nand_chip->ecc.size); + if (nand_chip->options & NAND_BUSWIDTH_16) + oob_index = BADBLOCK_MARKER_LENGTH; + else + oob_index = 1; + for (i = 0; i < ecclayout->eccbytes; i++, oob_index++) + ecclayout->eccpos[i] = oob_index; + /* no reserved-marker in ecclayout for this ecc-scheme */ + ecclayout->oobfree->offset = + ecclayout->eccpos[ecclayout->eccbytes - 1] + 1; + break; + + case OMAP_ECC_BCH4_CODE_HW_DETECTION_SW: +#ifdef CONFIG_MTD_NAND_ECC_BCH + pr_info("nand: using OMAP_ECC_BCH4_CODE_HW_DETECTION_SW\n"); + nand_chip->ecc.mode = NAND_ECC_HW; + nand_chip->ecc.size = 512; + nand_chip->ecc.bytes = 7; + nand_chip->ecc.strength = 4; + nand_chip->ecc.hwctl = omap_enable_hwecc_bch; + nand_chip->ecc.correct = nand_bch_correct_data; + nand_chip->ecc.calculate = omap_calculate_ecc_bch; + /* define ECC layout */ + ecclayout->eccbytes = nand_chip->ecc.bytes * + (mtd->writesize / + nand_chip->ecc.size); + oob_index = BADBLOCK_MARKER_LENGTH; + for (i = 0; i < ecclayout->eccbytes; i++, oob_index++) { + ecclayout->eccpos[i] = oob_index; + if (((i + 1) % nand_chip->ecc.bytes) == 0) + oob_index++; + } + /* include reserved-marker in ecclayout->oobfree calculation */ + ecclayout->oobfree->offset = 1 + + ecclayout->eccpos[ecclayout->eccbytes - 1] + 1; + /* software bch library is used for locating errors */ + nand_chip->ecc.priv = nand_bch_init(mtd, + nand_chip->ecc.size, + nand_chip->ecc.bytes, + &nand_chip->ecc.layout); + if (!nand_chip->ecc.priv) { + pr_err("nand: error: unable to use s/w BCH library\n"); + err = -EINVAL; + } + break; +#else + pr_err("nand: error: CONFIG_MTD_NAND_ECC_BCH not enabled\n"); + err = -EINVAL; + goto return_error; +#endif + + case OMAP_ECC_BCH4_CODE_HW: +#ifdef CONFIG_MTD_NAND_OMAP_BCH + pr_info("nand: using OMAP_ECC_BCH4_CODE_HW ECC scheme\n"); + nand_chip->ecc.mode = NAND_ECC_HW; + nand_chip->ecc.size = 512; + /* 14th bit is kept reserved for ROM-code compatibility */ + nand_chip->ecc.bytes = 7 + 1; + nand_chip->ecc.strength = 4; + nand_chip->ecc.hwctl = omap_enable_hwecc_bch; + nand_chip->ecc.correct = omap_elm_correct_data; + nand_chip->ecc.calculate = omap_calculate_ecc_bch; + nand_chip->ecc.read_page = omap_read_page_bch; + nand_chip->ecc.write_page = omap_write_page_bch; + /* define ECC layout */ + ecclayout->eccbytes = nand_chip->ecc.bytes * + (mtd->writesize / + nand_chip->ecc.size); + oob_index = BADBLOCK_MARKER_LENGTH; + for (i = 0; i < ecclayout->eccbytes; i++, oob_index++) + ecclayout->eccpos[i] = oob_index; + /* reserved marker already included in ecclayout->eccbytes */ + ecclayout->oobfree->offset = + ecclayout->eccpos[ecclayout->eccbytes - 1] + 1; + /* This ECC scheme requires ELM H/W block */ + if (is_elm_present(info, pdata->elm_of_node, BCH4_ECC) < 0) { + pr_err("nand: error: could not initialize ELM\n"); + err = -ENODEV; + goto return_error; + } + break; +#else + pr_err("nand: error: CONFIG_MTD_NAND_OMAP_BCH not enabled\n"); + err = -EINVAL; + goto return_error; +#endif + + case OMAP_ECC_BCH8_CODE_HW_DETECTION_SW: +#ifdef CONFIG_MTD_NAND_ECC_BCH + pr_info("nand: using OMAP_ECC_BCH8_CODE_HW_DETECTION_SW\n"); + nand_chip->ecc.mode = NAND_ECC_HW; + nand_chip->ecc.size = 512; + nand_chip->ecc.bytes = 13; + nand_chip->ecc.strength = 8; + nand_chip->ecc.hwctl = omap_enable_hwecc_bch; + nand_chip->ecc.correct = nand_bch_correct_data; + nand_chip->ecc.calculate = omap_calculate_ecc_bch; + /* define ECC layout */ + ecclayout->eccbytes = nand_chip->ecc.bytes * + (mtd->writesize / + nand_chip->ecc.size); + oob_index = BADBLOCK_MARKER_LENGTH; + for (i = 0; i < ecclayout->eccbytes; i++, oob_index++) { + ecclayout->eccpos[i] = oob_index; + if (((i + 1) % nand_chip->ecc.bytes) == 0) + oob_index++; + } + /* include reserved-marker in ecclayout->oobfree calculation */ + ecclayout->oobfree->offset = 1 + + ecclayout->eccpos[ecclayout->eccbytes - 1] + 1; + /* software bch library is used for locating errors */ + nand_chip->ecc.priv = nand_bch_init(mtd, + nand_chip->ecc.size, + nand_chip->ecc.bytes, + &nand_chip->ecc.layout); + if (!nand_chip->ecc.priv) { + pr_err("nand: error: unable to use s/w BCH library\n"); + err = -EINVAL; + goto return_error; + } + break; +#else + pr_err("nand: error: CONFIG_MTD_NAND_ECC_BCH not enabled\n"); + err = -EINVAL; + goto return_error; +#endif + + case OMAP_ECC_BCH8_CODE_HW: +#ifdef CONFIG_MTD_NAND_OMAP_BCH + pr_info("nand: using OMAP_ECC_BCH8_CODE_HW ECC scheme\n"); + nand_chip->ecc.mode = NAND_ECC_HW; + nand_chip->ecc.size = 512; + /* 14th bit is kept reserved for ROM-code compatibility */ + nand_chip->ecc.bytes = 13 + 1; + nand_chip->ecc.strength = 8; + nand_chip->ecc.hwctl = omap_enable_hwecc_bch; + nand_chip->ecc.correct = omap_elm_correct_data; + nand_chip->ecc.calculate = omap_calculate_ecc_bch; + nand_chip->ecc.read_page = omap_read_page_bch; + nand_chip->ecc.write_page = omap_write_page_bch; + /* This ECC scheme requires ELM H/W block */ + err = is_elm_present(info, pdata->elm_of_node, BCH8_ECC); + if (err < 0) { + pr_err("nand: error: could not initialize ELM\n"); + goto return_error; + } + /* define ECC layout */ + ecclayout->eccbytes = nand_chip->ecc.bytes * + (mtd->writesize / + nand_chip->ecc.size); + oob_index = BADBLOCK_MARKER_LENGTH; + for (i = 0; i < ecclayout->eccbytes; i++, oob_index++) + ecclayout->eccpos[i] = oob_index; + /* reserved marker already included in ecclayout->eccbytes */ + ecclayout->oobfree->offset = + ecclayout->eccpos[ecclayout->eccbytes - 1] + 1; + break; +#else + pr_err("nand: error: CONFIG_MTD_NAND_OMAP_BCH not enabled\n"); + err = -EINVAL; + goto return_error; +#endif + + case OMAP_ECC_BCH16_CODE_HW: +#ifdef CONFIG_MTD_NAND_OMAP_BCH + pr_info("using OMAP_ECC_BCH16_CODE_HW ECC scheme\n"); + nand_chip->ecc.mode = NAND_ECC_HW; + nand_chip->ecc.size = 512; + nand_chip->ecc.bytes = 26; + nand_chip->ecc.strength = 16; + nand_chip->ecc.hwctl = omap_enable_hwecc_bch; + nand_chip->ecc.correct = omap_elm_correct_data; + nand_chip->ecc.calculate = omap_calculate_ecc_bch; + nand_chip->ecc.read_page = omap_read_page_bch; + nand_chip->ecc.write_page = omap_write_page_bch; + /* This ECC scheme requires ELM H/W block */ + err = is_elm_present(info, pdata->elm_of_node, BCH16_ECC); + if (err < 0) { + pr_err("ELM is required for this ECC scheme\n"); + goto return_error; + } + /* define ECC layout */ + ecclayout->eccbytes = nand_chip->ecc.bytes * + (mtd->writesize / + nand_chip->ecc.size); + oob_index = BADBLOCK_MARKER_LENGTH; + for (i = 0; i < ecclayout->eccbytes; i++, oob_index++) + ecclayout->eccpos[i] = oob_index; + /* reserved marker already included in ecclayout->eccbytes */ + ecclayout->oobfree->offset = + ecclayout->eccpos[ecclayout->eccbytes - 1] + 1; + break; +#else + pr_err("nand: error: CONFIG_MTD_NAND_OMAP_BCH not enabled\n"); + err = -EINVAL; + goto return_error; +#endif + default: + pr_err("nand: error: invalid or unsupported ECC scheme\n"); + err = -EINVAL; + goto return_error; + } + + /* all OOB bytes from oobfree->offset till end off OOB are free */ + ecclayout->oobfree->length = mtd->oobsize - ecclayout->oobfree->offset; + /* check if NAND device's OOB is enough to store ECC signatures */ + if (mtd->oobsize < (ecclayout->eccbytes + BADBLOCK_MARKER_LENGTH)) { + pr_err("not enough OOB bytes required = %d, available=%d\n", + ecclayout->eccbytes, mtd->oobsize); + err = -EINVAL; + goto return_error; + } + + /* second phase scan */ + if (nand_scan_tail(mtd)) { + err = -ENXIO; + goto return_error; + } + + ppdata.of_node = pdata->of_node; + mtd_device_parse_register(mtd, NULL, &ppdata, pdata->parts, + pdata->nr_parts); + + platform_set_drvdata(pdev, mtd); + + return 0; + +return_error: + if (info->dma) + dma_release_channel(info->dma); + if (nand_chip->ecc.priv) { + nand_bch_free(nand_chip->ecc.priv); + nand_chip->ecc.priv = NULL; + } + return err; +} + +static int omap_nand_remove(struct platform_device *pdev) +{ + struct mtd_info *mtd = platform_get_drvdata(pdev); + struct nand_chip *nand_chip = mtd->priv; + struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, + mtd); + if (nand_chip->ecc.priv) { + nand_bch_free(nand_chip->ecc.priv); + nand_chip->ecc.priv = NULL; + } + if (info->dma) + dma_release_channel(info->dma); + nand_release(mtd); + return 0; +} + +static struct platform_driver omap_nand_driver = { + .probe = omap_nand_probe, + .remove = omap_nand_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(omap_nand_driver); + +MODULE_ALIAS("platform:" DRIVER_NAME); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Glue layer for NAND flash on TI OMAP boards"); diff --git a/drivers/mtd/nand/orion_nand.c b/drivers/mtd/nand/orion_nand.c new file mode 100644 index 00000000000..471b4df3a5a --- /dev/null +++ b/drivers/mtd/nand/orion_nand.c @@ -0,0 +1,237 @@ +/* + * drivers/mtd/nand/orion_nand.c + * + * NAND support for Marvell Orion SoC platforms + * + * Tzachi Perelstein <tzachi@marvell.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <asm/io.h> +#include <asm/sizes.h> +#include <linux/platform_data/mtd-orion_nand.h> + +static void orion_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl) +{ + struct nand_chip *nc = mtd->priv; + struct orion_nand_data *board = nc->priv; + u32 offs; + + if (cmd == NAND_CMD_NONE) + return; + + if (ctrl & NAND_CLE) + offs = (1 << board->cle); + else if (ctrl & NAND_ALE) + offs = (1 << board->ale); + else + return; + + if (nc->options & NAND_BUSWIDTH_16) + offs <<= 1; + + writeb(cmd, nc->IO_ADDR_W + offs); +} + +static void orion_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + void __iomem *io_base = chip->IO_ADDR_R; + uint64_t *buf64; + int i = 0; + + while (len && (unsigned long)buf & 7) { + *buf++ = readb(io_base); + len--; + } + buf64 = (uint64_t *)buf; + while (i < len/8) { + /* + * Since GCC has no proper constraint (PR 43518) + * force x variable to r2/r3 registers as ldrd instruction + * requires first register to be even. + */ + register uint64_t x asm ("r2"); + + asm volatile ("ldrd\t%0, [%1]" : "=&r" (x) : "r" (io_base)); + buf64[i++] = x; + } + i *= 8; + while (i < len) + buf[i++] = readb(io_base); +} + +static int __init orion_nand_probe(struct platform_device *pdev) +{ + struct mtd_info *mtd; + struct mtd_part_parser_data ppdata = {}; + struct nand_chip *nc; + struct orion_nand_data *board; + struct resource *res; + struct clk *clk; + void __iomem *io_base; + int ret = 0; + u32 val = 0; + + nc = kzalloc(sizeof(struct nand_chip) + sizeof(struct mtd_info), GFP_KERNEL); + if (!nc) { + ret = -ENOMEM; + goto no_res; + } + mtd = (struct mtd_info *)(nc + 1); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENODEV; + goto no_res; + } + + io_base = ioremap(res->start, resource_size(res)); + if (!io_base) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -EIO; + goto no_res; + } + + if (pdev->dev.of_node) { + board = devm_kzalloc(&pdev->dev, sizeof(struct orion_nand_data), + GFP_KERNEL); + if (!board) { + ret = -ENOMEM; + goto no_res; + } + if (!of_property_read_u32(pdev->dev.of_node, "cle", &val)) + board->cle = (u8)val; + else + board->cle = 0; + if (!of_property_read_u32(pdev->dev.of_node, "ale", &val)) + board->ale = (u8)val; + else + board->ale = 1; + if (!of_property_read_u32(pdev->dev.of_node, + "bank-width", &val)) + board->width = (u8)val * 8; + else + board->width = 8; + if (!of_property_read_u32(pdev->dev.of_node, + "chip-delay", &val)) + board->chip_delay = (u8)val; + } else { + board = dev_get_platdata(&pdev->dev); + } + + mtd->priv = nc; + mtd->owner = THIS_MODULE; + + nc->priv = board; + nc->IO_ADDR_R = nc->IO_ADDR_W = io_base; + nc->cmd_ctrl = orion_nand_cmd_ctrl; + nc->read_buf = orion_nand_read_buf; + nc->ecc.mode = NAND_ECC_SOFT; + + if (board->chip_delay) + nc->chip_delay = board->chip_delay; + + WARN(board->width > 16, + "%d bit bus width out of range", + board->width); + + if (board->width == 16) + nc->options |= NAND_BUSWIDTH_16; + + if (board->dev_ready) + nc->dev_ready = board->dev_ready; + + platform_set_drvdata(pdev, mtd); + + /* Not all platforms can gate the clock, so it is not + an error if the clock does not exists. */ + clk = clk_get(&pdev->dev, NULL); + if (!IS_ERR(clk)) { + clk_prepare_enable(clk); + clk_put(clk); + } + + if (nand_scan(mtd, 1)) { + ret = -ENXIO; + goto no_dev; + } + + mtd->name = "orion_nand"; + ppdata.of_node = pdev->dev.of_node; + ret = mtd_device_parse_register(mtd, NULL, &ppdata, + board->parts, board->nr_parts); + if (ret) { + nand_release(mtd); + goto no_dev; + } + + return 0; + +no_dev: + if (!IS_ERR(clk)) { + clk_disable_unprepare(clk); + clk_put(clk); + } + iounmap(io_base); +no_res: + kfree(nc); + + return ret; +} + +static int orion_nand_remove(struct platform_device *pdev) +{ + struct mtd_info *mtd = platform_get_drvdata(pdev); + struct nand_chip *nc = mtd->priv; + struct clk *clk; + + nand_release(mtd); + + iounmap(nc->IO_ADDR_W); + + kfree(nc); + + clk = clk_get(&pdev->dev, NULL); + if (!IS_ERR(clk)) { + clk_disable_unprepare(clk); + clk_put(clk); + } + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id orion_nand_of_match_table[] = { + { .compatible = "marvell,orion-nand", }, + {}, +}; +#endif + +static struct platform_driver orion_nand_driver = { + .remove = orion_nand_remove, + .driver = { + .name = "orion_nand", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(orion_nand_of_match_table), + }, +}; + +module_platform_driver_probe(orion_nand_driver, orion_nand_probe); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Tzachi Perelstein"); +MODULE_DESCRIPTION("NAND glue for Orion platforms"); +MODULE_ALIAS("platform:orion_nand"); diff --git a/drivers/mtd/nand/pasemi_nand.c b/drivers/mtd/nand/pasemi_nand.c new file mode 100644 index 00000000000..2c98f9da747 --- /dev/null +++ b/drivers/mtd/nand/pasemi_nand.c @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2006-2007 PA Semi, Inc + * + * Author: Egor Martovetsky <egor@pasemi.com> + * Maintained by: Olof Johansson <olof@lixom.net> + * + * Driver for the PWRficient onchip NAND flash interface + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#undef DEBUG + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/nand_ecc.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/pci.h> + +#include <asm/io.h> + +#define LBICTRL_LPCCTL_NR 0x00004000 +#define CLE_PIN_CTL 15 +#define ALE_PIN_CTL 14 + +static unsigned int lpcctl; +static struct mtd_info *pasemi_nand_mtd; +static const char driver_name[] = "pasemi-nand"; + +static void pasemi_read_buf(struct mtd_info *mtd, u_char *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + + while (len > 0x800) { + memcpy_fromio(buf, chip->IO_ADDR_R, 0x800); + buf += 0x800; + len -= 0x800; + } + memcpy_fromio(buf, chip->IO_ADDR_R, len); +} + +static void pasemi_write_buf(struct mtd_info *mtd, const u_char *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + + while (len > 0x800) { + memcpy_toio(chip->IO_ADDR_R, buf, 0x800); + buf += 0x800; + len -= 0x800; + } + memcpy_toio(chip->IO_ADDR_R, buf, len); +} + +static void pasemi_hwcontrol(struct mtd_info *mtd, int cmd, + unsigned int ctrl) +{ + struct nand_chip *chip = mtd->priv; + + if (cmd == NAND_CMD_NONE) + return; + + if (ctrl & NAND_CLE) + out_8(chip->IO_ADDR_W + (1 << CLE_PIN_CTL), cmd); + else + out_8(chip->IO_ADDR_W + (1 << ALE_PIN_CTL), cmd); + + /* Push out posted writes */ + eieio(); + inl(lpcctl); +} + +int pasemi_device_ready(struct mtd_info *mtd) +{ + return !!(inl(lpcctl) & LBICTRL_LPCCTL_NR); +} + +static int pasemi_nand_probe(struct platform_device *ofdev) +{ + struct pci_dev *pdev; + struct device_node *np = ofdev->dev.of_node; + struct resource res; + struct nand_chip *chip; + int err = 0; + + err = of_address_to_resource(np, 0, &res); + + if (err) + return -EINVAL; + + /* We only support one device at the moment */ + if (pasemi_nand_mtd) + return -ENODEV; + + pr_debug("pasemi_nand at %pR\n", &res); + + /* Allocate memory for MTD device structure and private data */ + pasemi_nand_mtd = kzalloc(sizeof(struct mtd_info) + + sizeof(struct nand_chip), GFP_KERNEL); + if (!pasemi_nand_mtd) { + printk(KERN_WARNING + "Unable to allocate PASEMI NAND MTD device structure\n"); + err = -ENOMEM; + goto out; + } + + /* Get pointer to private data */ + chip = (struct nand_chip *)&pasemi_nand_mtd[1]; + + /* Link the private data with the MTD structure */ + pasemi_nand_mtd->priv = chip; + pasemi_nand_mtd->owner = THIS_MODULE; + + chip->IO_ADDR_R = of_iomap(np, 0); + chip->IO_ADDR_W = chip->IO_ADDR_R; + + if (!chip->IO_ADDR_R) { + err = -EIO; + goto out_mtd; + } + + pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa008, NULL); + if (!pdev) { + err = -ENODEV; + goto out_ior; + } + + lpcctl = pci_resource_start(pdev, 0); + pci_dev_put(pdev); + + if (!request_region(lpcctl, 4, driver_name)) { + err = -EBUSY; + goto out_ior; + } + + chip->cmd_ctrl = pasemi_hwcontrol; + chip->dev_ready = pasemi_device_ready; + chip->read_buf = pasemi_read_buf; + chip->write_buf = pasemi_write_buf; + chip->chip_delay = 0; + chip->ecc.mode = NAND_ECC_SOFT; + + /* Enable the following for a flash based bad block table */ + chip->bbt_options = NAND_BBT_USE_FLASH; + + /* Scan to find existence of the device */ + if (nand_scan(pasemi_nand_mtd, 1)) { + err = -ENXIO; + goto out_lpc; + } + + if (mtd_device_register(pasemi_nand_mtd, NULL, 0)) { + printk(KERN_ERR "pasemi_nand: Unable to register MTD device\n"); + err = -ENODEV; + goto out_lpc; + } + + printk(KERN_INFO "PA Semi NAND flash at %08llx, control at I/O %x\n", + res.start, lpcctl); + + return 0; + + out_lpc: + release_region(lpcctl, 4); + out_ior: + iounmap(chip->IO_ADDR_R); + out_mtd: + kfree(pasemi_nand_mtd); + out: + return err; +} + +static int pasemi_nand_remove(struct platform_device *ofdev) +{ + struct nand_chip *chip; + + if (!pasemi_nand_mtd) + return 0; + + chip = pasemi_nand_mtd->priv; + + /* Release resources, unregister device */ + nand_release(pasemi_nand_mtd); + + release_region(lpcctl, 4); + + iounmap(chip->IO_ADDR_R); + + /* Free the MTD device structure */ + kfree(pasemi_nand_mtd); + + pasemi_nand_mtd = NULL; + + return 0; +} + +static const struct of_device_id pasemi_nand_match[] = +{ + { + .compatible = "pasemi,localbus-nand", + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, pasemi_nand_match); + +static struct platform_driver pasemi_nand_driver = +{ + .driver = { + .name = driver_name, + .owner = THIS_MODULE, + .of_match_table = pasemi_nand_match, + }, + .probe = pasemi_nand_probe, + .remove = pasemi_nand_remove, +}; + +module_platform_driver(pasemi_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Egor Martovetsky <egor@pasemi.com>"); +MODULE_DESCRIPTION("NAND flash interface driver for PA Semi PWRficient"); diff --git a/drivers/mtd/nand/plat_nand.c b/drivers/mtd/nand/plat_nand.c new file mode 100644 index 00000000000..0b068a5c0bf --- /dev/null +++ b/drivers/mtd/nand/plat_nand.c @@ -0,0 +1,151 @@ +/* + * Generic NAND driver + * + * Author: Vitaly Wool <vitalywool@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> + +struct plat_nand_data { + struct nand_chip chip; + struct mtd_info mtd; + void __iomem *io_base; +}; + +static const char *part_probe_types[] = { "cmdlinepart", NULL }; + +/* + * Probe for the NAND device. + */ +static int plat_nand_probe(struct platform_device *pdev) +{ + struct platform_nand_data *pdata = dev_get_platdata(&pdev->dev); + struct mtd_part_parser_data ppdata; + struct plat_nand_data *data; + struct resource *res; + const char **part_types; + int err = 0; + + if (!pdata) { + dev_err(&pdev->dev, "platform_nand_data is missing\n"); + return -EINVAL; + } + + if (pdata->chip.nr_chips < 1) { + dev_err(&pdev->dev, "invalid number of chips specified\n"); + return -EINVAL; + } + + /* Allocate memory for the device structure (and zero it) */ + data = devm_kzalloc(&pdev->dev, sizeof(struct plat_nand_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + data->io_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(data->io_base)) + return PTR_ERR(data->io_base); + + data->chip.priv = &data; + data->mtd.priv = &data->chip; + data->mtd.owner = THIS_MODULE; + data->mtd.name = dev_name(&pdev->dev); + + data->chip.IO_ADDR_R = data->io_base; + data->chip.IO_ADDR_W = data->io_base; + data->chip.cmd_ctrl = pdata->ctrl.cmd_ctrl; + data->chip.dev_ready = pdata->ctrl.dev_ready; + data->chip.select_chip = pdata->ctrl.select_chip; + data->chip.write_buf = pdata->ctrl.write_buf; + data->chip.read_buf = pdata->ctrl.read_buf; + data->chip.read_byte = pdata->ctrl.read_byte; + data->chip.chip_delay = pdata->chip.chip_delay; + data->chip.options |= pdata->chip.options; + data->chip.bbt_options |= pdata->chip.bbt_options; + + data->chip.ecc.hwctl = pdata->ctrl.hwcontrol; + data->chip.ecc.layout = pdata->chip.ecclayout; + data->chip.ecc.mode = NAND_ECC_SOFT; + + platform_set_drvdata(pdev, data); + + /* Handle any platform specific setup */ + if (pdata->ctrl.probe) { + err = pdata->ctrl.probe(pdev); + if (err) + goto out; + } + + /* Scan to find existence of the device */ + if (nand_scan(&data->mtd, pdata->chip.nr_chips)) { + err = -ENXIO; + goto out; + } + + part_types = pdata->chip.part_probe_types ? : part_probe_types; + + ppdata.of_node = pdev->dev.of_node; + err = mtd_device_parse_register(&data->mtd, part_types, &ppdata, + pdata->chip.partitions, + pdata->chip.nr_partitions); + + if (!err) + return err; + + nand_release(&data->mtd); +out: + if (pdata->ctrl.remove) + pdata->ctrl.remove(pdev); + return err; +} + +/* + * Remove a NAND device. + */ +static int plat_nand_remove(struct platform_device *pdev) +{ + struct plat_nand_data *data = platform_get_drvdata(pdev); + struct platform_nand_data *pdata = dev_get_platdata(&pdev->dev); + + nand_release(&data->mtd); + if (pdata->ctrl.remove) + pdata->ctrl.remove(pdev); + + return 0; +} + +static const struct of_device_id plat_nand_match[] = { + { .compatible = "gen_nand" }, + {}, +}; +MODULE_DEVICE_TABLE(of, plat_nand_match); + +static struct platform_driver plat_nand_driver = { + .probe = plat_nand_probe, + .remove = plat_nand_remove, + .driver = { + .name = "gen_nand", + .owner = THIS_MODULE, + .of_match_table = plat_nand_match, + }, +}; + +module_platform_driver(plat_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Vitaly Wool"); +MODULE_DESCRIPTION("Simple generic NAND driver"); +MODULE_ALIAS("platform:gen_nand"); diff --git a/drivers/mtd/nand/ppchameleonevb.c b/drivers/mtd/nand/ppchameleonevb.c deleted file mode 100644 index eb7d4d443de..00000000000 --- a/drivers/mtd/nand/ppchameleonevb.c +++ /dev/null @@ -1,439 +0,0 @@ -/* - * drivers/mtd/nand/ppchameleonevb.c - * - * Copyright (C) 2003 DAVE Srl (info@wawnet.biz) - * - * Derived from drivers/mtd/nand/edb7312.c - * - * - * $Id: ppchameleonevb.c,v 1.7 2005/11/07 11:14:31 gleixner Exp $ - * - * 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. - * - * Overview: - * This is a device driver for the NAND flash devices found on the - * PPChameleon/PPChameleonEVB system. - * PPChameleon options (autodetected): - * - BA model: no NAND - * - ME model: 32MB (Samsung K9F5608U0B) - * - HI model: 128MB (Samsung K9F1G08UOM) - * PPChameleonEVB options: - * - 32MB (Samsung K9F5608U0B) - */ - -#include <linux/init.h> -#include <linux/slab.h> -#include <linux/module.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/nand.h> -#include <linux/mtd/partitions.h> -#include <asm/io.h> -#include <platforms/PPChameleonEVB.h> - -#undef USE_READY_BUSY_PIN -#define USE_READY_BUSY_PIN -/* see datasheets (tR) */ -#define NAND_BIG_DELAY_US 25 -#define NAND_SMALL_DELAY_US 10 - -/* handy sizes */ -#define SZ_4M 0x00400000 -#define NAND_SMALL_SIZE 0x02000000 -#define NAND_MTD_NAME "ppchameleon-nand" -#define NAND_EVB_MTD_NAME "ppchameleonevb-nand" - -/* GPIO pins used to drive NAND chip mounted on processor module */ -#define NAND_nCE_GPIO_PIN (0x80000000 >> 1) -#define NAND_CLE_GPIO_PIN (0x80000000 >> 2) -#define NAND_ALE_GPIO_PIN (0x80000000 >> 3) -#define NAND_RB_GPIO_PIN (0x80000000 >> 4) -/* GPIO pins used to drive NAND chip mounted on EVB */ -#define NAND_EVB_nCE_GPIO_PIN (0x80000000 >> 14) -#define NAND_EVB_CLE_GPIO_PIN (0x80000000 >> 15) -#define NAND_EVB_ALE_GPIO_PIN (0x80000000 >> 16) -#define NAND_EVB_RB_GPIO_PIN (0x80000000 >> 31) - -/* - * MTD structure for PPChameleonEVB board - */ -static struct mtd_info *ppchameleon_mtd = NULL; -static struct mtd_info *ppchameleonevb_mtd = NULL; - -/* - * Module stuff - */ -static unsigned long ppchameleon_fio_pbase = CFG_NAND0_PADDR; -static unsigned long ppchameleonevb_fio_pbase = CFG_NAND1_PADDR; - -#ifdef MODULE -module_param(ppchameleon_fio_pbase, ulong, 0); -module_param(ppchameleonevb_fio_pbase, ulong, 0); -#else -__setup("ppchameleon_fio_pbase=", ppchameleon_fio_pbase); -__setup("ppchameleonevb_fio_pbase=", ppchameleonevb_fio_pbase); -#endif - -#ifdef CONFIG_MTD_PARTITIONS -/* - * Define static partitions for flash devices - */ -static struct mtd_partition partition_info_hi[] = { - { .name = "PPChameleon HI Nand Flash", - offset = 0, - .size = 128 * 1024 * 1024 - } -}; - -static struct mtd_partition partition_info_me[] = { - { .name = "PPChameleon ME Nand Flash", - .offset = 0, - .size = 32 * 1024 * 1024 - } -}; - -static struct mtd_partition partition_info_evb[] = { - { .name = "PPChameleonEVB Nand Flash", - .offset = 0, - .size = 32 * 1024 * 1024 - } -}; - -#define NUM_PARTITIONS 1 - -extern int parse_cmdline_partitions(struct mtd_info *master, struct mtd_partition **pparts, const char *mtd_id); -#endif - -/* - * hardware specific access to control-lines - */ -static void ppchameleon_hwcontrol(struct mtd_info *mtdinfo, int cmd, - unsigned int ctrl) -{ - struct nand_chip *chip = mtd->priv; - - if (ctrl & NAND_CTRL_CHANGE) { -#error Missing headerfiles. No way to fix this. -tglx - switch (cmd) { - case NAND_CTL_SETCLE: - MACRO_NAND_CTL_SETCLE((unsigned long)CFG_NAND0_PADDR); - break; - case NAND_CTL_CLRCLE: - MACRO_NAND_CTL_CLRCLE((unsigned long)CFG_NAND0_PADDR); - break; - case NAND_CTL_SETALE: - MACRO_NAND_CTL_SETALE((unsigned long)CFG_NAND0_PADDR); - break; - case NAND_CTL_CLRALE: - MACRO_NAND_CTL_CLRALE((unsigned long)CFG_NAND0_PADDR); - break; - case NAND_CTL_SETNCE: - MACRO_NAND_ENABLE_CE((unsigned long)CFG_NAND0_PADDR); - break; - case NAND_CTL_CLRNCE: - MACRO_NAND_DISABLE_CE((unsigned long)CFG_NAND0_PADDR); - break; - } - } - if (cmd != NAND_CMD_NONE) - writeb(cmd, chip->IO_ADDR_W); -} - -static void ppchameleonevb_hwcontrol(struct mtd_info *mtdinfo, int cmd, - unsigned int ctrl) -{ - struct nand_chip *chip = mtd->priv; - - if (ctrl & NAND_CTRL_CHANGE) { -#error Missing headerfiles. No way to fix this. -tglx - switch (cmd) { - case NAND_CTL_SETCLE: - MACRO_NAND_CTL_SETCLE((unsigned long)CFG_NAND1_PADDR); - break; - case NAND_CTL_CLRCLE: - MACRO_NAND_CTL_CLRCLE((unsigned long)CFG_NAND1_PADDR); - break; - case NAND_CTL_SETALE: - MACRO_NAND_CTL_SETALE((unsigned long)CFG_NAND1_PADDR); - break; - case NAND_CTL_CLRALE: - MACRO_NAND_CTL_CLRALE((unsigned long)CFG_NAND1_PADDR); - break; - case NAND_CTL_SETNCE: - MACRO_NAND_ENABLE_CE((unsigned long)CFG_NAND1_PADDR); - break; - case NAND_CTL_CLRNCE: - MACRO_NAND_DISABLE_CE((unsigned long)CFG_NAND1_PADDR); - break; - } - } - if (cmd != NAND_CMD_NONE) - writeb(cmd, chip->IO_ADDR_W); -} - -#ifdef USE_READY_BUSY_PIN -/* - * read device ready pin - */ -static int ppchameleon_device_ready(struct mtd_info *minfo) -{ - if (in_be32((volatile unsigned *)GPIO0_IR) & NAND_RB_GPIO_PIN) - return 1; - return 0; -} - -static int ppchameleonevb_device_ready(struct mtd_info *minfo) -{ - if (in_be32((volatile unsigned *)GPIO0_IR) & NAND_EVB_RB_GPIO_PIN) - return 1; - return 0; -} -#endif - -#ifdef CONFIG_MTD_PARTITIONS -const char *part_probes[] = { "cmdlinepart", NULL }; -const char *part_probes_evb[] = { "cmdlinepart", NULL }; -#endif - -/* - * Main initialization routine - */ -static int __init ppchameleonevb_init(void) -{ - struct nand_chip *this; - const char *part_type = 0; - int mtd_parts_nb = 0; - struct mtd_partition *mtd_parts = 0; - void __iomem *ppchameleon_fio_base; - void __iomem *ppchameleonevb_fio_base; - - /********************************* - * Processor module NAND (if any) * - *********************************/ - /* Allocate memory for MTD device structure and private data */ - ppchameleon_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL); - if (!ppchameleon_mtd) { - printk("Unable to allocate PPChameleon NAND MTD device structure.\n"); - return -ENOMEM; - } - - /* map physical address */ - ppchameleon_fio_base = ioremap(ppchameleon_fio_pbase, SZ_4M); - if (!ppchameleon_fio_base) { - printk("ioremap PPChameleon NAND flash failed\n"); - kfree(ppchameleon_mtd); - return -EIO; - } - - /* Get pointer to private data */ - this = (struct nand_chip *)(&ppchameleon_mtd[1]); - - /* Initialize structures */ - memset(ppchameleon_mtd, 0, sizeof(struct mtd_info)); - memset(this, 0, sizeof(struct nand_chip)); - - /* Link the private data with the MTD structure */ - ppchameleon_mtd->priv = this; - ppchameleon_mtd->owner = THIS_MODULE; - - /* Initialize GPIOs */ - /* Pin mapping for NAND chip */ - /* - CE GPIO_01 - CLE GPIO_02 - ALE GPIO_03 - R/B GPIO_04 - */ - /* output select */ - out_be32((volatile unsigned *)GPIO0_OSRH, in_be32((volatile unsigned *)GPIO0_OSRH) & 0xC0FFFFFF); - /* three-state select */ - out_be32((volatile unsigned *)GPIO0_TSRH, in_be32((volatile unsigned *)GPIO0_TSRH) & 0xC0FFFFFF); - /* enable output driver */ - out_be32((volatile unsigned *)GPIO0_TCR, - in_be32((volatile unsigned *)GPIO0_TCR) | NAND_nCE_GPIO_PIN | NAND_CLE_GPIO_PIN | NAND_ALE_GPIO_PIN); -#ifdef USE_READY_BUSY_PIN - /* three-state select */ - out_be32((volatile unsigned *)GPIO0_TSRH, in_be32((volatile unsigned *)GPIO0_TSRH) & 0xFF3FFFFF); - /* high-impedecence */ - out_be32((volatile unsigned *)GPIO0_TCR, in_be32((volatile unsigned *)GPIO0_TCR) & (~NAND_RB_GPIO_PIN)); - /* input select */ - out_be32((volatile unsigned *)GPIO0_ISR1H, - (in_be32((volatile unsigned *)GPIO0_ISR1H) & 0xFF3FFFFF) | 0x00400000); -#endif - - /* insert callbacks */ - this->IO_ADDR_R = ppchameleon_fio_base; - this->IO_ADDR_W = ppchameleon_fio_base; - this->cmd_ctrl = ppchameleon_hwcontrol; -#ifdef USE_READY_BUSY_PIN - this->dev_ready = ppchameleon_device_ready; -#endif - this->chip_delay = NAND_BIG_DELAY_US; - /* ECC mode */ - this->ecc.mode = NAND_ECC_SOFT; - - /* Scan to find existence of the device (it could not be mounted) */ - if (nand_scan(ppchameleon_mtd, 1)) { - iounmap((void *)ppchameleon_fio_base); - ppchameleon_fio_base = NULL; - kfree(ppchameleon_mtd); - goto nand_evb_init; - } -#ifndef USE_READY_BUSY_PIN - /* Adjust delay if necessary */ - if (ppchameleon_mtd->size == NAND_SMALL_SIZE) - this->chip_delay = NAND_SMALL_DELAY_US; -#endif - -#ifdef CONFIG_MTD_PARTITIONS - ppchameleon_mtd->name = "ppchameleon-nand"; - mtd_parts_nb = parse_mtd_partitions(ppchameleon_mtd, part_probes, &mtd_parts, 0); - if (mtd_parts_nb > 0) - part_type = "command line"; - else - mtd_parts_nb = 0; -#endif - if (mtd_parts_nb == 0) { - if (ppchameleon_mtd->size == NAND_SMALL_SIZE) - mtd_parts = partition_info_me; - else - mtd_parts = partition_info_hi; - mtd_parts_nb = NUM_PARTITIONS; - part_type = "static"; - } - - /* Register the partitions */ - printk(KERN_NOTICE "Using %s partition definition\n", part_type); - add_mtd_partitions(ppchameleon_mtd, mtd_parts, mtd_parts_nb); - - nand_evb_init: - /**************************** - * EVB NAND (always present) * - ****************************/ - /* Allocate memory for MTD device structure and private data */ - ppchameleonevb_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL); - if (!ppchameleonevb_mtd) { - printk("Unable to allocate PPChameleonEVB NAND MTD device structure.\n"); - if (ppchameleon_fio_base) - iounmap(ppchameleon_fio_base); - return -ENOMEM; - } - - /* map physical address */ - ppchameleonevb_fio_base = ioremap(ppchameleonevb_fio_pbase, SZ_4M); - if (!ppchameleonevb_fio_base) { - printk("ioremap PPChameleonEVB NAND flash failed\n"); - kfree(ppchameleonevb_mtd); - if (ppchameleon_fio_base) - iounmap(ppchameleon_fio_base); - return -EIO; - } - - /* Get pointer to private data */ - this = (struct nand_chip *)(&ppchameleonevb_mtd[1]); - - /* Initialize structures */ - memset(ppchameleonevb_mtd, 0, sizeof(struct mtd_info)); - memset(this, 0, sizeof(struct nand_chip)); - - /* Link the private data with the MTD structure */ - ppchameleonevb_mtd->priv = this; - - /* Initialize GPIOs */ - /* Pin mapping for NAND chip */ - /* - CE GPIO_14 - CLE GPIO_15 - ALE GPIO_16 - R/B GPIO_31 - */ - /* output select */ - out_be32((volatile unsigned *)GPIO0_OSRH, in_be32((volatile unsigned *)GPIO0_OSRH) & 0xFFFFFFF0); - out_be32((volatile unsigned *)GPIO0_OSRL, in_be32((volatile unsigned *)GPIO0_OSRL) & 0x3FFFFFFF); - /* three-state select */ - out_be32((volatile unsigned *)GPIO0_TSRH, in_be32((volatile unsigned *)GPIO0_TSRH) & 0xFFFFFFF0); - out_be32((volatile unsigned *)GPIO0_TSRL, in_be32((volatile unsigned *)GPIO0_TSRL) & 0x3FFFFFFF); - /* enable output driver */ - out_be32((volatile unsigned *)GPIO0_TCR, in_be32((volatile unsigned *)GPIO0_TCR) | NAND_EVB_nCE_GPIO_PIN | - NAND_EVB_CLE_GPIO_PIN | NAND_EVB_ALE_GPIO_PIN); -#ifdef USE_READY_BUSY_PIN - /* three-state select */ - out_be32((volatile unsigned *)GPIO0_TSRL, in_be32((volatile unsigned *)GPIO0_TSRL) & 0xFFFFFFFC); - /* high-impedecence */ - out_be32((volatile unsigned *)GPIO0_TCR, in_be32((volatile unsigned *)GPIO0_TCR) & (~NAND_EVB_RB_GPIO_PIN)); - /* input select */ - out_be32((volatile unsigned *)GPIO0_ISR1L, - (in_be32((volatile unsigned *)GPIO0_ISR1L) & 0xFFFFFFFC) | 0x00000001); -#endif - - /* insert callbacks */ - this->IO_ADDR_R = ppchameleonevb_fio_base; - this->IO_ADDR_W = ppchameleonevb_fio_base; - this->cmd_ctrl = ppchameleonevb_hwcontrol; -#ifdef USE_READY_BUSY_PIN - this->dev_ready = ppchameleonevb_device_ready; -#endif - this->chip_delay = NAND_SMALL_DELAY_US; - - /* ECC mode */ - this->ecc.mode = NAND_ECC_SOFT; - - /* Scan to find existence of the device */ - if (nand_scan(ppchameleonevb_mtd, 1)) { - iounmap((void *)ppchameleonevb_fio_base); - kfree(ppchameleonevb_mtd); - if (ppchameleon_fio_base) - iounmap(ppchameleon_fio_base); - return -ENXIO; - } -#ifdef CONFIG_MTD_PARTITIONS - ppchameleonevb_mtd->name = NAND_EVB_MTD_NAME; - mtd_parts_nb = parse_mtd_partitions(ppchameleonevb_mtd, part_probes_evb, &mtd_parts, 0); - if (mtd_parts_nb > 0) - part_type = "command line"; - else - mtd_parts_nb = 0; -#endif - if (mtd_parts_nb == 0) { - mtd_parts = partition_info_evb; - mtd_parts_nb = NUM_PARTITIONS; - part_type = "static"; - } - - /* Register the partitions */ - printk(KERN_NOTICE "Using %s partition definition\n", part_type); - add_mtd_partitions(ppchameleonevb_mtd, mtd_parts, mtd_parts_nb); - - /* Return happy */ - return 0; -} - -module_init(ppchameleonevb_init); - -/* - * Clean up routine - */ -static void __exit ppchameleonevb_cleanup(void) -{ - struct nand_chip *this; - - /* Release resources, unregister device(s) */ - nand_release(ppchameleon_mtd); - nand_release(ppchameleonevb_mtd); - - /* Release iomaps */ - this = (struct nand_chip *) &ppchameleon_mtd[1]; - iounmap((void *) this->IO_ADDR_R; - this = (struct nand_chip *) &ppchameleonevb_mtd[1]; - iounmap((void *) this->IO_ADDR_R; - - /* Free the MTD device structure */ - kfree (ppchameleon_mtd); - kfree (ppchameleonevb_mtd); -} -module_exit(ppchameleonevb_cleanup); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("DAVE Srl <support-ppchameleon@dave-tech.it>"); -MODULE_DESCRIPTION("MTD map driver for DAVE Srl PPChameleonEVB board"); diff --git a/drivers/mtd/nand/pxa3xx_nand.c b/drivers/mtd/nand/pxa3xx_nand.c new file mode 100644 index 00000000000..96b0b1d27df --- /dev/null +++ b/drivers/mtd/nand/pxa3xx_nand.c @@ -0,0 +1,1888 @@ +/* + * drivers/mtd/nand/pxa3xx_nand.c + * + * Copyright © 2005 Intel Corporation + * Copyright © 2006 Marvell International Ltd. + * + * 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. + * + * See Documentation/mtd/nand/pxa3xx-nand.txt for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_mtd.h> + +#if defined(CONFIG_ARCH_PXA) || defined(CONFIG_ARCH_MMP) +#define ARCH_HAS_DMA +#endif + +#ifdef ARCH_HAS_DMA +#include <mach/dma.h> +#endif + +#include <linux/platform_data/mtd-nand-pxa3xx.h> + +#define CHIP_DELAY_TIMEOUT (2 * HZ/10) +#define NAND_STOP_DELAY (2 * HZ/50) +#define PAGE_CHUNK_SIZE (2048) + +/* + * Define a buffer size for the initial command that detects the flash device: + * STATUS, READID and PARAM. The largest of these is the PARAM command, + * needing 256 bytes. + */ +#define INIT_BUFFER_SIZE 256 + +/* registers and bit definitions */ +#define NDCR (0x00) /* Control register */ +#define NDTR0CS0 (0x04) /* Timing Parameter 0 for CS0 */ +#define NDTR1CS0 (0x0C) /* Timing Parameter 1 for CS0 */ +#define NDSR (0x14) /* Status Register */ +#define NDPCR (0x18) /* Page Count Register */ +#define NDBDR0 (0x1C) /* Bad Block Register 0 */ +#define NDBDR1 (0x20) /* Bad Block Register 1 */ +#define NDECCCTRL (0x28) /* ECC control */ +#define NDDB (0x40) /* Data Buffer */ +#define NDCB0 (0x48) /* Command Buffer0 */ +#define NDCB1 (0x4C) /* Command Buffer1 */ +#define NDCB2 (0x50) /* Command Buffer2 */ + +#define NDCR_SPARE_EN (0x1 << 31) +#define NDCR_ECC_EN (0x1 << 30) +#define NDCR_DMA_EN (0x1 << 29) +#define NDCR_ND_RUN (0x1 << 28) +#define NDCR_DWIDTH_C (0x1 << 27) +#define NDCR_DWIDTH_M (0x1 << 26) +#define NDCR_PAGE_SZ (0x1 << 24) +#define NDCR_NCSX (0x1 << 23) +#define NDCR_ND_MODE (0x3 << 21) +#define NDCR_NAND_MODE (0x0) +#define NDCR_CLR_PG_CNT (0x1 << 20) +#define NDCR_STOP_ON_UNCOR (0x1 << 19) +#define NDCR_RD_ID_CNT_MASK (0x7 << 16) +#define NDCR_RD_ID_CNT(x) (((x) << 16) & NDCR_RD_ID_CNT_MASK) + +#define NDCR_RA_START (0x1 << 15) +#define NDCR_PG_PER_BLK (0x1 << 14) +#define NDCR_ND_ARB_EN (0x1 << 12) +#define NDCR_INT_MASK (0xFFF) + +#define NDSR_MASK (0xfff) +#define NDSR_ERR_CNT_OFF (16) +#define NDSR_ERR_CNT_MASK (0x1f) +#define NDSR_ERR_CNT(sr) ((sr >> NDSR_ERR_CNT_OFF) & NDSR_ERR_CNT_MASK) +#define NDSR_RDY (0x1 << 12) +#define NDSR_FLASH_RDY (0x1 << 11) +#define NDSR_CS0_PAGED (0x1 << 10) +#define NDSR_CS1_PAGED (0x1 << 9) +#define NDSR_CS0_CMDD (0x1 << 8) +#define NDSR_CS1_CMDD (0x1 << 7) +#define NDSR_CS0_BBD (0x1 << 6) +#define NDSR_CS1_BBD (0x1 << 5) +#define NDSR_UNCORERR (0x1 << 4) +#define NDSR_CORERR (0x1 << 3) +#define NDSR_WRDREQ (0x1 << 2) +#define NDSR_RDDREQ (0x1 << 1) +#define NDSR_WRCMDREQ (0x1) + +#define NDCB0_LEN_OVRD (0x1 << 28) +#define NDCB0_ST_ROW_EN (0x1 << 26) +#define NDCB0_AUTO_RS (0x1 << 25) +#define NDCB0_CSEL (0x1 << 24) +#define NDCB0_EXT_CMD_TYPE_MASK (0x7 << 29) +#define NDCB0_EXT_CMD_TYPE(x) (((x) << 29) & NDCB0_EXT_CMD_TYPE_MASK) +#define NDCB0_CMD_TYPE_MASK (0x7 << 21) +#define NDCB0_CMD_TYPE(x) (((x) << 21) & NDCB0_CMD_TYPE_MASK) +#define NDCB0_NC (0x1 << 20) +#define NDCB0_DBC (0x1 << 19) +#define NDCB0_ADDR_CYC_MASK (0x7 << 16) +#define NDCB0_ADDR_CYC(x) (((x) << 16) & NDCB0_ADDR_CYC_MASK) +#define NDCB0_CMD2_MASK (0xff << 8) +#define NDCB0_CMD1_MASK (0xff) +#define NDCB0_ADDR_CYC_SHIFT (16) + +#define EXT_CMD_TYPE_DISPATCH 6 /* Command dispatch */ +#define EXT_CMD_TYPE_NAKED_RW 5 /* Naked read or Naked write */ +#define EXT_CMD_TYPE_READ 4 /* Read */ +#define EXT_CMD_TYPE_DISP_WR 4 /* Command dispatch with write */ +#define EXT_CMD_TYPE_FINAL 3 /* Final command */ +#define EXT_CMD_TYPE_LAST_RW 1 /* Last naked read/write */ +#define EXT_CMD_TYPE_MONO 0 /* Monolithic read/write */ + +/* macros for registers read/write */ +#define nand_writel(info, off, val) \ + writel_relaxed((val), (info)->mmio_base + (off)) + +#define nand_readl(info, off) \ + readl_relaxed((info)->mmio_base + (off)) + +/* error code and state */ +enum { + ERR_NONE = 0, + ERR_DMABUSERR = -1, + ERR_SENDCMD = -2, + ERR_UNCORERR = -3, + ERR_BBERR = -4, + ERR_CORERR = -5, +}; + +enum { + STATE_IDLE = 0, + STATE_PREPARED, + STATE_CMD_HANDLE, + STATE_DMA_READING, + STATE_DMA_WRITING, + STATE_DMA_DONE, + STATE_PIO_READING, + STATE_PIO_WRITING, + STATE_CMD_DONE, + STATE_READY, +}; + +enum pxa3xx_nand_variant { + PXA3XX_NAND_VARIANT_PXA, + PXA3XX_NAND_VARIANT_ARMADA370, +}; + +struct pxa3xx_nand_host { + struct nand_chip chip; + struct mtd_info *mtd; + void *info_data; + + /* page size of attached chip */ + int use_ecc; + int cs; + + /* calculated from pxa3xx_nand_flash data */ + unsigned int col_addr_cycles; + unsigned int row_addr_cycles; + size_t read_id_bytes; + +}; + +struct pxa3xx_nand_info { + struct nand_hw_control controller; + struct platform_device *pdev; + + struct clk *clk; + void __iomem *mmio_base; + unsigned long mmio_phys; + struct completion cmd_complete, dev_ready; + + unsigned int buf_start; + unsigned int buf_count; + unsigned int buf_size; + unsigned int data_buff_pos; + unsigned int oob_buff_pos; + + /* DMA information */ + int drcmr_dat; + int drcmr_cmd; + + unsigned char *data_buff; + unsigned char *oob_buff; + dma_addr_t data_buff_phys; + int data_dma_ch; + struct pxa_dma_desc *data_desc; + dma_addr_t data_desc_addr; + + struct pxa3xx_nand_host *host[NUM_CHIP_SELECT]; + unsigned int state; + + /* + * This driver supports NFCv1 (as found in PXA SoC) + * and NFCv2 (as found in Armada 370/XP SoC). + */ + enum pxa3xx_nand_variant variant; + + int cs; + int use_ecc; /* use HW ECC ? */ + int ecc_bch; /* using BCH ECC? */ + int use_dma; /* use DMA ? */ + int use_spare; /* use spare ? */ + int need_wait; + + unsigned int data_size; /* data to be read from FIFO */ + unsigned int chunk_size; /* split commands chunk size */ + unsigned int oob_size; + unsigned int spare_size; + unsigned int ecc_size; + unsigned int ecc_err_cnt; + unsigned int max_bitflips; + int retcode; + + /* cached register value */ + uint32_t reg_ndcr; + uint32_t ndtr0cs0; + uint32_t ndtr1cs0; + + /* generated NDCBx register values */ + uint32_t ndcb0; + uint32_t ndcb1; + uint32_t ndcb2; + uint32_t ndcb3; +}; + +static bool use_dma = 1; +module_param(use_dma, bool, 0444); +MODULE_PARM_DESC(use_dma, "enable DMA for data transferring to/from NAND HW"); + +static struct pxa3xx_nand_timing timing[] = { + { 40, 80, 60, 100, 80, 100, 90000, 400, 40, }, + { 10, 0, 20, 40, 30, 40, 11123, 110, 10, }, + { 10, 25, 15, 25, 15, 30, 25000, 60, 10, }, + { 10, 35, 15, 25, 15, 25, 25000, 60, 10, }, +}; + +static struct pxa3xx_nand_flash builtin_flash_types[] = { +{ "DEFAULT FLASH", 0, 0, 2048, 8, 8, 0, &timing[0] }, +{ "64MiB 16-bit", 0x46ec, 32, 512, 16, 16, 4096, &timing[1] }, +{ "256MiB 8-bit", 0xdaec, 64, 2048, 8, 8, 2048, &timing[1] }, +{ "4GiB 8-bit", 0xd7ec, 128, 4096, 8, 8, 8192, &timing[1] }, +{ "128MiB 8-bit", 0xa12c, 64, 2048, 8, 8, 1024, &timing[2] }, +{ "128MiB 16-bit", 0xb12c, 64, 2048, 16, 16, 1024, &timing[2] }, +{ "512MiB 8-bit", 0xdc2c, 64, 2048, 8, 8, 4096, &timing[2] }, +{ "512MiB 16-bit", 0xcc2c, 64, 2048, 16, 16, 4096, &timing[2] }, +{ "256MiB 16-bit", 0xba20, 64, 2048, 16, 16, 2048, &timing[3] }, +}; + +static u8 bbt_pattern[] = {'M', 'V', 'B', 'b', 't', '0' }; +static u8 bbt_mirror_pattern[] = {'1', 't', 'b', 'B', 'V', 'M' }; + +static struct nand_bbt_descr bbt_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 8, + .len = 6, + .veroffs = 14, + .maxblocks = 8, /* Last 8 blocks in each chip */ + .pattern = bbt_pattern +}; + +static struct nand_bbt_descr bbt_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 8, + .len = 6, + .veroffs = 14, + .maxblocks = 8, /* Last 8 blocks in each chip */ + .pattern = bbt_mirror_pattern +}; + +static struct nand_ecclayout ecc_layout_2KB_bch4bit = { + .eccbytes = 32, + .eccpos = { + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63}, + .oobfree = { {2, 30} } +}; + +static struct nand_ecclayout ecc_layout_4KB_bch4bit = { + .eccbytes = 64, + .eccpos = { + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 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}, + /* Bootrom looks in bytes 0 & 5 for bad blocks */ + .oobfree = { {6, 26}, { 64, 32} } +}; + +static struct nand_ecclayout ecc_layout_4KB_bch8bit = { + .eccbytes = 128, + .eccpos = { + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63}, + .oobfree = { } +}; + +/* Define a default flash type setting serve as flash detecting only */ +#define DEFAULT_FLASH_TYPE (&builtin_flash_types[0]) + +#define NDTR0_tCH(c) (min((c), 7) << 19) +#define NDTR0_tCS(c) (min((c), 7) << 16) +#define NDTR0_tWH(c) (min((c), 7) << 11) +#define NDTR0_tWP(c) (min((c), 7) << 8) +#define NDTR0_tRH(c) (min((c), 7) << 3) +#define NDTR0_tRP(c) (min((c), 7) << 0) + +#define NDTR1_tR(c) (min((c), 65535) << 16) +#define NDTR1_tWHR(c) (min((c), 15) << 4) +#define NDTR1_tAR(c) (min((c), 15) << 0) + +/* convert nano-seconds to nand flash controller clock cycles */ +#define ns2cycle(ns, clk) (int)((ns) * (clk / 1000000) / 1000) + +static const struct of_device_id pxa3xx_nand_dt_ids[] = { + { + .compatible = "marvell,pxa3xx-nand", + .data = (void *)PXA3XX_NAND_VARIANT_PXA, + }, + { + .compatible = "marvell,armada370-nand", + .data = (void *)PXA3XX_NAND_VARIANT_ARMADA370, + }, + {} +}; +MODULE_DEVICE_TABLE(of, pxa3xx_nand_dt_ids); + +static enum pxa3xx_nand_variant +pxa3xx_nand_get_variant(struct platform_device *pdev) +{ + const struct of_device_id *of_id = + of_match_device(pxa3xx_nand_dt_ids, &pdev->dev); + if (!of_id) + return PXA3XX_NAND_VARIANT_PXA; + return (enum pxa3xx_nand_variant)of_id->data; +} + +static void pxa3xx_nand_set_timing(struct pxa3xx_nand_host *host, + const struct pxa3xx_nand_timing *t) +{ + struct pxa3xx_nand_info *info = host->info_data; + unsigned long nand_clk = clk_get_rate(info->clk); + uint32_t ndtr0, ndtr1; + + ndtr0 = NDTR0_tCH(ns2cycle(t->tCH, nand_clk)) | + NDTR0_tCS(ns2cycle(t->tCS, nand_clk)) | + NDTR0_tWH(ns2cycle(t->tWH, nand_clk)) | + NDTR0_tWP(ns2cycle(t->tWP, nand_clk)) | + NDTR0_tRH(ns2cycle(t->tRH, nand_clk)) | + NDTR0_tRP(ns2cycle(t->tRP, nand_clk)); + + ndtr1 = NDTR1_tR(ns2cycle(t->tR, nand_clk)) | + NDTR1_tWHR(ns2cycle(t->tWHR, nand_clk)) | + NDTR1_tAR(ns2cycle(t->tAR, nand_clk)); + + info->ndtr0cs0 = ndtr0; + info->ndtr1cs0 = ndtr1; + nand_writel(info, NDTR0CS0, ndtr0); + nand_writel(info, NDTR1CS0, ndtr1); +} + +/* + * Set the data and OOB size, depending on the selected + * spare and ECC configuration. + * Only applicable to READ0, READOOB and PAGEPROG commands. + */ +static void pxa3xx_set_datasize(struct pxa3xx_nand_info *info, + struct mtd_info *mtd) +{ + int oob_enable = info->reg_ndcr & NDCR_SPARE_EN; + + info->data_size = mtd->writesize; + if (!oob_enable) + return; + + info->oob_size = info->spare_size; + if (!info->use_ecc) + info->oob_size += info->ecc_size; +} + +/** + * NOTE: it is a must to set ND_RUN firstly, then write + * command buffer, otherwise, it does not work. + * We enable all the interrupt at the same time, and + * let pxa3xx_nand_irq to handle all logic. + */ +static void pxa3xx_nand_start(struct pxa3xx_nand_info *info) +{ + uint32_t ndcr; + + ndcr = info->reg_ndcr; + + if (info->use_ecc) { + ndcr |= NDCR_ECC_EN; + if (info->ecc_bch) + nand_writel(info, NDECCCTRL, 0x1); + } else { + ndcr &= ~NDCR_ECC_EN; + if (info->ecc_bch) + nand_writel(info, NDECCCTRL, 0x0); + } + + if (info->use_dma) + ndcr |= NDCR_DMA_EN; + else + ndcr &= ~NDCR_DMA_EN; + + if (info->use_spare) + ndcr |= NDCR_SPARE_EN; + else + ndcr &= ~NDCR_SPARE_EN; + + ndcr |= NDCR_ND_RUN; + + /* clear status bits and run */ + nand_writel(info, NDCR, 0); + nand_writel(info, NDSR, NDSR_MASK); + nand_writel(info, NDCR, ndcr); +} + +static void pxa3xx_nand_stop(struct pxa3xx_nand_info *info) +{ + uint32_t ndcr; + int timeout = NAND_STOP_DELAY; + + /* wait RUN bit in NDCR become 0 */ + ndcr = nand_readl(info, NDCR); + while ((ndcr & NDCR_ND_RUN) && (timeout-- > 0)) { + ndcr = nand_readl(info, NDCR); + udelay(1); + } + + if (timeout <= 0) { + ndcr &= ~NDCR_ND_RUN; + nand_writel(info, NDCR, ndcr); + } + /* clear status bits */ + nand_writel(info, NDSR, NDSR_MASK); +} + +static void __maybe_unused +enable_int(struct pxa3xx_nand_info *info, uint32_t int_mask) +{ + uint32_t ndcr; + + ndcr = nand_readl(info, NDCR); + nand_writel(info, NDCR, ndcr & ~int_mask); +} + +static void disable_int(struct pxa3xx_nand_info *info, uint32_t int_mask) +{ + uint32_t ndcr; + + ndcr = nand_readl(info, NDCR); + nand_writel(info, NDCR, ndcr | int_mask); +} + +static void handle_data_pio(struct pxa3xx_nand_info *info) +{ + unsigned int do_bytes = min(info->data_size, info->chunk_size); + + switch (info->state) { + case STATE_PIO_WRITING: + __raw_writesl(info->mmio_base + NDDB, + info->data_buff + info->data_buff_pos, + DIV_ROUND_UP(do_bytes, 4)); + + if (info->oob_size > 0) + __raw_writesl(info->mmio_base + NDDB, + info->oob_buff + info->oob_buff_pos, + DIV_ROUND_UP(info->oob_size, 4)); + break; + case STATE_PIO_READING: + __raw_readsl(info->mmio_base + NDDB, + info->data_buff + info->data_buff_pos, + DIV_ROUND_UP(do_bytes, 4)); + + if (info->oob_size > 0) + __raw_readsl(info->mmio_base + NDDB, + info->oob_buff + info->oob_buff_pos, + DIV_ROUND_UP(info->oob_size, 4)); + break; + default: + dev_err(&info->pdev->dev, "%s: invalid state %d\n", __func__, + info->state); + BUG(); + } + + /* Update buffer pointers for multi-page read/write */ + info->data_buff_pos += do_bytes; + info->oob_buff_pos += info->oob_size; + info->data_size -= do_bytes; +} + +#ifdef ARCH_HAS_DMA +static void start_data_dma(struct pxa3xx_nand_info *info) +{ + struct pxa_dma_desc *desc = info->data_desc; + int dma_len = ALIGN(info->data_size + info->oob_size, 32); + + desc->ddadr = DDADR_STOP; + desc->dcmd = DCMD_ENDIRQEN | DCMD_WIDTH4 | DCMD_BURST32 | dma_len; + + switch (info->state) { + case STATE_DMA_WRITING: + desc->dsadr = info->data_buff_phys; + desc->dtadr = info->mmio_phys + NDDB; + desc->dcmd |= DCMD_INCSRCADDR | DCMD_FLOWTRG; + break; + case STATE_DMA_READING: + desc->dtadr = info->data_buff_phys; + desc->dsadr = info->mmio_phys + NDDB; + desc->dcmd |= DCMD_INCTRGADDR | DCMD_FLOWSRC; + break; + default: + dev_err(&info->pdev->dev, "%s: invalid state %d\n", __func__, + info->state); + BUG(); + } + + DRCMR(info->drcmr_dat) = DRCMR_MAPVLD | info->data_dma_ch; + DDADR(info->data_dma_ch) = info->data_desc_addr; + DCSR(info->data_dma_ch) |= DCSR_RUN; +} + +static void pxa3xx_nand_data_dma_irq(int channel, void *data) +{ + struct pxa3xx_nand_info *info = data; + uint32_t dcsr; + + dcsr = DCSR(channel); + DCSR(channel) = dcsr; + + if (dcsr & DCSR_BUSERR) { + info->retcode = ERR_DMABUSERR; + } + + info->state = STATE_DMA_DONE; + enable_int(info, NDCR_INT_MASK); + nand_writel(info, NDSR, NDSR_WRDREQ | NDSR_RDDREQ); +} +#else +static void start_data_dma(struct pxa3xx_nand_info *info) +{} +#endif + +static irqreturn_t pxa3xx_nand_irq(int irq, void *devid) +{ + struct pxa3xx_nand_info *info = devid; + unsigned int status, is_completed = 0, is_ready = 0; + unsigned int ready, cmd_done; + + if (info->cs == 0) { + ready = NDSR_FLASH_RDY; + cmd_done = NDSR_CS0_CMDD; + } else { + ready = NDSR_RDY; + cmd_done = NDSR_CS1_CMDD; + } + + status = nand_readl(info, NDSR); + + if (status & NDSR_UNCORERR) + info->retcode = ERR_UNCORERR; + if (status & NDSR_CORERR) { + info->retcode = ERR_CORERR; + if (info->variant == PXA3XX_NAND_VARIANT_ARMADA370 && + info->ecc_bch) + info->ecc_err_cnt = NDSR_ERR_CNT(status); + else + info->ecc_err_cnt = 1; + + /* + * Each chunk composing a page is corrected independently, + * and we need to store maximum number of corrected bitflips + * to return it to the MTD layer in ecc.read_page(). + */ + info->max_bitflips = max_t(unsigned int, + info->max_bitflips, + info->ecc_err_cnt); + } + if (status & (NDSR_RDDREQ | NDSR_WRDREQ)) { + /* whether use dma to transfer data */ + if (info->use_dma) { + disable_int(info, NDCR_INT_MASK); + info->state = (status & NDSR_RDDREQ) ? + STATE_DMA_READING : STATE_DMA_WRITING; + start_data_dma(info); + goto NORMAL_IRQ_EXIT; + } else { + info->state = (status & NDSR_RDDREQ) ? + STATE_PIO_READING : STATE_PIO_WRITING; + handle_data_pio(info); + } + } + if (status & cmd_done) { + info->state = STATE_CMD_DONE; + is_completed = 1; + } + if (status & ready) { + info->state = STATE_READY; + is_ready = 1; + } + + if (status & NDSR_WRCMDREQ) { + nand_writel(info, NDSR, NDSR_WRCMDREQ); + status &= ~NDSR_WRCMDREQ; + info->state = STATE_CMD_HANDLE; + + /* + * Command buffer registers NDCB{0-2} (and optionally NDCB3) + * must be loaded by writing directly either 12 or 16 + * bytes directly to NDCB0, four bytes at a time. + * + * Direct write access to NDCB1, NDCB2 and NDCB3 is ignored + * but each NDCBx register can be read. + */ + nand_writel(info, NDCB0, info->ndcb0); + nand_writel(info, NDCB0, info->ndcb1); + nand_writel(info, NDCB0, info->ndcb2); + + /* NDCB3 register is available in NFCv2 (Armada 370/XP SoC) */ + if (info->variant == PXA3XX_NAND_VARIANT_ARMADA370) + nand_writel(info, NDCB0, info->ndcb3); + } + + /* clear NDSR to let the controller exit the IRQ */ + nand_writel(info, NDSR, status); + if (is_completed) + complete(&info->cmd_complete); + if (is_ready) + complete(&info->dev_ready); +NORMAL_IRQ_EXIT: + return IRQ_HANDLED; +} + +static inline int is_buf_blank(uint8_t *buf, size_t len) +{ + for (; len > 0; len--) + if (*buf++ != 0xff) + return 0; + return 1; +} + +static void set_command_address(struct pxa3xx_nand_info *info, + unsigned int page_size, uint16_t column, int page_addr) +{ + /* small page addr setting */ + if (page_size < PAGE_CHUNK_SIZE) { + info->ndcb1 = ((page_addr & 0xFFFFFF) << 8) + | (column & 0xFF); + + info->ndcb2 = 0; + } else { + info->ndcb1 = ((page_addr & 0xFFFF) << 16) + | (column & 0xFFFF); + + if (page_addr & 0xFF0000) + info->ndcb2 = (page_addr & 0xFF0000) >> 16; + else + info->ndcb2 = 0; + } +} + +static void prepare_start_command(struct pxa3xx_nand_info *info, int command) +{ + struct pxa3xx_nand_host *host = info->host[info->cs]; + struct mtd_info *mtd = host->mtd; + + /* reset data and oob column point to handle data */ + info->buf_start = 0; + info->buf_count = 0; + info->oob_size = 0; + info->data_buff_pos = 0; + info->oob_buff_pos = 0; + info->use_ecc = 0; + info->use_spare = 1; + info->retcode = ERR_NONE; + info->ecc_err_cnt = 0; + info->ndcb3 = 0; + info->need_wait = 0; + + switch (command) { + case NAND_CMD_READ0: + case NAND_CMD_PAGEPROG: + info->use_ecc = 1; + case NAND_CMD_READOOB: + pxa3xx_set_datasize(info, mtd); + break; + case NAND_CMD_PARAM: + info->use_spare = 0; + break; + default: + info->ndcb1 = 0; + info->ndcb2 = 0; + break; + } + + /* + * If we are about to issue a read command, or about to set + * the write address, then clean the data buffer. + */ + if (command == NAND_CMD_READ0 || + command == NAND_CMD_READOOB || + command == NAND_CMD_SEQIN) { + + info->buf_count = mtd->writesize + mtd->oobsize; + memset(info->data_buff, 0xFF, info->buf_count); + } + +} + +static int prepare_set_command(struct pxa3xx_nand_info *info, int command, + int ext_cmd_type, uint16_t column, int page_addr) +{ + int addr_cycle, exec_cmd; + struct pxa3xx_nand_host *host; + struct mtd_info *mtd; + + host = info->host[info->cs]; + mtd = host->mtd; + addr_cycle = 0; + exec_cmd = 1; + + if (info->cs != 0) + info->ndcb0 = NDCB0_CSEL; + else + info->ndcb0 = 0; + + if (command == NAND_CMD_SEQIN) + exec_cmd = 0; + + addr_cycle = NDCB0_ADDR_CYC(host->row_addr_cycles + + host->col_addr_cycles); + + switch (command) { + case NAND_CMD_READOOB: + case NAND_CMD_READ0: + info->buf_start = column; + info->ndcb0 |= NDCB0_CMD_TYPE(0) + | addr_cycle + | NAND_CMD_READ0; + + if (command == NAND_CMD_READOOB) + info->buf_start += mtd->writesize; + + /* + * Multiple page read needs an 'extended command type' field, + * which is either naked-read or last-read according to the + * state. + */ + if (mtd->writesize == PAGE_CHUNK_SIZE) { + info->ndcb0 |= NDCB0_DBC | (NAND_CMD_READSTART << 8); + } else if (mtd->writesize > PAGE_CHUNK_SIZE) { + info->ndcb0 |= NDCB0_DBC | (NAND_CMD_READSTART << 8) + | NDCB0_LEN_OVRD + | NDCB0_EXT_CMD_TYPE(ext_cmd_type); + info->ndcb3 = info->chunk_size + + info->oob_size; + } + + set_command_address(info, mtd->writesize, column, page_addr); + break; + + case NAND_CMD_SEQIN: + + info->buf_start = column; + set_command_address(info, mtd->writesize, 0, page_addr); + + /* + * Multiple page programming needs to execute the initial + * SEQIN command that sets the page address. + */ + if (mtd->writesize > PAGE_CHUNK_SIZE) { + info->ndcb0 |= NDCB0_CMD_TYPE(0x1) + | NDCB0_EXT_CMD_TYPE(ext_cmd_type) + | addr_cycle + | command; + /* No data transfer in this case */ + info->data_size = 0; + exec_cmd = 1; + } + break; + + case NAND_CMD_PAGEPROG: + if (is_buf_blank(info->data_buff, + (mtd->writesize + mtd->oobsize))) { + exec_cmd = 0; + break; + } + + /* Second command setting for large pages */ + if (mtd->writesize > PAGE_CHUNK_SIZE) { + /* + * Multiple page write uses the 'extended command' + * field. This can be used to issue a command dispatch + * or a naked-write depending on the current stage. + */ + info->ndcb0 |= NDCB0_CMD_TYPE(0x1) + | NDCB0_LEN_OVRD + | NDCB0_EXT_CMD_TYPE(ext_cmd_type); + info->ndcb3 = info->chunk_size + + info->oob_size; + + /* + * This is the command dispatch that completes a chunked + * page program operation. + */ + if (info->data_size == 0) { + info->ndcb0 = NDCB0_CMD_TYPE(0x1) + | NDCB0_EXT_CMD_TYPE(ext_cmd_type) + | command; + info->ndcb1 = 0; + info->ndcb2 = 0; + info->ndcb3 = 0; + } + } else { + info->ndcb0 |= NDCB0_CMD_TYPE(0x1) + | NDCB0_AUTO_RS + | NDCB0_ST_ROW_EN + | NDCB0_DBC + | (NAND_CMD_PAGEPROG << 8) + | NAND_CMD_SEQIN + | addr_cycle; + } + break; + + case NAND_CMD_PARAM: + info->buf_count = 256; + info->ndcb0 |= NDCB0_CMD_TYPE(0) + | NDCB0_ADDR_CYC(1) + | NDCB0_LEN_OVRD + | command; + info->ndcb1 = (column & 0xFF); + info->ndcb3 = 256; + info->data_size = 256; + break; + + case NAND_CMD_READID: + info->buf_count = host->read_id_bytes; + info->ndcb0 |= NDCB0_CMD_TYPE(3) + | NDCB0_ADDR_CYC(1) + | command; + info->ndcb1 = (column & 0xFF); + + info->data_size = 8; + break; + case NAND_CMD_STATUS: + info->buf_count = 1; + info->ndcb0 |= NDCB0_CMD_TYPE(4) + | NDCB0_ADDR_CYC(1) + | command; + + info->data_size = 8; + break; + + case NAND_CMD_ERASE1: + info->ndcb0 |= NDCB0_CMD_TYPE(2) + | NDCB0_AUTO_RS + | NDCB0_ADDR_CYC(3) + | NDCB0_DBC + | (NAND_CMD_ERASE2 << 8) + | NAND_CMD_ERASE1; + info->ndcb1 = page_addr; + info->ndcb2 = 0; + + break; + case NAND_CMD_RESET: + info->ndcb0 |= NDCB0_CMD_TYPE(5) + | command; + + break; + + case NAND_CMD_ERASE2: + exec_cmd = 0; + break; + + default: + exec_cmd = 0; + dev_err(&info->pdev->dev, "non-supported command %x\n", + command); + break; + } + + return exec_cmd; +} + +static void nand_cmdfunc(struct mtd_info *mtd, unsigned command, + int column, int page_addr) +{ + struct pxa3xx_nand_host *host = mtd->priv; + struct pxa3xx_nand_info *info = host->info_data; + int ret, exec_cmd; + + /* + * if this is a x16 device ,then convert the input + * "byte" address into a "word" address appropriate + * for indexing a word-oriented device + */ + if (info->reg_ndcr & NDCR_DWIDTH_M) + column /= 2; + + /* + * There may be different NAND chip hooked to + * different chip select, so check whether + * chip select has been changed, if yes, reset the timing + */ + if (info->cs != host->cs) { + info->cs = host->cs; + nand_writel(info, NDTR0CS0, info->ndtr0cs0); + nand_writel(info, NDTR1CS0, info->ndtr1cs0); + } + + prepare_start_command(info, command); + + info->state = STATE_PREPARED; + exec_cmd = prepare_set_command(info, command, 0, column, page_addr); + + if (exec_cmd) { + init_completion(&info->cmd_complete); + init_completion(&info->dev_ready); + info->need_wait = 1; + pxa3xx_nand_start(info); + + ret = wait_for_completion_timeout(&info->cmd_complete, + CHIP_DELAY_TIMEOUT); + if (!ret) { + dev_err(&info->pdev->dev, "Wait time out!!!\n"); + /* Stop State Machine for next command cycle */ + pxa3xx_nand_stop(info); + } + } + info->state = STATE_IDLE; +} + +static void nand_cmdfunc_extended(struct mtd_info *mtd, + const unsigned command, + int column, int page_addr) +{ + struct pxa3xx_nand_host *host = mtd->priv; + struct pxa3xx_nand_info *info = host->info_data; + int ret, exec_cmd, ext_cmd_type; + + /* + * if this is a x16 device then convert the input + * "byte" address into a "word" address appropriate + * for indexing a word-oriented device + */ + if (info->reg_ndcr & NDCR_DWIDTH_M) + column /= 2; + + /* + * There may be different NAND chip hooked to + * different chip select, so check whether + * chip select has been changed, if yes, reset the timing + */ + if (info->cs != host->cs) { + info->cs = host->cs; + nand_writel(info, NDTR0CS0, info->ndtr0cs0); + nand_writel(info, NDTR1CS0, info->ndtr1cs0); + } + + /* Select the extended command for the first command */ + switch (command) { + case NAND_CMD_READ0: + case NAND_CMD_READOOB: + ext_cmd_type = EXT_CMD_TYPE_MONO; + break; + case NAND_CMD_SEQIN: + ext_cmd_type = EXT_CMD_TYPE_DISPATCH; + break; + case NAND_CMD_PAGEPROG: + ext_cmd_type = EXT_CMD_TYPE_NAKED_RW; + break; + default: + ext_cmd_type = 0; + break; + } + + prepare_start_command(info, command); + + /* + * Prepare the "is ready" completion before starting a command + * transaction sequence. If the command is not executed the + * completion will be completed, see below. + * + * We can do that inside the loop because the command variable + * is invariant and thus so is the exec_cmd. + */ + info->need_wait = 1; + init_completion(&info->dev_ready); + do { + info->state = STATE_PREPARED; + exec_cmd = prepare_set_command(info, command, ext_cmd_type, + column, page_addr); + if (!exec_cmd) { + info->need_wait = 0; + complete(&info->dev_ready); + break; + } + + init_completion(&info->cmd_complete); + pxa3xx_nand_start(info); + + ret = wait_for_completion_timeout(&info->cmd_complete, + CHIP_DELAY_TIMEOUT); + if (!ret) { + dev_err(&info->pdev->dev, "Wait time out!!!\n"); + /* Stop State Machine for next command cycle */ + pxa3xx_nand_stop(info); + break; + } + + /* Check if the sequence is complete */ + if (info->data_size == 0 && command != NAND_CMD_PAGEPROG) + break; + + /* + * After a splitted program command sequence has issued + * the command dispatch, the command sequence is complete. + */ + if (info->data_size == 0 && + command == NAND_CMD_PAGEPROG && + ext_cmd_type == EXT_CMD_TYPE_DISPATCH) + break; + + if (command == NAND_CMD_READ0 || command == NAND_CMD_READOOB) { + /* Last read: issue a 'last naked read' */ + if (info->data_size == info->chunk_size) + ext_cmd_type = EXT_CMD_TYPE_LAST_RW; + else + ext_cmd_type = EXT_CMD_TYPE_NAKED_RW; + + /* + * If a splitted program command has no more data to transfer, + * the command dispatch must be issued to complete. + */ + } else if (command == NAND_CMD_PAGEPROG && + info->data_size == 0) { + ext_cmd_type = EXT_CMD_TYPE_DISPATCH; + } + } while (1); + + info->state = STATE_IDLE; +} + +static int pxa3xx_nand_write_page_hwecc(struct mtd_info *mtd, + struct nand_chip *chip, const uint8_t *buf, int oob_required) +{ + chip->write_buf(mtd, buf, mtd->writesize); + chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); + + return 0; +} + +static int pxa3xx_nand_read_page_hwecc(struct mtd_info *mtd, + struct nand_chip *chip, uint8_t *buf, int oob_required, + int page) +{ + struct pxa3xx_nand_host *host = mtd->priv; + struct pxa3xx_nand_info *info = host->info_data; + + chip->read_buf(mtd, buf, mtd->writesize); + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + + if (info->retcode == ERR_CORERR && info->use_ecc) { + mtd->ecc_stats.corrected += info->ecc_err_cnt; + + } else if (info->retcode == ERR_UNCORERR) { + /* + * for blank page (all 0xff), HW will calculate its ECC as + * 0, which is different from the ECC information within + * OOB, ignore such uncorrectable errors + */ + if (is_buf_blank(buf, mtd->writesize)) + info->retcode = ERR_NONE; + else + mtd->ecc_stats.failed++; + } + + return info->max_bitflips; +} + +static uint8_t pxa3xx_nand_read_byte(struct mtd_info *mtd) +{ + struct pxa3xx_nand_host *host = mtd->priv; + struct pxa3xx_nand_info *info = host->info_data; + char retval = 0xFF; + + if (info->buf_start < info->buf_count) + /* Has just send a new command? */ + retval = info->data_buff[info->buf_start++]; + + return retval; +} + +static u16 pxa3xx_nand_read_word(struct mtd_info *mtd) +{ + struct pxa3xx_nand_host *host = mtd->priv; + struct pxa3xx_nand_info *info = host->info_data; + u16 retval = 0xFFFF; + + if (!(info->buf_start & 0x01) && info->buf_start < info->buf_count) { + retval = *((u16 *)(info->data_buff+info->buf_start)); + info->buf_start += 2; + } + return retval; +} + +static void pxa3xx_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + struct pxa3xx_nand_host *host = mtd->priv; + struct pxa3xx_nand_info *info = host->info_data; + int real_len = min_t(size_t, len, info->buf_count - info->buf_start); + + memcpy(buf, info->data_buff + info->buf_start, real_len); + info->buf_start += real_len; +} + +static void pxa3xx_nand_write_buf(struct mtd_info *mtd, + const uint8_t *buf, int len) +{ + struct pxa3xx_nand_host *host = mtd->priv; + struct pxa3xx_nand_info *info = host->info_data; + int real_len = min_t(size_t, len, info->buf_count - info->buf_start); + + memcpy(info->data_buff + info->buf_start, buf, real_len); + info->buf_start += real_len; +} + +static void pxa3xx_nand_select_chip(struct mtd_info *mtd, int chip) +{ + return; +} + +static int pxa3xx_nand_waitfunc(struct mtd_info *mtd, struct nand_chip *this) +{ + struct pxa3xx_nand_host *host = mtd->priv; + struct pxa3xx_nand_info *info = host->info_data; + int ret; + + if (info->need_wait) { + ret = wait_for_completion_timeout(&info->dev_ready, + CHIP_DELAY_TIMEOUT); + info->need_wait = 0; + if (!ret) { + dev_err(&info->pdev->dev, "Ready time out!!!\n"); + return NAND_STATUS_FAIL; + } + } + + /* pxa3xx_nand_send_command has waited for command complete */ + if (this->state == FL_WRITING || this->state == FL_ERASING) { + if (info->retcode == ERR_NONE) + return 0; + else + return NAND_STATUS_FAIL; + } + + return NAND_STATUS_READY; +} + +static int pxa3xx_nand_config_flash(struct pxa3xx_nand_info *info, + const struct pxa3xx_nand_flash *f) +{ + struct platform_device *pdev = info->pdev; + struct pxa3xx_nand_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct pxa3xx_nand_host *host = info->host[info->cs]; + uint32_t ndcr = 0x0; /* enable all interrupts */ + + if (f->page_size != 2048 && f->page_size != 512) { + dev_err(&pdev->dev, "Current only support 2048 and 512 size\n"); + return -EINVAL; + } + + if (f->flash_width != 16 && f->flash_width != 8) { + dev_err(&pdev->dev, "Only support 8bit and 16 bit!\n"); + return -EINVAL; + } + + /* calculate flash information */ + host->read_id_bytes = (f->page_size == 2048) ? 4 : 2; + + /* calculate addressing information */ + host->col_addr_cycles = (f->page_size == 2048) ? 2 : 1; + + if (f->num_blocks * f->page_per_block > 65536) + host->row_addr_cycles = 3; + else + host->row_addr_cycles = 2; + + ndcr |= (pdata->enable_arbiter) ? NDCR_ND_ARB_EN : 0; + ndcr |= (host->col_addr_cycles == 2) ? NDCR_RA_START : 0; + ndcr |= (f->page_per_block == 64) ? NDCR_PG_PER_BLK : 0; + ndcr |= (f->page_size == 2048) ? NDCR_PAGE_SZ : 0; + ndcr |= (f->flash_width == 16) ? NDCR_DWIDTH_M : 0; + ndcr |= (f->dfc_width == 16) ? NDCR_DWIDTH_C : 0; + + ndcr |= NDCR_RD_ID_CNT(host->read_id_bytes); + ndcr |= NDCR_SPARE_EN; /* enable spare by default */ + + info->reg_ndcr = ndcr; + + pxa3xx_nand_set_timing(host, f->timing); + return 0; +} + +static int pxa3xx_nand_detect_config(struct pxa3xx_nand_info *info) +{ + /* + * We set 0 by hard coding here, for we don't support keep_config + * when there is more than one chip attached to the controller + */ + struct pxa3xx_nand_host *host = info->host[0]; + uint32_t ndcr = nand_readl(info, NDCR); + + if (ndcr & NDCR_PAGE_SZ) { + /* Controller's FIFO size */ + info->chunk_size = 2048; + host->read_id_bytes = 4; + } else { + info->chunk_size = 512; + host->read_id_bytes = 2; + } + + /* Set an initial chunk size */ + info->reg_ndcr = ndcr & ~NDCR_INT_MASK; + info->ndtr0cs0 = nand_readl(info, NDTR0CS0); + info->ndtr1cs0 = nand_readl(info, NDTR1CS0); + return 0; +} + +#ifdef ARCH_HAS_DMA +static int pxa3xx_nand_init_buff(struct pxa3xx_nand_info *info) +{ + struct platform_device *pdev = info->pdev; + int data_desc_offset = info->buf_size - sizeof(struct pxa_dma_desc); + + if (use_dma == 0) { + info->data_buff = kmalloc(info->buf_size, GFP_KERNEL); + if (info->data_buff == NULL) + return -ENOMEM; + return 0; + } + + info->data_buff = dma_alloc_coherent(&pdev->dev, info->buf_size, + &info->data_buff_phys, GFP_KERNEL); + if (info->data_buff == NULL) { + dev_err(&pdev->dev, "failed to allocate dma buffer\n"); + return -ENOMEM; + } + + info->data_desc = (void *)info->data_buff + data_desc_offset; + info->data_desc_addr = info->data_buff_phys + data_desc_offset; + + info->data_dma_ch = pxa_request_dma("nand-data", DMA_PRIO_LOW, + pxa3xx_nand_data_dma_irq, info); + if (info->data_dma_ch < 0) { + dev_err(&pdev->dev, "failed to request data dma\n"); + dma_free_coherent(&pdev->dev, info->buf_size, + info->data_buff, info->data_buff_phys); + return info->data_dma_ch; + } + + /* + * Now that DMA buffers are allocated we turn on + * DMA proper for I/O operations. + */ + info->use_dma = 1; + return 0; +} + +static void pxa3xx_nand_free_buff(struct pxa3xx_nand_info *info) +{ + struct platform_device *pdev = info->pdev; + if (info->use_dma) { + pxa_free_dma(info->data_dma_ch); + dma_free_coherent(&pdev->dev, info->buf_size, + info->data_buff, info->data_buff_phys); + } else { + kfree(info->data_buff); + } +} +#else +static int pxa3xx_nand_init_buff(struct pxa3xx_nand_info *info) +{ + info->data_buff = kmalloc(info->buf_size, GFP_KERNEL); + if (info->data_buff == NULL) + return -ENOMEM; + return 0; +} + +static void pxa3xx_nand_free_buff(struct pxa3xx_nand_info *info) +{ + kfree(info->data_buff); +} +#endif + +static int pxa3xx_nand_sensing(struct pxa3xx_nand_info *info) +{ + struct mtd_info *mtd; + struct nand_chip *chip; + int ret; + + mtd = info->host[info->cs]->mtd; + chip = mtd->priv; + + /* use the common timing to make a try */ + ret = pxa3xx_nand_config_flash(info, &builtin_flash_types[0]); + if (ret) + return ret; + + chip->cmdfunc(mtd, NAND_CMD_RESET, 0, 0); + ret = chip->waitfunc(mtd, chip); + if (ret & NAND_STATUS_FAIL) + return -ENODEV; + + return 0; +} + +static int pxa_ecc_init(struct pxa3xx_nand_info *info, + struct nand_ecc_ctrl *ecc, + int strength, int ecc_stepsize, int page_size) +{ + if (strength == 1 && ecc_stepsize == 512 && page_size == 2048) { + info->chunk_size = 2048; + info->spare_size = 40; + info->ecc_size = 24; + ecc->mode = NAND_ECC_HW; + ecc->size = 512; + ecc->strength = 1; + + } else if (strength == 1 && ecc_stepsize == 512 && page_size == 512) { + info->chunk_size = 512; + info->spare_size = 8; + info->ecc_size = 8; + ecc->mode = NAND_ECC_HW; + ecc->size = 512; + ecc->strength = 1; + + /* + * Required ECC: 4-bit correction per 512 bytes + * Select: 16-bit correction per 2048 bytes + */ + } else if (strength == 4 && ecc_stepsize == 512 && page_size == 2048) { + info->ecc_bch = 1; + info->chunk_size = 2048; + info->spare_size = 32; + info->ecc_size = 32; + ecc->mode = NAND_ECC_HW; + ecc->size = info->chunk_size; + ecc->layout = &ecc_layout_2KB_bch4bit; + ecc->strength = 16; + + } else if (strength == 4 && ecc_stepsize == 512 && page_size == 4096) { + info->ecc_bch = 1; + info->chunk_size = 2048; + info->spare_size = 32; + info->ecc_size = 32; + ecc->mode = NAND_ECC_HW; + ecc->size = info->chunk_size; + ecc->layout = &ecc_layout_4KB_bch4bit; + ecc->strength = 16; + + /* + * Required ECC: 8-bit correction per 512 bytes + * Select: 16-bit correction per 1024 bytes + */ + } else if (strength == 8 && ecc_stepsize == 512 && page_size == 4096) { + info->ecc_bch = 1; + info->chunk_size = 1024; + info->spare_size = 0; + info->ecc_size = 32; + ecc->mode = NAND_ECC_HW; + ecc->size = info->chunk_size; + ecc->layout = &ecc_layout_4KB_bch8bit; + ecc->strength = 16; + } else { + dev_err(&info->pdev->dev, + "ECC strength %d at page size %d is not supported\n", + strength, page_size); + return -ENODEV; + } + + dev_info(&info->pdev->dev, "ECC strength %d, ECC step size %d\n", + ecc->strength, ecc->size); + return 0; +} + +static int pxa3xx_nand_scan(struct mtd_info *mtd) +{ + struct pxa3xx_nand_host *host = mtd->priv; + struct pxa3xx_nand_info *info = host->info_data; + struct platform_device *pdev = info->pdev; + struct pxa3xx_nand_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct nand_flash_dev pxa3xx_flash_ids[2], *def = NULL; + const struct pxa3xx_nand_flash *f = NULL; + struct nand_chip *chip = mtd->priv; + uint32_t id = -1; + uint64_t chipsize; + int i, ret, num; + uint16_t ecc_strength, ecc_step; + + if (pdata->keep_config && !pxa3xx_nand_detect_config(info)) + goto KEEP_CONFIG; + + ret = pxa3xx_nand_sensing(info); + if (ret) { + dev_info(&info->pdev->dev, "There is no chip on cs %d!\n", + info->cs); + + return ret; + } + + chip->cmdfunc(mtd, NAND_CMD_READID, 0, 0); + id = *((uint16_t *)(info->data_buff)); + if (id != 0) + dev_info(&info->pdev->dev, "Detect a flash id %x\n", id); + else { + dev_warn(&info->pdev->dev, + "Read out ID 0, potential timing set wrong!!\n"); + + return -EINVAL; + } + + num = ARRAY_SIZE(builtin_flash_types) + pdata->num_flash - 1; + for (i = 0; i < num; i++) { + if (i < pdata->num_flash) + f = pdata->flash + i; + else + f = &builtin_flash_types[i - pdata->num_flash + 1]; + + /* find the chip in default list */ + if (f->chip_id == id) + break; + } + + if (i >= (ARRAY_SIZE(builtin_flash_types) + pdata->num_flash - 1)) { + dev_err(&info->pdev->dev, "ERROR!! flash not defined!!!\n"); + + return -EINVAL; + } + + ret = pxa3xx_nand_config_flash(info, f); + if (ret) { + dev_err(&info->pdev->dev, "ERROR! Configure failed\n"); + return ret; + } + + pxa3xx_flash_ids[0].name = f->name; + pxa3xx_flash_ids[0].dev_id = (f->chip_id >> 8) & 0xffff; + pxa3xx_flash_ids[0].pagesize = f->page_size; + chipsize = (uint64_t)f->num_blocks * f->page_per_block * f->page_size; + pxa3xx_flash_ids[0].chipsize = chipsize >> 20; + pxa3xx_flash_ids[0].erasesize = f->page_size * f->page_per_block; + if (f->flash_width == 16) + pxa3xx_flash_ids[0].options = NAND_BUSWIDTH_16; + pxa3xx_flash_ids[1].name = NULL; + def = pxa3xx_flash_ids; +KEEP_CONFIG: + if (info->reg_ndcr & NDCR_DWIDTH_M) + chip->options |= NAND_BUSWIDTH_16; + + /* Device detection must be done with ECC disabled */ + if (info->variant == PXA3XX_NAND_VARIANT_ARMADA370) + nand_writel(info, NDECCCTRL, 0x0); + + if (nand_scan_ident(mtd, 1, def)) + return -ENODEV; + + if (pdata->flash_bbt) { + /* + * We'll use a bad block table stored in-flash and don't + * allow writing the bad block marker to the flash. + */ + chip->bbt_options |= NAND_BBT_USE_FLASH | + NAND_BBT_NO_OOB_BBM; + chip->bbt_td = &bbt_main_descr; + chip->bbt_md = &bbt_mirror_descr; + } + + /* + * If the page size is bigger than the FIFO size, let's check + * we are given the right variant and then switch to the extended + * (aka splitted) command handling, + */ + if (mtd->writesize > PAGE_CHUNK_SIZE) { + if (info->variant == PXA3XX_NAND_VARIANT_ARMADA370) { + chip->cmdfunc = nand_cmdfunc_extended; + } else { + dev_err(&info->pdev->dev, + "unsupported page size on this variant\n"); + return -ENODEV; + } + } + + if (pdata->ecc_strength && pdata->ecc_step_size) { + ecc_strength = pdata->ecc_strength; + ecc_step = pdata->ecc_step_size; + } else { + ecc_strength = chip->ecc_strength_ds; + ecc_step = chip->ecc_step_ds; + } + + /* Set default ECC strength requirements on non-ONFI devices */ + if (ecc_strength < 1 && ecc_step < 1) { + ecc_strength = 1; + ecc_step = 512; + } + + ret = pxa_ecc_init(info, &chip->ecc, ecc_strength, + ecc_step, mtd->writesize); + if (ret) + return ret; + + /* calculate addressing information */ + if (mtd->writesize >= 2048) + host->col_addr_cycles = 2; + else + host->col_addr_cycles = 1; + + /* release the initial buffer */ + kfree(info->data_buff); + + /* allocate the real data + oob buffer */ + info->buf_size = mtd->writesize + mtd->oobsize; + ret = pxa3xx_nand_init_buff(info); + if (ret) + return ret; + info->oob_buff = info->data_buff + mtd->writesize; + + if ((mtd->size >> chip->page_shift) > 65536) + host->row_addr_cycles = 3; + else + host->row_addr_cycles = 2; + return nand_scan_tail(mtd); +} + +static int alloc_nand_resource(struct platform_device *pdev) +{ + struct pxa3xx_nand_platform_data *pdata; + struct pxa3xx_nand_info *info; + struct pxa3xx_nand_host *host; + struct nand_chip *chip = NULL; + struct mtd_info *mtd; + struct resource *r; + int ret, irq, cs; + + pdata = dev_get_platdata(&pdev->dev); + info = devm_kzalloc(&pdev->dev, sizeof(*info) + (sizeof(*mtd) + + sizeof(*host)) * pdata->num_cs, GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->pdev = pdev; + info->variant = pxa3xx_nand_get_variant(pdev); + for (cs = 0; cs < pdata->num_cs; cs++) { + mtd = (struct mtd_info *)((unsigned int)&info[1] + + (sizeof(*mtd) + sizeof(*host)) * cs); + chip = (struct nand_chip *)(&mtd[1]); + host = (struct pxa3xx_nand_host *)chip; + info->host[cs] = host; + host->mtd = mtd; + host->cs = cs; + host->info_data = info; + mtd->priv = host; + mtd->owner = THIS_MODULE; + + chip->ecc.read_page = pxa3xx_nand_read_page_hwecc; + chip->ecc.write_page = pxa3xx_nand_write_page_hwecc; + chip->controller = &info->controller; + chip->waitfunc = pxa3xx_nand_waitfunc; + chip->select_chip = pxa3xx_nand_select_chip; + chip->read_word = pxa3xx_nand_read_word; + chip->read_byte = pxa3xx_nand_read_byte; + chip->read_buf = pxa3xx_nand_read_buf; + chip->write_buf = pxa3xx_nand_write_buf; + chip->options |= NAND_NO_SUBPAGE_WRITE; + chip->cmdfunc = nand_cmdfunc; + } + + spin_lock_init(&chip->controller->lock); + init_waitqueue_head(&chip->controller->wq); + info->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(info->clk)) { + dev_err(&pdev->dev, "failed to get nand clock\n"); + return PTR_ERR(info->clk); + } + ret = clk_prepare_enable(info->clk); + if (ret < 0) + return ret; + + if (use_dma) { + /* + * This is a dirty hack to make this driver work from + * devicetree bindings. It can be removed once we have + * a prober DMA controller framework for DT. + */ + if (pdev->dev.of_node && + of_machine_is_compatible("marvell,pxa3xx")) { + info->drcmr_dat = 97; + info->drcmr_cmd = 99; + } else { + r = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (r == NULL) { + dev_err(&pdev->dev, + "no resource defined for data DMA\n"); + ret = -ENXIO; + goto fail_disable_clk; + } + info->drcmr_dat = r->start; + + r = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (r == NULL) { + dev_err(&pdev->dev, + "no resource defined for cmd DMA\n"); + ret = -ENXIO; + goto fail_disable_clk; + } + info->drcmr_cmd = r->start; + } + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no IRQ resource defined\n"); + ret = -ENXIO; + goto fail_disable_clk; + } + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + info->mmio_base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(info->mmio_base)) { + ret = PTR_ERR(info->mmio_base); + goto fail_disable_clk; + } + info->mmio_phys = r->start; + + /* Allocate a buffer to allow flash detection */ + info->buf_size = INIT_BUFFER_SIZE; + info->data_buff = kmalloc(info->buf_size, GFP_KERNEL); + if (info->data_buff == NULL) { + ret = -ENOMEM; + goto fail_disable_clk; + } + + /* initialize all interrupts to be disabled */ + disable_int(info, NDSR_MASK); + + ret = request_irq(irq, pxa3xx_nand_irq, 0, pdev->name, info); + if (ret < 0) { + dev_err(&pdev->dev, "failed to request IRQ\n"); + goto fail_free_buf; + } + + platform_set_drvdata(pdev, info); + + return 0; + +fail_free_buf: + free_irq(irq, info); + kfree(info->data_buff); +fail_disable_clk: + clk_disable_unprepare(info->clk); + return ret; +} + +static int pxa3xx_nand_remove(struct platform_device *pdev) +{ + struct pxa3xx_nand_info *info = platform_get_drvdata(pdev); + struct pxa3xx_nand_platform_data *pdata; + int irq, cs; + + if (!info) + return 0; + + pdata = dev_get_platdata(&pdev->dev); + + irq = platform_get_irq(pdev, 0); + if (irq >= 0) + free_irq(irq, info); + pxa3xx_nand_free_buff(info); + + clk_disable_unprepare(info->clk); + + for (cs = 0; cs < pdata->num_cs; cs++) + nand_release(info->host[cs]->mtd); + return 0; +} + +static int pxa3xx_nand_probe_dt(struct platform_device *pdev) +{ + struct pxa3xx_nand_platform_data *pdata; + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *of_id = + of_match_device(pxa3xx_nand_dt_ids, &pdev->dev); + + if (!of_id) + return 0; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + if (of_get_property(np, "marvell,nand-enable-arbiter", NULL)) + pdata->enable_arbiter = 1; + if (of_get_property(np, "marvell,nand-keep-config", NULL)) + pdata->keep_config = 1; + of_property_read_u32(np, "num-cs", &pdata->num_cs); + pdata->flash_bbt = of_get_nand_on_flash_bbt(np); + + pdata->ecc_strength = of_get_nand_ecc_strength(np); + if (pdata->ecc_strength < 0) + pdata->ecc_strength = 0; + + pdata->ecc_step_size = of_get_nand_ecc_step_size(np); + if (pdata->ecc_step_size < 0) + pdata->ecc_step_size = 0; + + pdev->dev.platform_data = pdata; + + return 0; +} + +static int pxa3xx_nand_probe(struct platform_device *pdev) +{ + struct pxa3xx_nand_platform_data *pdata; + struct mtd_part_parser_data ppdata = {}; + struct pxa3xx_nand_info *info; + int ret, cs, probe_success; + +#ifndef ARCH_HAS_DMA + if (use_dma) { + use_dma = 0; + dev_warn(&pdev->dev, + "This platform can't do DMA on this device\n"); + } +#endif + ret = pxa3xx_nand_probe_dt(pdev); + if (ret) + return ret; + + pdata = dev_get_platdata(&pdev->dev); + if (!pdata) { + dev_err(&pdev->dev, "no platform data defined\n"); + return -ENODEV; + } + + ret = alloc_nand_resource(pdev); + if (ret) { + dev_err(&pdev->dev, "alloc nand resource failed\n"); + return ret; + } + + info = platform_get_drvdata(pdev); + probe_success = 0; + for (cs = 0; cs < pdata->num_cs; cs++) { + struct mtd_info *mtd = info->host[cs]->mtd; + + /* + * The mtd name matches the one used in 'mtdparts' kernel + * parameter. This name cannot be changed or otherwise + * user's mtd partitions configuration would get broken. + */ + mtd->name = "pxa3xx_nand-0"; + info->cs = cs; + ret = pxa3xx_nand_scan(mtd); + if (ret) { + dev_warn(&pdev->dev, "failed to scan nand at cs %d\n", + cs); + continue; + } + + ppdata.of_node = pdev->dev.of_node; + ret = mtd_device_parse_register(mtd, NULL, + &ppdata, pdata->parts[cs], + pdata->nr_parts[cs]); + if (!ret) + probe_success = 1; + } + + if (!probe_success) { + pxa3xx_nand_remove(pdev); + return -ENODEV; + } + + return 0; +} + +#ifdef CONFIG_PM +static int pxa3xx_nand_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct pxa3xx_nand_info *info = platform_get_drvdata(pdev); + struct pxa3xx_nand_platform_data *pdata; + struct mtd_info *mtd; + int cs; + + pdata = dev_get_platdata(&pdev->dev); + if (info->state) { + dev_err(&pdev->dev, "driver busy, state = %d\n", info->state); + return -EAGAIN; + } + + for (cs = 0; cs < pdata->num_cs; cs++) { + mtd = info->host[cs]->mtd; + mtd_suspend(mtd); + } + + return 0; +} + +static int pxa3xx_nand_resume(struct platform_device *pdev) +{ + struct pxa3xx_nand_info *info = platform_get_drvdata(pdev); + struct pxa3xx_nand_platform_data *pdata; + struct mtd_info *mtd; + int cs; + + pdata = dev_get_platdata(&pdev->dev); + /* We don't want to handle interrupt without calling mtd routine */ + disable_int(info, NDCR_INT_MASK); + + /* + * Directly set the chip select to a invalid value, + * then the driver would reset the timing according + * to current chip select at the beginning of cmdfunc + */ + info->cs = 0xff; + + /* + * As the spec says, the NDSR would be updated to 0x1800 when + * doing the nand_clk disable/enable. + * To prevent it damaging state machine of the driver, clear + * all status before resume + */ + nand_writel(info, NDSR, NDSR_MASK); + for (cs = 0; cs < pdata->num_cs; cs++) { + mtd = info->host[cs]->mtd; + mtd_resume(mtd); + } + + return 0; +} +#else +#define pxa3xx_nand_suspend NULL +#define pxa3xx_nand_resume NULL +#endif + +static struct platform_driver pxa3xx_nand_driver = { + .driver = { + .name = "pxa3xx-nand", + .of_match_table = pxa3xx_nand_dt_ids, + }, + .probe = pxa3xx_nand_probe, + .remove = pxa3xx_nand_remove, + .suspend = pxa3xx_nand_suspend, + .resume = pxa3xx_nand_resume, +}; + +module_platform_driver(pxa3xx_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("PXA3xx NAND controller driver"); diff --git a/drivers/mtd/nand/r852.c b/drivers/mtd/nand/r852.c new file mode 100644 index 00000000000..baea83f4dea --- /dev/null +++ b/drivers/mtd/nand/r852.c @@ -0,0 +1,1085 @@ +/* + * Copyright © 2009 - Maxim Levitsky + * driver for Ricoh xD readers + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/jiffies.h> +#include <linux/workqueue.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <asm/byteorder.h> +#include <linux/sched.h> +#include "sm_common.h" +#include "r852.h" + + +static bool r852_enable_dma = 1; +module_param(r852_enable_dma, bool, S_IRUGO); +MODULE_PARM_DESC(r852_enable_dma, "Enable usage of the DMA (default)"); + +static int debug; +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug level (0-2)"); + +/* read register */ +static inline uint8_t r852_read_reg(struct r852_device *dev, int address) +{ + uint8_t reg = readb(dev->mmio + address); + return reg; +} + +/* write register */ +static inline void r852_write_reg(struct r852_device *dev, + int address, uint8_t value) +{ + writeb(value, dev->mmio + address); + mmiowb(); +} + + +/* read dword sized register */ +static inline uint32_t r852_read_reg_dword(struct r852_device *dev, int address) +{ + uint32_t reg = le32_to_cpu(readl(dev->mmio + address)); + return reg; +} + +/* write dword sized register */ +static inline void r852_write_reg_dword(struct r852_device *dev, + int address, uint32_t value) +{ + writel(cpu_to_le32(value), dev->mmio + address); + mmiowb(); +} + +/* returns pointer to our private structure */ +static inline struct r852_device *r852_get_dev(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + return chip->priv; +} + + +/* check if controller supports dma */ +static void r852_dma_test(struct r852_device *dev) +{ + dev->dma_usable = (r852_read_reg(dev, R852_DMA_CAP) & + (R852_DMA1 | R852_DMA2)) == (R852_DMA1 | R852_DMA2); + + if (!dev->dma_usable) + message("Non dma capable device detected, dma disabled"); + + if (!r852_enable_dma) { + message("disabling dma on user request"); + dev->dma_usable = 0; + } +} + +/* + * Enable dma. Enables ether first or second stage of the DMA, + * Expects dev->dma_dir and dev->dma_state be set + */ +static void r852_dma_enable(struct r852_device *dev) +{ + uint8_t dma_reg, dma_irq_reg; + + /* Set up dma settings */ + dma_reg = r852_read_reg_dword(dev, R852_DMA_SETTINGS); + dma_reg &= ~(R852_DMA_READ | R852_DMA_INTERNAL | R852_DMA_MEMORY); + + if (dev->dma_dir) + dma_reg |= R852_DMA_READ; + + if (dev->dma_state == DMA_INTERNAL) { + dma_reg |= R852_DMA_INTERNAL; + /* Precaution to make sure HW doesn't write */ + /* to random kernel memory */ + r852_write_reg_dword(dev, R852_DMA_ADDR, + cpu_to_le32(dev->phys_bounce_buffer)); + } else { + dma_reg |= R852_DMA_MEMORY; + r852_write_reg_dword(dev, R852_DMA_ADDR, + cpu_to_le32(dev->phys_dma_addr)); + } + + /* Precaution: make sure write reached the device */ + r852_read_reg_dword(dev, R852_DMA_ADDR); + + r852_write_reg_dword(dev, R852_DMA_SETTINGS, dma_reg); + + /* Set dma irq */ + dma_irq_reg = r852_read_reg_dword(dev, R852_DMA_IRQ_ENABLE); + r852_write_reg_dword(dev, R852_DMA_IRQ_ENABLE, + dma_irq_reg | + R852_DMA_IRQ_INTERNAL | + R852_DMA_IRQ_ERROR | + R852_DMA_IRQ_MEMORY); +} + +/* + * Disable dma, called from the interrupt handler, which specifies + * success of the operation via 'error' argument + */ +static void r852_dma_done(struct r852_device *dev, int error) +{ + WARN_ON(dev->dma_stage == 0); + + r852_write_reg_dword(dev, R852_DMA_IRQ_STA, + r852_read_reg_dword(dev, R852_DMA_IRQ_STA)); + + r852_write_reg_dword(dev, R852_DMA_SETTINGS, 0); + r852_write_reg_dword(dev, R852_DMA_IRQ_ENABLE, 0); + + /* Precaution to make sure HW doesn't write to random kernel memory */ + r852_write_reg_dword(dev, R852_DMA_ADDR, + cpu_to_le32(dev->phys_bounce_buffer)); + r852_read_reg_dword(dev, R852_DMA_ADDR); + + dev->dma_error = error; + dev->dma_stage = 0; + + if (dev->phys_dma_addr && dev->phys_dma_addr != dev->phys_bounce_buffer) + pci_unmap_single(dev->pci_dev, dev->phys_dma_addr, R852_DMA_LEN, + dev->dma_dir ? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE); +} + +/* + * Wait, till dma is done, which includes both phases of it + */ +static int r852_dma_wait(struct r852_device *dev) +{ + long timeout = wait_for_completion_timeout(&dev->dma_done, + msecs_to_jiffies(1000)); + if (!timeout) { + dbg("timeout waiting for DMA interrupt"); + return -ETIMEDOUT; + } + + return 0; +} + +/* + * Read/Write one page using dma. Only pages can be read (512 bytes) +*/ +static void r852_do_dma(struct r852_device *dev, uint8_t *buf, int do_read) +{ + int bounce = 0; + unsigned long flags; + int error; + + dev->dma_error = 0; + + /* Set dma direction */ + dev->dma_dir = do_read; + dev->dma_stage = 1; + reinit_completion(&dev->dma_done); + + dbg_verbose("doing dma %s ", do_read ? "read" : "write"); + + /* Set initial dma state: for reading first fill on board buffer, + from device, for writes first fill the buffer from memory*/ + dev->dma_state = do_read ? DMA_INTERNAL : DMA_MEMORY; + + /* if incoming buffer is not page aligned, we should do bounce */ + if ((unsigned long)buf & (R852_DMA_LEN-1)) + bounce = 1; + + if (!bounce) { + dev->phys_dma_addr = pci_map_single(dev->pci_dev, (void *)buf, + R852_DMA_LEN, + (do_read ? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE)); + + if (pci_dma_mapping_error(dev->pci_dev, dev->phys_dma_addr)) + bounce = 1; + } + + if (bounce) { + dbg_verbose("dma: using bounce buffer"); + dev->phys_dma_addr = dev->phys_bounce_buffer; + if (!do_read) + memcpy(dev->bounce_buffer, buf, R852_DMA_LEN); + } + + /* Enable DMA */ + spin_lock_irqsave(&dev->irqlock, flags); + r852_dma_enable(dev); + spin_unlock_irqrestore(&dev->irqlock, flags); + + /* Wait till complete */ + error = r852_dma_wait(dev); + + if (error) { + r852_dma_done(dev, error); + return; + } + + if (do_read && bounce) + memcpy((void *)buf, dev->bounce_buffer, R852_DMA_LEN); +} + +/* + * Program data lines of the nand chip to send data to it + */ +static void r852_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) +{ + struct r852_device *dev = r852_get_dev(mtd); + uint32_t reg; + + /* Don't allow any access to hardware if we suspect card removal */ + if (dev->card_unstable) + return; + + /* Special case for whole sector read */ + if (len == R852_DMA_LEN && dev->dma_usable) { + r852_do_dma(dev, (uint8_t *)buf, 0); + return; + } + + /* write DWORD chinks - faster */ + while (len >= 4) { + reg = buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24; + r852_write_reg_dword(dev, R852_DATALINE, reg); + buf += 4; + len -= 4; + + } + + /* write rest */ + while (len > 0) { + r852_write_reg(dev, R852_DATALINE, *buf++); + len--; + } +} + +/* + * Read data lines of the nand chip to retrieve data + */ +static void r852_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + struct r852_device *dev = r852_get_dev(mtd); + uint32_t reg; + + if (dev->card_unstable) { + /* since we can't signal error here, at least, return + predictable buffer */ + memset(buf, 0, len); + return; + } + + /* special case for whole sector read */ + if (len == R852_DMA_LEN && dev->dma_usable) { + r852_do_dma(dev, buf, 1); + return; + } + + /* read in dword sized chunks */ + while (len >= 4) { + + reg = r852_read_reg_dword(dev, R852_DATALINE); + *buf++ = reg & 0xFF; + *buf++ = (reg >> 8) & 0xFF; + *buf++ = (reg >> 16) & 0xFF; + *buf++ = (reg >> 24) & 0xFF; + len -= 4; + } + + /* read the reset by bytes */ + while (len--) + *buf++ = r852_read_reg(dev, R852_DATALINE); +} + +/* + * Read one byte from nand chip + */ +static uint8_t r852_read_byte(struct mtd_info *mtd) +{ + struct r852_device *dev = r852_get_dev(mtd); + + /* Same problem as in r852_read_buf.... */ + if (dev->card_unstable) + return 0; + + return r852_read_reg(dev, R852_DATALINE); +} + +/* + * Control several chip lines & send commands + */ +static void r852_cmdctl(struct mtd_info *mtd, int dat, unsigned int ctrl) +{ + struct r852_device *dev = r852_get_dev(mtd); + + if (dev->card_unstable) + return; + + if (ctrl & NAND_CTRL_CHANGE) { + + dev->ctlreg &= ~(R852_CTL_DATA | R852_CTL_COMMAND | + R852_CTL_ON | R852_CTL_CARDENABLE); + + if (ctrl & NAND_ALE) + dev->ctlreg |= R852_CTL_DATA; + + if (ctrl & NAND_CLE) + dev->ctlreg |= R852_CTL_COMMAND; + + if (ctrl & NAND_NCE) + dev->ctlreg |= (R852_CTL_CARDENABLE | R852_CTL_ON); + else + dev->ctlreg &= ~R852_CTL_WRITE; + + /* when write is stareted, enable write access */ + if (dat == NAND_CMD_ERASE1) + dev->ctlreg |= R852_CTL_WRITE; + + r852_write_reg(dev, R852_CTL, dev->ctlreg); + } + + /* HACK: NAND_CMD_SEQIN is called without NAND_CTRL_CHANGE, but we need + to set write mode */ + if (dat == NAND_CMD_SEQIN && (dev->ctlreg & R852_CTL_COMMAND)) { + dev->ctlreg |= R852_CTL_WRITE; + r852_write_reg(dev, R852_CTL, dev->ctlreg); + } + + if (dat != NAND_CMD_NONE) + r852_write_reg(dev, R852_DATALINE, dat); +} + +/* + * Wait till card is ready. + * based on nand_wait, but returns errors on DMA error + */ +static int r852_wait(struct mtd_info *mtd, struct nand_chip *chip) +{ + struct r852_device *dev = chip->priv; + + unsigned long timeout; + int status; + + timeout = jiffies + (chip->state == FL_ERASING ? + msecs_to_jiffies(400) : msecs_to_jiffies(20)); + + while (time_before(jiffies, timeout)) + if (chip->dev_ready(mtd)) + break; + + chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); + status = (int)chip->read_byte(mtd); + + /* Unfortunelly, no way to send detailed error status... */ + if (dev->dma_error) { + status |= NAND_STATUS_FAIL; + dev->dma_error = 0; + } + return status; +} + +/* + * Check if card is ready + */ + +static int r852_ready(struct mtd_info *mtd) +{ + struct r852_device *dev = r852_get_dev(mtd); + return !(r852_read_reg(dev, R852_CARD_STA) & R852_CARD_STA_BUSY); +} + + +/* + * Set ECC engine mode +*/ + +static void r852_ecc_hwctl(struct mtd_info *mtd, int mode) +{ + struct r852_device *dev = r852_get_dev(mtd); + + if (dev->card_unstable) + return; + + switch (mode) { + case NAND_ECC_READ: + case NAND_ECC_WRITE: + /* enable ecc generation/check*/ + dev->ctlreg |= R852_CTL_ECC_ENABLE; + + /* flush ecc buffer */ + r852_write_reg(dev, R852_CTL, + dev->ctlreg | R852_CTL_ECC_ACCESS); + + r852_read_reg_dword(dev, R852_DATALINE); + r852_write_reg(dev, R852_CTL, dev->ctlreg); + return; + + case NAND_ECC_READSYN: + /* disable ecc generation */ + dev->ctlreg &= ~R852_CTL_ECC_ENABLE; + r852_write_reg(dev, R852_CTL, dev->ctlreg); + } +} + +/* + * Calculate ECC, only used for writes + */ + +static int r852_ecc_calculate(struct mtd_info *mtd, const uint8_t *dat, + uint8_t *ecc_code) +{ + struct r852_device *dev = r852_get_dev(mtd); + struct sm_oob *oob = (struct sm_oob *)ecc_code; + uint32_t ecc1, ecc2; + + if (dev->card_unstable) + return 0; + + dev->ctlreg &= ~R852_CTL_ECC_ENABLE; + r852_write_reg(dev, R852_CTL, dev->ctlreg | R852_CTL_ECC_ACCESS); + + ecc1 = r852_read_reg_dword(dev, R852_DATALINE); + ecc2 = r852_read_reg_dword(dev, R852_DATALINE); + + oob->ecc1[0] = (ecc1) & 0xFF; + oob->ecc1[1] = (ecc1 >> 8) & 0xFF; + oob->ecc1[2] = (ecc1 >> 16) & 0xFF; + + oob->ecc2[0] = (ecc2) & 0xFF; + oob->ecc2[1] = (ecc2 >> 8) & 0xFF; + oob->ecc2[2] = (ecc2 >> 16) & 0xFF; + + r852_write_reg(dev, R852_CTL, dev->ctlreg); + return 0; +} + +/* + * Correct the data using ECC, hw did almost everything for us + */ + +static int r852_ecc_correct(struct mtd_info *mtd, uint8_t *dat, + uint8_t *read_ecc, uint8_t *calc_ecc) +{ + uint16_t ecc_reg; + uint8_t ecc_status, err_byte; + int i, error = 0; + + struct r852_device *dev = r852_get_dev(mtd); + + if (dev->card_unstable) + return 0; + + if (dev->dma_error) { + dev->dma_error = 0; + return -1; + } + + r852_write_reg(dev, R852_CTL, dev->ctlreg | R852_CTL_ECC_ACCESS); + ecc_reg = r852_read_reg_dword(dev, R852_DATALINE); + r852_write_reg(dev, R852_CTL, dev->ctlreg); + + for (i = 0 ; i <= 1 ; i++) { + + ecc_status = (ecc_reg >> 8) & 0xFF; + + /* ecc uncorrectable error */ + if (ecc_status & R852_ECC_FAIL) { + dbg("ecc: unrecoverable error, in half %d", i); + error = -1; + goto exit; + } + + /* correctable error */ + if (ecc_status & R852_ECC_CORRECTABLE) { + + err_byte = ecc_reg & 0xFF; + dbg("ecc: recoverable error, " + "in half %d, byte %d, bit %d", i, + err_byte, ecc_status & R852_ECC_ERR_BIT_MSK); + + dat[err_byte] ^= + 1 << (ecc_status & R852_ECC_ERR_BIT_MSK); + error++; + } + + dat += 256; + ecc_reg >>= 16; + } +exit: + return error; +} + +/* + * This is copy of nand_read_oob_std + * nand_read_oob_syndrome assumes we can send column address - we can't + */ +static int r852_read_oob(struct mtd_info *mtd, struct nand_chip *chip, + int page) +{ + chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page); + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + return 0; +} + +/* + * Start the nand engine + */ + +static void r852_engine_enable(struct r852_device *dev) +{ + if (r852_read_reg_dword(dev, R852_HW) & R852_HW_UNKNOWN) { + r852_write_reg(dev, R852_CTL, R852_CTL_RESET | R852_CTL_ON); + r852_write_reg_dword(dev, R852_HW, R852_HW_ENABLED); + } else { + r852_write_reg_dword(dev, R852_HW, R852_HW_ENABLED); + r852_write_reg(dev, R852_CTL, R852_CTL_RESET | R852_CTL_ON); + } + msleep(300); + r852_write_reg(dev, R852_CTL, 0); +} + + +/* + * Stop the nand engine + */ + +static void r852_engine_disable(struct r852_device *dev) +{ + r852_write_reg_dword(dev, R852_HW, 0); + r852_write_reg(dev, R852_CTL, R852_CTL_RESET); +} + +/* + * Test if card is present + */ + +static void r852_card_update_present(struct r852_device *dev) +{ + unsigned long flags; + uint8_t reg; + + spin_lock_irqsave(&dev->irqlock, flags); + reg = r852_read_reg(dev, R852_CARD_STA); + dev->card_detected = !!(reg & R852_CARD_STA_PRESENT); + spin_unlock_irqrestore(&dev->irqlock, flags); +} + +/* + * Update card detection IRQ state according to current card state + * which is read in r852_card_update_present + */ +static void r852_update_card_detect(struct r852_device *dev) +{ + int card_detect_reg = r852_read_reg(dev, R852_CARD_IRQ_ENABLE); + dev->card_unstable = 0; + + card_detect_reg &= ~(R852_CARD_IRQ_REMOVE | R852_CARD_IRQ_INSERT); + card_detect_reg |= R852_CARD_IRQ_GENABLE; + + card_detect_reg |= dev->card_detected ? + R852_CARD_IRQ_REMOVE : R852_CARD_IRQ_INSERT; + + r852_write_reg(dev, R852_CARD_IRQ_ENABLE, card_detect_reg); +} + +static ssize_t r852_media_type_show(struct device *sys_dev, + struct device_attribute *attr, char *buf) +{ + struct mtd_info *mtd = container_of(sys_dev, struct mtd_info, dev); + struct r852_device *dev = r852_get_dev(mtd); + char *data = dev->sm ? "smartmedia" : "xd"; + + strcpy(buf, data); + return strlen(data); +} + +static DEVICE_ATTR(media_type, S_IRUGO, r852_media_type_show, NULL); + + +/* Detect properties of card in slot */ +static void r852_update_media_status(struct r852_device *dev) +{ + uint8_t reg; + unsigned long flags; + int readonly; + + spin_lock_irqsave(&dev->irqlock, flags); + if (!dev->card_detected) { + message("card removed"); + spin_unlock_irqrestore(&dev->irqlock, flags); + return ; + } + + readonly = r852_read_reg(dev, R852_CARD_STA) & R852_CARD_STA_RO; + reg = r852_read_reg(dev, R852_DMA_CAP); + dev->sm = (reg & (R852_DMA1 | R852_DMA2)) && (reg & R852_SMBIT); + + message("detected %s %s card in slot", + dev->sm ? "SmartMedia" : "xD", + readonly ? "readonly" : "writeable"); + + dev->readonly = readonly; + spin_unlock_irqrestore(&dev->irqlock, flags); +} + +/* + * Register the nand device + * Called when the card is detected + */ +static int r852_register_nand_device(struct r852_device *dev) +{ + dev->mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL); + + if (!dev->mtd) + goto error1; + + WARN_ON(dev->card_registred); + + dev->mtd->owner = THIS_MODULE; + dev->mtd->priv = dev->chip; + dev->mtd->dev.parent = &dev->pci_dev->dev; + + if (dev->readonly) + dev->chip->options |= NAND_ROM; + + r852_engine_enable(dev); + + if (sm_register_device(dev->mtd, dev->sm)) + goto error2; + + if (device_create_file(&dev->mtd->dev, &dev_attr_media_type)) + message("can't create media type sysfs attribute"); + + dev->card_registred = 1; + return 0; +error2: + kfree(dev->mtd); +error1: + /* Force card redetect */ + dev->card_detected = 0; + return -1; +} + +/* + * Unregister the card + */ + +static void r852_unregister_nand_device(struct r852_device *dev) +{ + if (!dev->card_registred) + return; + + device_remove_file(&dev->mtd->dev, &dev_attr_media_type); + nand_release(dev->mtd); + r852_engine_disable(dev); + dev->card_registred = 0; + kfree(dev->mtd); + dev->mtd = NULL; +} + +/* Card state updater */ +static void r852_card_detect_work(struct work_struct *work) +{ + struct r852_device *dev = + container_of(work, struct r852_device, card_detect_work.work); + + r852_card_update_present(dev); + r852_update_card_detect(dev); + dev->card_unstable = 0; + + /* False alarm */ + if (dev->card_detected == dev->card_registred) + goto exit; + + /* Read media properties */ + r852_update_media_status(dev); + + /* Register the card */ + if (dev->card_detected) + r852_register_nand_device(dev); + else + r852_unregister_nand_device(dev); +exit: + r852_update_card_detect(dev); +} + +/* Ack + disable IRQ generation */ +static void r852_disable_irqs(struct r852_device *dev) +{ + uint8_t reg; + reg = r852_read_reg(dev, R852_CARD_IRQ_ENABLE); + r852_write_reg(dev, R852_CARD_IRQ_ENABLE, reg & ~R852_CARD_IRQ_MASK); + + reg = r852_read_reg_dword(dev, R852_DMA_IRQ_ENABLE); + r852_write_reg_dword(dev, R852_DMA_IRQ_ENABLE, + reg & ~R852_DMA_IRQ_MASK); + + r852_write_reg(dev, R852_CARD_IRQ_STA, R852_CARD_IRQ_MASK); + r852_write_reg_dword(dev, R852_DMA_IRQ_STA, R852_DMA_IRQ_MASK); +} + +/* Interrupt handler */ +static irqreturn_t r852_irq(int irq, void *data) +{ + struct r852_device *dev = (struct r852_device *)data; + + uint8_t card_status, dma_status; + unsigned long flags; + irqreturn_t ret = IRQ_NONE; + + spin_lock_irqsave(&dev->irqlock, flags); + + /* handle card detection interrupts first */ + card_status = r852_read_reg(dev, R852_CARD_IRQ_STA); + r852_write_reg(dev, R852_CARD_IRQ_STA, card_status); + + if (card_status & (R852_CARD_IRQ_INSERT|R852_CARD_IRQ_REMOVE)) { + + ret = IRQ_HANDLED; + dev->card_detected = !!(card_status & R852_CARD_IRQ_INSERT); + + /* we shouldn't receive any interrupts if we wait for card + to settle */ + WARN_ON(dev->card_unstable); + + /* disable irqs while card is unstable */ + /* this will timeout DMA if active, but better that garbage */ + r852_disable_irqs(dev); + + if (dev->card_unstable) + goto out; + + /* let, card state to settle a bit, and then do the work */ + dev->card_unstable = 1; + queue_delayed_work(dev->card_workqueue, + &dev->card_detect_work, msecs_to_jiffies(100)); + goto out; + } + + + /* Handle dma interrupts */ + dma_status = r852_read_reg_dword(dev, R852_DMA_IRQ_STA); + r852_write_reg_dword(dev, R852_DMA_IRQ_STA, dma_status); + + if (dma_status & R852_DMA_IRQ_MASK) { + + ret = IRQ_HANDLED; + + if (dma_status & R852_DMA_IRQ_ERROR) { + dbg("received dma error IRQ"); + r852_dma_done(dev, -EIO); + complete(&dev->dma_done); + goto out; + } + + /* received DMA interrupt out of nowhere? */ + WARN_ON_ONCE(dev->dma_stage == 0); + + if (dev->dma_stage == 0) + goto out; + + /* done device access */ + if (dev->dma_state == DMA_INTERNAL && + (dma_status & R852_DMA_IRQ_INTERNAL)) { + + dev->dma_state = DMA_MEMORY; + dev->dma_stage++; + } + + /* done memory DMA */ + if (dev->dma_state == DMA_MEMORY && + (dma_status & R852_DMA_IRQ_MEMORY)) { + dev->dma_state = DMA_INTERNAL; + dev->dma_stage++; + } + + /* Enable 2nd half of dma dance */ + if (dev->dma_stage == 2) + r852_dma_enable(dev); + + /* Operation done */ + if (dev->dma_stage == 3) { + r852_dma_done(dev, 0); + complete(&dev->dma_done); + } + goto out; + } + + /* Handle unknown interrupts */ + if (dma_status) + dbg("bad dma IRQ status = %x", dma_status); + + if (card_status & ~R852_CARD_STA_CD) + dbg("strange card status = %x", card_status); + +out: + spin_unlock_irqrestore(&dev->irqlock, flags); + return ret; +} + +static int r852_probe(struct pci_dev *pci_dev, const struct pci_device_id *id) +{ + int error; + struct nand_chip *chip; + struct r852_device *dev; + + /* pci initialization */ + error = pci_enable_device(pci_dev); + + if (error) + goto error1; + + pci_set_master(pci_dev); + + error = pci_set_dma_mask(pci_dev, DMA_BIT_MASK(32)); + if (error) + goto error2; + + error = pci_request_regions(pci_dev, DRV_NAME); + + if (error) + goto error3; + + error = -ENOMEM; + + /* init nand chip, but register it only on card insert */ + chip = kzalloc(sizeof(struct nand_chip), GFP_KERNEL); + + if (!chip) + goto error4; + + /* commands */ + chip->cmd_ctrl = r852_cmdctl; + chip->waitfunc = r852_wait; + chip->dev_ready = r852_ready; + + /* I/O */ + chip->read_byte = r852_read_byte; + chip->read_buf = r852_read_buf; + chip->write_buf = r852_write_buf; + + /* ecc */ + chip->ecc.mode = NAND_ECC_HW_SYNDROME; + chip->ecc.size = R852_DMA_LEN; + chip->ecc.bytes = SM_OOB_SIZE; + chip->ecc.strength = 2; + chip->ecc.hwctl = r852_ecc_hwctl; + chip->ecc.calculate = r852_ecc_calculate; + chip->ecc.correct = r852_ecc_correct; + + /* TODO: hack */ + chip->ecc.read_oob = r852_read_oob; + + /* init our device structure */ + dev = kzalloc(sizeof(struct r852_device), GFP_KERNEL); + + if (!dev) + goto error5; + + chip->priv = dev; + dev->chip = chip; + dev->pci_dev = pci_dev; + pci_set_drvdata(pci_dev, dev); + + dev->bounce_buffer = pci_alloc_consistent(pci_dev, R852_DMA_LEN, + &dev->phys_bounce_buffer); + + if (!dev->bounce_buffer) + goto error6; + + + error = -ENODEV; + dev->mmio = pci_ioremap_bar(pci_dev, 0); + + if (!dev->mmio) + goto error7; + + error = -ENOMEM; + dev->tmp_buffer = kzalloc(SM_SECTOR_SIZE, GFP_KERNEL); + + if (!dev->tmp_buffer) + goto error8; + + init_completion(&dev->dma_done); + + dev->card_workqueue = create_freezable_workqueue(DRV_NAME); + + if (!dev->card_workqueue) + goto error9; + + INIT_DELAYED_WORK(&dev->card_detect_work, r852_card_detect_work); + + /* shutdown everything - precation */ + r852_engine_disable(dev); + r852_disable_irqs(dev); + + r852_dma_test(dev); + + dev->irq = pci_dev->irq; + spin_lock_init(&dev->irqlock); + + dev->card_detected = 0; + r852_card_update_present(dev); + + /*register irq handler*/ + error = -ENODEV; + if (request_irq(pci_dev->irq, &r852_irq, IRQF_SHARED, + DRV_NAME, dev)) + goto error10; + + /* kick initial present test */ + queue_delayed_work(dev->card_workqueue, + &dev->card_detect_work, 0); + + + printk(KERN_NOTICE DRV_NAME ": driver loaded successfully\n"); + return 0; + +error10: + destroy_workqueue(dev->card_workqueue); +error9: + kfree(dev->tmp_buffer); +error8: + pci_iounmap(pci_dev, dev->mmio); +error7: + pci_free_consistent(pci_dev, R852_DMA_LEN, + dev->bounce_buffer, dev->phys_bounce_buffer); +error6: + kfree(dev); +error5: + kfree(chip); +error4: + pci_release_regions(pci_dev); +error3: +error2: + pci_disable_device(pci_dev); +error1: + return error; +} + +static void r852_remove(struct pci_dev *pci_dev) +{ + struct r852_device *dev = pci_get_drvdata(pci_dev); + + /* Stop detect workqueue - + we are going to unregister the device anyway*/ + cancel_delayed_work_sync(&dev->card_detect_work); + destroy_workqueue(dev->card_workqueue); + + /* Unregister the device, this might make more IO */ + r852_unregister_nand_device(dev); + + /* Stop interrupts */ + r852_disable_irqs(dev); + synchronize_irq(dev->irq); + free_irq(dev->irq, dev); + + /* Cleanup */ + kfree(dev->tmp_buffer); + pci_iounmap(pci_dev, dev->mmio); + pci_free_consistent(pci_dev, R852_DMA_LEN, + dev->bounce_buffer, dev->phys_bounce_buffer); + + kfree(dev->chip); + kfree(dev); + + /* Shutdown the PCI device */ + pci_release_regions(pci_dev); + pci_disable_device(pci_dev); +} + +static void r852_shutdown(struct pci_dev *pci_dev) +{ + struct r852_device *dev = pci_get_drvdata(pci_dev); + + cancel_delayed_work_sync(&dev->card_detect_work); + r852_disable_irqs(dev); + synchronize_irq(dev->irq); + pci_disable_device(pci_dev); +} + +#ifdef CONFIG_PM_SLEEP +static int r852_suspend(struct device *device) +{ + struct r852_device *dev = pci_get_drvdata(to_pci_dev(device)); + + if (dev->ctlreg & R852_CTL_CARDENABLE) + return -EBUSY; + + /* First make sure the detect work is gone */ + cancel_delayed_work_sync(&dev->card_detect_work); + + /* Turn off the interrupts and stop the device */ + r852_disable_irqs(dev); + r852_engine_disable(dev); + + /* If card was pulled off just during the suspend, which is very + unlikely, we will remove it on resume, it too late now + anyway... */ + dev->card_unstable = 0; + return 0; +} + +static int r852_resume(struct device *device) +{ + struct r852_device *dev = pci_get_drvdata(to_pci_dev(device)); + + r852_disable_irqs(dev); + r852_card_update_present(dev); + r852_engine_disable(dev); + + + /* If card status changed, just do the work */ + if (dev->card_detected != dev->card_registred) { + dbg("card was %s during low power state", + dev->card_detected ? "added" : "removed"); + + queue_delayed_work(dev->card_workqueue, + &dev->card_detect_work, msecs_to_jiffies(1000)); + return 0; + } + + /* Otherwise, initialize the card */ + if (dev->card_registred) { + r852_engine_enable(dev); + dev->chip->select_chip(dev->mtd, 0); + dev->chip->cmdfunc(dev->mtd, NAND_CMD_RESET, -1, -1); + dev->chip->select_chip(dev->mtd, -1); + } + + /* Program card detection IRQ */ + r852_update_card_detect(dev); + return 0; +} +#endif + +static const struct pci_device_id r852_pci_id_tbl[] = { + + { PCI_VDEVICE(RICOH, 0x0852), }, + { }, +}; + +MODULE_DEVICE_TABLE(pci, r852_pci_id_tbl); + +static SIMPLE_DEV_PM_OPS(r852_pm_ops, r852_suspend, r852_resume); + +static struct pci_driver r852_pci_driver = { + .name = DRV_NAME, + .id_table = r852_pci_id_tbl, + .probe = r852_probe, + .remove = r852_remove, + .shutdown = r852_shutdown, + .driver.pm = &r852_pm_ops, +}; + +module_pci_driver(r852_pci_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Maxim Levitsky <maximlevitsky@gmail.com>"); +MODULE_DESCRIPTION("Ricoh 85xx xD/smartmedia card reader driver"); diff --git a/drivers/mtd/nand/r852.h b/drivers/mtd/nand/r852.h new file mode 100644 index 00000000000..e6a21d9d22c --- /dev/null +++ b/drivers/mtd/nand/r852.h @@ -0,0 +1,161 @@ +/* + * Copyright © 2009 - Maxim Levitsky + * driver for Ricoh xD readers + * + * 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/pci.h> +#include <linux/completion.h> +#include <linux/workqueue.h> +#include <linux/mtd/nand.h> +#include <linux/spinlock.h> + + +/* nand interface + ecc + byte write/read does one cycle on nand data lines. + dword write/read does 4 cycles + if R852_CTL_ECC_ACCESS is set in R852_CTL, then dword read reads + results of ecc correction, if DMA read was done before. + If write was done two dword reads read generated ecc checksums +*/ +#define R852_DATALINE 0x00 + +/* control register */ +#define R852_CTL 0x04 +#define R852_CTL_COMMAND 0x01 /* send command (#CLE)*/ +#define R852_CTL_DATA 0x02 /* read/write data (#ALE)*/ +#define R852_CTL_ON 0x04 /* only seem to controls the hd led, */ + /* but has to be set on start...*/ +#define R852_CTL_RESET 0x08 /* unknown, set only on start once*/ +#define R852_CTL_CARDENABLE 0x10 /* probably (#CE) - always set*/ +#define R852_CTL_ECC_ENABLE 0x20 /* enable ecc engine */ +#define R852_CTL_ECC_ACCESS 0x40 /* read/write ecc via reg #0*/ +#define R852_CTL_WRITE 0x80 /* set when performing writes (#WP) */ + +/* card detection status */ +#define R852_CARD_STA 0x05 + +#define R852_CARD_STA_CD 0x01 /* state of #CD line, same as 0x04 */ +#define R852_CARD_STA_RO 0x02 /* card is readonly */ +#define R852_CARD_STA_PRESENT 0x04 /* card is present (#CD) */ +#define R852_CARD_STA_ABSENT 0x08 /* card is absent */ +#define R852_CARD_STA_BUSY 0x80 /* card is busy - (#R/B) */ + +/* card detection irq status & enable*/ +#define R852_CARD_IRQ_STA 0x06 /* IRQ status */ +#define R852_CARD_IRQ_ENABLE 0x07 /* IRQ enable */ + +#define R852_CARD_IRQ_CD 0x01 /* fire when #CD lights, same as 0x04*/ +#define R852_CARD_IRQ_REMOVE 0x04 /* detect card removal */ +#define R852_CARD_IRQ_INSERT 0x08 /* detect card insert */ +#define R852_CARD_IRQ_UNK1 0x10 /* unknown */ +#define R852_CARD_IRQ_GENABLE 0x80 /* general enable */ +#define R852_CARD_IRQ_MASK 0x1D + + + +/* hardware enable */ +#define R852_HW 0x08 +#define R852_HW_ENABLED 0x01 /* hw enabled */ +#define R852_HW_UNKNOWN 0x80 + + +/* dma capabilities */ +#define R852_DMA_CAP 0x09 +#define R852_SMBIT 0x20 /* if set with bit #6 or bit #7, then */ + /* hw is smartmedia */ +#define R852_DMA1 0x40 /* if set w/bit #7, dma is supported */ +#define R852_DMA2 0x80 /* if set w/bit #6, dma is supported */ + + +/* physical DMA address - 32 bit value*/ +#define R852_DMA_ADDR 0x0C + + +/* dma settings */ +#define R852_DMA_SETTINGS 0x10 +#define R852_DMA_MEMORY 0x01 /* (memory <-> internal hw buffer) */ +#define R852_DMA_READ 0x02 /* 0 = write, 1 = read */ +#define R852_DMA_INTERNAL 0x04 /* (internal hw buffer <-> card) */ + +/* dma IRQ status */ +#define R852_DMA_IRQ_STA 0x14 + +/* dma IRQ enable */ +#define R852_DMA_IRQ_ENABLE 0x18 + +#define R852_DMA_IRQ_MEMORY 0x01 /* (memory <-> internal hw buffer) */ +#define R852_DMA_IRQ_ERROR 0x02 /* error did happen */ +#define R852_DMA_IRQ_INTERNAL 0x04 /* (internal hw buffer <-> card) */ +#define R852_DMA_IRQ_MASK 0x07 /* mask of all IRQ bits */ + + +/* ECC syndrome format - read from reg #0 will return two copies of these for + each half of the page. + first byte is error byte location, and second, bit location + flags */ +#define R852_ECC_ERR_BIT_MSK 0x07 /* error bit location */ +#define R852_ECC_CORRECT 0x10 /* no errors - (guessed) */ +#define R852_ECC_CORRECTABLE 0x20 /* correctable error exist */ +#define R852_ECC_FAIL 0x40 /* non correctable error detected */ + +#define R852_DMA_LEN 512 + +#define DMA_INTERNAL 0 +#define DMA_MEMORY 1 + +struct r852_device { + void __iomem *mmio; /* mmio */ + struct mtd_info *mtd; /* mtd backpointer */ + struct nand_chip *chip; /* nand chip backpointer */ + struct pci_dev *pci_dev; /* pci backpointer */ + + /* dma area */ + dma_addr_t phys_dma_addr; /* bus address of buffer*/ + struct completion dma_done; /* data transfer done */ + + dma_addr_t phys_bounce_buffer; /* bus address of bounce buffer */ + uint8_t *bounce_buffer; /* virtual address of bounce buffer */ + + int dma_dir; /* 1 = read, 0 = write */ + int dma_stage; /* 0 - idle, 1 - first step, + 2 - second step */ + + int dma_state; /* 0 = internal, 1 = memory */ + int dma_error; /* dma errors */ + int dma_usable; /* is it possible to use dma */ + + /* card status area */ + struct delayed_work card_detect_work; + struct workqueue_struct *card_workqueue; + int card_registred; /* card registered with mtd */ + int card_detected; /* card detected in slot */ + int card_unstable; /* whenever the card is inserted, + is not known yet */ + int readonly; /* card is readonly */ + int sm; /* Is card smartmedia */ + + /* interrupt handling */ + spinlock_t irqlock; /* IRQ protecting lock */ + int irq; /* irq num */ + /* misc */ + void *tmp_buffer; /* temporary buffer */ + uint8_t ctlreg; /* cached contents of control reg */ +}; + +#define DRV_NAME "r852" + + +#define dbg(format, ...) \ + if (debug) \ + printk(KERN_DEBUG DRV_NAME ": " format "\n", ## __VA_ARGS__) + +#define dbg_verbose(format, ...) \ + if (debug > 1) \ + printk(KERN_DEBUG DRV_NAME ": " format "\n", ## __VA_ARGS__) + + +#define message(format, ...) \ + printk(KERN_INFO DRV_NAME ": " format "\n", ## __VA_ARGS__) diff --git a/drivers/mtd/nand/rtc_from4.c b/drivers/mtd/nand/rtc_from4.c deleted file mode 100644 index f8c49645324..00000000000 --- a/drivers/mtd/nand/rtc_from4.c +++ /dev/null @@ -1,658 +0,0 @@ -/* - * drivers/mtd/nand/rtc_from4.c - * - * Copyright (C) 2004 Red Hat, Inc. - * - * Derived from drivers/mtd/nand/spia.c - * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com) - * - * $Id: rtc_from4.c,v 1.10 2005/11/07 11:14:31 gleixner Exp $ - * - * 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. - * - * Overview: - * This is a device driver for the AG-AND flash device found on the - * Renesas Technology Corp. Flash ROM 4-slot interface board (FROM_BOARD4), - * which utilizes the Renesas HN29V1G91T-30 part. - * This chip is a 1 GBibit (128MiB x 8 bits) AG-AND flash device. - */ - -#include <linux/delay.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <linux/slab.h> -#include <linux/rslib.h> -#include <linux/module.h> -#include <linux/mtd/compatmac.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/nand.h> -#include <linux/mtd/partitions.h> -#include <asm/io.h> - -/* - * MTD structure for Renesas board - */ -static struct mtd_info *rtc_from4_mtd = NULL; - -#define RTC_FROM4_MAX_CHIPS 2 - -/* HS77x9 processor register defines */ -#define SH77X9_BCR1 ((volatile unsigned short *)(0xFFFFFF60)) -#define SH77X9_BCR2 ((volatile unsigned short *)(0xFFFFFF62)) -#define SH77X9_WCR1 ((volatile unsigned short *)(0xFFFFFF64)) -#define SH77X9_WCR2 ((volatile unsigned short *)(0xFFFFFF66)) -#define SH77X9_MCR ((volatile unsigned short *)(0xFFFFFF68)) -#define SH77X9_PCR ((volatile unsigned short *)(0xFFFFFF6C)) -#define SH77X9_FRQCR ((volatile unsigned short *)(0xFFFFFF80)) - -/* - * Values specific to the Renesas Technology Corp. FROM_BOARD4 (used with HS77x9 processor) - */ -/* Address where flash is mapped */ -#define RTC_FROM4_FIO_BASE 0x14000000 - -/* CLE and ALE are tied to address lines 5 & 4, respectively */ -#define RTC_FROM4_CLE (1 << 5) -#define RTC_FROM4_ALE (1 << 4) - -/* address lines A24-A22 used for chip selection */ -#define RTC_FROM4_NAND_ADDR_SLOT3 (0x00800000) -#define RTC_FROM4_NAND_ADDR_SLOT4 (0x00C00000) -#define RTC_FROM4_NAND_ADDR_FPGA (0x01000000) -/* mask address lines A24-A22 used for chip selection */ -#define RTC_FROM4_NAND_ADDR_MASK (RTC_FROM4_NAND_ADDR_SLOT3 | RTC_FROM4_NAND_ADDR_SLOT4 | RTC_FROM4_NAND_ADDR_FPGA) - -/* FPGA status register for checking device ready (bit zero) */ -#define RTC_FROM4_FPGA_SR (RTC_FROM4_NAND_ADDR_FPGA | 0x00000002) -#define RTC_FROM4_DEVICE_READY 0x0001 - -/* FPGA Reed-Solomon ECC Control register */ - -#define RTC_FROM4_RS_ECC_CTL (RTC_FROM4_NAND_ADDR_FPGA | 0x00000050) -#define RTC_FROM4_RS_ECC_CTL_CLR (1 << 7) -#define RTC_FROM4_RS_ECC_CTL_GEN (1 << 6) -#define RTC_FROM4_RS_ECC_CTL_FD_E (1 << 5) - -/* FPGA Reed-Solomon ECC code base */ -#define RTC_FROM4_RS_ECC (RTC_FROM4_NAND_ADDR_FPGA | 0x00000060) -#define RTC_FROM4_RS_ECCN (RTC_FROM4_NAND_ADDR_FPGA | 0x00000080) - -/* FPGA Reed-Solomon ECC check register */ -#define RTC_FROM4_RS_ECC_CHK (RTC_FROM4_NAND_ADDR_FPGA | 0x00000070) -#define RTC_FROM4_RS_ECC_CHK_ERROR (1 << 7) - -#define ERR_STAT_ECC_AVAILABLE 0x20 - -/* Undefine for software ECC */ -#define RTC_FROM4_HWECC 1 - -/* Define as 1 for no virtual erase blocks (in JFFS2) */ -#define RTC_FROM4_NO_VIRTBLOCKS 0 - -/* - * Module stuff - */ -static void __iomem *rtc_from4_fio_base = (void *)P2SEGADDR(RTC_FROM4_FIO_BASE); - -static const struct mtd_partition partition_info[] = { - { - .name = "Renesas flash partition 1", - .offset = 0, - .size = MTDPART_SIZ_FULL}, -}; - -#define NUM_PARTITIONS 1 - -/* - * hardware specific flash bbt decriptors - * Note: this is to allow debugging by disabling - * NAND_BBT_CREATE and/or NAND_BBT_WRITE - * - */ -static uint8_t bbt_pattern[] = { 'B', 'b', 't', '0' }; -static uint8_t mirror_pattern[] = { '1', 't', 'b', 'B' }; - -static struct nand_bbt_descr rtc_from4_bbt_main_descr = { - .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE - | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, - .offs = 40, - .len = 4, - .veroffs = 44, - .maxblocks = 4, - .pattern = bbt_pattern -}; - -static struct nand_bbt_descr rtc_from4_bbt_mirror_descr = { - .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE - | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, - .offs = 40, - .len = 4, - .veroffs = 44, - .maxblocks = 4, - .pattern = mirror_pattern -}; - -#ifdef RTC_FROM4_HWECC - -/* the Reed Solomon control structure */ -static struct rs_control *rs_decoder; - -/* - * hardware specific Out Of Band information - */ -static struct nand_ecclayout rtc_from4_nand_oobinfo = { - .eccbytes = 32, - .eccpos = { - 0, 1, 2, 3, 4, 5, 6, 7, - 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, 26, 27, 28, 29, 30, 31}, - .oobfree = {{32, 32}} -}; - -/* Aargh. I missed the reversed bit order, when I - * was talking to Renesas about the FPGA. - * - * The table is used for bit reordering and inversion - * of the ecc byte which we get from the FPGA - */ -static uint8_t revbits[256] = { - 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, - 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, - 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, - 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, - 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, - 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, - 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, - 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, - 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, - 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, - 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, - 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, - 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, - 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, - 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, - 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, - 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, - 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, - 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, - 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, - 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, - 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, - 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, - 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, - 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, - 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, - 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, - 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, - 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, - 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, - 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, - 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff, -}; - -#endif - -/* - * rtc_from4_hwcontrol - hardware specific access to control-lines - * @mtd: MTD device structure - * @cmd: hardware control command - * - * Address lines (A5 and A4) are used to control Command and Address Latch - * Enable on this board, so set the read/write address appropriately. - * - * Chip Enable is also controlled by the Chip Select (CS5) and - * Address lines (A24-A22), so no action is required here. - * - */ -static void rtc_from4_hwcontrol(struct mtd_info *mtd, int cmd, - unsigned int ctrl) -{ - struct nand_chip *chip = (mtd->priv); - - if (cmd == NAND_CMD_NONE) - return; - - if (ctrl & NAND_CLE) - writeb(cmd, chip->IO_ADDR_W | RTC_FROM4_CLE); - else - writeb(cmd, chip->IO_ADDR_W | RTC_FROM4_ALE); -} - -/* - * rtc_from4_nand_select_chip - hardware specific chip select - * @mtd: MTD device structure - * @chip: Chip to select (0 == slot 3, 1 == slot 4) - * - * The chip select is based on address lines A24-A22. - * This driver uses flash slots 3 and 4 (A23-A22). - * - */ -static void rtc_from4_nand_select_chip(struct mtd_info *mtd, int chip) -{ - struct nand_chip *this = mtd->priv; - - this->IO_ADDR_R = (void __iomem *)((unsigned long)this->IO_ADDR_R & ~RTC_FROM4_NAND_ADDR_MASK); - this->IO_ADDR_W = (void __iomem *)((unsigned long)this->IO_ADDR_W & ~RTC_FROM4_NAND_ADDR_MASK); - - switch (chip) { - - case 0: /* select slot 3 chip */ - this->IO_ADDR_R = (void __iomem *)((unsigned long)this->IO_ADDR_R | RTC_FROM4_NAND_ADDR_SLOT3); - this->IO_ADDR_W = (void __iomem *)((unsigned long)this->IO_ADDR_W | RTC_FROM4_NAND_ADDR_SLOT3); - break; - case 1: /* select slot 4 chip */ - this->IO_ADDR_R = (void __iomem *)((unsigned long)this->IO_ADDR_R | RTC_FROM4_NAND_ADDR_SLOT4); - this->IO_ADDR_W = (void __iomem *)((unsigned long)this->IO_ADDR_W | RTC_FROM4_NAND_ADDR_SLOT4); - break; - - } -} - -/* - * rtc_from4_nand_device_ready - hardware specific ready/busy check - * @mtd: MTD device structure - * - * This board provides the Ready/Busy state in the status register - * of the FPGA. Bit zero indicates the RDY(1)/BSY(0) signal. - * - */ -static int rtc_from4_nand_device_ready(struct mtd_info *mtd) -{ - unsigned short status; - - status = *((volatile unsigned short *)(rtc_from4_fio_base + RTC_FROM4_FPGA_SR)); - - return (status & RTC_FROM4_DEVICE_READY); - -} - -/* - * deplete - code to perform device recovery in case there was a power loss - * @mtd: MTD device structure - * @chip: Chip to select (0 == slot 3, 1 == slot 4) - * - * If there was a sudden loss of power during an erase operation, a - * "device recovery" operation must be performed when power is restored - * to ensure correct operation. This routine performs the required steps - * for the requested chip. - * - * See page 86 of the data sheet for details. - * - */ -static void deplete(struct mtd_info *mtd, int chip) -{ - struct nand_chip *this = mtd->priv; - - /* wait until device is ready */ - while (!this->dev_ready(mtd)) ; - - this->select_chip(mtd, chip); - - /* Send the commands for device recovery, phase 1 */ - this->cmdfunc(mtd, NAND_CMD_DEPLETE1, 0x0000, 0x0000); - this->cmdfunc(mtd, NAND_CMD_DEPLETE2, -1, -1); - - /* Send the commands for device recovery, phase 2 */ - this->cmdfunc(mtd, NAND_CMD_DEPLETE1, 0x0000, 0x0004); - this->cmdfunc(mtd, NAND_CMD_DEPLETE2, -1, -1); - -} - -#ifdef RTC_FROM4_HWECC -/* - * rtc_from4_enable_hwecc - hardware specific hardware ECC enable function - * @mtd: MTD device structure - * @mode: I/O mode; read or write - * - * enable hardware ECC for data read or write - * - */ -static void rtc_from4_enable_hwecc(struct mtd_info *mtd, int mode) -{ - volatile unsigned short *rs_ecc_ctl = (volatile unsigned short *)(rtc_from4_fio_base + RTC_FROM4_RS_ECC_CTL); - unsigned short status; - - switch (mode) { - case NAND_ECC_READ: - status = RTC_FROM4_RS_ECC_CTL_CLR | RTC_FROM4_RS_ECC_CTL_FD_E; - - *rs_ecc_ctl = status; - break; - - case NAND_ECC_READSYN: - status = 0x00; - - *rs_ecc_ctl = status; - break; - - case NAND_ECC_WRITE: - status = RTC_FROM4_RS_ECC_CTL_CLR | RTC_FROM4_RS_ECC_CTL_GEN | RTC_FROM4_RS_ECC_CTL_FD_E; - - *rs_ecc_ctl = status; - break; - - default: - BUG(); - break; - } - -} - -/* - * rtc_from4_calculate_ecc - hardware specific code to read ECC code - * @mtd: MTD device structure - * @dat: buffer containing the data to generate ECC codes - * @ecc_code ECC codes calculated - * - * The ECC code is calculated by the FPGA. All we have to do is read the values - * from the FPGA registers. - * - * Note: We read from the inverted registers, since data is inverted before - * the code is calculated. So all 0xff data (blank page) results in all 0xff rs code - * - */ -static void rtc_from4_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code) -{ - volatile unsigned short *rs_eccn = (volatile unsigned short *)(rtc_from4_fio_base + RTC_FROM4_RS_ECCN); - unsigned short value; - int i; - - for (i = 0; i < 8; i++) { - value = *rs_eccn; - ecc_code[i] = (unsigned char)value; - rs_eccn++; - } - ecc_code[7] |= 0x0f; /* set the last four bits (not used) */ -} - -/* - * rtc_from4_correct_data - hardware specific code to correct data using ECC code - * @mtd: MTD device structure - * @buf: buffer containing the data to generate ECC codes - * @ecc1 ECC codes read - * @ecc2 ECC codes calculated - * - * The FPGA tells us fast, if there's an error or not. If no, we go back happy - * else we read the ecc results from the fpga and call the rs library to decode - * and hopefully correct the error. - * - */ -static int rtc_from4_correct_data(struct mtd_info *mtd, const u_char *buf, u_char *ecc1, u_char *ecc2) -{ - int i, j, res; - unsigned short status; - uint16_t par[6], syn[6]; - uint8_t ecc[8]; - volatile unsigned short *rs_ecc; - - status = *((volatile unsigned short *)(rtc_from4_fio_base + RTC_FROM4_RS_ECC_CHK)); - - if (!(status & RTC_FROM4_RS_ECC_CHK_ERROR)) { - return 0; - } - - /* Read the syndrom pattern from the FPGA and correct the bitorder */ - rs_ecc = (volatile unsigned short *)(rtc_from4_fio_base + RTC_FROM4_RS_ECC); - for (i = 0; i < 8; i++) { - ecc[i] = revbits[(*rs_ecc) & 0xFF]; - rs_ecc++; - } - - /* convert into 6 10bit syndrome fields */ - par[5] = rs_decoder->index_of[(((uint16_t) ecc[0] >> 0) & 0x0ff) | (((uint16_t) ecc[1] << 8) & 0x300)]; - par[4] = rs_decoder->index_of[(((uint16_t) ecc[1] >> 2) & 0x03f) | (((uint16_t) ecc[2] << 6) & 0x3c0)]; - par[3] = rs_decoder->index_of[(((uint16_t) ecc[2] >> 4) & 0x00f) | (((uint16_t) ecc[3] << 4) & 0x3f0)]; - par[2] = rs_decoder->index_of[(((uint16_t) ecc[3] >> 6) & 0x003) | (((uint16_t) ecc[4] << 2) & 0x3fc)]; - par[1] = rs_decoder->index_of[(((uint16_t) ecc[5] >> 0) & 0x0ff) | (((uint16_t) ecc[6] << 8) & 0x300)]; - par[0] = (((uint16_t) ecc[6] >> 2) & 0x03f) | (((uint16_t) ecc[7] << 6) & 0x3c0); - - /* Convert to computable syndrome */ - for (i = 0; i < 6; i++) { - syn[i] = par[0]; - for (j = 1; j < 6; j++) - if (par[j] != rs_decoder->nn) - syn[i] ^= rs_decoder->alpha_to[rs_modnn(rs_decoder, par[j] + i * j)]; - - /* Convert to index form */ - syn[i] = rs_decoder->index_of[syn[i]]; - } - - /* Let the library code do its magic. */ - res = decode_rs8(rs_decoder, (uint8_t *) buf, par, 512, syn, 0, NULL, 0xff, NULL); - if (res > 0) { - DEBUG(MTD_DEBUG_LEVEL0, "rtc_from4_correct_data: " "ECC corrected %d errors on read\n", res); - } - return res; -} - -/** - * rtc_from4_errstat - perform additional error status checks - * @mtd: MTD device structure - * @this: NAND chip structure - * @state: state or the operation - * @status: status code returned from read status - * @page: startpage inside the chip, must be called with (page & this->pagemask) - * - * Perform additional error status checks on erase and write failures - * to determine if errors are correctable. For this device, correctable - * 1-bit errors on erase and write are considered acceptable. - * - * note: see pages 34..37 of data sheet for details. - * - */ -static int rtc_from4_errstat(struct mtd_info *mtd, struct nand_chip *this, - int state, int status, int page) -{ - int er_stat = 0; - int rtn, retlen; - size_t len; - uint8_t *buf; - int i; - - this->cmdfunc(mtd, NAND_CMD_STATUS_CLEAR, -1, -1); - - if (state == FL_ERASING) { - - for (i = 0; i < 4; i++) { - if (!(status & 1 << (i + 1))) - continue; - this->cmdfunc(mtd, (NAND_CMD_STATUS_ERROR + i + 1), - -1, -1); - rtn = this->read_byte(mtd); - this->cmdfunc(mtd, NAND_CMD_STATUS_RESET, -1, -1); - - /* err_ecc_not_avail */ - if (!(rtn & ERR_STAT_ECC_AVAILABLE)) - er_stat |= 1 << (i + 1); - } - - } else if (state == FL_WRITING) { - - unsigned long corrected = mtd->ecc_stats.corrected; - - /* single bank write logic */ - this->cmdfunc(mtd, NAND_CMD_STATUS_ERROR, -1, -1); - rtn = this->read_byte(mtd); - this->cmdfunc(mtd, NAND_CMD_STATUS_RESET, -1, -1); - - if (!(rtn & ERR_STAT_ECC_AVAILABLE)) { - /* err_ecc_not_avail */ - er_stat |= 1 << 1; - goto out; - } - - len = mtd->writesize; - buf = kmalloc(len, GFP_KERNEL); - if (!buf) { - printk(KERN_ERR "rtc_from4_errstat: Out of memory!\n"); - er_stat = 1; - goto out; - } - - /* recovery read */ - rtn = nand_do_read(mtd, page, len, &retlen, buf); - - /* if read failed or > 1-bit error corrected */ - if (rtn || (mtd->ecc_stats.corrected - corrected) > 1) { - er_stat |= 1 << 1; - kfree(buf); - } - - rtn = status; - if (er_stat == 0) { /* if ECC is available */ - rtn = (status & ~NAND_STATUS_FAIL); /* clear the error bit */ - } - - return rtn; -} -#endif - -/* - * Main initialization routine - */ -static int __init rtc_from4_init(void) -{ - struct nand_chip *this; - unsigned short bcr1, bcr2, wcr2; - int i; - - /* Allocate memory for MTD device structure and private data */ - rtc_from4_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL); - if (!rtc_from4_mtd) { - printk("Unable to allocate Renesas NAND MTD device structure.\n"); - return -ENOMEM; - } - - /* Get pointer to private data */ - this = (struct nand_chip *)(&rtc_from4_mtd[1]); - - /* Initialize structures */ - memset(rtc_from4_mtd, 0, sizeof(struct mtd_info)); - memset(this, 0, sizeof(struct nand_chip)); - - /* Link the private data with the MTD structure */ - rtc_from4_mtd->priv = this; - rtc_from4_mtd->owner = THIS_MODULE; - - /* set area 5 as PCMCIA mode to clear the spec of tDH(Data hold time;9ns min) */ - bcr1 = *SH77X9_BCR1 & ~0x0002; - bcr1 |= 0x0002; - *SH77X9_BCR1 = bcr1; - - /* set */ - bcr2 = *SH77X9_BCR2 & ~0x0c00; - bcr2 |= 0x0800; - *SH77X9_BCR2 = bcr2; - - /* set area 5 wait states */ - wcr2 = *SH77X9_WCR2 & ~0x1c00; - wcr2 |= 0x1c00; - *SH77X9_WCR2 = wcr2; - - /* Set address of NAND IO lines */ - this->IO_ADDR_R = rtc_from4_fio_base; - this->IO_ADDR_W = rtc_from4_fio_base; - /* Set address of hardware control function */ - this->cmd_ctrl = rtc_from4_hwcontrol; - /* Set address of chip select function */ - this->select_chip = rtc_from4_nand_select_chip; - /* command delay time (in us) */ - this->chip_delay = 100; - /* return the status of the Ready/Busy line */ - this->dev_ready = rtc_from4_nand_device_ready; - -#ifdef RTC_FROM4_HWECC - printk(KERN_INFO "rtc_from4_init: using hardware ECC detection.\n"); - - this->ecc.mode = NAND_ECC_HW_SYNDROME; - this->ecc.size = 512; - this->ecc.bytes = 8; - /* return the status of extra status and ECC checks */ - this->errstat = rtc_from4_errstat; - /* set the nand_oobinfo to support FPGA H/W error detection */ - this->ecc.layout = &rtc_from4_nand_oobinfo; - this->ecc.hwctl = rtc_from4_enable_hwecc; - this->ecc.calculate = rtc_from4_calculate_ecc; - this->ecc.correct = rtc_from4_correct_data; -#else - printk(KERN_INFO "rtc_from4_init: using software ECC detection.\n"); - - this->ecc.mode = NAND_ECC_SOFT; -#endif - - /* set the bad block tables to support debugging */ - this->bbt_td = &rtc_from4_bbt_main_descr; - this->bbt_md = &rtc_from4_bbt_mirror_descr; - - /* Scan to find existence of the device */ - if (nand_scan(rtc_from4_mtd, RTC_FROM4_MAX_CHIPS)) { - kfree(rtc_from4_mtd); - return -ENXIO; - } - - /* Perform 'device recovery' for each chip in case there was a power loss. */ - for (i = 0; i < this->numchips; i++) { - deplete(rtc_from4_mtd, i); - } - -#if RTC_FROM4_NO_VIRTBLOCKS - /* use a smaller erase block to minimize wasted space when a block is bad */ - /* note: this uses eight times as much RAM as using the default and makes */ - /* mounts take four times as long. */ - rtc_from4_mtd->flags |= MTD_NO_VIRTBLOCKS; -#endif - - /* Register the partitions */ - add_mtd_partitions(rtc_from4_mtd, partition_info, NUM_PARTITIONS); - -#ifdef RTC_FROM4_HWECC - /* We could create the decoder on demand, if memory is a concern. - * This way we have it handy, if an error happens - * - * Symbolsize is 10 (bits) - * Primitve polynomial is x^10+x^3+1 - * first consecutive root is 0 - * primitve element to generate roots = 1 - * generator polinomial degree = 6 - */ - rs_decoder = init_rs(10, 0x409, 0, 1, 6); - if (!rs_decoder) { - printk(KERN_ERR "Could not create a RS decoder\n"); - nand_release(rtc_from4_mtd); - kfree(rtc_from4_mtd); - return -ENOMEM; - } -#endif - /* Return happy */ - return 0; -} - -module_init(rtc_from4_init); - -/* - * Clean up routine - */ -static void __exit rtc_from4_cleanup(void) -{ - /* Release resource, unregister partitions */ - nand_release(rtc_from4_mtd); - - /* Free the MTD device structure */ - kfree(rtc_from4_mtd); - -#ifdef RTC_FROM4_HWECC - /* Free the reed solomon resources */ - if (rs_decoder) { - free_rs(rs_decoder); - } -#endif -} - -module_exit(rtc_from4_cleanup); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("d.marlin <dmarlin@redhat.com"); -MODULE_DESCRIPTION("Board-specific glue layer for AG-AND flash on Renesas FROM_BOARD4"); diff --git a/drivers/mtd/nand/s3c2410.c b/drivers/mtd/nand/s3c2410.c index ff5cef24d5b..79acbb8691b 100644 --- a/drivers/mtd/nand/s3c2410.c +++ b/drivers/mtd/nand/s3c2410.c @@ -1,26 +1,10 @@ /* linux/drivers/mtd/nand/s3c2410.c * - * Copyright (c) 2004,2005 Simtec Electronics - * http://www.simtec.co.uk/products/SWLINUX/ + * Copyright © 2004-2008 Simtec Electronics + * http://armlinux.simtec.co.uk/ * Ben Dooks <ben@simtec.co.uk> * - * Samsung S3C2410/S3C240 NAND driver - * - * Changelog: - * 21-Sep-2004 BJD Initial version - * 23-Sep-2004 BJD Mulitple device support - * 28-Sep-2004 BJD Fixed ECC placement for Hardware mode - * 12-Oct-2004 BJD Fixed errors in use of platform data - * 18-Feb-2005 BJD Fix sparse errors - * 14-Mar-2005 BJD Applied tglx's code reduction patch - * 02-May-2005 BJD Fixed s3c2440 support - * 02-May-2005 BJD Reduced hwcontrol decode - * 20-Jun-2005 BJD Updated s3c2440 support, fixed timing bug - * 08-Jul-2005 BJD Fix OOPS when no platform data supplied - * 20-Oct-2005 BJD Fix timing calculation bug - * 14-Jan-2006 BJD Allow clock to be stopped when idle - * - * $Id: s3c2410.c,v 1.23 2006/04/01 18:06:29 bjd Exp $ + * Samsung S3C2410/S3C2440/S3C2412 NAND driver * * 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 @@ -37,44 +21,66 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#define pr_fmt(fmt) "nand-s3c2410: " fmt + #ifdef CONFIG_MTD_NAND_S3C2410_DEBUG #define DEBUG #endif #include <linux/module.h> #include <linux/types.h> -#include <linux/init.h> #include <linux/kernel.h> #include <linux/string.h> +#include <linux/io.h> #include <linux/ioport.h> #include <linux/platform_device.h> #include <linux/delay.h> #include <linux/err.h> #include <linux/slab.h> #include <linux/clk.h> +#include <linux/cpufreq.h> #include <linux/mtd/mtd.h> #include <linux/mtd/nand.h> #include <linux/mtd/nand_ecc.h> #include <linux/mtd/partitions.h> -#include <asm/io.h> - -#include <asm/arch/regs-nand.h> -#include <asm/arch/nand.h> - -#ifdef CONFIG_MTD_NAND_S3C2410_HWECC -static int hardware_ecc = 1; -#else -static int hardware_ecc = 0; -#endif - -#ifdef CONFIG_MTD_NAND_S3C2410_CLKSTOP -static int clock_stop = 1; -#else -static const int clock_stop = 0; -#endif - +#include <linux/platform_data/mtd-nand-s3c2410.h> + +#define S3C2410_NFREG(x) (x) + +#define S3C2410_NFCONF S3C2410_NFREG(0x00) +#define S3C2410_NFCMD S3C2410_NFREG(0x04) +#define S3C2410_NFADDR S3C2410_NFREG(0x08) +#define S3C2410_NFDATA S3C2410_NFREG(0x0C) +#define S3C2410_NFSTAT S3C2410_NFREG(0x10) +#define S3C2410_NFECC S3C2410_NFREG(0x14) +#define S3C2440_NFCONT S3C2410_NFREG(0x04) +#define S3C2440_NFCMD S3C2410_NFREG(0x08) +#define S3C2440_NFADDR S3C2410_NFREG(0x0C) +#define S3C2440_NFDATA S3C2410_NFREG(0x10) +#define S3C2440_NFSTAT S3C2410_NFREG(0x20) +#define S3C2440_NFMECC0 S3C2410_NFREG(0x2C) +#define S3C2412_NFSTAT S3C2410_NFREG(0x28) +#define S3C2412_NFMECC0 S3C2410_NFREG(0x34) +#define S3C2410_NFCONF_EN (1<<15) +#define S3C2410_NFCONF_INITECC (1<<12) +#define S3C2410_NFCONF_nFCE (1<<11) +#define S3C2410_NFCONF_TACLS(x) ((x)<<8) +#define S3C2410_NFCONF_TWRPH0(x) ((x)<<4) +#define S3C2410_NFCONF_TWRPH1(x) ((x)<<0) +#define S3C2410_NFSTAT_BUSY (1<<0) +#define S3C2440_NFCONF_TACLS(x) ((x)<<12) +#define S3C2440_NFCONF_TWRPH0(x) ((x)<<8) +#define S3C2440_NFCONF_TWRPH1(x) ((x)<<4) +#define S3C2440_NFCONT_INITECC (1<<4) +#define S3C2440_NFCONT_nFCE (1<<1) +#define S3C2440_NFCONT_ENABLE (1<<0) +#define S3C2440_NFSTAT_READY (1<<0) +#define S3C2412_NFCONF_NANDBOOT (1<<31) +#define S3C2412_NFCONT_INIT_MAIN_ECC (1<<5) +#define S3C2412_NFCONT_nFCE0 (1<<1) +#define S3C2412_NFSTAT_READY (1<<0) /* new oob placement block for use with hardware ecc generation */ @@ -89,6 +95,14 @@ static struct nand_ecclayout nand_hw_eccoob = { struct s3c2410_nand_info; +/** + * struct s3c2410_nand_mtd - driver MTD structure + * @mtd: The MTD instance to pass to the MTD layer. + * @chip: The NAND chip information. + * @set: The platform information supplied for this set of NAND chips. + * @info: Link back to the hardware information. + * @scan_res: The result from calling nand_scan_ident(). +*/ struct s3c2410_nand_mtd { struct mtd_info mtd; struct nand_chip chip; @@ -103,8 +117,29 @@ enum s3c_cpu_type { TYPE_S3C2440, }; +enum s3c_nand_clk_state { + CLOCK_DISABLE = 0, + CLOCK_ENABLE, + CLOCK_SUSPEND, +}; + /* overview of the s3c2410 nand state */ +/** + * struct s3c2410_nand_info - NAND controller state. + * @mtds: An array of MTD instances on this controoler. + * @platform: The platform data for this board. + * @device: The platform device we bound to. + * @clk: The clock resource for this controller. + * @regs: The area mapped for the hardware registers. + * @sel_reg: Pointer to the register controlling the NAND selection. + * @sel_bit: The bit in @sel_reg to select the NAND chip. + * @mtd_count: The number of MTDs created from this controller. + * @save_sel: The contents of @sel_reg to be saved over suspend. + * @clk_rate: The clock rate from @clk. + * @clk_state: The current clock state. + * @cpu_type: The exact type of this controller. + */ struct s3c2410_nand_info { /* mtd info */ struct nand_hw_control controller; @@ -113,14 +148,20 @@ struct s3c2410_nand_info { /* device info */ struct device *device; - struct resource *area; struct clk *clk; void __iomem *regs; void __iomem *sel_reg; int sel_bit; int mtd_count; + unsigned long save_sel; + unsigned long clk_rate; + enum s3c_nand_clk_state clk_state; enum s3c_cpu_type cpu_type; + +#ifdef CONFIG_CPU_FREQ + struct notifier_block freq_transition; +#endif }; /* conversion functions */ @@ -142,29 +183,63 @@ static struct s3c2410_nand_info *to_nand_info(struct platform_device *dev) static struct s3c2410_platform_nand *to_nand_plat(struct platform_device *dev) { - return dev->dev.platform_data; + return dev_get_platdata(&dev->dev); +} + +static inline int allow_clk_suspend(struct s3c2410_nand_info *info) +{ +#ifdef CONFIG_MTD_NAND_S3C2410_CLKSTOP + return 1; +#else + return 0; +#endif } -static inline int allow_clk_stop(struct s3c2410_nand_info *info) +/** + * s3c2410_nand_clk_set_state - Enable, disable or suspend NAND clock. + * @info: The controller instance. + * @new_state: State to which clock should be set. + */ +static void s3c2410_nand_clk_set_state(struct s3c2410_nand_info *info, + enum s3c_nand_clk_state new_state) { - return clock_stop; + if (!allow_clk_suspend(info) && new_state == CLOCK_SUSPEND) + return; + + if (info->clk_state == CLOCK_ENABLE) { + if (new_state != CLOCK_ENABLE) + clk_disable(info->clk); + } else { + if (new_state == CLOCK_ENABLE) + clk_enable(info->clk); + } + + info->clk_state = new_state; } /* timing calculations */ #define NS_IN_KHZ 1000000 +/** + * s3c_nand_calc_rate - calculate timing data. + * @wanted: The cycle time in nanoseconds. + * @clk: The clock rate in kHz. + * @max: The maximum divider value. + * + * Calculate the timing value from the given parameters. + */ static int s3c_nand_calc_rate(int wanted, unsigned long clk, int max) { int result; - result = (wanted * clk) / NS_IN_KHZ; - result++; + result = DIV_ROUND_UP((wanted * clk), NS_IN_KHZ); pr_debug("result %d from %ld, %d\n", result, clk, wanted); if (result > max) { - printk("%d ns is too big for current clock rate %ld\n", wanted, clk); + pr_err("%d ns is too big for current clock rate %ld\n", + wanted, clk); return -1; } @@ -174,21 +249,30 @@ static int s3c_nand_calc_rate(int wanted, unsigned long clk, int max) return result; } -#define to_ns(ticks,clk) (((ticks) * NS_IN_KHZ) / (unsigned int)(clk)) +#define to_ns(ticks, clk) (((ticks) * NS_IN_KHZ) / (unsigned int)(clk)) /* controller setup */ -static int s3c2410_nand_inithw(struct s3c2410_nand_info *info, - struct platform_device *pdev) +/** + * s3c2410_nand_setrate - setup controller timing information. + * @info: The controller instance. + * + * Given the information supplied by the platform, calculate and set + * the necessary timing registers in the hardware to generate the + * necessary timing cycles to the hardware. + */ +static int s3c2410_nand_setrate(struct s3c2410_nand_info *info) { - struct s3c2410_platform_nand *plat = to_nand_plat(pdev); - unsigned long clkrate = clk_get_rate(info->clk); + struct s3c2410_platform_nand *plat = info->platform; int tacls_max = (info->cpu_type == TYPE_S3C2412) ? 8 : 4; int tacls, twrph0, twrph1; - unsigned long cfg = 0; + unsigned long clkrate = clk_get_rate(info->clk); + unsigned long uninitialized_var(set), cfg, uninitialized_var(mask); + unsigned long flags; /* calculate the timing information for the controller */ + info->clk_rate = clkrate; clkrate /= 1000; /* turn clock into kHz for ease of use */ if (plat != NULL) { @@ -208,35 +292,92 @@ static int s3c2410_nand_inithw(struct s3c2410_nand_info *info, } dev_info(info->device, "Tacls=%d, %dns Twrph0=%d %dns, Twrph1=%d %dns\n", - tacls, to_ns(tacls, clkrate), twrph0, to_ns(twrph0, clkrate), twrph1, to_ns(twrph1, clkrate)); - - switch (info->cpu_type) { - case TYPE_S3C2410: - cfg = S3C2410_NFCONF_EN; - cfg |= S3C2410_NFCONF_TACLS(tacls - 1); - cfg |= S3C2410_NFCONF_TWRPH0(twrph0 - 1); - cfg |= S3C2410_NFCONF_TWRPH1(twrph1 - 1); + tacls, to_ns(tacls, clkrate), twrph0, to_ns(twrph0, clkrate), + twrph1, to_ns(twrph1, clkrate)); + + switch (info->cpu_type) { + case TYPE_S3C2410: + mask = (S3C2410_NFCONF_TACLS(3) | + S3C2410_NFCONF_TWRPH0(7) | + S3C2410_NFCONF_TWRPH1(7)); + set = S3C2410_NFCONF_EN; + set |= S3C2410_NFCONF_TACLS(tacls - 1); + set |= S3C2410_NFCONF_TWRPH0(twrph0 - 1); + set |= S3C2410_NFCONF_TWRPH1(twrph1 - 1); break; - case TYPE_S3C2440: - case TYPE_S3C2412: - cfg = S3C2440_NFCONF_TACLS(tacls - 1); - cfg |= S3C2440_NFCONF_TWRPH0(twrph0 - 1); - cfg |= S3C2440_NFCONF_TWRPH1(twrph1 - 1); + case TYPE_S3C2440: + case TYPE_S3C2412: + mask = (S3C2440_NFCONF_TACLS(tacls_max - 1) | + S3C2440_NFCONF_TWRPH0(7) | + S3C2440_NFCONF_TWRPH1(7)); - /* enable the controller and de-assert nFCE */ + set = S3C2440_NFCONF_TACLS(tacls - 1); + set |= S3C2440_NFCONF_TWRPH0(twrph0 - 1); + set |= S3C2440_NFCONF_TWRPH1(twrph1 - 1); + break; - writel(S3C2440_NFCONT_ENABLE, info->regs + S3C2440_NFCONT); + default: + BUG(); } - dev_dbg(info->device, "NF_CONF is 0x%lx\n", cfg); + local_irq_save(flags); + cfg = readl(info->regs + S3C2410_NFCONF); + cfg &= ~mask; + cfg |= set; writel(cfg, info->regs + S3C2410_NFCONF); + + local_irq_restore(flags); + + dev_dbg(info->device, "NF_CONF is 0x%lx\n", cfg); + return 0; } -/* select chip */ +/** + * s3c2410_nand_inithw - basic hardware initialisation + * @info: The hardware state. + * + * Do the basic initialisation of the hardware, using s3c2410_nand_setrate() + * to setup the hardware access speeds and set the controller to be enabled. +*/ +static int s3c2410_nand_inithw(struct s3c2410_nand_info *info) +{ + int ret; + + ret = s3c2410_nand_setrate(info); + if (ret < 0) + return ret; + + switch (info->cpu_type) { + case TYPE_S3C2410: + default: + break; + + case TYPE_S3C2440: + case TYPE_S3C2412: + /* enable the controller and de-assert nFCE */ + + writel(S3C2440_NFCONT_ENABLE, info->regs + S3C2440_NFCONT); + } + return 0; +} + +/** + * s3c2410_nand_select_chip - select the given nand chip + * @mtd: The MTD instance for this chip. + * @chip: The chip number. + * + * This is called by the MTD layer to either select a given chip for the + * @mtd instance, or to indicate that the access has finished and the + * chip can be de-selected. + * + * The routine ensures that the nFCE line is correctly setup, and any + * platform specific selection code is called to route nFCE to the specific + * chip. + */ static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip) { struct s3c2410_nand_info *info; @@ -247,8 +388,8 @@ static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip) nmtd = this->priv; info = nmtd->info; - if (chip != -1 && allow_clk_stop(info)) - clk_enable(info->clk); + if (chip != -1) + s3c2410_nand_clk_set_state(info, CLOCK_ENABLE); cur = readl(info->sel_reg); @@ -270,8 +411,8 @@ static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip) writel(cur, info->sel_reg); - if (chip == -1 && allow_clk_stop(info)) - clk_disable(info->clk); + if (chip == -1) + s3c2410_nand_clk_set_state(info, CLOCK_SUSPEND); } /* s3c2410_nand_hwcontrol @@ -283,7 +424,7 @@ static void s3c2410_nand_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl) { struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); - + if (cmd == NAND_CMD_NONE) return; @@ -334,18 +475,75 @@ static int s3c2412_nand_devready(struct mtd_info *mtd) /* ECC handling functions */ +#ifdef CONFIG_MTD_NAND_S3C2410_HWECC static int s3c2410_nand_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc) { - pr_debug("s3c2410_nand_correct_data(%p,%p,%p,%p)\n", mtd, dat, read_ecc, calc_ecc); + struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); + unsigned int diff0, diff1, diff2; + unsigned int bit, byte; + + pr_debug("%s(%p,%p,%p,%p)\n", __func__, mtd, dat, read_ecc, calc_ecc); - pr_debug("eccs: read %02x,%02x,%02x vs calc %02x,%02x,%02x\n", - read_ecc[0], read_ecc[1], read_ecc[2], calc_ecc[0], calc_ecc[1], calc_ecc[2]); + diff0 = read_ecc[0] ^ calc_ecc[0]; + diff1 = read_ecc[1] ^ calc_ecc[1]; + diff2 = read_ecc[2] ^ calc_ecc[2]; - if (read_ecc[0] == calc_ecc[0] && read_ecc[1] == calc_ecc[1] && read_ecc[2] == calc_ecc[2]) + pr_debug("%s: rd %*phN calc %*phN diff %02x%02x%02x\n", + __func__, 3, read_ecc, 3, calc_ecc, + diff0, diff1, diff2); + + if (diff0 == 0 && diff1 == 0 && diff2 == 0) + return 0; /* ECC is ok */ + + /* sometimes people do not think about using the ECC, so check + * to see if we have an 0xff,0xff,0xff read ECC and then ignore + * the error, on the assumption that this is an un-eccd page. + */ + if (read_ecc[0] == 0xff && read_ecc[1] == 0xff && read_ecc[2] == 0xff + && info->platform->ignore_unset_ecc) return 0; - /* we curently have no method for correcting the error */ + /* Can we correct this ECC (ie, one row and column change). + * Note, this is similar to the 256 error code on smartmedia */ + + if (((diff0 ^ (diff0 >> 1)) & 0x55) == 0x55 && + ((diff1 ^ (diff1 >> 1)) & 0x55) == 0x55 && + ((diff2 ^ (diff2 >> 1)) & 0x55) == 0x55) { + /* calculate the bit position of the error */ + + bit = ((diff2 >> 3) & 1) | + ((diff2 >> 4) & 2) | + ((diff2 >> 5) & 4); + + /* calculate the byte position of the error */ + + byte = ((diff2 << 7) & 0x100) | + ((diff1 << 0) & 0x80) | + ((diff1 << 1) & 0x40) | + ((diff1 << 2) & 0x20) | + ((diff1 << 3) & 0x10) | + ((diff0 >> 4) & 0x08) | + ((diff0 >> 3) & 0x04) | + ((diff0 >> 2) & 0x02) | + ((diff0 >> 1) & 0x01); + + dev_dbg(info->device, "correcting error bit %d, byte %d\n", + bit, byte); + + dat[byte] ^= (1 << bit); + return 1; + } + + /* if there is only one bit difference in the ECC, then + * one of only a row or column parity has changed, which + * means the error is most probably in the ECC itself */ + + diff0 |= (diff1 << 8); + diff0 |= (diff2 << 16); + + if ((diff0 & ~(1<<fls(diff0))) == 0) + return 1; return -1; } @@ -366,6 +564,16 @@ static void s3c2410_nand_enable_hwecc(struct mtd_info *mtd, int mode) writel(ctrl, info->regs + S3C2410_NFCONF); } +static void s3c2412_nand_enable_hwecc(struct mtd_info *mtd, int mode) +{ + struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); + unsigned long ctrl; + + ctrl = readl(info->regs + S3C2440_NFCONT); + writel(ctrl | S3C2412_NFCONT_INIT_MAIN_ECC, + info->regs + S3C2440_NFCONT); +} + static void s3c2440_nand_enable_hwecc(struct mtd_info *mtd, int mode) { struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); @@ -375,7 +583,8 @@ static void s3c2440_nand_enable_hwecc(struct mtd_info *mtd, int mode) writel(ctrl | S3C2440_NFCONT_INITECC, info->regs + S3C2440_NFCONT); } -static int s3c2410_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code) +static int s3c2410_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, + u_char *ecc_code) { struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); @@ -383,12 +592,28 @@ static int s3c2410_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u ecc_code[1] = readb(info->regs + S3C2410_NFECC + 1); ecc_code[2] = readb(info->regs + S3C2410_NFECC + 2); - pr_debug("calculate_ecc: returning ecc %02x,%02x,%02x\n", ecc_code[0], ecc_code[1], ecc_code[2]); + pr_debug("%s: returning ecc %*phN\n", __func__, 3, ecc_code); return 0; } -static int s3c2440_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code) +static int s3c2412_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, + u_char *ecc_code) +{ + struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); + unsigned long ecc = readl(info->regs + S3C2412_NFMECC0); + + ecc_code[0] = ecc; + ecc_code[1] = ecc >> 8; + ecc_code[2] = ecc >> 16; + + pr_debug("%s: returning ecc %*phN\n", __func__, 3, ecc_code); + + return 0; +} + +static int s3c2440_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, + u_char *ecc_code) { struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); unsigned long ecc = readl(info->regs + S3C2440_NFMECC0); @@ -397,10 +622,11 @@ static int s3c2440_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u ecc_code[1] = ecc >> 8; ecc_code[2] = ecc >> 16; - pr_debug("calculate_ecc: returning ecc %02x,%02x,%02x\n", ecc_code[0], ecc_code[1], ecc_code[2]); + pr_debug("%s: returning ecc %06lx\n", __func__, ecc & 0xffffff); return 0; } +#endif /* over-ride the standard functions for a little more speed. We can * use read/write block to move the data buffers to/from the controller @@ -412,26 +638,105 @@ static void s3c2410_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len) readsb(this->IO_ADDR_R, buf, len); } -static void s3c2410_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len) +static void s3c2440_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len) +{ + struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); + + readsl(info->regs + S3C2440_NFDATA, buf, len >> 2); + + /* cleanup if we've got less than a word to do */ + if (len & 3) { + buf += len & ~3; + + for (; len & 3; len--) + *buf++ = readb(info->regs + S3C2440_NFDATA); + } +} + +static void s3c2410_nand_write_buf(struct mtd_info *mtd, const u_char *buf, + int len) { struct nand_chip *this = mtd->priv; writesb(this->IO_ADDR_W, buf, len); } +static void s3c2440_nand_write_buf(struct mtd_info *mtd, const u_char *buf, + int len) +{ + struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); + + writesl(info->regs + S3C2440_NFDATA, buf, len >> 2); + + /* cleanup any fractional write */ + if (len & 3) { + buf += len & ~3; + + for (; len & 3; len--, buf++) + writeb(*buf, info->regs + S3C2440_NFDATA); + } +} + +/* cpufreq driver support */ + +#ifdef CONFIG_CPU_FREQ + +static int s3c2410_nand_cpufreq_transition(struct notifier_block *nb, + unsigned long val, void *data) +{ + struct s3c2410_nand_info *info; + unsigned long newclk; + + info = container_of(nb, struct s3c2410_nand_info, freq_transition); + newclk = clk_get_rate(info->clk); + + if ((val == CPUFREQ_POSTCHANGE && newclk < info->clk_rate) || + (val == CPUFREQ_PRECHANGE && newclk > info->clk_rate)) { + s3c2410_nand_setrate(info); + } + + return 0; +} + +static inline int s3c2410_nand_cpufreq_register(struct s3c2410_nand_info *info) +{ + info->freq_transition.notifier_call = s3c2410_nand_cpufreq_transition; + + return cpufreq_register_notifier(&info->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); +} + +static inline void +s3c2410_nand_cpufreq_deregister(struct s3c2410_nand_info *info) +{ + cpufreq_unregister_notifier(&info->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); +} + +#else +static inline int s3c2410_nand_cpufreq_register(struct s3c2410_nand_info *info) +{ + return 0; +} + +static inline void +s3c2410_nand_cpufreq_deregister(struct s3c2410_nand_info *info) +{ +} +#endif + /* device management functions */ -static int s3c2410_nand_remove(struct platform_device *pdev) +static int s3c24xx_nand_remove(struct platform_device *pdev) { struct s3c2410_nand_info *info = to_nand_info(pdev); - platform_set_drvdata(pdev, NULL); - if (info == NULL) return 0; - /* first thing we need to do is release all our mtds - * and their partitions, then go through freeing the - * resources used + s3c2410_nand_cpufreq_deregister(info); + + /* Release all our mtds and their partitions, then go through + * freeing the resources used */ if (info->mtds != NULL) { @@ -442,62 +747,40 @@ static int s3c2410_nand_remove(struct platform_device *pdev) pr_debug("releasing mtd %d (%p)\n", mtdno, ptr); nand_release(&ptr->mtd); } - - kfree(info->mtds); } /* free the common resources */ - if (info->clk != NULL && !IS_ERR(info->clk)) { - if (!allow_clk_stop(info)) - clk_disable(info->clk); - clk_put(info->clk); - } - - if (info->regs != NULL) { - iounmap(info->regs); - info->regs = NULL; - } - - if (info->area != NULL) { - release_resource(info->area); - kfree(info->area); - info->area = NULL; - } - - kfree(info); + if (!IS_ERR(info->clk)) + s3c2410_nand_clk_set_state(info, CLOCK_DISABLE); return 0; } -#ifdef CONFIG_MTD_PARTITIONS static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info, struct s3c2410_nand_mtd *mtd, struct s3c2410_nand_set *set) { - if (set == NULL) - return add_mtd_device(&mtd->mtd); + if (set) { + mtd->mtd.name = set->name; - if (set->nr_partitions > 0 && set->partitions != NULL) { - return add_mtd_partitions(&mtd->mtd, set->partitions, set->nr_partitions); + return mtd_device_parse_register(&mtd->mtd, NULL, NULL, + set->partitions, set->nr_partitions); } - return add_mtd_device(&mtd->mtd); + return -ENODEV; } -#else -static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info, - struct s3c2410_nand_mtd *mtd, - struct s3c2410_nand_set *set) -{ - return add_mtd_device(&mtd->mtd); -} -#endif -/* s3c2410_nand_init_chip +/** + * s3c2410_nand_init_chip - initialise a single instance of an chip + * @info: The base NAND controller the chip is on. + * @nmtd: The new controller MTD instance to fill in. + * @set: The information passed from the board specific platform data. * - * init a single instance of an chip -*/ - + * Initialise the given @nmtd from the information in @info and @set. This + * readies the structure for use with the MTD layer functions by ensuring + * all pointers are setup and the necessary control routines selected. + */ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info, struct s3c2410_nand_mtd *nmtd, struct s3c2410_nand_set *set) @@ -510,7 +793,7 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info, chip->select_chip = s3c2410_nand_select_chip; chip->chip_delay = 50; chip->priv = nmtd; - chip->options = 0; + chip->options = set->options; chip->controller = &info->controller; switch (info->cpu_type) { @@ -528,6 +811,8 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info, info->sel_bit = S3C2440_NFCONT_nFCE; chip->cmd_ctrl = s3c2440_nand_hwcontrol; chip->dev_ready = s3c2440_nand_devready; + chip->read_buf = s3c2440_nand_read_buf; + chip->write_buf = s3c2440_nand_write_buf; break; case TYPE_S3C2412: @@ -541,7 +826,7 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info, dev_info(info->device, "System booted from NAND\n"); break; - } + } chip->IO_ADDR_R = chip->IO_ADDR_W; @@ -550,44 +835,108 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info, nmtd->mtd.owner = THIS_MODULE; nmtd->set = set; - if (hardware_ecc) { +#ifdef CONFIG_MTD_NAND_S3C2410_HWECC + chip->ecc.calculate = s3c2410_nand_calculate_ecc; + chip->ecc.correct = s3c2410_nand_correct_data; + chip->ecc.mode = NAND_ECC_HW; + chip->ecc.strength = 1; + + switch (info->cpu_type) { + case TYPE_S3C2410: + chip->ecc.hwctl = s3c2410_nand_enable_hwecc; chip->ecc.calculate = s3c2410_nand_calculate_ecc; - chip->ecc.correct = s3c2410_nand_correct_data; - chip->ecc.mode = NAND_ECC_HW; - chip->ecc.size = 512; - chip->ecc.bytes = 3; - chip->ecc.layout = &nand_hw_eccoob; + break; + + case TYPE_S3C2412: + chip->ecc.hwctl = s3c2412_nand_enable_hwecc; + chip->ecc.calculate = s3c2412_nand_calculate_ecc; + break; + + case TYPE_S3C2440: + chip->ecc.hwctl = s3c2440_nand_enable_hwecc; + chip->ecc.calculate = s3c2440_nand_calculate_ecc; + break; + } +#else + chip->ecc.mode = NAND_ECC_SOFT; +#endif - switch (info->cpu_type) { - case TYPE_S3C2410: - chip->ecc.hwctl = s3c2410_nand_enable_hwecc; - chip->ecc.calculate = s3c2410_nand_calculate_ecc; - break; + if (set->ecc_layout != NULL) + chip->ecc.layout = set->ecc_layout; - case TYPE_S3C2412: - case TYPE_S3C2440: - chip->ecc.hwctl = s3c2440_nand_enable_hwecc; - chip->ecc.calculate = s3c2440_nand_calculate_ecc; - break; + if (set->disable_ecc) + chip->ecc.mode = NAND_ECC_NONE; - } + switch (chip->ecc.mode) { + case NAND_ECC_NONE: + dev_info(info->device, "NAND ECC disabled\n"); + break; + case NAND_ECC_SOFT: + dev_info(info->device, "NAND soft ECC\n"); + break; + case NAND_ECC_HW: + dev_info(info->device, "NAND hardware ECC\n"); + break; + default: + dev_info(info->device, "NAND ECC UNKNOWN\n"); + break; + } + + /* If you use u-boot BBT creation code, specifying this flag will + * let the kernel fish out the BBT from the NAND, and also skip the + * full NAND scan that can take 1/2s or so. Little things... */ + if (set->flash_bbt) { + chip->bbt_options |= NAND_BBT_USE_FLASH; + chip->options |= NAND_SKIP_BBTSCAN; + } +} + +/** + * s3c2410_nand_update_chip - post probe update + * @info: The controller instance. + * @nmtd: The driver version of the MTD instance. + * + * This routine is called after the chip probe has successfully completed + * and the relevant per-chip information updated. This call ensure that + * we update the internal state accordingly. + * + * The internal state is currently limited to the ECC state information. +*/ +static void s3c2410_nand_update_chip(struct s3c2410_nand_info *info, + struct s3c2410_nand_mtd *nmtd) +{ + struct nand_chip *chip = &nmtd->chip; + + dev_dbg(info->device, "chip %p => page shift %d\n", + chip, chip->page_shift); + + if (chip->ecc.mode != NAND_ECC_HW) + return; + + /* change the behaviour depending on whether we are using + * the large or small page nand device */ + + if (chip->page_shift > 10) { + chip->ecc.size = 256; + chip->ecc.bytes = 3; } else { - chip->ecc.mode = NAND_ECC_SOFT; + chip->ecc.size = 512; + chip->ecc.bytes = 3; + chip->ecc.layout = &nand_hw_eccoob; } } -/* s3c2410_nand_probe +/* s3c24xx_nand_probe * * called by device layer when it finds a device matching * one our driver can handled. This code checks to see if * it can allocate all necessary resources then calls the * nand layer to look for devices */ - -static int s3c24xx_nand_probe(struct platform_device *pdev, - enum s3c_cpu_type cpu_type) +static int s3c24xx_nand_probe(struct platform_device *pdev) { struct s3c2410_platform_nand *plat = to_nand_plat(pdev); + enum s3c_cpu_type cpu_type; struct s3c2410_nand_info *info; struct s3c2410_nand_mtd *nmtd; struct s3c2410_nand_set *sets; @@ -597,16 +946,16 @@ static int s3c24xx_nand_probe(struct platform_device *pdev, int nr_sets; int setno; + cpu_type = platform_get_device_id(pdev)->driver_data; + pr_debug("s3c2410_nand_probe(%p)\n", pdev); - info = kmalloc(sizeof(*info), GFP_KERNEL); + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); if (info == NULL) { - dev_err(&pdev->dev, "no memory for flash info\n"); err = -ENOMEM; goto exit_error; } - memzero(info, sizeof(*info)); platform_set_drvdata(pdev, info); spin_lock_init(&info->controller.lock); @@ -614,37 +963,28 @@ static int s3c24xx_nand_probe(struct platform_device *pdev, /* get the clock source and enable it */ - info->clk = clk_get(&pdev->dev, "nand"); + info->clk = devm_clk_get(&pdev->dev, "nand"); if (IS_ERR(info->clk)) { - dev_err(&pdev->dev, "failed to get clock"); + dev_err(&pdev->dev, "failed to get clock\n"); err = -ENOENT; goto exit_error; } - clk_enable(info->clk); + s3c2410_nand_clk_set_state(info, CLOCK_ENABLE); /* allocate and map the resource */ /* currently we assume we have the one resource */ - res = pdev->resource; - size = res->end - res->start + 1; - - info->area = request_mem_region(res->start, size, pdev->name); - - if (info->area == NULL) { - dev_err(&pdev->dev, "cannot reserve register region\n"); - err = -ENOENT; - goto exit_error; - } + res = pdev->resource; + size = resource_size(res); - info->device = &pdev->dev; - info->platform = plat; - info->regs = ioremap(res->start, size); - info->cpu_type = cpu_type; + info->device = &pdev->dev; + info->platform = plat; + info->cpu_type = cpu_type; - if (info->regs == NULL) { - dev_err(&pdev->dev, "cannot reserve register region\n"); - err = -EIO; + info->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(info->regs)) { + err = PTR_ERR(info->regs); goto exit_error; } @@ -652,7 +992,7 @@ static int s3c24xx_nand_probe(struct platform_device *pdev, /* initialise the hardware */ - err = s3c2410_nand_inithw(info, pdev); + err = s3c2410_nand_inithw(info); if (err != 0) goto exit_error; @@ -664,27 +1004,29 @@ static int s3c24xx_nand_probe(struct platform_device *pdev, /* allocate our information */ size = nr_sets * sizeof(*info->mtds); - info->mtds = kmalloc(size, GFP_KERNEL); + info->mtds = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); if (info->mtds == NULL) { - dev_err(&pdev->dev, "failed to allocate mtd storage\n"); err = -ENOMEM; goto exit_error; } - memzero(info->mtds, size); - /* initialise all possible chips */ nmtd = info->mtds; for (setno = 0; setno < nr_sets; setno++, nmtd++) { - pr_debug("initialising set %d (%p, info %p)\n", setno, nmtd, info); + pr_debug("initialising set %d (%p, info %p)\n", + setno, nmtd, info); s3c2410_nand_init_chip(info, nmtd, sets); - nmtd->scan_res = nand_scan(&nmtd->mtd, (sets) ? sets->nr_chips : 1); + nmtd->scan_res = nand_scan_ident(&nmtd->mtd, + (sets) ? sets->nr_chips : 1, + NULL); if (nmtd->scan_res == 0) { + s3c2410_nand_update_chip(info, nmtd); + nand_scan_tail(&nmtd->mtd); s3c2410_nand_add_partition(info, nmtd, sets); } @@ -692,16 +1034,22 @@ static int s3c24xx_nand_probe(struct platform_device *pdev, sets++; } - if (allow_clk_stop(info)) { + err = s3c2410_nand_cpufreq_register(info); + if (err < 0) { + dev_err(&pdev->dev, "failed to init cpufreq support\n"); + goto exit_error; + } + + if (allow_clk_suspend(info)) { dev_info(&pdev->dev, "clock idle support enabled\n"); - clk_disable(info->clk); + s3c2410_nand_clk_set_state(info, CLOCK_SUSPEND); } pr_debug("initialised ok\n"); return 0; exit_error: - s3c2410_nand_remove(pdev); + s3c24xx_nand_remove(pdev); if (err == 0) err = -EINVAL; @@ -716,8 +1064,16 @@ static int s3c24xx_nand_suspend(struct platform_device *dev, pm_message_t pm) struct s3c2410_nand_info *info = platform_get_drvdata(dev); if (info) { - if (!allow_clk_stop(info)) - clk_disable(info->clk); + info->save_sel = readl(info->sel_reg); + + /* For the moment, we must ensure nFCE is high during + * the time we are suspended. This really should be + * handled by suspending the MTDs we are using, but + * that is currently not the case. */ + + writel(info->save_sel | info->sel_bit, info->sel_reg); + + s3c2410_nand_clk_set_state(info, CLOCK_DISABLE); } return 0; @@ -726,13 +1082,20 @@ static int s3c24xx_nand_suspend(struct platform_device *dev, pm_message_t pm) static int s3c24xx_nand_resume(struct platform_device *dev) { struct s3c2410_nand_info *info = platform_get_drvdata(dev); + unsigned long sel; if (info) { - clk_enable(info->clk); - s3c2410_nand_inithw(info, dev); + s3c2410_nand_clk_set_state(info, CLOCK_ENABLE); + s3c2410_nand_inithw(info); - if (allow_clk_stop(info)) - clk_disable(info->clk); + /* Restore the state of the nFCE line. */ + + sel = readl(info->sel_reg); + sel &= ~info->sel_bit; + sel |= info->save_sel & info->sel_bit; + writel(sel, info->sel_reg); + + s3c2410_nand_clk_set_state(info, CLOCK_SUSPEND); } return 0; @@ -745,72 +1108,38 @@ static int s3c24xx_nand_resume(struct platform_device *dev) /* driver device registration */ -static int s3c2410_nand_probe(struct platform_device *dev) -{ - return s3c24xx_nand_probe(dev, TYPE_S3C2410); -} - -static int s3c2440_nand_probe(struct platform_device *dev) -{ - return s3c24xx_nand_probe(dev, TYPE_S3C2440); -} - -static int s3c2412_nand_probe(struct platform_device *dev) -{ - return s3c24xx_nand_probe(dev, TYPE_S3C2412); -} - -static struct platform_driver s3c2410_nand_driver = { - .probe = s3c2410_nand_probe, - .remove = s3c2410_nand_remove, - .suspend = s3c24xx_nand_suspend, - .resume = s3c24xx_nand_resume, - .driver = { - .name = "s3c2410-nand", - .owner = THIS_MODULE, +static struct platform_device_id s3c24xx_driver_ids[] = { + { + .name = "s3c2410-nand", + .driver_data = TYPE_S3C2410, + }, { + .name = "s3c2440-nand", + .driver_data = TYPE_S3C2440, + }, { + .name = "s3c2412-nand", + .driver_data = TYPE_S3C2412, + }, { + .name = "s3c6400-nand", + .driver_data = TYPE_S3C2412, /* compatible with 2412 */ }, + { } }; -static struct platform_driver s3c2440_nand_driver = { - .probe = s3c2440_nand_probe, - .remove = s3c2410_nand_remove, - .suspend = s3c24xx_nand_suspend, - .resume = s3c24xx_nand_resume, - .driver = { - .name = "s3c2440-nand", - .owner = THIS_MODULE, - }, -}; +MODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids); -static struct platform_driver s3c2412_nand_driver = { - .probe = s3c2412_nand_probe, - .remove = s3c2410_nand_remove, +static struct platform_driver s3c24xx_nand_driver = { + .probe = s3c24xx_nand_probe, + .remove = s3c24xx_nand_remove, .suspend = s3c24xx_nand_suspend, .resume = s3c24xx_nand_resume, + .id_table = s3c24xx_driver_ids, .driver = { - .name = "s3c2412-nand", + .name = "s3c24xx-nand", .owner = THIS_MODULE, }, }; -static int __init s3c2410_nand_init(void) -{ - printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics\n"); - - platform_driver_register(&s3c2412_nand_driver); - platform_driver_register(&s3c2440_nand_driver); - return platform_driver_register(&s3c2410_nand_driver); -} - -static void __exit s3c2410_nand_exit(void) -{ - platform_driver_unregister(&s3c2412_nand_driver); - platform_driver_unregister(&s3c2440_nand_driver); - platform_driver_unregister(&s3c2410_nand_driver); -} - -module_init(s3c2410_nand_init); -module_exit(s3c2410_nand_exit); +module_platform_driver(s3c24xx_nand_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); diff --git a/drivers/mtd/nand/sh_flctl.c b/drivers/mtd/nand/sh_flctl.c new file mode 100644 index 00000000000..c0670237e7a --- /dev/null +++ b/drivers/mtd/nand/sh_flctl.c @@ -0,0 +1,1203 @@ +/* + * SuperH FLCTL nand controller + * + * Copyright (c) 2008 Renesas Solutions Corp. + * Copyright (c) 2008 Atom Create Engineering Co., Ltd. + * + * Based on fsl_elbc_nand.c, Copyright (c) 2006-2007 Freescale Semiconductor + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_mtd.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/sh_dma.h> +#include <linux/slab.h> +#include <linux/string.h> + +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/sh_flctl.h> + +static struct nand_ecclayout flctl_4secc_oob_16 = { + .eccbytes = 10, + .eccpos = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + .oobfree = { + {.offset = 12, + . length = 4} }, +}; + +static struct nand_ecclayout flctl_4secc_oob_64 = { + .eccbytes = 4 * 10, + .eccpos = { + 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63 }, + .oobfree = { + {.offset = 2, .length = 4}, + {.offset = 16, .length = 6}, + {.offset = 32, .length = 6}, + {.offset = 48, .length = 6} }, +}; + +static uint8_t scan_ff_pattern[] = { 0xff, 0xff }; + +static struct nand_bbt_descr flctl_4secc_smallpage = { + .options = NAND_BBT_SCAN2NDPAGE, + .offs = 11, + .len = 1, + .pattern = scan_ff_pattern, +}; + +static struct nand_bbt_descr flctl_4secc_largepage = { + .options = NAND_BBT_SCAN2NDPAGE, + .offs = 0, + .len = 2, + .pattern = scan_ff_pattern, +}; + +static void empty_fifo(struct sh_flctl *flctl) +{ + writel(flctl->flintdmacr_base | AC1CLR | AC0CLR, FLINTDMACR(flctl)); + writel(flctl->flintdmacr_base, FLINTDMACR(flctl)); +} + +static void start_translation(struct sh_flctl *flctl) +{ + writeb(TRSTRT, FLTRCR(flctl)); +} + +static void timeout_error(struct sh_flctl *flctl, const char *str) +{ + dev_err(&flctl->pdev->dev, "Timeout occurred in %s\n", str); +} + +static void wait_completion(struct sh_flctl *flctl) +{ + uint32_t timeout = LOOP_TIMEOUT_MAX; + + while (timeout--) { + if (readb(FLTRCR(flctl)) & TREND) { + writeb(0x0, FLTRCR(flctl)); + return; + } + udelay(1); + } + + timeout_error(flctl, __func__); + writeb(0x0, FLTRCR(flctl)); +} + +static void flctl_dma_complete(void *param) +{ + struct sh_flctl *flctl = param; + + complete(&flctl->dma_complete); +} + +static void flctl_release_dma(struct sh_flctl *flctl) +{ + if (flctl->chan_fifo0_rx) { + dma_release_channel(flctl->chan_fifo0_rx); + flctl->chan_fifo0_rx = NULL; + } + if (flctl->chan_fifo0_tx) { + dma_release_channel(flctl->chan_fifo0_tx); + flctl->chan_fifo0_tx = NULL; + } +} + +static void flctl_setup_dma(struct sh_flctl *flctl) +{ + dma_cap_mask_t mask; + struct dma_slave_config cfg; + struct platform_device *pdev = flctl->pdev; + struct sh_flctl_platform_data *pdata = dev_get_platdata(&pdev->dev); + int ret; + + if (!pdata) + return; + + if (pdata->slave_id_fifo0_tx <= 0 || pdata->slave_id_fifo0_rx <= 0) + return; + + /* We can only either use DMA for both Tx and Rx or not use it at all */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + flctl->chan_fifo0_tx = dma_request_channel(mask, shdma_chan_filter, + (void *)(uintptr_t)pdata->slave_id_fifo0_tx); + dev_dbg(&pdev->dev, "%s: TX: got channel %p\n", __func__, + flctl->chan_fifo0_tx); + + if (!flctl->chan_fifo0_tx) + return; + + memset(&cfg, 0, sizeof(cfg)); + cfg.slave_id = pdata->slave_id_fifo0_tx; + cfg.direction = DMA_MEM_TO_DEV; + cfg.dst_addr = (dma_addr_t)FLDTFIFO(flctl); + cfg.src_addr = 0; + ret = dmaengine_slave_config(flctl->chan_fifo0_tx, &cfg); + if (ret < 0) + goto err; + + flctl->chan_fifo0_rx = dma_request_channel(mask, shdma_chan_filter, + (void *)(uintptr_t)pdata->slave_id_fifo0_rx); + dev_dbg(&pdev->dev, "%s: RX: got channel %p\n", __func__, + flctl->chan_fifo0_rx); + + if (!flctl->chan_fifo0_rx) + goto err; + + cfg.slave_id = pdata->slave_id_fifo0_rx; + cfg.direction = DMA_DEV_TO_MEM; + cfg.dst_addr = 0; + cfg.src_addr = (dma_addr_t)FLDTFIFO(flctl); + ret = dmaengine_slave_config(flctl->chan_fifo0_rx, &cfg); + if (ret < 0) + goto err; + + init_completion(&flctl->dma_complete); + + return; + +err: + flctl_release_dma(flctl); +} + +static void set_addr(struct mtd_info *mtd, int column, int page_addr) +{ + struct sh_flctl *flctl = mtd_to_flctl(mtd); + uint32_t addr = 0; + + if (column == -1) { + addr = page_addr; /* ERASE1 */ + } else if (page_addr != -1) { + /* SEQIN, READ0, etc.. */ + if (flctl->chip.options & NAND_BUSWIDTH_16) + column >>= 1; + if (flctl->page_size) { + addr = column & 0x0FFF; + addr |= (page_addr & 0xff) << 16; + addr |= ((page_addr >> 8) & 0xff) << 24; + /* big than 128MB */ + if (flctl->rw_ADRCNT == ADRCNT2_E) { + uint32_t addr2; + addr2 = (page_addr >> 16) & 0xff; + writel(addr2, FLADR2(flctl)); + } + } else { + addr = column; + addr |= (page_addr & 0xff) << 8; + addr |= ((page_addr >> 8) & 0xff) << 16; + addr |= ((page_addr >> 16) & 0xff) << 24; + } + } + writel(addr, FLADR(flctl)); +} + +static void wait_rfifo_ready(struct sh_flctl *flctl) +{ + uint32_t timeout = LOOP_TIMEOUT_MAX; + + while (timeout--) { + uint32_t val; + /* check FIFO */ + val = readl(FLDTCNTR(flctl)) >> 16; + if (val & 0xFF) + return; + udelay(1); + } + timeout_error(flctl, __func__); +} + +static void wait_wfifo_ready(struct sh_flctl *flctl) +{ + uint32_t len, timeout = LOOP_TIMEOUT_MAX; + + while (timeout--) { + /* check FIFO */ + len = (readl(FLDTCNTR(flctl)) >> 16) & 0xFF; + if (len >= 4) + return; + udelay(1); + } + timeout_error(flctl, __func__); +} + +static enum flctl_ecc_res_t wait_recfifo_ready + (struct sh_flctl *flctl, int sector_number) +{ + uint32_t timeout = LOOP_TIMEOUT_MAX; + void __iomem *ecc_reg[4]; + int i; + int state = FL_SUCCESS; + uint32_t data, size; + + /* + * First this loops checks in FLDTCNTR if we are ready to read out the + * oob data. This is the case if either all went fine without errors or + * if the bottom part of the loop corrected the errors or marked them as + * uncorrectable and the controller is given time to push the data into + * the FIFO. + */ + while (timeout--) { + /* check if all is ok and we can read out the OOB */ + size = readl(FLDTCNTR(flctl)) >> 24; + if ((size & 0xFF) == 4) + return state; + + /* check if a correction code has been calculated */ + if (!(readl(FL4ECCCR(flctl)) & _4ECCEND)) { + /* + * either we wait for the fifo to be filled or a + * correction pattern is being generated + */ + udelay(1); + continue; + } + + /* check for an uncorrectable error */ + if (readl(FL4ECCCR(flctl)) & _4ECCFA) { + /* check if we face a non-empty page */ + for (i = 0; i < 512; i++) { + if (flctl->done_buff[i] != 0xff) { + state = FL_ERROR; /* can't correct */ + break; + } + } + + if (state == FL_SUCCESS) + dev_dbg(&flctl->pdev->dev, + "reading empty sector %d, ecc error ignored\n", + sector_number); + + writel(0, FL4ECCCR(flctl)); + continue; + } + + /* start error correction */ + ecc_reg[0] = FL4ECCRESULT0(flctl); + ecc_reg[1] = FL4ECCRESULT1(flctl); + ecc_reg[2] = FL4ECCRESULT2(flctl); + ecc_reg[3] = FL4ECCRESULT3(flctl); + + for (i = 0; i < 3; i++) { + uint8_t org; + unsigned int index; + + data = readl(ecc_reg[i]); + + 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); + } + state = FL_REPAIRABLE; + writel(0, FL4ECCCR(flctl)); + } + + timeout_error(flctl, __func__); + return FL_TIMEOUT; /* timeout */ +} + +static void wait_wecfifo_ready(struct sh_flctl *flctl) +{ + uint32_t timeout = LOOP_TIMEOUT_MAX; + uint32_t len; + + while (timeout--) { + /* check FLECFIFO */ + len = (readl(FLDTCNTR(flctl)) >> 24) & 0xFF; + if (len >= 4) + return; + udelay(1); + } + timeout_error(flctl, __func__); +} + +static int flctl_dma_fifo0_transfer(struct sh_flctl *flctl, unsigned long *buf, + int len, enum dma_data_direction dir) +{ + struct dma_async_tx_descriptor *desc = NULL; + struct dma_chan *chan; + enum dma_transfer_direction tr_dir; + dma_addr_t dma_addr; + dma_cookie_t cookie = -EINVAL; + uint32_t reg; + int ret; + + if (dir == DMA_FROM_DEVICE) { + chan = flctl->chan_fifo0_rx; + tr_dir = DMA_DEV_TO_MEM; + } else { + chan = flctl->chan_fifo0_tx; + tr_dir = DMA_MEM_TO_DEV; + } + + dma_addr = dma_map_single(chan->device->dev, buf, len, dir); + + if (dma_addr) + desc = dmaengine_prep_slave_single(chan, dma_addr, len, + tr_dir, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + + if (desc) { + reg = readl(FLINTDMACR(flctl)); + reg |= DREQ0EN; + writel(reg, FLINTDMACR(flctl)); + + desc->callback = flctl_dma_complete; + desc->callback_param = flctl; + cookie = dmaengine_submit(desc); + + dma_async_issue_pending(chan); + } else { + /* DMA failed, fall back to PIO */ + flctl_release_dma(flctl); + dev_warn(&flctl->pdev->dev, + "DMA failed, falling back to PIO\n"); + ret = -EIO; + goto out; + } + + ret = + wait_for_completion_timeout(&flctl->dma_complete, + msecs_to_jiffies(3000)); + + if (ret <= 0) { + chan->device->device_control(chan, DMA_TERMINATE_ALL, 0); + dev_err(&flctl->pdev->dev, "wait_for_completion_timeout\n"); + } + +out: + reg = readl(FLINTDMACR(flctl)); + reg &= ~DREQ0EN; + writel(reg, FLINTDMACR(flctl)); + + dma_unmap_single(chan->device->dev, dma_addr, len, dir); + + /* ret > 0 is success */ + return ret; +} + +static void read_datareg(struct sh_flctl *flctl, int offset) +{ + unsigned long data; + unsigned long *buf = (unsigned long *)&flctl->done_buff[offset]; + + wait_completion(flctl); + + data = readl(FLDATAR(flctl)); + *buf = le32_to_cpu(data); +} + +static void read_fiforeg(struct sh_flctl *flctl, int rlen, int offset) +{ + int i, len_4align; + unsigned long *buf = (unsigned long *)&flctl->done_buff[offset]; + + len_4align = (rlen + 3) / 4; + + /* initiate DMA transfer */ + if (flctl->chan_fifo0_rx && rlen >= 32 && + flctl_dma_fifo0_transfer(flctl, buf, rlen, DMA_DEV_TO_MEM) > 0) + goto convert; /* DMA success */ + + /* do polling transfer */ + for (i = 0; i < len_4align; i++) { + wait_rfifo_ready(flctl); + buf[i] = readl(FLDTFIFO(flctl)); + } + +convert: + for (i = 0; i < len_4align; i++) + buf[i] = be32_to_cpu(buf[i]); +} + +static enum flctl_ecc_res_t read_ecfiforeg + (struct sh_flctl *flctl, uint8_t *buff, int sector) +{ + int i; + enum flctl_ecc_res_t res; + unsigned long *ecc_buf = (unsigned long *)buff; + + res = wait_recfifo_ready(flctl , sector); + + if (res != FL_ERROR) { + for (i = 0; i < 4; i++) { + ecc_buf[i] = readl(FLECFIFO(flctl)); + ecc_buf[i] = be32_to_cpu(ecc_buf[i]); + } + } + + return res; +} + +static void write_fiforeg(struct sh_flctl *flctl, int rlen, + unsigned int offset) +{ + int i, len_4align; + unsigned long *buf = (unsigned long *)&flctl->done_buff[offset]; + + len_4align = (rlen + 3) / 4; + for (i = 0; i < len_4align; i++) { + wait_wfifo_ready(flctl); + writel(cpu_to_be32(buf[i]), FLDTFIFO(flctl)); + } +} + +static void write_ec_fiforeg(struct sh_flctl *flctl, int rlen, + unsigned int offset) +{ + int i, len_4align; + unsigned long *buf = (unsigned long *)&flctl->done_buff[offset]; + + len_4align = (rlen + 3) / 4; + + for (i = 0; i < len_4align; i++) + buf[i] = cpu_to_be32(buf[i]); + + /* initiate DMA transfer */ + if (flctl->chan_fifo0_tx && rlen >= 32 && + flctl_dma_fifo0_transfer(flctl, buf, rlen, DMA_MEM_TO_DEV) > 0) + return; /* DMA success */ + + /* do polling transfer */ + for (i = 0; i < len_4align; i++) { + wait_wecfifo_ready(flctl); + writel(buf[i], FLECFIFO(flctl)); + } +} + +static void set_cmd_regs(struct mtd_info *mtd, uint32_t cmd, uint32_t flcmcdr_val) +{ + struct sh_flctl *flctl = mtd_to_flctl(mtd); + uint32_t flcmncr_val = flctl->flcmncr_base & ~SEL_16BIT; + uint32_t flcmdcr_val, addr_len_bytes = 0; + + /* Set SNAND bit if page size is 2048byte */ + if (flctl->page_size) + flcmncr_val |= SNAND_E; + else + flcmncr_val &= ~SNAND_E; + + /* default FLCMDCR val */ + flcmdcr_val = DOCMD1_E | DOADR_E; + + /* Set for FLCMDCR */ + switch (cmd) { + case NAND_CMD_ERASE1: + addr_len_bytes = flctl->erase_ADRCNT; + flcmdcr_val |= DOCMD2_E; + break; + case NAND_CMD_READ0: + case NAND_CMD_READOOB: + case NAND_CMD_RNDOUT: + addr_len_bytes = flctl->rw_ADRCNT; + flcmdcr_val |= CDSRC_E; + if (flctl->chip.options & NAND_BUSWIDTH_16) + flcmncr_val |= SEL_16BIT; + break; + case NAND_CMD_SEQIN: + /* This case is that cmd is READ0 or READ1 or READ00 */ + flcmdcr_val &= ~DOADR_E; /* ONLY execute 1st cmd */ + break; + case NAND_CMD_PAGEPROG: + addr_len_bytes = flctl->rw_ADRCNT; + flcmdcr_val |= DOCMD2_E | CDSRC_E | SELRW; + if (flctl->chip.options & NAND_BUSWIDTH_16) + flcmncr_val |= SEL_16BIT; + break; + case NAND_CMD_READID: + flcmncr_val &= ~SNAND_E; + flcmdcr_val |= CDSRC_E; + addr_len_bytes = ADRCNT_1; + break; + case NAND_CMD_STATUS: + case NAND_CMD_RESET: + flcmncr_val &= ~SNAND_E; + flcmdcr_val &= ~(DOADR_E | DOSR_E); + break; + default: + break; + } + + /* Set address bytes parameter */ + flcmdcr_val |= addr_len_bytes; + + /* Now actually write */ + writel(flcmncr_val, FLCMNCR(flctl)); + writel(flcmdcr_val, FLCMDCR(flctl)); + writel(flcmcdr_val, FLCMCDR(flctl)); +} + +static int flctl_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf, int oob_required, int page) +{ + chip->read_buf(mtd, buf, mtd->writesize); + if (oob_required) + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + return 0; +} + +static int flctl_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf, int oob_required) +{ + chip->write_buf(mtd, buf, mtd->writesize); + chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); + return 0; +} + +static void execmd_read_page_sector(struct mtd_info *mtd, int page_addr) +{ + struct sh_flctl *flctl = mtd_to_flctl(mtd); + int sector, page_sectors; + enum flctl_ecc_res_t ecc_result; + + page_sectors = flctl->page_size ? 4 : 1; + + set_cmd_regs(mtd, NAND_CMD_READ0, + (NAND_CMD_READSTART << 8) | NAND_CMD_READ0); + + writel(readl(FLCMNCR(flctl)) | ACM_SACCES_MODE | _4ECCCORRECT, + FLCMNCR(flctl)); + writel(readl(FLCMDCR(flctl)) | page_sectors, FLCMDCR(flctl)); + writel(page_addr << 2, FLADR(flctl)); + + empty_fifo(flctl); + start_translation(flctl); + + for (sector = 0; sector < page_sectors; sector++) { + read_fiforeg(flctl, 512, 512 * sector); + + ecc_result = read_ecfiforeg(flctl, + &flctl->done_buff[mtd->writesize + 16 * sector], + sector); + + switch (ecc_result) { + case FL_REPAIRABLE: + dev_info(&flctl->pdev->dev, + "applied ecc on page 0x%x", page_addr); + flctl->mtd.ecc_stats.corrected++; + break; + case FL_ERROR: + dev_warn(&flctl->pdev->dev, + "page 0x%x contains corrupted data\n", + page_addr); + flctl->mtd.ecc_stats.failed++; + break; + default: + ; + } + } + + wait_completion(flctl); + + writel(readl(FLCMNCR(flctl)) & ~(ACM_SACCES_MODE | _4ECCCORRECT), + FLCMNCR(flctl)); +} + +static void execmd_read_oob(struct mtd_info *mtd, int page_addr) +{ + struct sh_flctl *flctl = mtd_to_flctl(mtd); + int page_sectors = flctl->page_size ? 4 : 1; + int i; + + set_cmd_regs(mtd, NAND_CMD_READ0, + (NAND_CMD_READSTART << 8) | NAND_CMD_READ0); + + empty_fifo(flctl); + + for (i = 0; i < page_sectors; i++) { + set_addr(mtd, (512 + 16) * i + 512 , page_addr); + writel(16, FLDTCNTR(flctl)); + + start_translation(flctl); + read_fiforeg(flctl, 16, 16 * i); + wait_completion(flctl); + } +} + +static void execmd_write_page_sector(struct mtd_info *mtd) +{ + struct sh_flctl *flctl = mtd_to_flctl(mtd); + int page_addr = flctl->seqin_page_addr; + int sector, page_sectors; + + page_sectors = flctl->page_size ? 4 : 1; + + set_cmd_regs(mtd, NAND_CMD_PAGEPROG, + (NAND_CMD_PAGEPROG << 8) | NAND_CMD_SEQIN); + + empty_fifo(flctl); + writel(readl(FLCMNCR(flctl)) | ACM_SACCES_MODE, FLCMNCR(flctl)); + writel(readl(FLCMDCR(flctl)) | page_sectors, FLCMDCR(flctl)); + writel(page_addr << 2, FLADR(flctl)); + start_translation(flctl); + + for (sector = 0; sector < page_sectors; sector++) { + write_fiforeg(flctl, 512, 512 * sector); + write_ec_fiforeg(flctl, 16, mtd->writesize + 16 * sector); + } + + wait_completion(flctl); + writel(readl(FLCMNCR(flctl)) & ~ACM_SACCES_MODE, FLCMNCR(flctl)); +} + +static void execmd_write_oob(struct mtd_info *mtd) +{ + struct sh_flctl *flctl = mtd_to_flctl(mtd); + int page_addr = flctl->seqin_page_addr; + int sector, page_sectors; + + page_sectors = flctl->page_size ? 4 : 1; + + set_cmd_regs(mtd, NAND_CMD_PAGEPROG, + (NAND_CMD_PAGEPROG << 8) | NAND_CMD_SEQIN); + + for (sector = 0; sector < page_sectors; sector++) { + empty_fifo(flctl); + set_addr(mtd, sector * 528 + 512, page_addr); + writel(16, FLDTCNTR(flctl)); /* set read size */ + + start_translation(flctl); + write_fiforeg(flctl, 16, 16 * sector); + wait_completion(flctl); + } +} + +static void flctl_cmdfunc(struct mtd_info *mtd, unsigned int command, + int column, int page_addr) +{ + struct sh_flctl *flctl = mtd_to_flctl(mtd); + uint32_t read_cmd = 0; + + pm_runtime_get_sync(&flctl->pdev->dev); + + flctl->read_bytes = 0; + if (command != NAND_CMD_PAGEPROG) + flctl->index = 0; + + switch (command) { + case NAND_CMD_READ1: + case NAND_CMD_READ0: + if (flctl->hwecc) { + /* read page with hwecc */ + execmd_read_page_sector(mtd, page_addr); + break; + } + if (flctl->page_size) + set_cmd_regs(mtd, command, (NAND_CMD_READSTART << 8) + | command); + else + set_cmd_regs(mtd, command, command); + + set_addr(mtd, 0, page_addr); + + flctl->read_bytes = mtd->writesize + mtd->oobsize; + if (flctl->chip.options & NAND_BUSWIDTH_16) + column >>= 1; + flctl->index += column; + goto read_normal_exit; + + case NAND_CMD_READOOB: + if (flctl->hwecc) { + /* read page with hwecc */ + execmd_read_oob(mtd, page_addr); + break; + } + + if (flctl->page_size) { + set_cmd_regs(mtd, command, (NAND_CMD_READSTART << 8) + | NAND_CMD_READ0); + set_addr(mtd, mtd->writesize, page_addr); + } else { + set_cmd_regs(mtd, command, command); + set_addr(mtd, 0, page_addr); + } + flctl->read_bytes = mtd->oobsize; + goto read_normal_exit; + + case NAND_CMD_RNDOUT: + if (flctl->hwecc) + break; + + if (flctl->page_size) + set_cmd_regs(mtd, command, (NAND_CMD_RNDOUTSTART << 8) + | command); + else + set_cmd_regs(mtd, command, command); + + set_addr(mtd, column, 0); + + flctl->read_bytes = mtd->writesize + mtd->oobsize - column; + goto read_normal_exit; + + case NAND_CMD_READID: + set_cmd_regs(mtd, command, command); + + /* READID is always performed using an 8-bit bus */ + if (flctl->chip.options & NAND_BUSWIDTH_16) + column <<= 1; + set_addr(mtd, column, 0); + + flctl->read_bytes = 8; + writel(flctl->read_bytes, FLDTCNTR(flctl)); /* set read size */ + empty_fifo(flctl); + start_translation(flctl); + read_fiforeg(flctl, flctl->read_bytes, 0); + wait_completion(flctl); + break; + + case NAND_CMD_ERASE1: + flctl->erase1_page_addr = page_addr; + break; + + case NAND_CMD_ERASE2: + set_cmd_regs(mtd, NAND_CMD_ERASE1, + (command << 8) | NAND_CMD_ERASE1); + set_addr(mtd, -1, flctl->erase1_page_addr); + start_translation(flctl); + wait_completion(flctl); + break; + + case NAND_CMD_SEQIN: + if (!flctl->page_size) { + /* output read command */ + if (column >= mtd->writesize) { + column -= mtd->writesize; + read_cmd = NAND_CMD_READOOB; + } else if (column < 256) { + read_cmd = NAND_CMD_READ0; + } else { + column -= 256; + read_cmd = NAND_CMD_READ1; + } + } + flctl->seqin_column = column; + flctl->seqin_page_addr = page_addr; + flctl->seqin_read_cmd = read_cmd; + break; + + case NAND_CMD_PAGEPROG: + empty_fifo(flctl); + if (!flctl->page_size) { + set_cmd_regs(mtd, NAND_CMD_SEQIN, + flctl->seqin_read_cmd); + set_addr(mtd, -1, -1); + writel(0, FLDTCNTR(flctl)); /* set 0 size */ + start_translation(flctl); + wait_completion(flctl); + } + if (flctl->hwecc) { + /* write page with hwecc */ + if (flctl->seqin_column == mtd->writesize) + execmd_write_oob(mtd); + else if (!flctl->seqin_column) + execmd_write_page_sector(mtd); + else + printk(KERN_ERR "Invalid address !?\n"); + break; + } + set_cmd_regs(mtd, command, (command << 8) | NAND_CMD_SEQIN); + set_addr(mtd, flctl->seqin_column, flctl->seqin_page_addr); + writel(flctl->index, FLDTCNTR(flctl)); /* set write size */ + start_translation(flctl); + write_fiforeg(flctl, flctl->index, 0); + wait_completion(flctl); + break; + + case NAND_CMD_STATUS: + set_cmd_regs(mtd, command, command); + set_addr(mtd, -1, -1); + + flctl->read_bytes = 1; + writel(flctl->read_bytes, FLDTCNTR(flctl)); /* set read size */ + start_translation(flctl); + read_datareg(flctl, 0); /* read and end */ + break; + + case NAND_CMD_RESET: + set_cmd_regs(mtd, command, command); + set_addr(mtd, -1, -1); + + writel(0, FLDTCNTR(flctl)); /* set 0 size */ + start_translation(flctl); + wait_completion(flctl); + break; + + default: + break; + } + goto runtime_exit; + +read_normal_exit: + writel(flctl->read_bytes, FLDTCNTR(flctl)); /* set read size */ + empty_fifo(flctl); + start_translation(flctl); + read_fiforeg(flctl, flctl->read_bytes, 0); + wait_completion(flctl); +runtime_exit: + pm_runtime_put_sync(&flctl->pdev->dev); + return; +} + +static void flctl_select_chip(struct mtd_info *mtd, int chipnr) +{ + struct sh_flctl *flctl = mtd_to_flctl(mtd); + int ret; + + switch (chipnr) { + case -1: + flctl->flcmncr_base &= ~CE0_ENABLE; + + pm_runtime_get_sync(&flctl->pdev->dev); + writel(flctl->flcmncr_base, FLCMNCR(flctl)); + + if (flctl->qos_request) { + dev_pm_qos_remove_request(&flctl->pm_qos); + flctl->qos_request = 0; + } + + pm_runtime_put_sync(&flctl->pdev->dev); + break; + case 0: + flctl->flcmncr_base |= CE0_ENABLE; + + if (!flctl->qos_request) { + ret = dev_pm_qos_add_request(&flctl->pdev->dev, + &flctl->pm_qos, + DEV_PM_QOS_RESUME_LATENCY, + 100); + if (ret < 0) + dev_err(&flctl->pdev->dev, + "PM QoS request failed: %d\n", ret); + flctl->qos_request = 1; + } + + if (flctl->holden) { + pm_runtime_get_sync(&flctl->pdev->dev); + writel(HOLDEN, FLHOLDCR(flctl)); + pm_runtime_put_sync(&flctl->pdev->dev); + } + break; + default: + BUG(); + } +} + +static void flctl_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) +{ + struct sh_flctl *flctl = mtd_to_flctl(mtd); + + memcpy(&flctl->done_buff[flctl->index], buf, len); + flctl->index += len; +} + +static uint8_t flctl_read_byte(struct mtd_info *mtd) +{ + struct sh_flctl *flctl = mtd_to_flctl(mtd); + uint8_t data; + + data = flctl->done_buff[flctl->index]; + flctl->index++; + return data; +} + +static uint16_t flctl_read_word(struct mtd_info *mtd) +{ + struct sh_flctl *flctl = mtd_to_flctl(mtd); + uint16_t *buf = (uint16_t *)&flctl->done_buff[flctl->index]; + + flctl->index += 2; + return *buf; +} + +static void flctl_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + struct sh_flctl *flctl = mtd_to_flctl(mtd); + + memcpy(buf, &flctl->done_buff[flctl->index], len); + flctl->index += len; +} + +static int flctl_chip_init_tail(struct mtd_info *mtd) +{ + struct sh_flctl *flctl = mtd_to_flctl(mtd); + struct nand_chip *chip = &flctl->chip; + + if (mtd->writesize == 512) { + flctl->page_size = 0; + if (chip->chipsize > (32 << 20)) { + /* big than 32MB */ + flctl->rw_ADRCNT = ADRCNT_4; + flctl->erase_ADRCNT = ADRCNT_3; + } else if (chip->chipsize > (2 << 16)) { + /* big than 128KB */ + flctl->rw_ADRCNT = ADRCNT_3; + flctl->erase_ADRCNT = ADRCNT_2; + } else { + flctl->rw_ADRCNT = ADRCNT_2; + flctl->erase_ADRCNT = ADRCNT_1; + } + } else { + flctl->page_size = 1; + if (chip->chipsize > (128 << 20)) { + /* big than 128MB */ + flctl->rw_ADRCNT = ADRCNT2_E; + flctl->erase_ADRCNT = ADRCNT_3; + } else if (chip->chipsize > (8 << 16)) { + /* big than 512KB */ + flctl->rw_ADRCNT = ADRCNT_4; + flctl->erase_ADRCNT = ADRCNT_2; + } else { + flctl->rw_ADRCNT = ADRCNT_3; + flctl->erase_ADRCNT = ADRCNT_1; + } + } + + if (flctl->hwecc) { + if (mtd->writesize == 512) { + chip->ecc.layout = &flctl_4secc_oob_16; + chip->badblock_pattern = &flctl_4secc_smallpage; + } else { + chip->ecc.layout = &flctl_4secc_oob_64; + chip->badblock_pattern = &flctl_4secc_largepage; + } + + chip->ecc.size = 512; + chip->ecc.bytes = 10; + chip->ecc.strength = 4; + chip->ecc.read_page = flctl_read_page_hwecc; + chip->ecc.write_page = flctl_write_page_hwecc; + chip->ecc.mode = NAND_ECC_HW; + + /* 4 symbols ECC enabled */ + flctl->flcmncr_base |= _4ECCEN; + } else { + chip->ecc.mode = NAND_ECC_SOFT; + } + + return 0; +} + +static irqreturn_t flctl_handle_flste(int irq, void *dev_id) +{ + struct sh_flctl *flctl = dev_id; + + dev_err(&flctl->pdev->dev, "flste irq: %x\n", readl(FLINTDMACR(flctl))); + writel(flctl->flintdmacr_base, FLINTDMACR(flctl)); + + return IRQ_HANDLED; +} + +struct flctl_soc_config { + unsigned long flcmncr_val; + unsigned has_hwecc:1; + unsigned use_holden:1; +}; + +static struct flctl_soc_config flctl_sh7372_config = { + .flcmncr_val = CLK_16B_12L_4H | TYPESEL_SET | SHBUSSEL, + .has_hwecc = 1, + .use_holden = 1, +}; + +static const struct of_device_id of_flctl_match[] = { + { .compatible = "renesas,shmobile-flctl-sh7372", + .data = &flctl_sh7372_config }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_flctl_match); + +static struct sh_flctl_platform_data *flctl_parse_dt(struct device *dev) +{ + const struct of_device_id *match; + struct flctl_soc_config *config; + struct sh_flctl_platform_data *pdata; + struct device_node *dn = dev->of_node; + int ret; + + match = of_match_device(of_flctl_match, dev); + if (match) + config = (struct flctl_soc_config *)match->data; + else { + dev_err(dev, "%s: no OF configuration attached\n", __func__); + return NULL; + } + + pdata = devm_kzalloc(dev, sizeof(struct sh_flctl_platform_data), + GFP_KERNEL); + if (!pdata) + return NULL; + + /* set SoC specific options */ + pdata->flcmncr_val = config->flcmncr_val; + pdata->has_hwecc = config->has_hwecc; + pdata->use_holden = config->use_holden; + + /* parse user defined options */ + ret = of_get_nand_bus_width(dn); + if (ret == 16) + pdata->flcmncr_val |= SEL_16BIT; + else if (ret != 8) { + dev_err(dev, "%s: invalid bus width\n", __func__); + return NULL; + } + + return pdata; +} + +static int flctl_probe(struct platform_device *pdev) +{ + struct resource *res; + struct sh_flctl *flctl; + struct mtd_info *flctl_mtd; + struct nand_chip *nand; + struct sh_flctl_platform_data *pdata; + int ret; + int irq; + struct mtd_part_parser_data ppdata = {}; + + flctl = devm_kzalloc(&pdev->dev, sizeof(struct sh_flctl), GFP_KERNEL); + if (!flctl) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + flctl->reg = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(flctl->reg)) + return PTR_ERR(flctl->reg); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "failed to get flste irq data\n"); + return -ENXIO; + } + + ret = devm_request_irq(&pdev->dev, irq, flctl_handle_flste, IRQF_SHARED, + "flste", flctl); + if (ret) { + dev_err(&pdev->dev, "request interrupt failed.\n"); + return ret; + } + + if (pdev->dev.of_node) + pdata = flctl_parse_dt(&pdev->dev); + else + pdata = dev_get_platdata(&pdev->dev); + + if (!pdata) { + dev_err(&pdev->dev, "no setup data defined\n"); + return -EINVAL; + } + + platform_set_drvdata(pdev, flctl); + flctl_mtd = &flctl->mtd; + nand = &flctl->chip; + flctl_mtd->priv = nand; + flctl->pdev = pdev; + flctl->hwecc = pdata->has_hwecc; + flctl->holden = pdata->use_holden; + flctl->flcmncr_base = pdata->flcmncr_val; + flctl->flintdmacr_base = flctl->hwecc ? (STERINTE | ECERB) : STERINTE; + + /* Set address of hardware control function */ + /* 20 us command delay time */ + nand->chip_delay = 20; + + nand->read_byte = flctl_read_byte; + nand->write_buf = flctl_write_buf; + nand->read_buf = flctl_read_buf; + nand->select_chip = flctl_select_chip; + nand->cmdfunc = flctl_cmdfunc; + + if (pdata->flcmncr_val & SEL_16BIT) { + nand->options |= NAND_BUSWIDTH_16; + nand->read_word = flctl_read_word; + } + + pm_runtime_enable(&pdev->dev); + pm_runtime_resume(&pdev->dev); + + flctl_setup_dma(flctl); + + ret = nand_scan_ident(flctl_mtd, 1, NULL); + if (ret) + goto err_chip; + + ret = flctl_chip_init_tail(flctl_mtd); + if (ret) + goto err_chip; + + ret = nand_scan_tail(flctl_mtd); + if (ret) + goto err_chip; + + ppdata.of_node = pdev->dev.of_node; + ret = mtd_device_parse_register(flctl_mtd, NULL, &ppdata, pdata->parts, + pdata->nr_parts); + + return 0; + +err_chip: + flctl_release_dma(flctl); + pm_runtime_disable(&pdev->dev); + return ret; +} + +static int flctl_remove(struct platform_device *pdev) +{ + struct sh_flctl *flctl = platform_get_drvdata(pdev); + + flctl_release_dma(flctl); + nand_release(&flctl->mtd); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static struct platform_driver flctl_driver = { + .remove = flctl_remove, + .driver = { + .name = "sh_flctl", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(of_flctl_match), + }, +}; + +module_platform_driver_probe(flctl_driver, flctl_probe); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yoshihiro Shimoda"); +MODULE_DESCRIPTION("SuperH FLCTL driver"); +MODULE_ALIAS("platform:sh_flctl"); diff --git a/drivers/mtd/nand/sharpsl.c b/drivers/mtd/nand/sharpsl.c index 51c7288ab49..e81059b5838 100644 --- a/drivers/mtd/nand/sharpsl.c +++ b/drivers/mtd/nand/sharpsl.c @@ -2,8 +2,7 @@ * drivers/mtd/nand/sharpsl.c * * Copyright (C) 2004 Richard Purdie - * - * $Id: sharpsl.c,v 1.7 2005/11/07 11:14:31 gleixner Exp $ + * Copyright (C) 2008 Dmitry Baryshkov * * Based on Sharp's NAND driver sharp_sl.c * @@ -21,22 +20,31 @@ #include <linux/mtd/nand.h> #include <linux/mtd/nand_ecc.h> #include <linux/mtd/partitions.h> +#include <linux/mtd/sharpsl.h> #include <linux/interrupt.h> +#include <linux/platform_device.h> + #include <asm/io.h> -#include <asm/hardware.h> +#include <mach/hardware.h> #include <asm/mach-types.h> -static void __iomem *sharpsl_io_base; -static int sharpsl_phys_base = 0x0C000000; +struct sharpsl_nand { + struct mtd_info mtd; + struct nand_chip chip; + + void __iomem *io; +}; + +#define mtd_to_sharpsl(_mtd) container_of(_mtd, struct sharpsl_nand, mtd) /* register offset */ -#define ECCLPLB sharpsl_io_base+0x00 /* line parity 7 - 0 bit */ -#define ECCLPUB sharpsl_io_base+0x04 /* line parity 15 - 8 bit */ -#define ECCCP sharpsl_io_base+0x08 /* column parity 5 - 0 bit */ -#define ECCCNTR sharpsl_io_base+0x0C /* ECC byte counter */ -#define ECCCLRR sharpsl_io_base+0x10 /* cleare ECC */ -#define FLASHIO sharpsl_io_base+0x14 /* Flash I/O */ -#define FLASHCTL sharpsl_io_base+0x18 /* Flash Control */ +#define ECCLPLB 0x00 /* line parity 7 - 0 bit */ +#define ECCLPUB 0x04 /* line parity 15 - 8 bit */ +#define ECCCP 0x08 /* column parity 5 - 0 bit */ +#define ECCCNTR 0x0C /* ECC byte counter */ +#define ECCCLRR 0x10 /* cleare ECC */ +#define FLASHIO 0x14 /* Flash I/O */ +#define FLASHCTL 0x18 /* Flash Control */ /* Flash control bit */ #define FLRYBY (1 << 5) @@ -47,35 +55,6 @@ static int sharpsl_phys_base = 0x0C000000; #define FLCE0 (1 << 0) /* - * MTD structure for SharpSL - */ -static struct mtd_info *sharpsl_mtd = NULL; - -/* - * Define partitions for flash device - */ -#define DEFAULT_NUM_PARTITIONS 3 - -static int nr_partitions; -static struct mtd_partition sharpsl_nand_default_partition_info[] = { - { - .name = "System Area", - .offset = 0, - .size = 7 * 1024 * 1024, - }, - { - .name = "Root Filesystem", - .offset = 7 * 1024 * 1024, - .size = 30 * 1024 * 1024, - }, - { - .name = "Home Filesystem", - .offset = MTDPART_OFS_APPEND, - .size = MTDPART_SIZ_FULL, - }, -}; - -/* * hardware specific access to control-lines * ctrl: * NAND_CNE: bit 0 -> ! bit 0 & 4 @@ -86,6 +65,7 @@ static struct mtd_partition sharpsl_nand_default_partition_info[] = { static void sharpsl_nand_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl) { + struct sharpsl_nand *sharpsl = mtd_to_sharpsl(mtd); struct nand_chip *chip = mtd->priv; if (ctrl & NAND_CTRL_CHANGE) { @@ -95,103 +75,87 @@ static void sharpsl_nand_hwcontrol(struct mtd_info *mtd, int cmd, bits ^= 0x11; - writeb((readb(FLASHCTL) & ~0x17) | bits, FLASHCTL); + writeb((readb(sharpsl->io + FLASHCTL) & ~0x17) | bits, sharpsl->io + FLASHCTL); } if (cmd != NAND_CMD_NONE) writeb(cmd, chip->IO_ADDR_W); } -static uint8_t scan_ff_pattern[] = { 0xff, 0xff }; - -static struct nand_bbt_descr sharpsl_bbt = { - .options = 0, - .offs = 4, - .len = 2, - .pattern = scan_ff_pattern -}; - -static struct nand_bbt_descr sharpsl_akita_bbt = { - .options = 0, - .offs = 4, - .len = 1, - .pattern = scan_ff_pattern -}; - -static struct nand_ecclayout akita_oobinfo = { - .eccbytes = 24, - .eccpos = { - 0x5, 0x1, 0x2, 0x3, 0x6, 0x7, 0x15, 0x11, - 0x12, 0x13, 0x16, 0x17, 0x25, 0x21, 0x22, 0x23, - 0x26, 0x27, 0x35, 0x31, 0x32, 0x33, 0x36, 0x37}, - .oobfree = {{0x08, 0x09}} -}; - static int sharpsl_nand_dev_ready(struct mtd_info *mtd) { - return !((readb(FLASHCTL) & FLRYBY) == 0); + struct sharpsl_nand *sharpsl = mtd_to_sharpsl(mtd); + return !((readb(sharpsl->io + FLASHCTL) & FLRYBY) == 0); } static void sharpsl_nand_enable_hwecc(struct mtd_info *mtd, int mode) { - writeb(0, ECCCLRR); + struct sharpsl_nand *sharpsl = mtd_to_sharpsl(mtd); + writeb(0, sharpsl->io + ECCCLRR); } static int sharpsl_nand_calculate_ecc(struct mtd_info *mtd, const u_char * dat, u_char * ecc_code) { - ecc_code[0] = ~readb(ECCLPUB); - ecc_code[1] = ~readb(ECCLPLB); - ecc_code[2] = (~readb(ECCCP) << 2) | 0x03; - return readb(ECCCNTR) != 0; + struct sharpsl_nand *sharpsl = mtd_to_sharpsl(mtd); + ecc_code[0] = ~readb(sharpsl->io + ECCLPUB); + ecc_code[1] = ~readb(sharpsl->io + ECCLPLB); + ecc_code[2] = (~readb(sharpsl->io + ECCCP) << 2) | 0x03; + return readb(sharpsl->io + ECCCNTR) != 0; } -#ifdef CONFIG_MTD_PARTITIONS -const char *part_probes[] = { "cmdlinepart", NULL }; -#endif - /* * Main initialization routine */ -static int __init sharpsl_nand_init(void) +static int sharpsl_nand_probe(struct platform_device *pdev) { struct nand_chip *this; - struct mtd_partition *sharpsl_partition_info; + struct resource *r; int err = 0; + struct sharpsl_nand *sharpsl; + struct sharpsl_nand_platform_data *data = dev_get_platdata(&pdev->dev); + + if (!data) { + dev_err(&pdev->dev, "no platform data!\n"); + return -EINVAL; + } /* Allocate memory for MTD device structure and private data */ - sharpsl_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL); - if (!sharpsl_mtd) { - printk("Unable to allocate SharpSL NAND MTD device structure.\n"); + sharpsl = kzalloc(sizeof(struct sharpsl_nand), GFP_KERNEL); + if (!sharpsl) return -ENOMEM; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "no io memory resource defined!\n"); + err = -ENODEV; + goto err_get_res; } - /* map physical adress */ - sharpsl_io_base = ioremap(sharpsl_phys_base, 0x1000); - if (!sharpsl_io_base) { - printk("ioremap to access Sharp SL NAND chip failed\n"); - kfree(sharpsl_mtd); - return -EIO; + /* map physical address */ + sharpsl->io = ioremap(r->start, resource_size(r)); + if (!sharpsl->io) { + dev_err(&pdev->dev, "ioremap to access Sharp SL NAND chip failed\n"); + err = -EIO; + goto err_ioremap; } /* Get pointer to private data */ - this = (struct nand_chip *)(&sharpsl_mtd[1]); - - /* Initialize structures */ - memset(sharpsl_mtd, 0, sizeof(struct mtd_info)); - memset(this, 0, sizeof(struct nand_chip)); + this = (struct nand_chip *)(&sharpsl->chip); /* Link the private data with the MTD structure */ - sharpsl_mtd->priv = this; - sharpsl_mtd->owner = THIS_MODULE; + sharpsl->mtd.priv = this; + sharpsl->mtd.owner = THIS_MODULE; + + platform_set_drvdata(pdev, sharpsl); /* * PXA initialize */ - writeb(readb(FLASHCTL) | FLWP, FLASHCTL); + writeb(readb(sharpsl->io + FLASHCTL) | FLWP, sharpsl->io + FLASHCTL); /* Set address of NAND IO lines */ - this->IO_ADDR_R = FLASHIO; - this->IO_ADDR_W = FLASHIO; + this->IO_ADDR_R = sharpsl->io + FLASHIO; + this->IO_ADDR_W = sharpsl->io + FLASHIO; /* Set address of hardware control function */ this->cmd_ctrl = sharpsl_nand_hwcontrol; this->dev_ready = sharpsl_nand_dev_ready; @@ -201,68 +165,68 @@ static int __init sharpsl_nand_init(void) this->ecc.mode = NAND_ECC_HW; this->ecc.size = 256; this->ecc.bytes = 3; - this->badblock_pattern = &sharpsl_bbt; - if (machine_is_akita() || machine_is_borzoi()) { - this->badblock_pattern = &sharpsl_akita_bbt; - this->ecc.layout = &akita_oobinfo; - } + this->ecc.strength = 1; + this->badblock_pattern = data->badblock_pattern; + this->ecc.layout = data->ecc_layout; this->ecc.hwctl = sharpsl_nand_enable_hwecc; this->ecc.calculate = sharpsl_nand_calculate_ecc; this->ecc.correct = nand_correct_data; /* Scan to find existence of the device */ - err = nand_scan(sharpsl_mtd, 1); - if (err) { - iounmap(sharpsl_io_base); - kfree(sharpsl_mtd); - return err; - } + err = nand_scan(&sharpsl->mtd, 1); + if (err) + goto err_scan; /* Register the partitions */ - sharpsl_mtd->name = "sharpsl-nand"; - nr_partitions = parse_mtd_partitions(sharpsl_mtd, part_probes, &sharpsl_partition_info, 0); - - if (nr_partitions <= 0) { - nr_partitions = DEFAULT_NUM_PARTITIONS; - sharpsl_partition_info = sharpsl_nand_default_partition_info; - if (machine_is_poodle()) { - sharpsl_partition_info[1].size = 22 * 1024 * 1024; - } else if (machine_is_corgi() || machine_is_shepherd()) { - sharpsl_partition_info[1].size = 25 * 1024 * 1024; - } else if (machine_is_husky()) { - sharpsl_partition_info[1].size = 53 * 1024 * 1024; - } else if (machine_is_spitz()) { - sharpsl_partition_info[1].size = 5 * 1024 * 1024; - } else if (machine_is_akita()) { - sharpsl_partition_info[1].size = 58 * 1024 * 1024; - } else if (machine_is_borzoi()) { - sharpsl_partition_info[1].size = 32 * 1024 * 1024; - } - } + sharpsl->mtd.name = "sharpsl-nand"; - add_mtd_partitions(sharpsl_mtd, sharpsl_partition_info, nr_partitions); + err = mtd_device_parse_register(&sharpsl->mtd, NULL, NULL, + data->partitions, data->nr_partitions); + if (err) + goto err_add; /* Return happy */ return 0; -} -module_init(sharpsl_nand_init); +err_add: + nand_release(&sharpsl->mtd); + +err_scan: + iounmap(sharpsl->io); +err_ioremap: +err_get_res: + kfree(sharpsl); + return err; +} /* * Clean up routine */ -static void __exit sharpsl_nand_cleanup(void) +static int sharpsl_nand_remove(struct platform_device *pdev) { + struct sharpsl_nand *sharpsl = platform_get_drvdata(pdev); + /* Release resources, unregister device */ - nand_release(sharpsl_mtd); + nand_release(&sharpsl->mtd); - iounmap(sharpsl_io_base); + iounmap(sharpsl->io); /* Free the MTD device structure */ - kfree(sharpsl_mtd); + kfree(sharpsl); + + return 0; } -module_exit(sharpsl_nand_cleanup); +static struct platform_driver sharpsl_nand_driver = { + .driver = { + .name = "sharpsl-nand", + .owner = THIS_MODULE, + }, + .probe = sharpsl_nand_probe, + .remove = sharpsl_nand_remove, +}; + +module_platform_driver(sharpsl_nand_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Richard Purdie <rpurdie@rpsys.net>"); diff --git a/drivers/mtd/nand/sm_common.c b/drivers/mtd/nand/sm_common.c new file mode 100644 index 00000000000..e06b5e5d328 --- /dev/null +++ b/drivers/mtd/nand/sm_common.c @@ -0,0 +1,141 @@ +/* + * Copyright © 2009 - Maxim Levitsky + * Common routines & support for xD format + * + * 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/kernel.h> +#include <linux/mtd/nand.h> +#include <linux/module.h> +#include <linux/sizes.h> +#include "sm_common.h" + +static struct nand_ecclayout nand_oob_sm = { + .eccbytes = 6, + .eccpos = {8, 9, 10, 13, 14, 15}, + .oobfree = { + {.offset = 0 , .length = 4}, /* reserved */ + {.offset = 6 , .length = 2}, /* LBA1 */ + {.offset = 11, .length = 2} /* LBA2 */ + } +}; + +/* NOTE: This layout is is not compatabable with SmartMedia, */ +/* because the 256 byte devices have page depenent oob layout */ +/* However it does preserve the bad block markers */ +/* If you use smftl, it will bypass this and work correctly */ +/* If you not, then you break SmartMedia compliance anyway */ + +static struct nand_ecclayout nand_oob_sm_small = { + .eccbytes = 3, + .eccpos = {0, 1, 2}, + .oobfree = { + {.offset = 3 , .length = 2}, /* reserved */ + {.offset = 6 , .length = 2}, /* LBA1 */ + } +}; + + +static int sm_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ + struct mtd_oob_ops ops; + struct sm_oob oob; + int ret; + + memset(&oob, -1, SM_OOB_SIZE); + oob.block_status = 0x0F; + + /* As long as this function is called on erase block boundaries + it will work correctly for 256 byte nand */ + ops.mode = MTD_OPS_PLACE_OOB; + ops.ooboffs = 0; + ops.ooblen = mtd->oobsize; + ops.oobbuf = (void *)&oob; + ops.datbuf = NULL; + + + ret = mtd_write_oob(mtd, ofs, &ops); + if (ret < 0 || ops.oobretlen != SM_OOB_SIZE) { + printk(KERN_NOTICE + "sm_common: can't mark sector at %i as bad\n", + (int)ofs); + return -EIO; + } + + return 0; +} + +static struct nand_flash_dev nand_smartmedia_flash_ids[] = { + LEGACY_ID_NAND("SmartMedia 2MiB 3,3V ROM", 0x5d, 2, SZ_8K, NAND_ROM), + LEGACY_ID_NAND("SmartMedia 4MiB 3,3V", 0xe3, 4, SZ_8K, 0), + LEGACY_ID_NAND("SmartMedia 4MiB 3,3/5V", 0xe5, 4, SZ_8K, 0), + LEGACY_ID_NAND("SmartMedia 4MiB 5V", 0x6b, 4, SZ_8K, 0), + LEGACY_ID_NAND("SmartMedia 4MiB 3,3V ROM", 0xd5, 4, SZ_8K, NAND_ROM), + LEGACY_ID_NAND("SmartMedia 8MiB 3,3V", 0xe6, 8, SZ_8K, 0), + LEGACY_ID_NAND("SmartMedia 8MiB 3,3V ROM", 0xd6, 8, SZ_8K, NAND_ROM), + LEGACY_ID_NAND("SmartMedia 16MiB 3,3V", 0x73, 16, SZ_16K, 0), + LEGACY_ID_NAND("SmartMedia 16MiB 3,3V ROM", 0x57, 16, SZ_16K, NAND_ROM), + LEGACY_ID_NAND("SmartMedia 32MiB 3,3V", 0x75, 32, SZ_16K, 0), + LEGACY_ID_NAND("SmartMedia 32MiB 3,3V ROM", 0x58, 32, SZ_16K, NAND_ROM), + LEGACY_ID_NAND("SmartMedia 64MiB 3,3V", 0x76, 64, SZ_16K, 0), + LEGACY_ID_NAND("SmartMedia 64MiB 3,3V ROM", 0xd9, 64, SZ_16K, NAND_ROM), + LEGACY_ID_NAND("SmartMedia 128MiB 3,3V", 0x79, 128, SZ_16K, 0), + LEGACY_ID_NAND("SmartMedia 128MiB 3,3V ROM", 0xda, 128, SZ_16K, NAND_ROM), + LEGACY_ID_NAND("SmartMedia 256MiB 3, 3V", 0x71, 256, SZ_16K, 0), + LEGACY_ID_NAND("SmartMedia 256MiB 3,3V ROM", 0x5b, 256, SZ_16K, NAND_ROM), + {NULL} +}; + +static struct nand_flash_dev nand_xd_flash_ids[] = { + LEGACY_ID_NAND("xD 16MiB 3,3V", 0x73, 16, SZ_16K, 0), + LEGACY_ID_NAND("xD 32MiB 3,3V", 0x75, 32, SZ_16K, 0), + LEGACY_ID_NAND("xD 64MiB 3,3V", 0x76, 64, SZ_16K, 0), + LEGACY_ID_NAND("xD 128MiB 3,3V", 0x79, 128, SZ_16K, 0), + LEGACY_ID_NAND("xD 256MiB 3,3V", 0x71, 256, SZ_16K, NAND_BROKEN_XD), + LEGACY_ID_NAND("xD 512MiB 3,3V", 0xdc, 512, SZ_16K, NAND_BROKEN_XD), + LEGACY_ID_NAND("xD 1GiB 3,3V", 0xd3, 1024, SZ_16K, NAND_BROKEN_XD), + LEGACY_ID_NAND("xD 2GiB 3,3V", 0xd5, 2048, SZ_16K, NAND_BROKEN_XD), + {NULL} +}; + +int sm_register_device(struct mtd_info *mtd, int smartmedia) +{ + struct nand_chip *chip = mtd->priv; + int ret; + + chip->options |= NAND_SKIP_BBTSCAN; + + /* Scan for card properties */ + ret = nand_scan_ident(mtd, 1, smartmedia ? + nand_smartmedia_flash_ids : nand_xd_flash_ids); + + if (ret) + return ret; + + /* Bad block marker position */ + chip->badblockpos = 0x05; + chip->badblockbits = 7; + chip->block_markbad = sm_block_markbad; + + /* ECC layout */ + if (mtd->writesize == SM_SECTOR_SIZE) + chip->ecc.layout = &nand_oob_sm; + else if (mtd->writesize == SM_SMALL_PAGE) + chip->ecc.layout = &nand_oob_sm_small; + else + return -ENODEV; + + ret = nand_scan_tail(mtd); + + if (ret) + return ret; + + return mtd_device_register(mtd, NULL, 0); +} +EXPORT_SYMBOL_GPL(sm_register_device); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Maxim Levitsky <maximlevitsky@gmail.com>"); +MODULE_DESCRIPTION("Common SmartMedia/xD functions"); diff --git a/drivers/mtd/nand/sm_common.h b/drivers/mtd/nand/sm_common.h new file mode 100644 index 00000000000..00f4a83359b --- /dev/null +++ b/drivers/mtd/nand/sm_common.h @@ -0,0 +1,61 @@ +/* + * Copyright © 2009 - Maxim Levitsky + * Common routines & support for SmartMedia/xD format + * + * 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/bitops.h> +#include <linux/mtd/mtd.h> + +/* Full oob structure as written on the flash */ +struct sm_oob { + uint32_t reserved; + uint8_t data_status; + uint8_t block_status; + uint8_t lba_copy1[2]; + uint8_t ecc2[3]; + uint8_t lba_copy2[2]; + uint8_t ecc1[3]; +} __attribute__((packed)); + + +/* one sector is always 512 bytes, but it can consist of two nand pages */ +#define SM_SECTOR_SIZE 512 + +/* oob area is also 16 bytes, but might be from two pages */ +#define SM_OOB_SIZE 16 + +/* This is maximum zone size, and all devices that have more that one zone + have this size */ +#define SM_MAX_ZONE_SIZE 1024 + +/* support for small page nand */ +#define SM_SMALL_PAGE 256 +#define SM_SMALL_OOB_SIZE 8 + + +extern int sm_register_device(struct mtd_info *mtd, int smartmedia); + + +static inline int sm_sector_valid(struct sm_oob *oob) +{ + return hweight16(oob->data_status) >= 5; +} + +static inline int sm_block_valid(struct sm_oob *oob) +{ + return hweight16(oob->block_status) >= 7; +} + +static inline int sm_block_erased(struct sm_oob *oob) +{ + static const uint32_t erased_pattern[4] = { + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + + /* First test for erased block */ + if (!memcmp(oob, erased_pattern, sizeof(*oob))) + return 1; + return 0; +} diff --git a/drivers/mtd/nand/socrates_nand.c b/drivers/mtd/nand/socrates_nand.c new file mode 100644 index 00000000000..fe8058a4505 --- /dev/null +++ b/drivers/mtd/nand/socrates_nand.c @@ -0,0 +1,254 @@ +/* + * 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_address.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; +} + +/* + * 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; +} + +/* + * Probe for the NAND device. + */ +static int socrates_nand_probe(struct platform_device *ofdev) +{ + struct socrates_nand_host *host; + struct mtd_info *mtd; + struct nand_chip *nand_chip; + int res; + struct mtd_part_parser_data ppdata; + + /* Allocate memory for the device structure (and zero it) */ + host = devm_kzalloc(&ofdev->dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + host->io_base = of_iomap(ofdev->dev.of_node, 0); + if (host->io_base == NULL) { + dev_err(&ofdev->dev, "ioremap failed\n"); + 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; + ppdata.of_node = ofdev->dev.of_node; + + /*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->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, NULL)) { + res = -ENXIO; + goto out; + } + + /* second phase scan */ + if (nand_scan_tail(mtd)) { + res = -ENXIO; + goto out; + } + + res = mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0); + if (!res) + return res; + + nand_release(mtd); + +out: + iounmap(host->io_base); + return res; +} + +/* + * Remove a NAND device. + */ +static int socrates_nand_remove(struct platform_device *ofdev) +{ + struct socrates_nand_host *host = dev_get_drvdata(&ofdev->dev); + struct mtd_info *mtd = &host->mtd; + + nand_release(mtd); + + iounmap(host->io_base); + + return 0; +} + +static const struct of_device_id socrates_nand_match[] = +{ + { + .compatible = "abb,socrates-nand", + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, socrates_nand_match); + +static struct platform_driver socrates_nand_driver = { + .driver = { + .name = "socrates_nand", + .owner = THIS_MODULE, + .of_match_table = socrates_nand_match, + }, + .probe = socrates_nand_probe, + .remove = socrates_nand_remove, +}; + +module_platform_driver(socrates_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ilya Yanok"); +MODULE_DESCRIPTION("NAND driver for Socrates board"); diff --git a/drivers/mtd/nand/spia.c b/drivers/mtd/nand/spia.c deleted file mode 100644 index 1f6d429b158..00000000000 --- a/drivers/mtd/nand/spia.c +++ /dev/null @@ -1,178 +0,0 @@ -/* - * drivers/mtd/nand/spia.c - * - * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com) - * - * - * 10-29-2001 TG change to support hardwarespecific access - * to controllines (due to change in nand.c) - * page_cache added - * - * $Id: spia.c,v 1.25 2005/11/07 11:14:31 gleixner Exp $ - * - * 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. - * - * Overview: - * This is a device driver for the NAND flash device found on the - * SPIA board which utilizes the Toshiba TC58V64AFT part. This is - * a 64Mibit (8MiB x 8 bits) NAND flash device. - */ - -#include <linux/kernel.h> -#include <linux/init.h> -#include <linux/slab.h> -#include <linux/module.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/nand.h> -#include <linux/mtd/partitions.h> -#include <asm/io.h> - -/* - * MTD structure for SPIA board - */ -static struct mtd_info *spia_mtd = NULL; - -/* - * Values specific to the SPIA board (used with EP7212 processor) - */ -#define SPIA_IO_BASE 0xd0000000 /* Start of EP7212 IO address space */ -#define SPIA_FIO_BASE 0xf0000000 /* Address where flash is mapped */ -#define SPIA_PEDR 0x0080 /* - * IO offset to Port E data register - * where the CLE, ALE and NCE pins - * are wired to. - */ -#define SPIA_PEDDR 0x00c0 /* - * IO offset to Port E data direction - * register so we can control the IO - * lines. - */ - -/* - * Module stuff - */ - -static int spia_io_base = SPIA_IO_BASE; -static int spia_fio_base = SPIA_FIO_BASE; -static int spia_pedr = SPIA_PEDR; -static int spia_peddr = SPIA_PEDDR; - -module_param(spia_io_base, int, 0); -module_param(spia_fio_base, int, 0); -module_param(spia_pedr, int, 0); -module_param(spia_peddr, int, 0); - -/* - * Define partitions for flash device - */ -static const struct mtd_partition partition_info[] = { - { - .name = "SPIA flash partition 1", - .offset = 0, - .size = 2 * 1024 * 1024}, - { - .name = "SPIA flash partition 2", - .offset = 2 * 1024 * 1024, - .size = 6 * 1024 * 1024} -}; - -#define NUM_PARTITIONS 2 - -/* - * hardware specific access to control-lines - * - * ctrl: - * NAND_CNE: bit 0 -> bit 2 - * NAND_CLE: bit 1 -> bit 0 - * NAND_ALE: bit 2 -> bit 1 - */ -static void spia_hwcontrol(struct mtd_info *mtd, int cmd) -{ - struct nand_chip *chip = mtd->priv; - - if (ctrl & NAND_CTRL_CHANGE) { - void __iomem *addr = spia_io_base + spia_pedr; - unsigned char bits; - - bits = (ctrl & NAND_CNE) << 2; - bits |= (ctrl & NAND_CLE | NAND_ALE) >> 1; - writeb((readb(addr) & ~0x7) | bits, addr); - } - - if (cmd != NAND_CMD_NONE) - writeb(cmd, chip->IO_ADDR_W); -} - -/* - * Main initialization routine - */ -static int __init spia_init(void) -{ - struct nand_chip *this; - - /* Allocate memory for MTD device structure and private data */ - spia_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL); - if (!spia_mtd) { - printk("Unable to allocate SPIA NAND MTD device structure.\n"); - return -ENOMEM; - } - - /* Get pointer to private data */ - this = (struct nand_chip *)(&spia_mtd[1]); - - /* Initialize structures */ - memset(spia_mtd, 0, sizeof(struct mtd_info)); - memset(this, 0, sizeof(struct nand_chip)); - - /* Link the private data with the MTD structure */ - spia_mtd->priv = this; - spia_mtd->owner = THIS_MODULE; - - /* - * Set GPIO Port E control register so that the pins are configured - * to be outputs for controlling the NAND flash. - */ - (*(volatile unsigned char *)(spia_io_base + spia_peddr)) = 0x07; - - /* Set address of NAND IO lines */ - this->IO_ADDR_R = (void __iomem *)spia_fio_base; - this->IO_ADDR_W = (void __iomem *)spia_fio_base; - /* Set address of hardware control function */ - this->cmd_ctrl = spia_hwcontrol; - /* 15 us command delay time */ - this->chip_delay = 15; - - /* Scan to find existence of the device */ - if (nand_scan(spia_mtd, 1)) { - kfree(spia_mtd); - return -ENXIO; - } - - /* Register the partitions */ - add_mtd_partitions(spia_mtd, partition_info, NUM_PARTITIONS); - - /* Return happy */ - return 0; -} - -module_init(spia_init); - -/* - * Clean up routine - */ -static void __exit spia_cleanup(void) -{ - /* Release resources, unregister device */ - nand_release(spia_mtd); - - /* Free the MTD device structure */ - kfree(spia_mtd); -} - -module_exit(spia_cleanup); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Steven J. Hill <sjhill@realitydiluted.com"); -MODULE_DESCRIPTION("Board-specific glue layer for NAND flash on SPIA board"); diff --git a/drivers/mtd/nand/tmio_nand.c b/drivers/mtd/nand/tmio_nand.c new file mode 100644 index 00000000000..fb8fd35fa66 --- /dev/null +++ b/drivers/mtd/nand/tmio_nand.c @@ -0,0 +1,508 @@ +/* + * Toshiba TMIO NAND flash controller driver + * + * Slightly murky pre-git history of the driver: + * + * Copyright (c) Ian Molton 2004, 2005, 2008 + * Original work, independent of sharps code. Included hardware ECC support. + * Hard ECC did not work for writes in the early revisions. + * Copyright (c) Dirk Opfer 2005. + * Modifications developed from sharps code but + * NOT containing any, ported onto Ians base. + * Copyright (c) Chris Humbert 2005 + * Copyright (c) Dmitry Baryshkov 2008 + * Minor fixes + * + * Parts copyright Sebastian Carlier + * + * This file is licensed under + * the terms of the GNU General Public License version 2. This program + * is licensed "as is" without any warranty of any kind, whether express + * or implied. + * + */ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tmio.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/nand_ecc.h> +#include <linux/mtd/partitions.h> +#include <linux/slab.h> + +/*--------------------------------------------------------------------------*/ + +/* + * NAND Flash Host Controller Configuration Register + */ +#define CCR_COMMAND 0x04 /* w Command */ +#define CCR_BASE 0x10 /* l NAND Flash Control Reg Base Addr */ +#define CCR_INTP 0x3d /* b Interrupt Pin */ +#define CCR_INTE 0x48 /* b Interrupt Enable */ +#define CCR_EC 0x4a /* b Event Control */ +#define CCR_ICC 0x4c /* b Internal Clock Control */ +#define CCR_ECCC 0x5b /* b ECC Control */ +#define CCR_NFTC 0x60 /* b NAND Flash Transaction Control */ +#define CCR_NFM 0x61 /* b NAND Flash Monitor */ +#define CCR_NFPSC 0x62 /* b NAND Flash Power Supply Control */ +#define CCR_NFDC 0x63 /* b NAND Flash Detect Control */ + +/* + * NAND Flash Control Register + */ +#define FCR_DATA 0x00 /* bwl Data Register */ +#define FCR_MODE 0x04 /* b Mode Register */ +#define FCR_STATUS 0x05 /* b Status Register */ +#define FCR_ISR 0x06 /* b Interrupt Status Register */ +#define FCR_IMR 0x07 /* b Interrupt Mask Register */ + +/* FCR_MODE Register Command List */ +#define FCR_MODE_DATA 0x94 /* Data Data_Mode */ +#define FCR_MODE_COMMAND 0x95 /* Data Command_Mode */ +#define FCR_MODE_ADDRESS 0x96 /* Data Address_Mode */ + +#define FCR_MODE_HWECC_CALC 0xB4 /* HW-ECC Data */ +#define FCR_MODE_HWECC_RESULT 0xD4 /* HW-ECC Calc result Read_Mode */ +#define FCR_MODE_HWECC_RESET 0xF4 /* HW-ECC Reset */ + +#define FCR_MODE_POWER_ON 0x0C /* Power Supply ON to SSFDC card */ +#define FCR_MODE_POWER_OFF 0x08 /* Power Supply OFF to SSFDC card */ + +#define FCR_MODE_LED_OFF 0x00 /* LED OFF */ +#define FCR_MODE_LED_ON 0x04 /* LED ON */ + +#define FCR_MODE_EJECT_ON 0x68 /* Ejection events active */ +#define FCR_MODE_EJECT_OFF 0x08 /* Ejection events ignored */ + +#define FCR_MODE_LOCK 0x6C /* Lock_Mode. Eject Switch Invalid */ +#define FCR_MODE_UNLOCK 0x0C /* UnLock_Mode. Eject Switch is valid */ + +#define FCR_MODE_CONTROLLER_ID 0x40 /* Controller ID Read */ +#define FCR_MODE_STANDBY 0x00 /* SSFDC card Changes Standby State */ + +#define FCR_MODE_WE 0x80 +#define FCR_MODE_ECC1 0x40 +#define FCR_MODE_ECC0 0x20 +#define FCR_MODE_CE 0x10 +#define FCR_MODE_PCNT1 0x08 +#define FCR_MODE_PCNT0 0x04 +#define FCR_MODE_ALE 0x02 +#define FCR_MODE_CLE 0x01 + +#define FCR_STATUS_BUSY 0x80 + +/*--------------------------------------------------------------------------*/ + +struct tmio_nand { + struct mtd_info mtd; + struct nand_chip chip; + + struct platform_device *dev; + + void __iomem *ccr; + void __iomem *fcr; + unsigned long fcr_base; + + unsigned int irq; + + /* for tmio_nand_read_byte */ + u8 read; + unsigned read_good:1; +}; + +#define mtd_to_tmio(m) container_of(m, struct tmio_nand, mtd) + + +/*--------------------------------------------------------------------------*/ + +static void tmio_nand_hwcontrol(struct mtd_info *mtd, int cmd, + unsigned int ctrl) +{ + struct tmio_nand *tmio = mtd_to_tmio(mtd); + struct nand_chip *chip = mtd->priv; + + if (ctrl & NAND_CTRL_CHANGE) { + u8 mode; + + if (ctrl & NAND_NCE) { + mode = FCR_MODE_DATA; + + if (ctrl & NAND_CLE) + mode |= FCR_MODE_CLE; + else + mode &= ~FCR_MODE_CLE; + + if (ctrl & NAND_ALE) + mode |= FCR_MODE_ALE; + else + mode &= ~FCR_MODE_ALE; + } else { + mode = FCR_MODE_STANDBY; + } + + tmio_iowrite8(mode, tmio->fcr + FCR_MODE); + tmio->read_good = 0; + } + + if (cmd != NAND_CMD_NONE) + tmio_iowrite8(cmd, chip->IO_ADDR_W); +} + +static int tmio_nand_dev_ready(struct mtd_info *mtd) +{ + struct tmio_nand *tmio = mtd_to_tmio(mtd); + + return !(tmio_ioread8(tmio->fcr + FCR_STATUS) & FCR_STATUS_BUSY); +} + +static irqreturn_t tmio_irq(int irq, void *__tmio) +{ + struct tmio_nand *tmio = __tmio; + struct nand_chip *nand_chip = &tmio->chip; + + /* disable RDYREQ interrupt */ + tmio_iowrite8(0x00, tmio->fcr + FCR_IMR); + + if (unlikely(!waitqueue_active(&nand_chip->controller->wq))) + dev_warn(&tmio->dev->dev, "spurious interrupt\n"); + + wake_up(&nand_chip->controller->wq); + return IRQ_HANDLED; +} + +/* + *The TMIO core has a RDYREQ interrupt on the posedge of #SMRB. + *This interrupt is normally disabled, but for long operations like + *erase and write, we enable it to wake us up. The irq handler + *disables the interrupt. + */ +static int +tmio_nand_wait(struct mtd_info *mtd, struct nand_chip *nand_chip) +{ + struct tmio_nand *tmio = mtd_to_tmio(mtd); + long timeout; + + /* enable RDYREQ interrupt */ + tmio_iowrite8(0x0f, tmio->fcr + FCR_ISR); + tmio_iowrite8(0x81, tmio->fcr + FCR_IMR); + + timeout = wait_event_timeout(nand_chip->controller->wq, + tmio_nand_dev_ready(mtd), + msecs_to_jiffies(nand_chip->state == FL_ERASING ? 400 : 20)); + + if (unlikely(!tmio_nand_dev_ready(mtd))) { + tmio_iowrite8(0x00, tmio->fcr + FCR_IMR); + dev_warn(&tmio->dev->dev, "still busy with %s after %d ms\n", + nand_chip->state == FL_ERASING ? "erase" : "program", + nand_chip->state == FL_ERASING ? 400 : 20); + + } else if (unlikely(!timeout)) { + tmio_iowrite8(0x00, tmio->fcr + FCR_IMR); + dev_warn(&tmio->dev->dev, "timeout waiting for interrupt\n"); + } + + nand_chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); + return nand_chip->read_byte(mtd); +} + +/* + *The TMIO controller combines two 8-bit data bytes into one 16-bit + *word. This function separates them so nand_base.c works as expected, + *especially its NAND_CMD_READID routines. + * + *To prevent stale data from being read, tmio_nand_hwcontrol() clears + *tmio->read_good. + */ +static u_char tmio_nand_read_byte(struct mtd_info *mtd) +{ + struct tmio_nand *tmio = mtd_to_tmio(mtd); + unsigned int data; + + if (tmio->read_good--) + return tmio->read; + + data = tmio_ioread16(tmio->fcr + FCR_DATA); + tmio->read = data >> 8; + return data; +} + +/* + *The TMIO controller converts an 8-bit NAND interface to a 16-bit + *bus interface, so all data reads and writes must be 16-bit wide. + *Thus, we implement 16-bit versions of the read, write, and verify + *buffer functions. + */ +static void +tmio_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len) +{ + struct tmio_nand *tmio = mtd_to_tmio(mtd); + + tmio_iowrite16_rep(tmio->fcr + FCR_DATA, buf, len >> 1); +} + +static void tmio_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len) +{ + struct tmio_nand *tmio = mtd_to_tmio(mtd); + + tmio_ioread16_rep(tmio->fcr + FCR_DATA, buf, len >> 1); +} + +static void tmio_nand_enable_hwecc(struct mtd_info *mtd, int mode) +{ + struct tmio_nand *tmio = mtd_to_tmio(mtd); + + tmio_iowrite8(FCR_MODE_HWECC_RESET, tmio->fcr + FCR_MODE); + tmio_ioread8(tmio->fcr + FCR_DATA); /* dummy read */ + tmio_iowrite8(FCR_MODE_HWECC_CALC, tmio->fcr + FCR_MODE); +} + +static int tmio_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, + u_char *ecc_code) +{ + struct tmio_nand *tmio = mtd_to_tmio(mtd); + unsigned int ecc; + + tmio_iowrite8(FCR_MODE_HWECC_RESULT, tmio->fcr + FCR_MODE); + + ecc = tmio_ioread16(tmio->fcr + FCR_DATA); + ecc_code[1] = ecc; /* 000-255 LP7-0 */ + ecc_code[0] = ecc >> 8; /* 000-255 LP15-8 */ + ecc = tmio_ioread16(tmio->fcr + FCR_DATA); + ecc_code[2] = ecc; /* 000-255 CP5-0,11b */ + ecc_code[4] = ecc >> 8; /* 256-511 LP7-0 */ + ecc = tmio_ioread16(tmio->fcr + FCR_DATA); + ecc_code[3] = ecc; /* 256-511 LP15-8 */ + ecc_code[5] = ecc >> 8; /* 256-511 CP5-0,11b */ + + tmio_iowrite8(FCR_MODE_DATA, tmio->fcr + FCR_MODE); + return 0; +} + +static int tmio_nand_correct_data(struct mtd_info *mtd, unsigned char *buf, + unsigned char *read_ecc, unsigned char *calc_ecc) +{ + int r0, r1; + + /* assume ecc.size = 512 and ecc.bytes = 6 */ + r0 = __nand_correct_data(buf, read_ecc, calc_ecc, 256); + if (r0 < 0) + return r0; + r1 = __nand_correct_data(buf + 256, read_ecc + 3, calc_ecc + 3, 256); + if (r1 < 0) + return r1; + return r0 + r1; +} + +static int tmio_hw_init(struct platform_device *dev, struct tmio_nand *tmio) +{ + const struct mfd_cell *cell = mfd_get_cell(dev); + int ret; + + if (cell->enable) { + ret = cell->enable(dev); + if (ret) + return ret; + } + + /* (4Ch) CLKRUN Enable 1st spcrunc */ + tmio_iowrite8(0x81, tmio->ccr + CCR_ICC); + + /* (10h)BaseAddress 0x1000 spba.spba2 */ + tmio_iowrite16(tmio->fcr_base, tmio->ccr + CCR_BASE); + tmio_iowrite16(tmio->fcr_base >> 16, tmio->ccr + CCR_BASE + 2); + + /* (04h)Command Register I/O spcmd */ + tmio_iowrite8(0x02, tmio->ccr + CCR_COMMAND); + + /* (62h) Power Supply Control ssmpwc */ + /* HardPowerOFF - SuspendOFF - PowerSupplyWait_4MS */ + tmio_iowrite8(0x02, tmio->ccr + CCR_NFPSC); + + /* (63h) Detect Control ssmdtc */ + tmio_iowrite8(0x02, tmio->ccr + CCR_NFDC); + + /* Interrupt status register clear sintst */ + tmio_iowrite8(0x0f, tmio->fcr + FCR_ISR); + + /* After power supply, Media are reset smode */ + tmio_iowrite8(FCR_MODE_POWER_ON, tmio->fcr + FCR_MODE); + tmio_iowrite8(FCR_MODE_COMMAND, tmio->fcr + FCR_MODE); + tmio_iowrite8(NAND_CMD_RESET, tmio->fcr + FCR_DATA); + + /* Standby Mode smode */ + tmio_iowrite8(FCR_MODE_STANDBY, tmio->fcr + FCR_MODE); + + mdelay(5); + + return 0; +} + +static void tmio_hw_stop(struct platform_device *dev, struct tmio_nand *tmio) +{ + const struct mfd_cell *cell = mfd_get_cell(dev); + + tmio_iowrite8(FCR_MODE_POWER_OFF, tmio->fcr + FCR_MODE); + if (cell->disable) + cell->disable(dev); +} + +static int tmio_probe(struct platform_device *dev) +{ + struct tmio_nand_data *data = dev_get_platdata(&dev->dev); + struct resource *fcr = platform_get_resource(dev, + IORESOURCE_MEM, 0); + struct resource *ccr = platform_get_resource(dev, + IORESOURCE_MEM, 1); + int irq = platform_get_irq(dev, 0); + struct tmio_nand *tmio; + struct mtd_info *mtd; + struct nand_chip *nand_chip; + int retval; + + if (data == NULL) + dev_warn(&dev->dev, "NULL platform data!\n"); + + tmio = devm_kzalloc(&dev->dev, sizeof(*tmio), GFP_KERNEL); + if (!tmio) + return -ENOMEM; + + tmio->dev = dev; + + platform_set_drvdata(dev, tmio); + mtd = &tmio->mtd; + nand_chip = &tmio->chip; + mtd->priv = nand_chip; + mtd->name = "tmio-nand"; + + tmio->ccr = devm_ioremap(&dev->dev, ccr->start, resource_size(ccr)); + if (!tmio->ccr) + return -EIO; + + tmio->fcr_base = fcr->start & 0xfffff; + tmio->fcr = devm_ioremap(&dev->dev, fcr->start, resource_size(fcr)); + if (!tmio->fcr) + return -EIO; + + retval = tmio_hw_init(dev, tmio); + if (retval) + return retval; + + /* Set address of NAND IO lines */ + nand_chip->IO_ADDR_R = tmio->fcr; + nand_chip->IO_ADDR_W = tmio->fcr; + + /* Set address of hardware control function */ + nand_chip->cmd_ctrl = tmio_nand_hwcontrol; + nand_chip->dev_ready = tmio_nand_dev_ready; + nand_chip->read_byte = tmio_nand_read_byte; + nand_chip->write_buf = tmio_nand_write_buf; + nand_chip->read_buf = tmio_nand_read_buf; + + /* set eccmode using hardware ECC */ + nand_chip->ecc.mode = NAND_ECC_HW; + nand_chip->ecc.size = 512; + nand_chip->ecc.bytes = 6; + nand_chip->ecc.strength = 2; + nand_chip->ecc.hwctl = tmio_nand_enable_hwecc; + nand_chip->ecc.calculate = tmio_nand_calculate_ecc; + nand_chip->ecc.correct = tmio_nand_correct_data; + + if (data) + nand_chip->badblock_pattern = data->badblock_pattern; + + /* 15 us command delay time */ + nand_chip->chip_delay = 15; + + retval = devm_request_irq(&dev->dev, irq, &tmio_irq, 0, + dev_name(&dev->dev), tmio); + if (retval) { + dev_err(&dev->dev, "request_irq error %d\n", retval); + goto err_irq; + } + + tmio->irq = irq; + nand_chip->waitfunc = tmio_nand_wait; + + /* Scan to find existence of the device */ + if (nand_scan(mtd, 1)) { + retval = -ENODEV; + goto err_irq; + } + /* Register the partitions */ + retval = mtd_device_parse_register(mtd, NULL, NULL, + data ? data->partition : NULL, + data ? data->num_partitions : 0); + if (!retval) + return retval; + + nand_release(mtd); + +err_irq: + tmio_hw_stop(dev, tmio); + return retval; +} + +static int tmio_remove(struct platform_device *dev) +{ + struct tmio_nand *tmio = platform_get_drvdata(dev); + + nand_release(&tmio->mtd); + tmio_hw_stop(dev, tmio); + return 0; +} + +#ifdef CONFIG_PM +static int tmio_suspend(struct platform_device *dev, pm_message_t state) +{ + const struct mfd_cell *cell = mfd_get_cell(dev); + + if (cell->suspend) + cell->suspend(dev); + + tmio_hw_stop(dev, platform_get_drvdata(dev)); + return 0; +} + +static int tmio_resume(struct platform_device *dev) +{ + const struct mfd_cell *cell = mfd_get_cell(dev); + + /* FIXME - is this required or merely another attack of the broken + * SHARP platform? Looks suspicious. + */ + tmio_hw_init(dev, platform_get_drvdata(dev)); + + if (cell->resume) + cell->resume(dev); + + return 0; +} +#else +#define tmio_suspend NULL +#define tmio_resume NULL +#endif + +static struct platform_driver tmio_driver = { + .driver.name = "tmio-nand", + .driver.owner = THIS_MODULE, + .probe = tmio_probe, + .remove = tmio_remove, + .suspend = tmio_suspend, + .resume = tmio_resume, +}; + +module_platform_driver(tmio_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ian Molton, Dirk Opfer, Chris Humbert, Dmitry Baryshkov"); +MODULE_DESCRIPTION("NAND flash driver on Toshiba Mobile IO controller"); +MODULE_ALIAS("platform:tmio-nand"); diff --git a/drivers/mtd/nand/toto.c b/drivers/mtd/nand/toto.c deleted file mode 100644 index f9e2d4a0ab8..00000000000 --- a/drivers/mtd/nand/toto.c +++ /dev/null @@ -1,208 +0,0 @@ -/* - * drivers/mtd/nand/toto.c - * - * Copyright (c) 2003 Texas Instruments - * - * Derived from drivers/mtd/autcpu12.c - * - * Copyright (c) 2002 Thomas Gleixner <tgxl@linutronix.de> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * Overview: - * This is a device driver for the NAND flash device found on the - * TI fido board. It supports 32MiB and 64MiB cards - * - * $Id: toto.c,v 1.5 2005/11/07 11:14:31 gleixner Exp $ - */ - -#include <linux/slab.h> -#include <linux/init.h> -#include <linux/module.h> -#include <linux/delay.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/nand.h> -#include <linux/mtd/partitions.h> -#include <asm/io.h> -#include <asm/arch/hardware.h> -#include <asm/sizes.h> -#include <asm/arch/toto.h> -#include <asm/arch-omap1510/hardware.h> -#include <asm/arch/gpio.h> - -#define CONFIG_NAND_WORKAROUND 1 - -/* - * MTD structure for TOTO board - */ -static struct mtd_info *toto_mtd = NULL; - -static unsigned long toto_io_base = OMAP_FLASH_1_BASE; - -/* - * Define partitions for flash devices - */ - -static struct mtd_partition partition_info64M[] = { - { .name = "toto kernel partition 1", - .offset = 0, - .size = 2 * SZ_1M }, - { .name = "toto file sys partition 2", - .offset = 2 * SZ_1M, - .size = 14 * SZ_1M }, - { .name = "toto user partition 3", - .offset = 16 * SZ_1M, - .size = 16 * SZ_1M }, - { .name = "toto devboard extra partition 4", - .offset = 32 * SZ_1M, - .size = 32 * SZ_1M }, -}; - -static struct mtd_partition partition_info32M[] = { - { .name = "toto kernel partition 1", - .offset = 0, - .size = 2 * SZ_1M }, - { .name = "toto file sys partition 2", - .offset = 2 * SZ_1M, - .size = 14 * SZ_1M }, - { .name = "toto user partition 3", - .offset = 16 * SZ_1M, - .size = 16 * SZ_1M }, -}; - -#define NUM_PARTITIONS32M 3 -#define NUM_PARTITIONS64M 4 - -/* - * hardware specific access to control-lines - * - * ctrl: - * NAND_NCE: bit 0 -> bit 14 (0x4000) - * NAND_CLE: bit 1 -> bit 12 (0x1000) - * NAND_ALE: bit 2 -> bit 1 (0x0002) - */ -static void toto_hwcontrol(struct mtd_info *mtd, int cmd, - unsigned int ctrl) -{ - struct nand_chip *chip = mtd->priv; - - if (ctrl & NAND_CTRL_CHANGE) { - unsigned long bits; - - /* hopefully enough time for tc make proceding write to clear */ - udelay(1); - - bits = (~ctrl & NAND_NCE) << 14; - bits |= (ctrl & NAND_CLE) << 12; - bits |= (ctrl & NAND_ALE) >> 1; - -#warning Wild guess as gpiosetout() is nowhere defined in the kernel source - tglx - gpiosetout(0x5002, bits); - -#ifdef CONFIG_NAND_WORKAROUND - /* "some" dev boards busted, blue wired to rts2 :( */ - rts2setout(2, (ctrl & NAND_CLE) << 1); -#endif - /* allow time to ensure gpio state to over take memory write */ - udelay(1); - } - - if (cmd != NAND_CMD_NONE) - writeb(cmd, chip->IO_ADDR_W); -} - -/* - * Main initialization routine - */ -static int __init toto_init(void) -{ - struct nand_chip *this; - int err = 0; - - /* Allocate memory for MTD device structure and private data */ - toto_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL); - if (!toto_mtd) { - printk(KERN_WARNING "Unable to allocate toto NAND MTD device structure.\n"); - err = -ENOMEM; - goto out; - } - - /* Get pointer to private data */ - this = (struct nand_chip *)(&toto_mtd[1]); - - /* Initialize structures */ - memset(toto_mtd, 0, sizeof(struct mtd_info)); - memset(this, 0, sizeof(struct nand_chip)); - - /* Link the private data with the MTD structure */ - toto_mtd->priv = this; - toto_mtd->owner = THIS_MODULE; - - /* Set address of NAND IO lines */ - this->IO_ADDR_R = toto_io_base; - this->IO_ADDR_W = toto_io_base; - this->cmd_ctrl = toto_hwcontrol; - this->dev_ready = NULL; - /* 25 us command delay time */ - this->chip_delay = 30; - this->ecc.mode = NAND_ECC_SOFT; - - /* Scan to find existance of the device */ - if (nand_scan(toto_mtd, 1)) { - err = -ENXIO; - goto out_mtd; - } - - /* Register the partitions */ - switch (toto_mtd->size) { - case SZ_64M: - add_mtd_partitions(toto_mtd, partition_info64M, NUM_PARTITIONS64M); - break; - case SZ_32M: - add_mtd_partitions(toto_mtd, partition_info32M, NUM_PARTITIONS32M); - break; - default:{ - printk(KERN_WARNING "Unsupported Nand device\n"); - err = -ENXIO; - goto out_buf; - } - } - - gpioreserve(NAND_MASK); /* claim our gpios */ - archflashwp(0, 0); /* open up flash for writing */ - - goto out; - - out_mtd: - kfree(toto_mtd); - out: - return err; -} - -module_init(toto_init); - -/* - * Clean up routine - */ -static void __exit toto_cleanup(void) -{ - /* Release resources, unregister device */ - nand_release(toto_mtd); - - /* Free the MTD device structure */ - kfree(toto_mtd); - - /* stop flash writes */ - archflashwp(0, 1); - - /* release gpios to system */ - gpiorelease(NAND_MASK); -} - -module_exit(toto_cleanup); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Richard Woodruff <r-woodruff2@ti.com>"); -MODULE_DESCRIPTION("Glue layer for NAND flash on toto board"); diff --git a/drivers/mtd/nand/ts7250.c b/drivers/mtd/nand/ts7250.c deleted file mode 100644 index f40081069ab..00000000000 --- a/drivers/mtd/nand/ts7250.c +++ /dev/null @@ -1,206 +0,0 @@ -/* - * drivers/mtd/nand/ts7250.c - * - * Copyright (C) 2004 Technologic Systems (support@embeddedARM.com) - * - * Derived from drivers/mtd/nand/edb7312.c - * Copyright (C) 2004 Marius Gröger (mag@sysgo.de) - * - * Derived from drivers/mtd/nand/autcpu12.c - * Copyright (c) 2001 Thomas Gleixner (gleixner@autronix.de) - * - * $Id: ts7250.c,v 1.4 2004/12/30 22:02:07 joff Exp $ - * - * 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. - * - * Overview: - * This is a device driver for the NAND flash device found on the - * TS-7250 board which utilizes a Samsung 32 Mbyte part. - */ - -#include <linux/slab.h> -#include <linux/module.h> -#include <linux/init.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/nand.h> -#include <linux/mtd/partitions.h> -#include <asm/io.h> -#include <asm/arch/hardware.h> -#include <asm/sizes.h> -#include <asm/mach-types.h> - -/* - * MTD structure for TS7250 board - */ -static struct mtd_info *ts7250_mtd = NULL; - -#ifdef CONFIG_MTD_PARTITIONS -static const char *part_probes[] = { "cmdlinepart", NULL }; - -#define NUM_PARTITIONS 3 - -/* - * Define static partitions for flash device - */ -static struct mtd_partition partition_info32[] = { - { - .name = "TS-BOOTROM", - .offset = 0x00000000, - .size = 0x00004000, - }, { - .name = "Linux", - .offset = 0x00004000, - .size = 0x01d00000, - }, { - .name = "RedBoot", - .offset = 0x01d04000, - .size = 0x002fc000, - }, -}; - -/* - * Define static partitions for flash device - */ -static struct mtd_partition partition_info128[] = { - { - .name = "TS-BOOTROM", - .offset = 0x00000000, - .size = 0x00004000, - }, { - .name = "Linux", - .offset = 0x00004000, - .size = 0x07d00000, - }, { - .name = "RedBoot", - .offset = 0x07d04000, - .size = 0x002fc000, - }, -}; -#endif - - -/* - * hardware specific access to control-lines - * - * ctrl: - * NAND_NCE: bit 0 -> bit 2 - * NAND_CLE: bit 1 -> bit 1 - * NAND_ALE: bit 2 -> bit 0 - */ -static void ts7250_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl) -{ - struct nand_chip *chip = mtd->priv; - - if (ctrl & NAND_CTRL_CHANGE) { - unsigned long addr = TS72XX_NAND_CONTROL_VIRT_BASE; - unsigned char bits; - - bits = (ctrl & NAND_NCE) << 2; - bits |= ctrl & NAND_CLE; - bits |= (ctrl & NAND_ALE) >> 2; - - __raw_writeb((__raw_readb(addr) & ~0x7) | bits, addr); - } - - if (cmd != NAND_CMD_NONE) - writeb(cmd, chip->IO_ADDR_W); -} - -/* - * read device ready pin - */ -static int ts7250_device_ready(struct mtd_info *mtd) -{ - return __raw_readb(TS72XX_NAND_BUSY_VIRT_BASE) & 0x20; -} - -/* - * Main initialization routine - */ -static int __init ts7250_init(void) -{ - struct nand_chip *this; - const char *part_type = 0; - int mtd_parts_nb = 0; - struct mtd_partition *mtd_parts = 0; - - if (!machine_is_ts72xx() || board_is_ts7200()) - return -ENXIO; - - /* Allocate memory for MTD device structure and private data */ - ts7250_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL); - if (!ts7250_mtd) { - printk("Unable to allocate TS7250 NAND MTD device structure.\n"); - return -ENOMEM; - } - - /* Get pointer to private data */ - this = (struct nand_chip *)(&ts7250_mtd[1]); - - /* Initialize structures */ - memset(ts7250_mtd, 0, sizeof(struct mtd_info)); - memset(this, 0, sizeof(struct nand_chip)); - - /* Link the private data with the MTD structure */ - ts7250_mtd->priv = this; - ts7250_mtd->owner = THIS_MODULE; - - /* insert callbacks */ - this->IO_ADDR_R = (void *)TS72XX_NAND_DATA_VIRT_BASE; - this->IO_ADDR_W = (void *)TS72XX_NAND_DATA_VIRT_BASE; - this->cmd_ctrl = ts7250_hwcontrol; - this->dev_ready = ts7250_device_ready; - this->chip_delay = 15; - this->ecc.mode = NAND_ECC_SOFT; - - printk("Searching for NAND flash...\n"); - /* Scan to find existence of the device */ - if (nand_scan(ts7250_mtd, 1)) { - kfree(ts7250_mtd); - return -ENXIO; - } -#ifdef CONFIG_MTD_PARTITIONS - ts7250_mtd->name = "ts7250-nand"; - mtd_parts_nb = parse_mtd_partitions(ts7250_mtd, part_probes, &mtd_parts, 0); - if (mtd_parts_nb > 0) - part_type = "command line"; - else - mtd_parts_nb = 0; -#endif - if (mtd_parts_nb == 0) { - mtd_parts = partition_info32; - if (ts7250_mtd->size >= (128 * 0x100000)) - mtd_parts = partition_info128; - mtd_parts_nb = NUM_PARTITIONS; - part_type = "static"; - } - - /* Register the partitions */ - printk(KERN_NOTICE "Using %s partition definition\n", part_type); - add_mtd_partitions(ts7250_mtd, mtd_parts, mtd_parts_nb); - - /* Return happy */ - return 0; -} - -module_init(ts7250_init); - -/* - * Clean up routine - */ -static void __exit ts7250_cleanup(void) -{ - /* Unregister the device */ - del_mtd_device(ts7250_mtd); - - /* Free the MTD device structure */ - kfree(ts7250_mtd); -} - -module_exit(ts7250_cleanup); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Jesse Off <joff@embeddedARM.com>"); -MODULE_DESCRIPTION("MTD map driver for Technologic Systems TS-7250 board"); diff --git a/drivers/mtd/nand/txx9ndfmc.c b/drivers/mtd/nand/txx9ndfmc.c new file mode 100644 index 00000000000..c1622a5ba81 --- /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/err.h> +#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; + const char *mtdname; +}; + +#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; +}; + +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_get_platdata(&dev->dev); + + 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 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_get_platdata(&dev->dev); + + 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); + struct nand_chip *chip = mtd->priv; + int eccbytes; + 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); + for (eccbytes = chip->ecc.bytes; eccbytes > 0; eccbytes -= 3) { + ecc_code[1] = txx9ndfmc_read(dev, TXX9_NDFDTR); + ecc_code[0] = txx9ndfmc_read(dev, TXX9_NDFDTR); + ecc_code[2] = txx9ndfmc_read(dev, TXX9_NDFDTR); + ecc_code += 3; + } + txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_OFF, TXX9_NDFMCR); + return 0; +} + +static int txx9ndfmc_correct_data(struct mtd_info *mtd, unsigned char *buf, + unsigned char *read_ecc, unsigned char *calc_ecc) +{ + struct nand_chip *chip = mtd->priv; + int eccsize; + int corrected = 0; + int stat; + + for (eccsize = chip->ecc.size; eccsize > 0; eccsize -= 256) { + stat = __nand_correct_data(buf, read_ecc, calc_ecc, 256); + if (stat < 0) + return stat; + corrected += stat; + buf += 256; + read_ecc += 3; + calc_ecc += 3; + } + return corrected; +} + +static void txx9ndfmc_enable_hwecc(struct mtd_info *mtd, int mode) +{ + 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_get_platdata(&dev->dev); + 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 txx9ndfmc_nand_scan(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + int ret; + + ret = nand_scan_ident(mtd, 1, NULL); + if (!ret) { + if (mtd->writesize >= 512) { + /* Hardware ECC 6 byte ECC per 512 Byte data */ + chip->ecc.size = 512; + chip->ecc.bytes = 6; + } + ret = nand_scan_tail(mtd); + } + return ret; +} + +static int __init txx9ndfmc_probe(struct platform_device *dev) +{ + struct txx9ndfmc_platform_data *plat = dev_get_platdata(&dev->dev); + int hold, spw; + int i; + struct txx9ndfmc_drvdata *drvdata; + unsigned long gbusclk = plat->gbus_clock; + struct resource *res; + + drvdata = devm_kzalloc(&dev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + res = platform_get_resource(dev, IORESOURCE_MEM, 0); + drvdata->base = devm_ioremap_resource(&dev->dev, res); + if (IS_ERR(drvdata->base)) + return PTR_ERR(drvdata->base); + + 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; + + if (!(plat->ch_mask & (1 << i))) + continue; + txx9_priv = kzalloc(sizeof(struct txx9ndfmc_priv), + GFP_KERNEL); + if (!txx9_priv) + 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->cmd_ctrl = txx9ndfmc_cmd_ctrl; + chip->dev_ready = txx9ndfmc_dev_ready; + chip->ecc.calculate = txx9ndfmc_calculate_ecc; + chip->ecc.correct = txx9ndfmc_correct_data; + chip->ecc.hwctl = txx9ndfmc_enable_hwecc; + chip->ecc.mode = NAND_ECC_HW; + /* txx9ndfmc_nand_scan will overwrite ecc.size and ecc.bytes */ + chip->ecc.size = 256; + chip->ecc.bytes = 3; + chip->ecc.strength = 1; + 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; + txx9_priv->mtdname = kasprintf(GFP_KERNEL, "%s.%u", + dev_name(&dev->dev), i); + } else { + txx9_priv->cs = -1; + txx9_priv->mtdname = kstrdup(dev_name(&dev->dev), + GFP_KERNEL); + } + if (!txx9_priv->mtdname) { + kfree(txx9_priv); + dev_err(&dev->dev, "Unable to allocate MTD name.\n"); + continue; + } + if (plat->wide_mask & (1 << i)) + chip->options |= NAND_BUSWIDTH_16; + + if (txx9ndfmc_nand_scan(mtd)) { + kfree(txx9_priv->mtdname); + kfree(txx9_priv); + continue; + } + mtd->name = txx9_priv->mtdname; + + mtd_device_parse_register(mtd, NULL, NULL, NULL, 0); + 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; + + 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; + + nand_release(mtd); + kfree(txx9_priv->mtdname); + 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, + }, +}; + +module_platform_driver_probe(txx9ndfmc_driver, txx9ndfmc_probe); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("TXx9 SoC NAND flash controller driver"); +MODULE_ALIAS("platform:txx9ndfmc"); diff --git a/drivers/mtd/nand/xway_nand.c b/drivers/mtd/nand/xway_nand.c new file mode 100644 index 00000000000..3f81dc8f214 --- /dev/null +++ b/drivers/mtd/nand/xway_nand.c @@ -0,0 +1,201 @@ +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * Copyright © 2012 John Crispin <blogic@openwrt.org> + */ + +#include <linux/mtd/nand.h> +#include <linux/of_gpio.h> +#include <linux/of_platform.h> + +#include <lantiq_soc.h> + +/* nand registers */ +#define EBU_ADDSEL1 0x24 +#define EBU_NAND_CON 0xB0 +#define EBU_NAND_WAIT 0xB4 +#define EBU_NAND_ECC0 0xB8 +#define EBU_NAND_ECC_AC 0xBC + +/* nand commands */ +#define NAND_CMD_ALE (1 << 2) +#define NAND_CMD_CLE (1 << 3) +#define NAND_CMD_CS (1 << 4) +#define NAND_WRITE_CMD_RESET 0xff +#define NAND_WRITE_CMD (NAND_CMD_CS | NAND_CMD_CLE) +#define NAND_WRITE_ADDR (NAND_CMD_CS | NAND_CMD_ALE) +#define NAND_WRITE_DATA (NAND_CMD_CS) +#define NAND_READ_DATA (NAND_CMD_CS) +#define NAND_WAIT_WR_C (1 << 3) +#define NAND_WAIT_RD (0x1) + +/* we need to tel the ebu which addr we mapped the nand to */ +#define ADDSEL1_MASK(x) (x << 4) +#define ADDSEL1_REGEN 1 + +/* we need to tell the EBU that we have nand attached and set it up properly */ +#define BUSCON1_SETUP (1 << 22) +#define BUSCON1_BCGEN_RES (0x3 << 12) +#define BUSCON1_WAITWRC2 (2 << 8) +#define BUSCON1_WAITRDC2 (2 << 6) +#define BUSCON1_HOLDC1 (1 << 4) +#define BUSCON1_RECOVC1 (1 << 2) +#define BUSCON1_CMULT4 1 + +#define NAND_CON_CE (1 << 20) +#define NAND_CON_OUT_CS1 (1 << 10) +#define NAND_CON_IN_CS1 (1 << 8) +#define NAND_CON_PRE_P (1 << 7) +#define NAND_CON_WP_P (1 << 6) +#define NAND_CON_SE_P (1 << 5) +#define NAND_CON_CS_P (1 << 4) +#define NAND_CON_CSMUX (1 << 1) +#define NAND_CON_NANDM 1 + +static void xway_reset_chip(struct nand_chip *chip) +{ + unsigned long nandaddr = (unsigned long) chip->IO_ADDR_W; + unsigned long flags; + + nandaddr &= ~NAND_WRITE_ADDR; + nandaddr |= NAND_WRITE_CMD; + + /* finish with a reset */ + spin_lock_irqsave(&ebu_lock, flags); + writeb(NAND_WRITE_CMD_RESET, (void __iomem *) nandaddr); + while ((ltq_ebu_r32(EBU_NAND_WAIT) & NAND_WAIT_WR_C) == 0) + ; + spin_unlock_irqrestore(&ebu_lock, flags); +} + +static void xway_select_chip(struct mtd_info *mtd, int chip) +{ + + switch (chip) { + case -1: + ltq_ebu_w32_mask(NAND_CON_CE, 0, EBU_NAND_CON); + ltq_ebu_w32_mask(NAND_CON_NANDM, 0, EBU_NAND_CON); + break; + case 0: + ltq_ebu_w32_mask(0, NAND_CON_NANDM, EBU_NAND_CON); + ltq_ebu_w32_mask(0, NAND_CON_CE, EBU_NAND_CON); + break; + default: + BUG(); + } +} + +static void xway_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl) +{ + struct nand_chip *this = mtd->priv; + unsigned long nandaddr = (unsigned long) this->IO_ADDR_W; + unsigned long flags; + + if (ctrl & NAND_CTRL_CHANGE) { + nandaddr &= ~(NAND_WRITE_CMD | NAND_WRITE_ADDR); + if (ctrl & NAND_CLE) + nandaddr |= NAND_WRITE_CMD; + else + nandaddr |= NAND_WRITE_ADDR; + this->IO_ADDR_W = (void __iomem *) nandaddr; + } + + if (cmd != NAND_CMD_NONE) { + spin_lock_irqsave(&ebu_lock, flags); + writeb(cmd, this->IO_ADDR_W); + while ((ltq_ebu_r32(EBU_NAND_WAIT) & NAND_WAIT_WR_C) == 0) + ; + spin_unlock_irqrestore(&ebu_lock, flags); + } +} + +static int xway_dev_ready(struct mtd_info *mtd) +{ + return ltq_ebu_r32(EBU_NAND_WAIT) & NAND_WAIT_RD; +} + +static unsigned char xway_read_byte(struct mtd_info *mtd) +{ + struct nand_chip *this = mtd->priv; + unsigned long nandaddr = (unsigned long) this->IO_ADDR_R; + unsigned long flags; + int ret; + + spin_lock_irqsave(&ebu_lock, flags); + ret = ltq_r8((void __iomem *)(nandaddr + NAND_READ_DATA)); + spin_unlock_irqrestore(&ebu_lock, flags); + + return ret; +} + +static int xway_nand_probe(struct platform_device *pdev) +{ + struct nand_chip *this = platform_get_drvdata(pdev); + unsigned long nandaddr = (unsigned long) this->IO_ADDR_W; + const __be32 *cs = of_get_property(pdev->dev.of_node, + "lantiq,cs", NULL); + u32 cs_flag = 0; + + /* load our CS from the DT. Either we find a valid 1 or default to 0 */ + if (cs && (*cs == 1)) + cs_flag = NAND_CON_IN_CS1 | NAND_CON_OUT_CS1; + + /* setup the EBU to run in NAND mode on our base addr */ + ltq_ebu_w32(CPHYSADDR(nandaddr) + | ADDSEL1_MASK(3) | ADDSEL1_REGEN, EBU_ADDSEL1); + + ltq_ebu_w32(BUSCON1_SETUP | BUSCON1_BCGEN_RES | BUSCON1_WAITWRC2 + | BUSCON1_WAITRDC2 | BUSCON1_HOLDC1 | BUSCON1_RECOVC1 + | BUSCON1_CMULT4, LTQ_EBU_BUSCON1); + + ltq_ebu_w32(NAND_CON_NANDM | NAND_CON_CSMUX | NAND_CON_CS_P + | NAND_CON_SE_P | NAND_CON_WP_P | NAND_CON_PRE_P + | cs_flag, EBU_NAND_CON); + + /* finish with a reset */ + xway_reset_chip(this); + + return 0; +} + +/* allow users to override the partition in DT using the cmdline */ +static const char *part_probes[] = { "cmdlinepart", "ofpart", NULL }; + +static struct platform_nand_data xway_nand_data = { + .chip = { + .nr_chips = 1, + .chip_delay = 30, + .part_probe_types = part_probes, + }, + .ctrl = { + .probe = xway_nand_probe, + .cmd_ctrl = xway_cmd_ctrl, + .dev_ready = xway_dev_ready, + .select_chip = xway_select_chip, + .read_byte = xway_read_byte, + } +}; + +/* + * Try to find the node inside the DT. If it is available attach out + * platform_nand_data + */ +static int __init xway_register_nand(void) +{ + struct device_node *node; + struct platform_device *pdev; + + node = of_find_compatible_node(NULL, NULL, "lantiq,nand-xway"); + if (!node) + return -ENOENT; + pdev = of_find_device_by_node(node); + if (!pdev) + return -EINVAL; + pdev->dev.platform_data = &xway_nand_data; + of_node_put(node); + return 0; +} + +subsys_initcall(xway_register_nand); diff --git a/drivers/mtd/nftlcore.c b/drivers/mtd/nftlcore.c index dd5cea8b4a7..46f27de018c 100644 --- a/drivers/mtd/nftlcore.c +++ b/drivers/mtd/nftlcore.c @@ -1,12 +1,22 @@ -/* Linux driver for NAND Flash Translation Layer */ -/* (c) 1999 Machine Vision Holdings, Inc. */ -/* Author: David Woodhouse <dwmw2@infradead.org> */ -/* $Id: nftlcore.c,v 1.98 2005/11/07 11:14:21 gleixner Exp $ */ - /* - The contents of this file are distributed under the GNU General - Public License version 2. The author places no additional - restrictions of any kind on it. + * Linux driver for NAND Flash Translation Layer + * + * Copyright © 1999 Machine Vision Holdings, Inc. + * Copyright © 1999-2010 David Woodhouse <dwmw2@infradead.org> + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define PRERELEASE @@ -16,13 +26,11 @@ #include <asm/errno.h> #include <asm/io.h> #include <asm/uaccess.h> -#include <linux/miscdevice.h> -#include <linux/pci.h> #include <linux/delay.h> #include <linux/slab.h> -#include <linux/sched.h> #include <linux/init.h> #include <linux/hdreg.h> +#include <linux/blkdev.h> #include <linux/kmod.h> #include <linux/mtd/mtd.h> @@ -42,32 +50,22 @@ static void nftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) struct NFTLrecord *nftl; unsigned long temp; - if (mtd->type != MTD_NANDFLASH) + if (!mtd_type_is_nand(mtd) || mtd->size > UINT_MAX) return; /* OK, this is moderately ugly. But probably safe. Alternatives? */ if (memcmp(mtd->name, "DiskOnChip", 10)) return; - if (!mtd->block_isbad) { - printk(KERN_ERR -"NFTL no longer supports the old DiskOnChip drivers loaded via docprobe.\n" -"Please use the new diskonchip driver under the NAND subsystem.\n"); - return; - } - - DEBUG(MTD_DEBUG_LEVEL1, "NFTL: add_mtd for %s\n", mtd->name); + pr_debug("NFTL: add_mtd for %s\n", mtd->name); - nftl = kmalloc(sizeof(struct NFTLrecord), GFP_KERNEL); + nftl = kzalloc(sizeof(struct NFTLrecord), GFP_KERNEL); - if (!nftl) { - printk(KERN_WARNING "NFTL: out of memory for data structures\n"); + if (!nftl) return; - } - memset(nftl, 0, sizeof(*nftl)); nftl->mbd.mtd = mtd; nftl->mbd.devnum = -1; - nftl->mbd.blksize = 512; + nftl->mbd.tr = tr; if (NFTL_mount(nftl) < 0) { @@ -125,12 +123,11 @@ static void nftl_remove_dev(struct mtd_blktrans_dev *dev) { struct NFTLrecord *nftl = (void *)dev; - DEBUG(MTD_DEBUG_LEVEL1, "NFTL: remove_dev (i=%d)\n", dev->devnum); + pr_debug("NFTL: remove_dev (i=%d)\n", dev->devnum); del_mtd_blktrans_dev(dev); kfree(nftl->ReplUnitTable); kfree(nftl->EUNtable); - kfree(nftl); } /* @@ -139,18 +136,18 @@ static void nftl_remove_dev(struct mtd_blktrans_dev *dev) int nftl_read_oob(struct mtd_info *mtd, loff_t offs, size_t len, size_t *retlen, uint8_t *buf) { + loff_t mask = mtd->writesize - 1; struct mtd_oob_ops ops; int res; - ops.mode = MTD_OOB_PLACE; - ops.ooboffs = offs & (mtd->writesize - 1); + ops.mode = MTD_OPS_PLACE_OOB; + ops.ooboffs = offs & mask; ops.ooblen = len; ops.oobbuf = buf; ops.datbuf = NULL; - ops.len = len; - res = mtd->read_oob(mtd, offs & ~(mtd->writesize - 1), &ops); - *retlen = ops.retlen; + res = mtd_read_oob(mtd, offs & ~mask, &ops); + *retlen = ops.oobretlen; return res; } @@ -160,44 +157,45 @@ int nftl_read_oob(struct mtd_info *mtd, loff_t offs, size_t len, int nftl_write_oob(struct mtd_info *mtd, loff_t offs, size_t len, size_t *retlen, uint8_t *buf) { + loff_t mask = mtd->writesize - 1; struct mtd_oob_ops ops; int res; - ops.mode = MTD_OOB_PLACE; - ops.ooboffs = offs & (mtd->writesize - 1); + ops.mode = MTD_OPS_PLACE_OOB; + ops.ooboffs = offs & mask; ops.ooblen = len; ops.oobbuf = buf; ops.datbuf = NULL; - ops.len = len; - res = mtd->write_oob(mtd, offs & ~(mtd->writesize - 1), &ops); - *retlen = ops.retlen; + res = mtd_write_oob(mtd, offs & ~mask, &ops); + *retlen = ops.oobretlen; return res; } +#ifdef CONFIG_NFTL_RW + /* * Write data and oob to flash */ static int nftl_write(struct mtd_info *mtd, loff_t offs, size_t len, size_t *retlen, uint8_t *buf, uint8_t *oob) { + loff_t mask = mtd->writesize - 1; struct mtd_oob_ops ops; int res; - ops.mode = MTD_OOB_PLACE; - ops.ooboffs = offs; + ops.mode = MTD_OPS_PLACE_OOB; + ops.ooboffs = offs & mask; ops.ooblen = mtd->oobsize; ops.oobbuf = oob; ops.datbuf = buf; ops.len = len; - res = mtd->write_oob(mtd, offs & ~(mtd->writesize - 1), &ops); + res = mtd_write_oob(mtd, offs & ~mask, &ops); *retlen = ops.retlen; return res; } -#ifdef CONFIG_NFTL_RW - /* Actual NFTL access routines */ /* NFTL_findfreeblock: Find a free Erase Unit on the NFTL partition. This function is used * when the give Virtual Unit Chain @@ -213,8 +211,8 @@ static u16 NFTL_findfreeblock(struct NFTLrecord *nftl, int desperate ) /* Normally, we force a fold to happen before we run out of free blocks completely */ if (!desperate && nftl->numfreeEUNs < 2) { - DEBUG(MTD_DEBUG_LEVEL1, "NFTL_findfreeblock: there are too few free EUNs\n"); - return 0xffff; + pr_debug("NFTL_findfreeblock: there are too few free EUNs\n"); + return BLOCK_NIL; } /* Scan for a free block */ @@ -236,11 +234,11 @@ static u16 NFTL_findfreeblock(struct NFTLrecord *nftl, int desperate ) printk("Argh! No free blocks found! LastFreeEUN = %d, " "FirstEUN = %d\n", nftl->LastFreeEUN, le16_to_cpu(nftl->MediaHdr.FirstPhysicalEUN)); - return 0xffff; + return BLOCK_NIL; } } while (pot != nftl->LastFreeEUN); - return 0xffff; + return BLOCK_NIL; } static u16 NFTL_foldchain (struct NFTLrecord *nftl, unsigned thisVUC, unsigned pendingblock ) @@ -284,8 +282,7 @@ static u16 NFTL_foldchain (struct NFTLrecord *nftl, unsigned thisVUC, unsigned p if (block == 2) { foldmark = oob.u.c.FoldMark | oob.u.c.FoldMark1; if (foldmark == FOLD_MARK_IN_PROGRESS) { - DEBUG(MTD_DEBUG_LEVEL1, - "Write Inhibited on EUN %d\n", thisEUN); + pr_debug("Write Inhibited on EUN %d\n", thisEUN); inplace = 0; } else { /* There's no other reason not to do inplace, @@ -350,7 +347,7 @@ static u16 NFTL_foldchain (struct NFTLrecord *nftl, unsigned thisVUC, unsigned p if (BlockLastState[block] != SECTOR_FREE && BlockMap[block] != BLOCK_NIL && BlockMap[block] != targetEUN) { - DEBUG(MTD_DEBUG_LEVEL1, "Setting inplace to 0. VUC %d, " + pr_debug("Setting inplace to 0. VUC %d, " "block %d was %x lastEUN, " "and is in EUN %d (%s) %d\n", thisVUC, block, BlockLastState[block], @@ -366,14 +363,14 @@ static u16 NFTL_foldchain (struct NFTLrecord *nftl, unsigned thisVUC, unsigned p pendingblock < ((thisVUC + 1)* (nftl->EraseSize / 512)) && BlockLastState[pendingblock - (thisVUC * (nftl->EraseSize / 512))] != SECTOR_FREE) { - DEBUG(MTD_DEBUG_LEVEL1, "Pending write not free in EUN %d. " + pr_debug("Pending write not free in EUN %d. " "Folding out of place.\n", targetEUN); inplace = 0; } } if (!inplace) { - DEBUG(MTD_DEBUG_LEVEL1, "Cannot fold Virtual Unit Chain %d in place. " + pr_debug("Cannot fold Virtual Unit Chain %d in place. " "Trying out-of-place\n", thisVUC); /* We need to find a targetEUN to fold into. */ targetEUN = NFTL_findfreeblock(nftl, 1); @@ -403,7 +400,7 @@ static u16 NFTL_foldchain (struct NFTLrecord *nftl, unsigned thisVUC, unsigned p and the Erase Unit into which we are supposed to be copying. Go for it. */ - DEBUG(MTD_DEBUG_LEVEL1,"Folding chain %d into unit %d\n", thisVUC, targetEUN); + pr_debug("Folding chain %d into unit %d\n", thisVUC, targetEUN); for (block = 0; block < nftl->EraseSize / 512 ; block++) { unsigned char movebuf[512]; int ret; @@ -419,12 +416,17 @@ static u16 NFTL_foldchain (struct NFTLrecord *nftl, unsigned thisVUC, unsigned p if (BlockMap[block] == BLOCK_NIL) continue; - ret = mtd->read(mtd, (nftl->EraseSize * BlockMap[block]) + (block * 512), - 512, &retlen, movebuf); - if (ret < 0 && ret != -EUCLEAN) { - ret = mtd->read(mtd, (nftl->EraseSize * BlockMap[block]) - + (block * 512), 512, &retlen, - movebuf); + ret = mtd_read(mtd, + (nftl->EraseSize * BlockMap[block]) + (block * 512), + 512, + &retlen, + movebuf); + if (ret < 0 && !mtd_is_bitflip(ret)) { + ret = mtd_read(mtd, + (nftl->EraseSize * BlockMap[block]) + (block * 512), + 512, + &retlen, + movebuf); if (ret != -EIO) printk("Error went away on retry.\n"); } @@ -437,7 +439,7 @@ static u16 NFTL_foldchain (struct NFTLrecord *nftl, unsigned thisVUC, unsigned p /* add the header so that it is now a valid chain */ oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum = cpu_to_le16(thisVUC); - oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum = 0xffff; + oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum = BLOCK_NIL; nftl_write_oob(mtd, (nftl->EraseSize * targetEUN) + 8, 8, &retlen, (char *)&oob.u); @@ -450,7 +452,7 @@ static u16 NFTL_foldchain (struct NFTLrecord *nftl, unsigned thisVUC, unsigned p has duplicate chains, we need to free one of the chains because it's not necessary any more. */ thisEUN = nftl->EUNtable[thisVUC]; - DEBUG(MTD_DEBUG_LEVEL1,"Want to erase\n"); + pr_debug("Want to erase\n"); /* For each block in the old chain (except the targetEUN of course), free it and make it available for future use */ @@ -521,7 +523,7 @@ static u16 NFTL_makefreeblock( struct NFTLrecord *nftl , unsigned pendingblock) if (ChainLength < 2) { printk(KERN_WARNING "No Virtual Unit Chains available for folding. " "Failing request\n"); - return 0xffff; + return BLOCK_NIL; } return NFTL_foldchain (nftl, LongestChain, pendingblock); @@ -563,7 +565,7 @@ static inline u16 NFTL_findwriteunit(struct NFTLrecord *nftl, unsigned block) (writeEUN * nftl->EraseSize) + blockofs, 8, &retlen, (char *)&bci); - DEBUG(MTD_DEBUG_LEVEL2, "Status of block %d in EUN %d is %x\n", + pr_debug("Status of block %d in EUN %d is %x\n", block , writeEUN, le16_to_cpu(bci.Status)); status = bci.Status | bci.Status1; @@ -584,7 +586,7 @@ static inline u16 NFTL_findwriteunit(struct NFTLrecord *nftl, unsigned block) printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%x\n", thisVUC); - return 0xffff; + return BLOCK_NIL; } /* Skip to next block in chain */ @@ -607,7 +609,7 @@ static inline u16 NFTL_findwriteunit(struct NFTLrecord *nftl, unsigned block) //u16 startEUN = nftl->EUNtable[thisVUC]; //printk("Write to VirtualUnitChain %d, calling makefreeblock()\n", thisVUC); - writeEUN = NFTL_makefreeblock(nftl, 0xffff); + writeEUN = NFTL_makefreeblock(nftl, BLOCK_NIL); if (writeEUN == BLOCK_NIL) { /* OK, we accept that the above comment is @@ -616,7 +618,7 @@ static inline u16 NFTL_findwriteunit(struct NFTLrecord *nftl, unsigned block) but they are reserved for when we're desperate. Well, now we're desperate. */ - DEBUG(MTD_DEBUG_LEVEL1, "Using desperate==1 to find free EUN to accommodate write to VUC %d\n", thisVUC); + pr_debug("Using desperate==1 to find free EUN to accommodate write to VUC %d\n", thisVUC); writeEUN = NFTL_findfreeblock(nftl, 1); } if (writeEUN == BLOCK_NIL) { @@ -679,7 +681,7 @@ static inline u16 NFTL_findwriteunit(struct NFTLrecord *nftl, unsigned block) printk(KERN_WARNING "Error folding to make room for Virtual Unit Chain 0x%x\n", thisVUC); - return 0xffff; + return BLOCK_NIL; } static int nftl_writeblock(struct mtd_blktrans_dev *mbd, unsigned long block, @@ -767,9 +769,9 @@ static int nftl_readblock(struct mtd_blktrans_dev *mbd, unsigned long block, } else { loff_t ptr = (lastgoodEUN * nftl->EraseSize) + blockofs; size_t retlen; - int res = mtd->read(mtd, ptr, 512, &retlen, buffer); + int res = mtd_read(mtd, ptr, 512, &retlen, buffer); - if (res < 0 && res != -EUCLEAN) + if (res < 0 && !mtd_is_bitflip(res)) return -EIO; } return 0; @@ -797,6 +799,7 @@ static struct mtd_blktrans_ops nftl_tr = { .name = "nftl", .major = NFTL_MAJOR, .part_bits = NFTL_PARTN_BITS, + .blksize = 512, .getgeo = nftl_getgeo, .readsect = nftl_readblock, #ifdef CONFIG_NFTL_RW @@ -807,12 +810,8 @@ static struct mtd_blktrans_ops nftl_tr = { .owner = THIS_MODULE, }; -extern char nftlmountrev[]; - static int __init init_nftl(void) { - printk(KERN_INFO "NFTL driver: nftlcore.c $Revision: 1.98 $, nftlmount.c %s\n", nftlmountrev); - return register_mtd_blktrans(&nftl_tr); } @@ -827,3 +826,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/nftlmount.c b/drivers/mtd/nftlmount.c index 067262ee8df..51b9d6af307 100644 --- a/drivers/mtd/nftlmount.c +++ b/drivers/mtd/nftlmount.c @@ -2,9 +2,8 @@ * NFTL mount code with extensive checks * * Author: Fabrice Bellard (fabrice.bellard@netgem.com) - * Copyright (C) 2000 Netgem S.A. - * - * $Id: nftlmount.c,v 1.41 2005/11/07 11:14:21 gleixner Exp $ + * Copyright © 2000 Netgem S.A. + * Copyright © 1999-2010 David Woodhouse <dwmw2@infradead.org> * * 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 @@ -31,16 +30,9 @@ #define SECTORSIZE 512 -char nftlmountrev[]="$Revision: 1.41 $"; - -extern int nftl_read_oob(struct mtd_info *mtd, loff_t offs, size_t len, - size_t *retlen, uint8_t *buf); -extern int nftl_write_oob(struct mtd_info *mtd, loff_t offs, size_t len, - size_t *retlen, uint8_t *buf); - /* find_boot_record: Find the NFTL Media Header and its Spare copy which contains the * various device information of the NFTL partition and Bad Unit Table. Update - * the ReplUnitTable[] table accroding to the Bad Unit Table. ReplUnitTable[] + * the ReplUnitTable[] table according to the Bad Unit Table. ReplUnitTable[] * is used for management of Erase Unit in other routines in nftl.c and nftlmount.c */ static int find_boot_record(struct NFTLrecord *nftl) @@ -60,7 +52,7 @@ static int find_boot_record(struct NFTLrecord *nftl) the mtd device accordingly. We could even get rid of nftl->EraseSize if there were any point in doing so. */ nftl->EraseSize = nftl->mbd.mtd->erasesize; - nftl->nb_blocks = nftl->mbd.mtd->size / nftl->EraseSize; + nftl->nb_blocks = (u32)nftl->mbd.mtd->size / nftl->EraseSize; nftl->MediaUnit = BLOCK_NIL; nftl->SpareMediaUnit = BLOCK_NIL; @@ -71,8 +63,8 @@ static int find_boot_record(struct NFTLrecord *nftl) /* Check for ANAND header first. Then can whinge if it's found but later checks fail */ - ret = mtd->read(mtd, block * nftl->EraseSize, SECTORSIZE, - &retlen, buf); + ret = mtd_read(mtd, block * nftl->EraseSize, SECTORSIZE, + &retlen, buf); /* We ignore ret in case the ECC of the MediaHeader is invalid (which is apparently acceptable) */ if (retlen != SECTORSIZE) { @@ -177,7 +169,7 @@ device is already correct. printk(KERN_NOTICE "WARNING: Support for NFTL with UnitSizeFactor 0x%02x is experimental\n", mh->UnitSizeFactor); nftl->EraseSize = nftl->mbd.mtd->erasesize << (0xff - mh->UnitSizeFactor); - nftl->nb_blocks = nftl->mbd.mtd->size / nftl->EraseSize; + nftl->nb_blocks = (u32)nftl->mbd.mtd->size / nftl->EraseSize; } #endif nftl->nb_boot_blocks = le16_to_cpu(mh->FirstPhysicalEUN); @@ -250,7 +242,8 @@ The new DiskOnChip driver already scanned the bad block table. Just query it. if (buf[i & (SECTORSIZE - 1)] != 0xff) nftl->ReplUnitTable[i] = BLOCK_RESERVED; #endif - if (nftl->mbd.mtd->block_isbad(nftl->mbd.mtd, i * nftl->EraseSize)) + if (mtd_block_isbad(nftl->mbd.mtd, + i * nftl->EraseSize)) nftl->ReplUnitTable[i] = BLOCK_RESERVED; } @@ -282,7 +275,7 @@ static int check_free_sectors(struct NFTLrecord *nftl, unsigned int address, int int i; for (i = 0; i < len; i += SECTORSIZE) { - if (mtd->read(mtd, address, SECTORSIZE, &retlen, buf)) + if (mtd_read(mtd, address, SECTORSIZE, &retlen, buf)) return -1; if (memcmpb(buf, 0xff, SECTORSIZE) != 0) return -1; @@ -305,7 +298,7 @@ static int check_free_sectors(struct NFTLrecord *nftl, unsigned int address, int * * Return: 0 when succeed, -1 on error. * - * ToDo: 1. Is it neceressary to check_free_sector after erasing ?? + * ToDo: 1. Is it necessary to check_free_sector after erasing ?? */ int NFTL_formatblock(struct NFTLrecord *nftl, int block) { @@ -334,7 +327,7 @@ int NFTL_formatblock(struct NFTLrecord *nftl, int block) instr->mtd = nftl->mbd.mtd; instr->addr = block * nftl->EraseSize; instr->len = nftl->EraseSize; - mtd->erase(mtd, instr); + mtd_erase(mtd, instr); if (instr->state == MTD_ERASE_FAILED) { printk("Error while formatting block %d\n", block); @@ -345,7 +338,7 @@ int NFTL_formatblock(struct NFTLrecord *nftl, int block) nb_erases = le32_to_cpu(uci.WearInfo); nb_erases++; - /* wrap (almost impossible with current flashs) or free block */ + /* wrap (almost impossible with current flash) or free block */ if (nb_erases == 0) nb_erases = 1; @@ -363,7 +356,7 @@ int NFTL_formatblock(struct NFTLrecord *nftl, int block) fail: /* could not format, update the bad block table (caller is responsible for setting the ReplUnitTable to BLOCK_RESERVED on failure) */ - nftl->mbd.mtd->block_markbad(nftl->mbd.mtd, instr->addr); + mtd_block_markbad(nftl->mbd.mtd, instr->addr); return -1; } @@ -371,10 +364,10 @@ fail: * Mark as 'IGNORE' each incorrect sector. This check is only done if the chain * was being folded when NFTL was interrupted. * - * The check_free_sectors in this function is neceressary. There is a possible + * The check_free_sectors in this function is necessary. There is a possible * situation that after writing the Data area, the Block Control Information is * not updated according (due to power failure or something) which leaves the block - * in an umconsistent state. So we have to check if a block is really FREE in this + * in an inconsistent state. So we have to check if a block is really FREE in this * case. */ static void check_sectors_in_chain(struct NFTLrecord *nftl, unsigned int first_block) { @@ -429,14 +422,14 @@ static void check_sectors_in_chain(struct NFTLrecord *nftl, unsigned int first_b } } -/* calc_chain_lenght: Walk through a Virtual Unit Chain and estimate chain length */ +/* calc_chain_length: Walk through a Virtual Unit Chain and estimate chain length */ static int calc_chain_length(struct NFTLrecord *nftl, unsigned int first_block) { unsigned int length = 0, block = first_block; for (;;) { length++; - /* avoid infinite loops, although this is guaranted not to + /* avoid infinite loops, although this is guaranteed not to happen because of the previous checks */ if (length >= nftl->nb_blocks) { printk("nftl: length too long %d !\n", length); @@ -455,11 +448,11 @@ static int calc_chain_length(struct NFTLrecord *nftl, unsigned int first_block) /* format_chain: Format an invalid Virtual Unit chain. It frees all the Erase Units in a * Virtual Unit Chain, i.e. all the units are disconnected. * - * It is not stricly correct to begin from the first block of the chain because + * It is not strictly correct to begin from the first block of the chain because * if we stop the code, we may see again a valid chain if there was a first_block * flag in a block inside it. But is it really a problem ? * - * FixMe: Figure out what the last statesment means. What if power failure when we are + * FixMe: Figure out what the last statement means. What if power failure when we are * in the for (;;) loop formatting blocks ?? */ static void format_chain(struct NFTLrecord *nftl, unsigned int first_block) @@ -493,7 +486,7 @@ static void format_chain(struct NFTLrecord *nftl, unsigned int first_block) * totally free (only 0xff). * * Definition: Free Erase Unit -- A properly erased/formatted Free Erase Unit should have meet the - * following critia: + * following criteria: * 1. */ static int check_and_mark_free_block(struct NFTLrecord *nftl, int block) { @@ -510,7 +503,7 @@ static int check_and_mark_free_block(struct NFTLrecord *nftl, int block) erase_mark = le16_to_cpu ((h1.EraseMark | h1.EraseMark1)); if (erase_mark != ERASE_MARK) { /* if no erase mark, the block must be totally free. This is - possible in two cases : empty filsystem or interrupted erase (very unlikely) */ + possible in two cases : empty filesystem or interrupted erase (very unlikely) */ if (check_free_sectors (nftl, block * nftl->EraseSize, nftl->EraseSize, 1) != 0) return -1; @@ -552,7 +545,7 @@ static int check_and_mark_free_block(struct NFTLrecord *nftl, int block) /* get_fold_mark: Read fold mark from Unit Control Information #2, we use FOLD_MARK_IN_PROGRESS * to indicate that we are in the progression of a Virtual Unit Chain folding. If the UCI #2 * is FOLD_MARK_IN_PROGRESS when mounting the NFTL, the (previous) folding process is interrupted - * for some reason. A clean up/check of the VUC is neceressary in this case. + * for some reason. A clean up/check of the VUC is necessary in this case. * * WARNING: return 0 if read error */ @@ -665,7 +658,7 @@ int NFTL_mount(struct NFTLrecord *s) printk("Block %d: incorrect logical block: %d expected: %d\n", block, logical_block, first_logical_block); /* the chain is incorrect : we must format it, - but we need to read it completly */ + but we need to read it completely */ do_format_chain = 1; } if (is_first_block) { @@ -677,7 +670,7 @@ int NFTL_mount(struct NFTLrecord *s) printk("Block %d: incorrectly marked as first block in chain\n", block); /* the chain is incorrect : we must format it, - but we need to read it completly */ + but we need to read it completely */ do_format_chain = 1; } else { printk("Block %d: folding in progress - ignoring first block flag\n", diff --git a/drivers/mtd/ofpart.c b/drivers/mtd/ofpart.c new file mode 100644 index 00000000000..aa26c32e1bc --- /dev/null +++ b/drivers/mtd/ofpart.c @@ -0,0 +1,198 @@ +/* + * Flash partitions described by the OF (or flattened) device tree + * + * Copyright © 2006 MontaVista Software Inc. + * Author: Vitaly Wool <vwool@ru.mvista.com> + * + * Revised to handle newer style flash binding by: + * Copyright © 2007 David Gibson, IBM Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/mtd/mtd.h> +#include <linux/slab.h> +#include <linux/mtd/partitions.h> + +static bool node_has_compatible(struct device_node *pp) +{ + return of_get_property(pp, "compatible", NULL); +} + +static int parse_ofpart_partitions(struct mtd_info *master, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct device_node *node; + const char *partname; + struct device_node *pp; + int nr_parts, i; + + + if (!data) + return 0; + + node = data->of_node; + if (!node) + return 0; + + /* First count the subnodes */ + nr_parts = 0; + for_each_child_of_node(node, pp) { + if (node_has_compatible(pp)) + continue; + + nr_parts++; + } + + if (nr_parts == 0) + return 0; + + *pparts = kzalloc(nr_parts * sizeof(**pparts), GFP_KERNEL); + if (!*pparts) + return -ENOMEM; + + i = 0; + for_each_child_of_node(node, pp) { + const __be32 *reg; + int len; + int a_cells, s_cells; + + if (node_has_compatible(pp)) + continue; + + reg = of_get_property(pp, "reg", &len); + if (!reg) { + nr_parts--; + continue; + } + + a_cells = of_n_addr_cells(pp); + s_cells = of_n_size_cells(pp); + (*pparts)[i].offset = of_read_number(reg, a_cells); + (*pparts)[i].size = of_read_number(reg + a_cells, s_cells); + + partname = of_get_property(pp, "label", &len); + if (!partname) + partname = of_get_property(pp, "name", &len); + (*pparts)[i].name = partname; + + if (of_get_property(pp, "read-only", &len)) + (*pparts)[i].mask_flags |= MTD_WRITEABLE; + + if (of_get_property(pp, "lock", &len)) + (*pparts)[i].mask_flags |= MTD_POWERUP_LOCK; + + i++; + } + + if (!i) { + of_node_put(pp); + pr_err("No valid partition found on %s\n", node->full_name); + kfree(*pparts); + *pparts = NULL; + return -EINVAL; + } + + return nr_parts; +} + +static struct mtd_part_parser ofpart_parser = { + .owner = THIS_MODULE, + .parse_fn = parse_ofpart_partitions, + .name = "ofpart", +}; + +static int parse_ofoldpart_partitions(struct mtd_info *master, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct device_node *dp; + int i, plen, nr_parts; + const struct { + __be32 offset, len; + } *part; + const char *names; + + if (!data) + return 0; + + dp = data->of_node; + if (!dp) + return 0; + + part = of_get_property(dp, "partitions", &plen); + if (!part) + return 0; /* No partitions found */ + + pr_warning("Device tree uses obsolete partition map binding: %s\n", + dp->full_name); + + nr_parts = plen / sizeof(part[0]); + + *pparts = kzalloc(nr_parts * sizeof(*(*pparts)), GFP_KERNEL); + if (!*pparts) + return -ENOMEM; + + names = of_get_property(dp, "partition-names", &plen); + + for (i = 0; i < nr_parts; i++) { + (*pparts)[i].offset = be32_to_cpu(part->offset); + (*pparts)[i].size = be32_to_cpu(part->len) & ~1; + /* bit 0 set signifies read only partition */ + if (be32_to_cpu(part->len) & 1) + (*pparts)[i].mask_flags = MTD_WRITEABLE; + + if (names && (plen > 0)) { + int len = strlen(names) + 1; + + (*pparts)[i].name = names; + plen -= len; + names += len; + } else { + (*pparts)[i].name = "unnamed"; + } + + part++; + } + + return nr_parts; +} + +static struct mtd_part_parser ofoldpart_parser = { + .owner = THIS_MODULE, + .parse_fn = parse_ofoldpart_partitions, + .name = "ofoldpart", +}; + +static int __init ofpart_parser_init(void) +{ + register_mtd_parser(&ofpart_parser); + register_mtd_parser(&ofoldpart_parser); + return 0; +} + +static void __exit ofpart_parser_exit(void) +{ + deregister_mtd_parser(&ofpart_parser); + deregister_mtd_parser(&ofoldpart_parser); +} + +module_init(ofpart_parser_init); +module_exit(ofpart_parser_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Parser for MTD partitioning information in device tree"); +MODULE_AUTHOR("Vitaly Wool, David Gibson"); +/* + * When MTD core cannot find the requested parser, it tries to load the module + * with the same name. Since we provide the ofoldpart parser, we should have + * the corresponding alias. + */ +MODULE_ALIAS("ofoldpart"); diff --git a/drivers/mtd/onenand/Kconfig b/drivers/mtd/onenand/Kconfig index 5930a03736d..ab2607273e8 100644 --- a/drivers/mtd/onenand/Kconfig +++ b/drivers/mtd/onenand/Kconfig @@ -1,37 +1,45 @@ -# -# linux/drivers/mtd/onenand/Kconfig -# - -menu "OneNAND Flash Device Drivers" - depends on MTD != n - -config MTD_ONENAND +menuconfig MTD_ONENAND tristate "OneNAND Device Support" depends on MTD + depends on HAS_IOMEM help This enables support for accessing all type of OneNAND flash devices. For further information see - <http://www.samsung.com/Products/Semiconductor/Flash/OneNAND_TM/index.htm>. + <http://www.samsung.com/Products/Semiconductor/OneNAND/index.htm> + +if MTD_ONENAND config MTD_ONENAND_VERIFY_WRITE bool "Verify OneNAND page writes" - depends on MTD_ONENAND help This adds an extra check when data is written to the flash. The OneNAND flash device internally checks only bits transitioning from 1 to 0. There is a rare possibility that even though the device thinks the write was successful, a bit could have been - flipped accidentaly due to device wear or something else. + flipped accidentally due to device wear or something else. config MTD_ONENAND_GENERIC tristate "OneNAND Flash device via platform device driver" - depends on MTD_ONENAND && ARM help Support for OneNAND flash via platform device driver. +config MTD_ONENAND_OMAP2 + tristate "OneNAND on OMAP2/OMAP3 support" + depends on ARCH_OMAP2 || ARCH_OMAP3 + help + Support for a OneNAND flash device connected to an OMAP2/OMAP3 CPU + via the GPMC memory controller. + +config MTD_ONENAND_SAMSUNG + tristate "OneNAND on Samsung SOC controller support" + depends on ARCH_S3C64XX || ARCH_S5PC100 || ARCH_S5PV210 || ARCH_EXYNOS4 + help + Support for a OneNAND flash device connected to an Samsung SOC. + S3C64XX/S5PC100 use command mapping method. + S5PC110/S5PC210 use generic OneNAND method. + config MTD_ONENAND_OTP bool "OneNAND OTP Support" - depends on MTD_ONENAND help One Block of the NAND Flash Array memory is reserved as a One-Time Programmable Block memory area. @@ -43,10 +51,20 @@ config MTD_ONENAND_OTP OTP block is fully-guaranteed to be a valid block. -config MTD_ONENAND_SYNC_READ - bool "OneNAND Sync. Burst Read Support" - depends on ARCH_OMAP +config MTD_ONENAND_2X_PROGRAM + bool "OneNAND 2X program support" help - This enables support for Sync. Burst Read. + The 2X Program is an extension of Program Operation. + Since the device is equipped with two DataRAMs, and two-plane NAND + Flash memory array, these two component enables simultaneous program + of 4KiB. Plane1 has only even blocks such as block0, block2, block4 + while Plane2 has only odd blocks such as block1, block3, block5. + So MTD regards it as 4KiB page size and 256KiB block size + + Now the following chips support it. (KFXXX16Q2M) + Demux: KFG2G16Q2M, KFH4G16Q2M, KFW8G16Q2M, + Mux: KFM2G16Q2M, KFN4G16Q2M, + + And more recent chips -endmenu +endif # MTD_ONENAND diff --git a/drivers/mtd/onenand/Makefile b/drivers/mtd/onenand/Makefile index 269cfe46734..9d6540e8b3d 100644 --- a/drivers/mtd/onenand/Makefile +++ b/drivers/mtd/onenand/Makefile @@ -7,5 +7,7 @@ obj-$(CONFIG_MTD_ONENAND) += onenand.o # Board specific. obj-$(CONFIG_MTD_ONENAND_GENERIC) += generic.o +obj-$(CONFIG_MTD_ONENAND_OMAP2) += omap2.o +obj-$(CONFIG_MTD_ONENAND_SAMSUNG) += samsung.o onenand-objs = onenand_base.o onenand_bbt.o diff --git a/drivers/mtd/onenand/generic.c b/drivers/mtd/onenand/generic.c index af06a80f44d..093c29ac1a1 100644 --- a/drivers/mtd/onenand/generic.c +++ b/drivers/mtd/onenand/generic.c @@ -13,45 +13,40 @@ */ #include <linux/module.h> -#include <linux/init.h> #include <linux/slab.h> #include <linux/platform_device.h> #include <linux/mtd/mtd.h> #include <linux/mtd/onenand.h> #include <linux/mtd/partitions.h> - #include <asm/io.h> -#include <asm/mach/flash.h> - -#define DRIVER_NAME "onenand" - -#ifdef CONFIG_MTD_PARTITIONS -static const char *part_probes[] = { "cmdlinepart", NULL, }; -#endif +/* + * Note: Driver name and platform data format have been updated! + * + * This version of the driver is named "onenand-flash" and takes struct + * onenand_platform_data as platform data. The old ARM-specific version + * with the name "onenand" used to take struct flash_platform_data. + */ +#define DRIVER_NAME "onenand-flash" struct onenand_info { struct mtd_info mtd; - struct mtd_partition *parts; struct onenand_chip onenand; }; -static int __devinit generic_onenand_probe(struct device *dev) +static int generic_onenand_probe(struct platform_device *pdev) { struct onenand_info *info; - struct platform_device *pdev = to_platform_device(dev); - struct flash_platform_data *pdata = pdev->dev.platform_data; + struct onenand_platform_data *pdata = dev_get_platdata(&pdev->dev); struct resource *res = pdev->resource; - unsigned long size = res->end - res->start + 1; + unsigned long size = resource_size(res); int err; - info = kmalloc(sizeof(struct onenand_info), GFP_KERNEL); + info = kzalloc(sizeof(struct onenand_info), GFP_KERNEL); if (!info) return -ENOMEM; - memset(info, 0, sizeof(struct onenand_info)); - - if (!request_mem_region(res->start, size, dev->driver->name)) { + if (!request_mem_region(res->start, size, dev_name(&pdev->dev))) { err = -EBUSY; goto out_free_info; } @@ -62,9 +57,10 @@ static int __devinit generic_onenand_probe(struct device *dev) goto out_release_mem_region; } - info->onenand.mmcontrol = pdata->mmcontrol; + info->onenand.mmcontrol = pdata ? pdata->mmcontrol : NULL; + info->onenand.irq = platform_get_irq(pdev, 0); - info->mtd.name = pdev->dev.bus_id; + info->mtd.name = dev_name(&pdev->dev); info->mtd.priv = &info->onenand; info->mtd.owner = THIS_MODULE; @@ -73,17 +69,11 @@ static int __devinit generic_onenand_probe(struct device *dev) goto out_iounmap; } -#ifdef CONFIG_MTD_PARTITIONS - err = parse_mtd_partitions(&info->mtd, part_probes, &info->parts, 0); - if (err > 0) - add_mtd_partitions(&info->mtd, info->parts, err); - else if (err < 0 && pdata->parts) - add_mtd_partitions(&info->mtd, pdata->parts, pdata->nr_parts); - else -#endif - err = add_mtd_device(&info->mtd); + err = mtd_device_parse_register(&info->mtd, NULL, NULL, + pdata ? pdata->parts : NULL, + pdata ? pdata->nr_parts : 0); - dev_set_drvdata(&pdev->dev, info); + platform_set_drvdata(pdev, info); return 0; @@ -97,21 +87,13 @@ out_free_info: return err; } -static int __devexit generic_onenand_remove(struct device *dev) +static int generic_onenand_remove(struct platform_device *pdev) { - struct platform_device *pdev = to_platform_device(dev); - struct onenand_info *info = dev_get_drvdata(&pdev->dev); + struct onenand_info *info = platform_get_drvdata(pdev); struct resource *res = pdev->resource; - unsigned long size = res->end - res->start + 1; - - dev_set_drvdata(&pdev->dev, NULL); + unsigned long size = resource_size(res); if (info) { - if (info->parts) - del_mtd_partitions(&info->mtd); - else - del_mtd_device(&info->mtd); - onenand_release(&info->mtd); release_mem_region(res->start, size); iounmap(info->onenand.base); @@ -121,28 +103,18 @@ static int __devexit generic_onenand_remove(struct device *dev) return 0; } -static struct device_driver generic_onenand_driver = { - .name = DRIVER_NAME, - .bus = &platform_bus_type, +static struct platform_driver generic_onenand_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, .probe = generic_onenand_probe, - .remove = __devexit_p(generic_onenand_remove), + .remove = generic_onenand_remove, }; -MODULE_ALIAS(DRIVER_NAME); - -static int __init generic_onenand_init(void) -{ - return driver_register(&generic_onenand_driver); -} - -static void __exit generic_onenand_exit(void) -{ - driver_unregister(&generic_onenand_driver); -} - -module_init(generic_onenand_init); -module_exit(generic_onenand_exit); +module_platform_driver(generic_onenand_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>"); MODULE_DESCRIPTION("Glue layer for OneNAND flash on generic boards"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/mtd/onenand/omap2.c b/drivers/mtd/onenand/omap2.c new file mode 100644 index 00000000000..d945473c388 --- /dev/null +++ b/drivers/mtd/onenand/omap2.c @@ -0,0 +1,816 @@ +/* + * linux/drivers/mtd/onenand/omap2.c + * + * OneNAND driver for OMAP2 / OMAP3 + * + * Copyright © 2005-2006 Nokia Corporation + * + * Author: Jarkko Lavinen <jarkko.lavinen@nokia.com> and Juha Yrjölä + * IRQ and DMA support written by Timo Teras + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; see the file COPYING. If not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/onenand.h> +#include <linux/mtd/partitions.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/regulator/consumer.h> + +#include <asm/mach/flash.h> +#include <linux/platform_data/mtd-onenand-omap2.h> +#include <asm/gpio.h> + +#include <linux/omap-dma.h> + +#define DRIVER_NAME "omap2-onenand" + +#define ONENAND_BUFRAM_SIZE (1024 * 5) + +struct omap2_onenand { + struct platform_device *pdev; + int gpmc_cs; + unsigned long phys_base; + unsigned int mem_size; + int gpio_irq; + struct mtd_info mtd; + struct onenand_chip onenand; + struct completion irq_done; + struct completion dma_done; + int dma_channel; + int freq; + int (*setup)(void __iomem *base, int *freq_ptr); + struct regulator *regulator; + u8 flags; +}; + +static void omap2_onenand_dma_cb(int lch, u16 ch_status, void *data) +{ + struct omap2_onenand *c = data; + + complete(&c->dma_done); +} + +static irqreturn_t omap2_onenand_interrupt(int irq, void *dev_id) +{ + struct omap2_onenand *c = dev_id; + + complete(&c->irq_done); + + return IRQ_HANDLED; +} + +static inline unsigned short read_reg(struct omap2_onenand *c, int reg) +{ + return readw(c->onenand.base + reg); +} + +static inline void write_reg(struct omap2_onenand *c, unsigned short value, + int reg) +{ + writew(value, c->onenand.base + reg); +} + +static void wait_err(char *msg, int state, unsigned int ctrl, unsigned int intr) +{ + printk(KERN_ERR "onenand_wait: %s! state %d ctrl 0x%04x intr 0x%04x\n", + msg, state, ctrl, intr); +} + +static void wait_warn(char *msg, int state, unsigned int ctrl, + unsigned int intr) +{ + printk(KERN_WARNING "onenand_wait: %s! state %d ctrl 0x%04x " + "intr 0x%04x\n", msg, state, ctrl, intr); +} + +static int omap2_onenand_wait(struct mtd_info *mtd, int state) +{ + struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); + struct onenand_chip *this = mtd->priv; + unsigned int intr = 0; + unsigned int ctrl, ctrl_mask; + unsigned long timeout; + u32 syscfg; + + if (state == FL_RESETING || state == FL_PREPARING_ERASE || + state == FL_VERIFYING_ERASE) { + int i = 21; + unsigned int intr_flags = ONENAND_INT_MASTER; + + switch (state) { + case FL_RESETING: + intr_flags |= ONENAND_INT_RESET; + break; + case FL_PREPARING_ERASE: + intr_flags |= ONENAND_INT_ERASE; + break; + case FL_VERIFYING_ERASE: + i = 101; + break; + } + + while (--i) { + udelay(1); + intr = read_reg(c, ONENAND_REG_INTERRUPT); + if (intr & ONENAND_INT_MASTER) + break; + } + ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); + if (ctrl & ONENAND_CTRL_ERROR) { + wait_err("controller error", state, ctrl, intr); + return -EIO; + } + if ((intr & intr_flags) == intr_flags) + return 0; + /* Continue in wait for interrupt branch */ + } + + if (state != FL_READING) { + int result; + + /* Turn interrupts on */ + syscfg = read_reg(c, ONENAND_REG_SYS_CFG1); + if (!(syscfg & ONENAND_SYS_CFG1_IOBE)) { + syscfg |= ONENAND_SYS_CFG1_IOBE; + write_reg(c, syscfg, ONENAND_REG_SYS_CFG1); + if (c->flags & ONENAND_IN_OMAP34XX) + /* Add a delay to let GPIO settle */ + syscfg = read_reg(c, ONENAND_REG_SYS_CFG1); + } + + reinit_completion(&c->irq_done); + if (c->gpio_irq) { + result = gpio_get_value(c->gpio_irq); + if (result == -1) { + ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); + intr = read_reg(c, ONENAND_REG_INTERRUPT); + wait_err("gpio error", state, ctrl, intr); + return -EIO; + } + } else + result = 0; + if (result == 0) { + int retry_cnt = 0; +retry: + result = wait_for_completion_timeout(&c->irq_done, + msecs_to_jiffies(20)); + if (result == 0) { + /* Timeout after 20ms */ + ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); + if (ctrl & ONENAND_CTRL_ONGO && + !this->ongoing) { + /* + * The operation seems to be still going + * so give it some more time. + */ + retry_cnt += 1; + if (retry_cnt < 3) + goto retry; + intr = read_reg(c, + ONENAND_REG_INTERRUPT); + wait_err("timeout", state, ctrl, intr); + return -EIO; + } + intr = read_reg(c, ONENAND_REG_INTERRUPT); + if ((intr & ONENAND_INT_MASTER) == 0) + wait_warn("timeout", state, ctrl, intr); + } + } + } else { + int retry_cnt = 0; + + /* Turn interrupts off */ + syscfg = read_reg(c, ONENAND_REG_SYS_CFG1); + syscfg &= ~ONENAND_SYS_CFG1_IOBE; + write_reg(c, syscfg, ONENAND_REG_SYS_CFG1); + + timeout = jiffies + msecs_to_jiffies(20); + while (1) { + if (time_before(jiffies, timeout)) { + intr = read_reg(c, ONENAND_REG_INTERRUPT); + if (intr & ONENAND_INT_MASTER) + break; + } else { + /* Timeout after 20ms */ + ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); + if (ctrl & ONENAND_CTRL_ONGO) { + /* + * The operation seems to be still going + * so give it some more time. + */ + retry_cnt += 1; + if (retry_cnt < 3) { + timeout = jiffies + + msecs_to_jiffies(20); + continue; + } + } + break; + } + } + } + + intr = read_reg(c, ONENAND_REG_INTERRUPT); + ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); + + if (intr & ONENAND_INT_READ) { + int ecc = read_reg(c, ONENAND_REG_ECC_STATUS); + + if (ecc) { + unsigned int addr1, addr8; + + addr1 = read_reg(c, ONENAND_REG_START_ADDRESS1); + addr8 = read_reg(c, ONENAND_REG_START_ADDRESS8); + if (ecc & ONENAND_ECC_2BIT_ALL) { + printk(KERN_ERR "onenand_wait: ECC error = " + "0x%04x, addr1 %#x, addr8 %#x\n", + ecc, addr1, addr8); + mtd->ecc_stats.failed++; + return -EBADMSG; + } else if (ecc & ONENAND_ECC_1BIT_ALL) { + printk(KERN_NOTICE "onenand_wait: correctable " + "ECC error = 0x%04x, addr1 %#x, " + "addr8 %#x\n", ecc, addr1, addr8); + mtd->ecc_stats.corrected++; + } + } + } else if (state == FL_READING) { + wait_err("timeout", state, ctrl, intr); + return -EIO; + } + + if (ctrl & ONENAND_CTRL_ERROR) { + wait_err("controller error", state, ctrl, intr); + if (ctrl & ONENAND_CTRL_LOCK) + printk(KERN_ERR "onenand_wait: " + "Device is write protected!!!\n"); + return -EIO; + } + + ctrl_mask = 0xFE9F; + if (this->ongoing) + ctrl_mask &= ~0x8000; + + if (ctrl & ctrl_mask) + wait_warn("unexpected controller status", state, ctrl, intr); + + return 0; +} + +static inline int omap2_onenand_bufferram_offset(struct mtd_info *mtd, int area) +{ + struct onenand_chip *this = mtd->priv; + + if (ONENAND_CURRENT_BUFFERRAM(this)) { + if (area == ONENAND_DATARAM) + return this->writesize; + if (area == ONENAND_SPARERAM) + return mtd->oobsize; + } + + return 0; +} + +#if defined(CONFIG_ARCH_OMAP3) || defined(MULTI_OMAP2) + +static int omap3_onenand_read_bufferram(struct mtd_info *mtd, int area, + unsigned char *buffer, int offset, + size_t count) +{ + struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); + struct onenand_chip *this = mtd->priv; + dma_addr_t dma_src, dma_dst; + int bram_offset; + unsigned long timeout; + void *buf = (void *)buffer; + size_t xtra; + volatile unsigned *done; + + bram_offset = omap2_onenand_bufferram_offset(mtd, area) + area + offset; + if (bram_offset & 3 || (size_t)buf & 3 || count < 384) + goto out_copy; + + /* panic_write() may be in an interrupt context */ + if (in_interrupt() || oops_in_progress) + goto out_copy; + + if (buf >= high_memory) { + struct page *p1; + + if (((size_t)buf & PAGE_MASK) != + ((size_t)(buf + count - 1) & PAGE_MASK)) + goto out_copy; + p1 = vmalloc_to_page(buf); + if (!p1) + goto out_copy; + buf = page_address(p1) + ((size_t)buf & ~PAGE_MASK); + } + + xtra = count & 3; + if (xtra) { + count -= xtra; + memcpy(buf + count, this->base + bram_offset + count, xtra); + } + + dma_src = c->phys_base + bram_offset; + dma_dst = dma_map_single(&c->pdev->dev, buf, count, DMA_FROM_DEVICE); + if (dma_mapping_error(&c->pdev->dev, dma_dst)) { + dev_err(&c->pdev->dev, + "Couldn't DMA map a %d byte buffer\n", + count); + goto out_copy; + } + + omap_set_dma_transfer_params(c->dma_channel, OMAP_DMA_DATA_TYPE_S32, + count >> 2, 1, 0, 0, 0); + omap_set_dma_src_params(c->dma_channel, 0, OMAP_DMA_AMODE_POST_INC, + dma_src, 0, 0); + omap_set_dma_dest_params(c->dma_channel, 0, OMAP_DMA_AMODE_POST_INC, + dma_dst, 0, 0); + + reinit_completion(&c->dma_done); + omap_start_dma(c->dma_channel); + + timeout = jiffies + msecs_to_jiffies(20); + done = &c->dma_done.done; + while (time_before(jiffies, timeout)) + if (*done) + break; + + dma_unmap_single(&c->pdev->dev, dma_dst, count, DMA_FROM_DEVICE); + + if (!*done) { + dev_err(&c->pdev->dev, "timeout waiting for DMA\n"); + goto out_copy; + } + + return 0; + +out_copy: + memcpy(buf, this->base + bram_offset, count); + return 0; +} + +static int omap3_onenand_write_bufferram(struct mtd_info *mtd, int area, + const unsigned char *buffer, + int offset, size_t count) +{ + struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); + struct onenand_chip *this = mtd->priv; + dma_addr_t dma_src, dma_dst; + int bram_offset; + unsigned long timeout; + void *buf = (void *)buffer; + volatile unsigned *done; + + bram_offset = omap2_onenand_bufferram_offset(mtd, area) + area + offset; + if (bram_offset & 3 || (size_t)buf & 3 || count < 384) + goto out_copy; + + /* panic_write() may be in an interrupt context */ + if (in_interrupt() || oops_in_progress) + goto out_copy; + + if (buf >= high_memory) { + struct page *p1; + + if (((size_t)buf & PAGE_MASK) != + ((size_t)(buf + count - 1) & PAGE_MASK)) + goto out_copy; + p1 = vmalloc_to_page(buf); + if (!p1) + goto out_copy; + buf = page_address(p1) + ((size_t)buf & ~PAGE_MASK); + } + + dma_src = dma_map_single(&c->pdev->dev, buf, count, DMA_TO_DEVICE); + dma_dst = c->phys_base + bram_offset; + if (dma_mapping_error(&c->pdev->dev, dma_src)) { + dev_err(&c->pdev->dev, + "Couldn't DMA map a %d byte buffer\n", + count); + return -1; + } + + omap_set_dma_transfer_params(c->dma_channel, OMAP_DMA_DATA_TYPE_S32, + count >> 2, 1, 0, 0, 0); + omap_set_dma_src_params(c->dma_channel, 0, OMAP_DMA_AMODE_POST_INC, + dma_src, 0, 0); + omap_set_dma_dest_params(c->dma_channel, 0, OMAP_DMA_AMODE_POST_INC, + dma_dst, 0, 0); + + reinit_completion(&c->dma_done); + omap_start_dma(c->dma_channel); + + timeout = jiffies + msecs_to_jiffies(20); + done = &c->dma_done.done; + while (time_before(jiffies, timeout)) + if (*done) + break; + + dma_unmap_single(&c->pdev->dev, dma_src, count, DMA_TO_DEVICE); + + if (!*done) { + dev_err(&c->pdev->dev, "timeout waiting for DMA\n"); + goto out_copy; + } + + return 0; + +out_copy: + memcpy(this->base + bram_offset, buf, count); + return 0; +} + +#else + +static int omap3_onenand_read_bufferram(struct mtd_info *mtd, int area, + unsigned char *buffer, int offset, + size_t count) +{ + return -ENOSYS; +} + +static int omap3_onenand_write_bufferram(struct mtd_info *mtd, int area, + const unsigned char *buffer, + int offset, size_t count) +{ + return -ENOSYS; +} + +#endif + +#if defined(CONFIG_ARCH_OMAP2) || defined(MULTI_OMAP2) + +static int omap2_onenand_read_bufferram(struct mtd_info *mtd, int area, + unsigned char *buffer, int offset, + size_t count) +{ + struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); + struct onenand_chip *this = mtd->priv; + dma_addr_t dma_src, dma_dst; + int bram_offset; + + bram_offset = omap2_onenand_bufferram_offset(mtd, area) + area + offset; + /* DMA is not used. Revisit PM requirements before enabling it. */ + if (1 || (c->dma_channel < 0) || + ((void *) buffer >= (void *) high_memory) || (bram_offset & 3) || + (((unsigned int) buffer) & 3) || (count < 1024) || (count & 3)) { + memcpy(buffer, (__force void *)(this->base + bram_offset), + count); + return 0; + } + + dma_src = c->phys_base + bram_offset; + dma_dst = dma_map_single(&c->pdev->dev, buffer, count, + DMA_FROM_DEVICE); + if (dma_mapping_error(&c->pdev->dev, dma_dst)) { + dev_err(&c->pdev->dev, + "Couldn't DMA map a %d byte buffer\n", + count); + return -1; + } + + omap_set_dma_transfer_params(c->dma_channel, OMAP_DMA_DATA_TYPE_S32, + count / 4, 1, 0, 0, 0); + omap_set_dma_src_params(c->dma_channel, 0, OMAP_DMA_AMODE_POST_INC, + dma_src, 0, 0); + omap_set_dma_dest_params(c->dma_channel, 0, OMAP_DMA_AMODE_POST_INC, + dma_dst, 0, 0); + + reinit_completion(&c->dma_done); + omap_start_dma(c->dma_channel); + wait_for_completion(&c->dma_done); + + dma_unmap_single(&c->pdev->dev, dma_dst, count, DMA_FROM_DEVICE); + + return 0; +} + +static int omap2_onenand_write_bufferram(struct mtd_info *mtd, int area, + const unsigned char *buffer, + int offset, size_t count) +{ + struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); + struct onenand_chip *this = mtd->priv; + dma_addr_t dma_src, dma_dst; + int bram_offset; + + bram_offset = omap2_onenand_bufferram_offset(mtd, area) + area + offset; + /* DMA is not used. Revisit PM requirements before enabling it. */ + if (1 || (c->dma_channel < 0) || + ((void *) buffer >= (void *) high_memory) || (bram_offset & 3) || + (((unsigned int) buffer) & 3) || (count < 1024) || (count & 3)) { + memcpy((__force void *)(this->base + bram_offset), buffer, + count); + return 0; + } + + dma_src = dma_map_single(&c->pdev->dev, (void *) buffer, count, + DMA_TO_DEVICE); + dma_dst = c->phys_base + bram_offset; + if (dma_mapping_error(&c->pdev->dev, dma_src)) { + dev_err(&c->pdev->dev, + "Couldn't DMA map a %d byte buffer\n", + count); + return -1; + } + + omap_set_dma_transfer_params(c->dma_channel, OMAP_DMA_DATA_TYPE_S16, + count / 2, 1, 0, 0, 0); + omap_set_dma_src_params(c->dma_channel, 0, OMAP_DMA_AMODE_POST_INC, + dma_src, 0, 0); + omap_set_dma_dest_params(c->dma_channel, 0, OMAP_DMA_AMODE_POST_INC, + dma_dst, 0, 0); + + reinit_completion(&c->dma_done); + omap_start_dma(c->dma_channel); + wait_for_completion(&c->dma_done); + + dma_unmap_single(&c->pdev->dev, dma_src, count, DMA_TO_DEVICE); + + return 0; +} + +#else + +static int omap2_onenand_read_bufferram(struct mtd_info *mtd, int area, + unsigned char *buffer, int offset, + size_t count) +{ + return -ENOSYS; +} + +static int omap2_onenand_write_bufferram(struct mtd_info *mtd, int area, + const unsigned char *buffer, + int offset, size_t count) +{ + return -ENOSYS; +} + +#endif + +static struct platform_driver omap2_onenand_driver; + +static void omap2_onenand_shutdown(struct platform_device *pdev) +{ + struct omap2_onenand *c = dev_get_drvdata(&pdev->dev); + + /* With certain content in the buffer RAM, the OMAP boot ROM code + * can recognize the flash chip incorrectly. Zero it out before + * soft reset. + */ + memset((__force void *)c->onenand.base, 0, ONENAND_BUFRAM_SIZE); +} + +static int omap2_onenand_enable(struct mtd_info *mtd) +{ + int ret; + struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); + + ret = regulator_enable(c->regulator); + if (ret != 0) + dev_err(&c->pdev->dev, "can't enable regulator\n"); + + return ret; +} + +static int omap2_onenand_disable(struct mtd_info *mtd) +{ + int ret; + struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); + + ret = regulator_disable(c->regulator); + if (ret != 0) + dev_err(&c->pdev->dev, "can't disable regulator\n"); + + return ret; +} + +static int omap2_onenand_probe(struct platform_device *pdev) +{ + struct omap_onenand_platform_data *pdata; + struct omap2_onenand *c; + struct onenand_chip *this; + int r; + struct resource *res; + struct mtd_part_parser_data ppdata = {}; + + pdata = dev_get_platdata(&pdev->dev); + if (pdata == NULL) { + dev_err(&pdev->dev, "platform data missing\n"); + return -ENODEV; + } + + c = kzalloc(sizeof(struct omap2_onenand), GFP_KERNEL); + if (!c) + return -ENOMEM; + + init_completion(&c->irq_done); + init_completion(&c->dma_done); + c->flags = pdata->flags; + c->gpmc_cs = pdata->cs; + c->gpio_irq = pdata->gpio_irq; + c->dma_channel = pdata->dma_channel; + if (c->dma_channel < 0) { + /* if -1, don't use DMA */ + c->gpio_irq = 0; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + r = -EINVAL; + dev_err(&pdev->dev, "error getting memory resource\n"); + goto err_kfree; + } + + c->phys_base = res->start; + c->mem_size = resource_size(res); + + if (request_mem_region(c->phys_base, c->mem_size, + pdev->dev.driver->name) == NULL) { + dev_err(&pdev->dev, "Cannot reserve memory region at 0x%08lx, size: 0x%x\n", + c->phys_base, c->mem_size); + r = -EBUSY; + goto err_kfree; + } + c->onenand.base = ioremap(c->phys_base, c->mem_size); + if (c->onenand.base == NULL) { + r = -ENOMEM; + goto err_release_mem_region; + } + + if (pdata->onenand_setup != NULL) { + r = pdata->onenand_setup(c->onenand.base, &c->freq); + if (r < 0) { + dev_err(&pdev->dev, "Onenand platform setup failed: " + "%d\n", r); + goto err_iounmap; + } + c->setup = pdata->onenand_setup; + } + + if (c->gpio_irq) { + if ((r = gpio_request(c->gpio_irq, "OneNAND irq")) < 0) { + dev_err(&pdev->dev, "Failed to request GPIO%d for " + "OneNAND\n", c->gpio_irq); + goto err_iounmap; + } + gpio_direction_input(c->gpio_irq); + + if ((r = request_irq(gpio_to_irq(c->gpio_irq), + omap2_onenand_interrupt, IRQF_TRIGGER_RISING, + pdev->dev.driver->name, c)) < 0) + goto err_release_gpio; + } + + if (c->dma_channel >= 0) { + r = omap_request_dma(0, pdev->dev.driver->name, + omap2_onenand_dma_cb, (void *) c, + &c->dma_channel); + if (r == 0) { + omap_set_dma_write_mode(c->dma_channel, + OMAP_DMA_WRITE_NON_POSTED); + omap_set_dma_src_data_pack(c->dma_channel, 1); + omap_set_dma_src_burst_mode(c->dma_channel, + OMAP_DMA_DATA_BURST_8); + omap_set_dma_dest_data_pack(c->dma_channel, 1); + omap_set_dma_dest_burst_mode(c->dma_channel, + OMAP_DMA_DATA_BURST_8); + } else { + dev_info(&pdev->dev, + "failed to allocate DMA for OneNAND, " + "using PIO instead\n"); + c->dma_channel = -1; + } + } + + dev_info(&pdev->dev, "initializing on CS%d, phys base 0x%08lx, virtual " + "base %p, freq %d MHz\n", c->gpmc_cs, c->phys_base, + c->onenand.base, c->freq); + + c->pdev = pdev; + c->mtd.name = dev_name(&pdev->dev); + c->mtd.priv = &c->onenand; + c->mtd.owner = THIS_MODULE; + + c->mtd.dev.parent = &pdev->dev; + + this = &c->onenand; + if (c->dma_channel >= 0) { + this->wait = omap2_onenand_wait; + if (c->flags & ONENAND_IN_OMAP34XX) { + this->read_bufferram = omap3_onenand_read_bufferram; + this->write_bufferram = omap3_onenand_write_bufferram; + } else { + this->read_bufferram = omap2_onenand_read_bufferram; + this->write_bufferram = omap2_onenand_write_bufferram; + } + } + + if (pdata->regulator_can_sleep) { + c->regulator = regulator_get(&pdev->dev, "vonenand"); + if (IS_ERR(c->regulator)) { + dev_err(&pdev->dev, "Failed to get regulator\n"); + r = PTR_ERR(c->regulator); + goto err_release_dma; + } + c->onenand.enable = omap2_onenand_enable; + c->onenand.disable = omap2_onenand_disable; + } + + if (pdata->skip_initial_unlocking) + this->options |= ONENAND_SKIP_INITIAL_UNLOCKING; + + if ((r = onenand_scan(&c->mtd, 1)) < 0) + goto err_release_regulator; + + ppdata.of_node = pdata->of_node; + r = mtd_device_parse_register(&c->mtd, NULL, &ppdata, + pdata ? pdata->parts : NULL, + pdata ? pdata->nr_parts : 0); + if (r) + goto err_release_onenand; + + platform_set_drvdata(pdev, c); + + return 0; + +err_release_onenand: + onenand_release(&c->mtd); +err_release_regulator: + regulator_put(c->regulator); +err_release_dma: + if (c->dma_channel != -1) + omap_free_dma(c->dma_channel); + if (c->gpio_irq) + free_irq(gpio_to_irq(c->gpio_irq), c); +err_release_gpio: + if (c->gpio_irq) + gpio_free(c->gpio_irq); +err_iounmap: + iounmap(c->onenand.base); +err_release_mem_region: + release_mem_region(c->phys_base, c->mem_size); +err_kfree: + kfree(c); + + return r; +} + +static int omap2_onenand_remove(struct platform_device *pdev) +{ + struct omap2_onenand *c = dev_get_drvdata(&pdev->dev); + + onenand_release(&c->mtd); + regulator_put(c->regulator); + if (c->dma_channel != -1) + omap_free_dma(c->dma_channel); + omap2_onenand_shutdown(pdev); + if (c->gpio_irq) { + free_irq(gpio_to_irq(c->gpio_irq), c); + gpio_free(c->gpio_irq); + } + iounmap(c->onenand.base); + release_mem_region(c->phys_base, c->mem_size); + kfree(c); + + return 0; +} + +static struct platform_driver omap2_onenand_driver = { + .probe = omap2_onenand_probe, + .remove = omap2_onenand_remove, + .shutdown = omap2_onenand_shutdown, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(omap2_onenand_driver); + +MODULE_ALIAS("platform:" DRIVER_NAME); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jarkko Lavinen <jarkko.lavinen@nokia.com>"); +MODULE_DESCRIPTION("Glue layer for OneNAND flash on OMAP2 / OMAP3"); diff --git a/drivers/mtd/onenand/onenand_base.c b/drivers/mtd/onenand/onenand_base.c index 84ec40d2543..635ee002769 100644 --- a/drivers/mtd/onenand/onenand_base.c +++ b/drivers/mtd/onenand/onenand_base.c @@ -1,9 +1,20 @@ /* * linux/drivers/mtd/onenand/onenand_base.c * - * Copyright (C) 2005 Samsung Electronics + * Copyright © 2005-2009 Samsung Electronics + * Copyright © 2007 Nokia Corporation + * * Kyungmin Park <kyungmin.park@samsung.com> * + * Credits: + * Adrian Hunter <ext-adrian.hunter@nokia.com>: + * auto-placement support, read-while load support, various fixes + * + * Vishak G <vishak.g at samsung.com>, Rohit Hagargundgi <h.rohit at samsung.com> + * Flex-OneNAND support + * Amul Kumar Saha <amul.saha at samsung.com> + * OTP support + * * 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. @@ -11,8 +22,11 @@ #include <linux/kernel.h> #include <linux/module.h> -#include <linux/init.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> #include <linux/sched.h> +#include <linux/delay.h> +#include <linux/interrupt.h> #include <linux/jiffies.h> #include <linux/mtd/mtd.h> #include <linux/mtd/onenand.h> @@ -20,6 +34,86 @@ #include <asm/io.h> +/* + * Multiblock erase if number of blocks to erase is 2 or more. + * Maximum number of blocks for simultaneous erase is 64. + */ +#define MB_ERASE_MIN_BLK_COUNT 2 +#define MB_ERASE_MAX_BLK_COUNT 64 + +/* Default Flex-OneNAND boundary and lock respectively */ +static int flex_bdry[MAX_DIES * 2] = { -1, 0, -1, 0 }; + +module_param_array(flex_bdry, int, NULL, 0400); +MODULE_PARM_DESC(flex_bdry, "SLC Boundary information for Flex-OneNAND" + "Syntax:flex_bdry=DIE_BDRY,LOCK,..." + "DIE_BDRY: SLC boundary of the die" + "LOCK: Locking information for SLC boundary" + " : 0->Set boundary in unlocked status" + " : 1->Set boundary in locked status"); + +/* Default OneNAND/Flex-OneNAND OTP options*/ +static int otp; + +module_param(otp, int, 0400); +MODULE_PARM_DESC(otp, "Corresponding behaviour of OneNAND in OTP" + "Syntax : otp=LOCK_TYPE" + "LOCK_TYPE : Keys issued, for specific OTP Lock type" + " : 0 -> Default (No Blocks Locked)" + " : 1 -> OTP Block lock" + " : 2 -> 1st Block lock" + " : 3 -> BOTH OTP Block and 1st Block lock"); + +/* + * flexonenand_oob_128 - oob info for Flex-Onenand with 4KB page + * For now, we expose only 64 out of 80 ecc bytes + */ +static struct nand_ecclayout flexonenand_oob_128 = { + .eccbytes = 64, + .eccpos = { + 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 102, 103, 104, 105 + }, + .oobfree = { + {2, 4}, {18, 4}, {34, 4}, {50, 4}, + {66, 4}, {82, 4}, {98, 4}, {114, 4} + } +}; + +/* + * onenand_oob_128 - oob info for OneNAND with 4KB page + * + * Based on specification: + * 4Gb M-die OneNAND Flash (KFM4G16Q4M, KFN8G16Q4M). Rev. 1.3, Apr. 2010 + * + * For eccpos we expose only 64 bytes out of 72 (see struct nand_ecclayout) + * + * oobfree uses the spare area fields marked as + * "Managed by internal ECC logic for Logical Sector Number area" + */ +static struct nand_ecclayout onenand_oob_128 = { + .eccbytes = 64, + .eccpos = { + 7, 8, 9, 10, 11, 12, 13, 14, 15, + 23, 24, 25, 26, 27, 28, 29, 30, 31, + 39, 40, 41, 42, 43, 44, 45, 46, 47, + 55, 56, 57, 58, 59, 60, 61, 62, 63, + 71, 72, 73, 74, 75, 76, 77, 78, 79, + 87, 88, 89, 90, 91, 92, 93, 94, 95, + 103, 104, 105, 106, 107, 108, 109, 110, 111, + 119 + }, + .oobfree = { + {2, 3}, {18, 3}, {34, 3}, {50, 3}, + {66, 3}, {82, 3}, {98, 3}, {114, 3} + } +}; + /** * onenand_oob_64 - oob info for large (2KB) page */ @@ -58,6 +152,14 @@ static const unsigned char ffchars[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 48 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 64 */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 80 */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 96 */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 112 */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 128 */ }; /** @@ -93,16 +195,9 @@ static void onenand_writew(unsigned short value, void __iomem *addr) */ static int onenand_block_address(struct onenand_chip *this, int block) { - if (this->device_id & ONENAND_DEVICE_IS_DDP) { - /* Device Flash Core select, NAND Flash Block Address */ - int dfs = 0; - - if (block & this->density_mask) - dfs = 1; - - return (dfs << ONENAND_DDP_SHIFT) | - (block & (this->density_mask - 1)); - } + /* Device Flash Core select, NAND Flash Block Address */ + if (block & this->density_mask) + return ONENAND_DDP_CHIP1 | (block ^ this->density_mask); return block; } @@ -117,17 +212,11 @@ static int onenand_block_address(struct onenand_chip *this, int block) */ static int onenand_bufferram_address(struct onenand_chip *this, int block) { - if (this->device_id & ONENAND_DEVICE_IS_DDP) { - /* Device BufferRAM Select */ - int dbs = 0; - - if (block & this->density_mask) - dbs = 1; + /* Device BufferRAM Select */ + if (block & this->density_mask) + return ONENAND_DDP_CHIP1; - return (dbs << ONENAND_DDP_SHIFT); - } - - return 0; + return ONENAND_DDP_CHIP0; } /** @@ -177,6 +266,98 @@ static int onenand_buffer_address(int dataram1, int sectors, int count) } /** + * flexonenand_block- For given address return block number + * @param this - OneNAND device structure + * @param addr - Address for which block number is needed + */ +static unsigned flexonenand_block(struct onenand_chip *this, loff_t addr) +{ + unsigned boundary, blk, die = 0; + + if (ONENAND_IS_DDP(this) && addr >= this->diesize[0]) { + die = 1; + addr -= this->diesize[0]; + } + + boundary = this->boundary[die]; + + blk = addr >> (this->erase_shift - 1); + if (blk > boundary) + blk = (blk + boundary + 1) >> 1; + + blk += die ? this->density_mask : 0; + return blk; +} + +inline unsigned onenand_block(struct onenand_chip *this, loff_t addr) +{ + if (!FLEXONENAND(this)) + return addr >> this->erase_shift; + return flexonenand_block(this, addr); +} + +/** + * flexonenand_addr - Return address of the block + * @this: OneNAND device structure + * @block: Block number on Flex-OneNAND + * + * Return address of the block + */ +static loff_t flexonenand_addr(struct onenand_chip *this, int block) +{ + loff_t ofs = 0; + int die = 0, boundary; + + if (ONENAND_IS_DDP(this) && block >= this->density_mask) { + block -= this->density_mask; + die = 1; + ofs = this->diesize[0]; + } + + boundary = this->boundary[die]; + ofs += (loff_t)block << (this->erase_shift - 1); + if (block > (boundary + 1)) + ofs += (loff_t)(block - boundary - 1) << (this->erase_shift - 1); + return ofs; +} + +loff_t onenand_addr(struct onenand_chip *this, int block) +{ + if (!FLEXONENAND(this)) + return (loff_t)block << this->erase_shift; + return flexonenand_addr(this, block); +} +EXPORT_SYMBOL(onenand_addr); + +/** + * onenand_get_density - [DEFAULT] Get OneNAND density + * @param dev_id OneNAND device ID + * + * Get OneNAND density from device ID + */ +static inline int onenand_get_density(int dev_id) +{ + int density = dev_id >> ONENAND_DEVICE_DENSITY_SHIFT; + return (density & ONENAND_DEVICE_DENSITY_MASK); +} + +/** + * flexonenand_region - [Flex-OneNAND] Return erase region of addr + * @param mtd MTD device structure + * @param addr address whose erase region needs to be identified + */ +int flexonenand_region(struct mtd_info *mtd, loff_t addr) +{ + int i; + + for (i = 0; i < mtd->numeraseregions; i++) + if (addr < mtd->eraseregions[i].offset) + break; + return i - 1; +} +EXPORT_SYMBOL(flexonenand_region); + +/** * onenand_command - [DEFAULT] Send command to OneNAND device * @param mtd MTD device structure * @param cmd the command to be sent @@ -189,31 +370,54 @@ static int onenand_buffer_address(int dataram1, int sectors, int count) static int onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, size_t len) { struct onenand_chip *this = mtd->priv; - int value, readcmd = 0, block_cmd = 0; - int block, page; - /* Now we use page size operation */ - int sectors = 4, count = 4; + int value, block, page; /* Address translation */ switch (cmd) { case ONENAND_CMD_UNLOCK: case ONENAND_CMD_LOCK: case ONENAND_CMD_LOCK_TIGHT: + case ONENAND_CMD_UNLOCK_ALL: block = -1; page = -1; break; + case FLEXONENAND_CMD_PI_ACCESS: + /* addr contains die index */ + block = addr * this->density_mask; + page = -1; + break; + case ONENAND_CMD_ERASE: + case ONENAND_CMD_MULTIBLOCK_ERASE: + case ONENAND_CMD_ERASE_VERIFY: case ONENAND_CMD_BUFFERRAM: case ONENAND_CMD_OTP_ACCESS: - block_cmd = 1; - block = (int) (addr >> this->erase_shift); + block = onenand_block(this, addr); page = -1; break; + case FLEXONENAND_CMD_READ_PI: + cmd = ONENAND_CMD_READ; + block = addr * this->density_mask; + page = 0; + break; + default: - block = (int) (addr >> this->erase_shift); - page = (int) (addr >> this->page_shift); + block = onenand_block(this, addr); + if (FLEXONENAND(this)) + page = (int) (addr - onenand_addr(this, block))>>\ + this->page_shift; + else + page = (int) (addr >> this->page_shift); + if (ONENAND_IS_2PLANE(this)) { + /* Make the even block number */ + block &= ~1; + /* Is it the odd plane? */ + if (addr & this->writesize) + block++; + page >>= 1; + } page &= this->page_mask; break; } @@ -224,8 +428,12 @@ static int onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, size_t le value = onenand_bufferram_address(this, block); this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); - /* Switch to the next data buffer */ - ONENAND_SET_NEXT_BUFFERRAM(this); + if (ONENAND_IS_2PLANE(this) || ONENAND_IS_4KB_PAGE(this)) + /* It is always BufferRAM0 */ + ONENAND_SET_BUFFERRAM0(this); + else + /* Switch to the next data buffer */ + ONENAND_SET_NEXT_BUFFERRAM(this); return 0; } @@ -235,24 +443,30 @@ static int onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, size_t le value = onenand_block_address(this, block); this->write_word(value, this->base + ONENAND_REG_START_ADDRESS1); - if (block_cmd) { - /* Select DataRAM for DDP */ - value = onenand_bufferram_address(this, block); - this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); - } + /* Select DataRAM for DDP */ + value = onenand_bufferram_address(this, block); + this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); } if (page != -1) { + /* Now we use page size operation */ + int sectors = 0, count = 0; int dataram; switch (cmd) { + case FLEXONENAND_CMD_RECOVER_LSB: case ONENAND_CMD_READ: case ONENAND_CMD_READOOB: - dataram = ONENAND_SET_NEXT_BUFFERRAM(this); - readcmd = 1; + if (ONENAND_IS_4KB_PAGE(this)) + /* It is always BufferRAM0 */ + dataram = ONENAND_SET_BUFFERRAM0(this); + else + dataram = ONENAND_SET_NEXT_BUFFERRAM(this); break; default: + if (ONENAND_IS_2PLANE(this) && cmd == ONENAND_CMD_PROG) + cmd = ONENAND_CMD_2X_PROG; dataram = ONENAND_CURRENT_BUFFERRAM(this); break; } @@ -264,12 +478,6 @@ static int onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, size_t le /* Write 'BSA, BSC' of DataRAM */ value = onenand_buffer_address(dataram, sectors, count); this->write_word(value, this->base + ONENAND_REG_START_BUFFER); - - if (readcmd) { - /* Select DataRAM for DDP */ - value = onenand_bufferram_address(this, block); - this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); - } } /* Interrupt clear */ @@ -282,6 +490,30 @@ static int onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, size_t le } /** + * onenand_read_ecc - return ecc status + * @param this onenand chip structure + */ +static inline int onenand_read_ecc(struct onenand_chip *this) +{ + int ecc, i, result = 0; + + if (!FLEXONENAND(this) && !ONENAND_IS_4KB_PAGE(this)) + return this->read_word(this->base + ONENAND_REG_ECC_STATUS); + + for (i = 0; i < 4; i++) { + ecc = this->read_word(this->base + ONENAND_REG_ECC_STATUS + i*2); + if (likely(!ecc)) + continue; + if (ecc & FLEXONENAND_UNCORRECTABLE_ERROR) + return ONENAND_ECC_2BIT_ALL; + else + result = ONENAND_ECC_1BIT_ALL; + } + + return result; +} + +/** * onenand_wait - [DEFAULT] wait until the command is done * @param mtd MTD device structure * @param state state to select the max. timeout value @@ -296,7 +528,7 @@ static int onenand_wait(struct mtd_info *mtd, int state) unsigned long timeout; unsigned int flags = ONENAND_INT_MASTER; unsigned int interrupt = 0; - unsigned int ctrl, ecc; + unsigned int ctrl; /* The 20 msec is enough */ timeout = jiffies + msecs_to_jiffies(20); @@ -306,38 +538,162 @@ static int onenand_wait(struct mtd_info *mtd, int state) if (interrupt & flags) break; - if (state != FL_READING) + if (state != FL_READING && state != FL_PREPARING_ERASE) cond_resched(); - touch_softlockup_watchdog(); } /* To get correct interrupt status in timeout case */ interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT); ctrl = this->read_word(this->base + ONENAND_REG_CTRL_STATUS); - if (ctrl & ONENAND_CTRL_ERROR) { - /* It maybe occur at initial bad block */ - DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: controller error = 0x%04x\n", ctrl); - /* Clear other interrupt bits for preventing ECC error */ - interrupt &= ONENAND_INT_MASTER; + /* + * In the Spec. it checks the controller status first + * However if you get the correct information in case of + * power off recovery (POR) test, it should read ECC status first + */ + if (interrupt & ONENAND_INT_READ) { + int ecc = onenand_read_ecc(this); + if (ecc) { + if (ecc & ONENAND_ECC_2BIT_ALL) { + printk(KERN_ERR "%s: ECC error = 0x%04x\n", + __func__, ecc); + mtd->ecc_stats.failed++; + return -EBADMSG; + } else if (ecc & ONENAND_ECC_1BIT_ALL) { + printk(KERN_DEBUG "%s: correctable ECC error = 0x%04x\n", + __func__, ecc); + mtd->ecc_stats.corrected++; + } + } + } else if (state == FL_READING) { + printk(KERN_ERR "%s: read timeout! ctrl=0x%04x intr=0x%04x\n", + __func__, ctrl, interrupt); + return -EIO; } - if (ctrl & ONENAND_CTRL_LOCK) { - DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: it's locked error = 0x%04x\n", ctrl); - return -EACCES; + if (state == FL_PREPARING_ERASE && !(interrupt & ONENAND_INT_ERASE)) { + printk(KERN_ERR "%s: mb erase timeout! ctrl=0x%04x intr=0x%04x\n", + __func__, ctrl, interrupt); + return -EIO; } - if (interrupt & ONENAND_INT_READ) { - ecc = this->read_word(this->base + ONENAND_REG_ECC_STATUS); - if (ecc & ONENAND_ECC_2BIT_ALL) { - DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: ECC error = 0x%04x\n", ecc); - return -EBADMSG; - } + if (!(interrupt & ONENAND_INT_MASTER)) { + printk(KERN_ERR "%s: timeout! ctrl=0x%04x intr=0x%04x\n", + __func__, ctrl, interrupt); + return -EIO; + } + + /* If there's controller error, it's a real error */ + if (ctrl & ONENAND_CTRL_ERROR) { + printk(KERN_ERR "%s: controller error = 0x%04x\n", + __func__, ctrl); + if (ctrl & ONENAND_CTRL_LOCK) + printk(KERN_ERR "%s: it's locked error.\n", __func__); + return -EIO; } return 0; } +/* + * onenand_interrupt - [DEFAULT] onenand interrupt handler + * @param irq onenand interrupt number + * @param dev_id interrupt data + * + * complete the work + */ +static irqreturn_t onenand_interrupt(int irq, void *data) +{ + struct onenand_chip *this = data; + + /* To handle shared interrupt */ + if (!this->complete.done) + complete(&this->complete); + + return IRQ_HANDLED; +} + +/* + * onenand_interrupt_wait - [DEFAULT] wait until the command is done + * @param mtd MTD device structure + * @param state state to select the max. timeout value + * + * Wait for command done. + */ +static int onenand_interrupt_wait(struct mtd_info *mtd, int state) +{ + struct onenand_chip *this = mtd->priv; + + wait_for_completion(&this->complete); + + return onenand_wait(mtd, state); +} + +/* + * onenand_try_interrupt_wait - [DEFAULT] try interrupt wait + * @param mtd MTD device structure + * @param state state to select the max. timeout value + * + * Try interrupt based wait (It is used one-time) + */ +static int onenand_try_interrupt_wait(struct mtd_info *mtd, int state) +{ + struct onenand_chip *this = mtd->priv; + unsigned long remain, timeout; + + /* We use interrupt wait first */ + this->wait = onenand_interrupt_wait; + + timeout = msecs_to_jiffies(100); + remain = wait_for_completion_timeout(&this->complete, timeout); + if (!remain) { + printk(KERN_INFO "OneNAND: There's no interrupt. " + "We use the normal wait\n"); + + /* Release the irq */ + free_irq(this->irq, this); + + this->wait = onenand_wait; + } + + return onenand_wait(mtd, state); +} + +/* + * onenand_setup_wait - [OneNAND Interface] setup onenand wait method + * @param mtd MTD device structure + * + * There's two method to wait onenand work + * 1. polling - read interrupt status register + * 2. interrupt - use the kernel interrupt method + */ +static void onenand_setup_wait(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + int syscfg; + + init_completion(&this->complete); + + if (this->irq <= 0) { + this->wait = onenand_wait; + return; + } + + if (request_irq(this->irq, &onenand_interrupt, + IRQF_SHARED, "onenand", this)) { + /* If we can't get irq, use the normal wait */ + this->wait = onenand_wait; + return; + } + + /* Enable interrupt */ + syscfg = this->read_word(this->base + ONENAND_REG_SYS_CFG1); + syscfg |= ONENAND_SYS_CFG1_IOBE; + this->write_word(syscfg, this->base + ONENAND_REG_SYS_CFG1); + + this->wait = onenand_try_interrupt_wait; +} + /** * onenand_bufferram_offset - [DEFAULT] BufferRAM offset * @param mtd MTD data structure @@ -351,8 +707,9 @@ static inline int onenand_bufferram_offset(struct mtd_info *mtd, int area) struct onenand_chip *this = mtd->priv; if (ONENAND_CURRENT_BUFFERRAM(this)) { + /* Note: the 'this->writesize' is a real page size */ if (area == ONENAND_DATARAM) - return mtd->writesize; + return this->writesize; if (area == ONENAND_SPARERAM) return mtd->oobsize; } @@ -478,6 +835,30 @@ static int onenand_write_bufferram(struct mtd_info *mtd, int area, } /** + * onenand_get_2x_blockpage - [GENERIC] Get blockpage at 2x program mode + * @param mtd MTD data structure + * @param addr address to check + * @return blockpage address + * + * Get blockpage address at 2x program mode + */ +static int onenand_get_2x_blockpage(struct mtd_info *mtd, loff_t addr) +{ + struct onenand_chip *this = mtd->priv; + int blockpage, block, page; + + /* Calculate the even block number */ + block = (int) (addr >> this->erase_shift) & ~1; + /* Is it the odd plane? */ + if (addr & this->writesize) + block++; + page = (int) (addr >> (this->page_shift + 1)) & this->page_mask; + blockpage = (block << 7) | page; + + return blockpage; +} + +/** * onenand_check_bufferram - [GENERIC] Check BufferRAM information * @param mtd MTD data structure * @param addr address to check @@ -488,22 +869,35 @@ static int onenand_write_bufferram(struct mtd_info *mtd, int area, static int onenand_check_bufferram(struct mtd_info *mtd, loff_t addr) { struct onenand_chip *this = mtd->priv; - int block, page; - int i; + int blockpage, found = 0; + unsigned int i; - block = (int) (addr >> this->erase_shift); - page = (int) (addr >> this->page_shift); - page &= this->page_mask; + if (ONENAND_IS_2PLANE(this)) + blockpage = onenand_get_2x_blockpage(mtd, addr); + else + blockpage = (int) (addr >> this->page_shift); + /* Is there valid data? */ i = ONENAND_CURRENT_BUFFERRAM(this); + if (this->bufferram[i].blockpage == blockpage) + found = 1; + else { + /* Check another BufferRAM */ + i = ONENAND_NEXT_BUFFERRAM(this); + if (this->bufferram[i].blockpage == blockpage) { + ONENAND_SET_NEXT_BUFFERRAM(this); + found = 1; + } + } - /* Is there valid data? */ - if (this->bufferram[i].block == block && - this->bufferram[i].page == page && - this->bufferram[i].valid) - return 1; + if (found && ONENAND_IS_DDP(this)) { + /* Select DataRAM for DDP */ + int block = onenand_block(this, addr); + int value = onenand_bufferram_address(this, block); + this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); + } - return 0; + return found; } /** @@ -514,31 +908,52 @@ static int onenand_check_bufferram(struct mtd_info *mtd, loff_t addr) * * Update BufferRAM information */ -static int onenand_update_bufferram(struct mtd_info *mtd, loff_t addr, +static void onenand_update_bufferram(struct mtd_info *mtd, loff_t addr, int valid) { struct onenand_chip *this = mtd->priv; - int block, page; - int i; + int blockpage; + unsigned int i; - block = (int) (addr >> this->erase_shift); - page = (int) (addr >> this->page_shift); - page &= this->page_mask; + if (ONENAND_IS_2PLANE(this)) + blockpage = onenand_get_2x_blockpage(mtd, addr); + else + blockpage = (int) (addr >> this->page_shift); - /* Invalidate BufferRAM */ - for (i = 0; i < MAX_BUFFERRAM; i++) { - if (this->bufferram[i].block == block && - this->bufferram[i].page == page) - this->bufferram[i].valid = 0; - } + /* Invalidate another BufferRAM */ + i = ONENAND_NEXT_BUFFERRAM(this); + if (this->bufferram[i].blockpage == blockpage) + this->bufferram[i].blockpage = -1; /* Update BufferRAM */ i = ONENAND_CURRENT_BUFFERRAM(this); - this->bufferram[i].block = block; - this->bufferram[i].page = page; - this->bufferram[i].valid = valid; + if (valid) + this->bufferram[i].blockpage = blockpage; + else + this->bufferram[i].blockpage = -1; +} - return 0; +/** + * onenand_invalidate_bufferram - [GENERIC] Invalidate BufferRAM information + * @param mtd MTD data structure + * @param addr start address to invalidate + * @param len length to invalidate + * + * Invalidate BufferRAM information + */ +static void onenand_invalidate_bufferram(struct mtd_info *mtd, loff_t addr, + unsigned int len) +{ + struct onenand_chip *this = mtd->priv; + int i; + loff_t end_addr = addr + len; + + /* Invalidate BufferRAM */ + for (i = 0; i < MAX_BUFFERRAM; i++) { + loff_t buf_addr = this->bufferram[i].blockpage << this->page_shift; + if (buf_addr >= addr && buf_addr < end_addr) + this->bufferram[i].blockpage = -1; + } } /** @@ -561,6 +976,8 @@ static int onenand_get_device(struct mtd_info *mtd, int new_state) if (this->state == FL_READY) { this->state = new_state; spin_unlock(&this->chip_lock); + if (new_state != FL_PM_SUSPENDED && this->enable) + this->enable(mtd); break; } if (new_state == FL_PM_SUSPENDED) { @@ -587,6 +1004,8 @@ static void onenand_release_device(struct mtd_info *mtd) { struct onenand_chip *this = mtd->priv; + if (this->state != FL_PM_SUSPENDED && this->disable) + this->disable(mtd); /* Release the chip */ spin_lock(&this->chip_lock); this->state = FL_READY; @@ -595,137 +1014,412 @@ static void onenand_release_device(struct mtd_info *mtd) } /** - * onenand_read - [MTD Interface] Read data from flash + * onenand_transfer_auto_oob - [INTERN] oob auto-placement transfer + * @param mtd MTD device structure + * @param buf destination address + * @param column oob offset to read from + * @param thislen oob length to read + */ +static int onenand_transfer_auto_oob(struct mtd_info *mtd, uint8_t *buf, int column, + int thislen) +{ + struct onenand_chip *this = mtd->priv; + struct nand_oobfree *free; + int readcol = column; + int readend = column + thislen; + int lastgap = 0; + unsigned int i; + uint8_t *oob_buf = this->oob_buf; + + free = this->ecclayout->oobfree; + for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES && free->length; i++, free++) { + if (readcol >= lastgap) + readcol += free->offset - lastgap; + if (readend >= lastgap) + readend += free->offset - lastgap; + lastgap = free->offset + free->length; + } + this->read_bufferram(mtd, ONENAND_SPARERAM, oob_buf, 0, mtd->oobsize); + free = this->ecclayout->oobfree; + for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES && free->length; i++, free++) { + int free_end = free->offset + free->length; + if (free->offset < readend && free_end > readcol) { + int st = max_t(int,free->offset,readcol); + int ed = min_t(int,free_end,readend); + int n = ed - st; + memcpy(buf, oob_buf + st, n); + buf += n; + } else if (column == 0) + break; + } + return 0; +} + +/** + * onenand_recover_lsb - [Flex-OneNAND] Recover LSB page data + * @param mtd MTD device structure + * @param addr address to recover + * @param status return value from onenand_wait / onenand_bbt_wait + * + * MLC NAND Flash cell has paired pages - LSB page and MSB page. LSB page has + * lower page address and MSB page has higher page address in paired pages. + * If power off occurs during MSB page program, the paired LSB page data can + * become corrupt. LSB page recovery read is a way to read LSB page though page + * data are corrupted. When uncorrectable error occurs as a result of LSB page + * read after power up, issue LSB page recovery read. + */ +static int onenand_recover_lsb(struct mtd_info *mtd, loff_t addr, int status) +{ + struct onenand_chip *this = mtd->priv; + int i; + + /* Recovery is only for Flex-OneNAND */ + if (!FLEXONENAND(this)) + return status; + + /* check if we failed due to uncorrectable error */ + if (!mtd_is_eccerr(status) && status != ONENAND_BBT_READ_ECC_ERROR) + return status; + + /* check if address lies in MLC region */ + i = flexonenand_region(mtd, addr); + if (mtd->eraseregions[i].erasesize < (1 << this->erase_shift)) + return status; + + /* We are attempting to reread, so decrement stats.failed + * which was incremented by onenand_wait due to read failure + */ + printk(KERN_INFO "%s: Attempting to recover from uncorrectable read\n", + __func__); + mtd->ecc_stats.failed--; + + /* Issue the LSB page recovery command */ + this->command(mtd, FLEXONENAND_CMD_RECOVER_LSB, addr, this->writesize); + return this->wait(mtd, FL_READING); +} + +/** + * onenand_mlc_read_ops_nolock - MLC OneNAND read main and/or out-of-band * @param mtd MTD device structure * @param from offset to read from - * @param len number of bytes to read - * @param retlen pointer to variable to store the number of read bytes - * @param buf the databuffer to put data + * @param ops: oob operation description structure * - * Read with ecc -*/ -static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf) + * MLC OneNAND / Flex-OneNAND has 4KB page size and 4KB dataram. + * So, read-while-load is not present. + */ +static int onenand_mlc_read_ops_nolock(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) { struct onenand_chip *this = mtd->priv; - int read = 0, column; - int thislen; + struct mtd_ecc_stats stats; + size_t len = ops->len; + size_t ooblen = ops->ooblen; + u_char *buf = ops->datbuf; + u_char *oobbuf = ops->oobbuf; + int read = 0, column, thislen; + int oobread = 0, oobcolumn, thisooblen, oobsize; int ret = 0; + int writesize = this->writesize; - DEBUG(MTD_DEBUG_LEVEL3, "onenand_read: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len); + pr_debug("%s: from = 0x%08x, len = %i\n", __func__, (unsigned int)from, + (int)len); + + if (ops->mode == MTD_OPS_AUTO_OOB) + oobsize = this->ecclayout->oobavail; + else + oobsize = mtd->oobsize; + + oobcolumn = from & (mtd->oobsize - 1); /* Do not allow reads past end of device */ - if ((from + len) > mtd->size) { - DEBUG(MTD_DEBUG_LEVEL0, "onenand_read: Attempt read beyond end of device\n"); - *retlen = 0; + if (from + len > mtd->size) { + printk(KERN_ERR "%s: Attempt read beyond end of device\n", + __func__); + ops->retlen = 0; + ops->oobretlen = 0; return -EINVAL; } - /* Grab the lock and see if the device is available */ - onenand_get_device(mtd, FL_READING); - - /* TODO handling oob */ + stats = mtd->ecc_stats; while (read < len) { - thislen = min_t(int, mtd->writesize, len - read); + cond_resched(); + + thislen = min_t(int, writesize, len - read); - column = from & (mtd->writesize - 1); - if (column + thislen > mtd->writesize) - thislen = mtd->writesize - column; + column = from & (writesize - 1); + if (column + thislen > writesize) + thislen = writesize - column; if (!onenand_check_bufferram(mtd, from)) { - this->command(mtd, ONENAND_CMD_READ, from, mtd->writesize); + this->command(mtd, ONENAND_CMD_READ, from, writesize); ret = this->wait(mtd, FL_READING); - /* First copy data and check return value for ECC handling */ - onenand_update_bufferram(mtd, from, 1); + if (unlikely(ret)) + ret = onenand_recover_lsb(mtd, from, ret); + onenand_update_bufferram(mtd, from, !ret); + if (mtd_is_eccerr(ret)) + ret = 0; + if (ret) + break; } this->read_bufferram(mtd, ONENAND_DATARAM, buf, column, thislen); + if (oobbuf) { + thisooblen = oobsize - oobcolumn; + thisooblen = min_t(int, thisooblen, ooblen - oobread); - read += thislen; + if (ops->mode == MTD_OPS_AUTO_OOB) + onenand_transfer_auto_oob(mtd, oobbuf, oobcolumn, thisooblen); + else + this->read_bufferram(mtd, ONENAND_SPARERAM, oobbuf, oobcolumn, thisooblen); + oobread += thisooblen; + oobbuf += thisooblen; + oobcolumn = 0; + } + read += thislen; if (read == len) break; - if (ret) { - DEBUG(MTD_DEBUG_LEVEL0, "onenand_read: read failed = %d\n", ret); - goto out; - } - from += thislen; buf += thislen; } -out: - /* Deselect and wake up anyone waiting on the device */ - onenand_release_device(mtd); + /* + * Return success, if no ECC failures, else -EBADMSG + * fs driver will take care of that, because + * retlen == desired len and result == -EBADMSG + */ + ops->retlen = read; + ops->oobretlen = oobread; + + if (ret) + return ret; + + if (mtd->ecc_stats.failed - stats.failed) + return -EBADMSG; + + /* return max bitflips per ecc step; ONENANDs correct 1 bit only */ + return mtd->ecc_stats.corrected != stats.corrected ? 1 : 0; +} + +/** + * onenand_read_ops_nolock - [OneNAND Interface] OneNAND read main and/or out-of-band + * @param mtd MTD device structure + * @param from offset to read from + * @param ops: oob operation description structure + * + * OneNAND read main and/or out-of-band data + */ +static int onenand_read_ops_nolock(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct onenand_chip *this = mtd->priv; + struct mtd_ecc_stats stats; + size_t len = ops->len; + size_t ooblen = ops->ooblen; + u_char *buf = ops->datbuf; + u_char *oobbuf = ops->oobbuf; + int read = 0, column, thislen; + int oobread = 0, oobcolumn, thisooblen, oobsize; + int ret = 0, boundary = 0; + int writesize = this->writesize; + + pr_debug("%s: from = 0x%08x, len = %i\n", __func__, (unsigned int)from, + (int)len); + + if (ops->mode == MTD_OPS_AUTO_OOB) + oobsize = this->ecclayout->oobavail; + else + oobsize = mtd->oobsize; + + oobcolumn = from & (mtd->oobsize - 1); + + /* Do not allow reads past end of device */ + if ((from + len) > mtd->size) { + printk(KERN_ERR "%s: Attempt read beyond end of device\n", + __func__); + ops->retlen = 0; + ops->oobretlen = 0; + return -EINVAL; + } + + stats = mtd->ecc_stats; + + /* Read-while-load method */ + + /* Do first load to bufferRAM */ + if (read < len) { + if (!onenand_check_bufferram(mtd, from)) { + this->command(mtd, ONENAND_CMD_READ, from, writesize); + ret = this->wait(mtd, FL_READING); + onenand_update_bufferram(mtd, from, !ret); + if (mtd_is_eccerr(ret)) + ret = 0; + } + } + + thislen = min_t(int, writesize, len - read); + column = from & (writesize - 1); + if (column + thislen > writesize) + thislen = writesize - column; + + while (!ret) { + /* If there is more to load then start next load */ + from += thislen; + if (read + thislen < len) { + this->command(mtd, ONENAND_CMD_READ, from, writesize); + /* + * Chip boundary handling in DDP + * Now we issued chip 1 read and pointed chip 1 + * bufferram so we have to point chip 0 bufferram. + */ + if (ONENAND_IS_DDP(this) && + unlikely(from == (this->chipsize >> 1))) { + this->write_word(ONENAND_DDP_CHIP0, this->base + ONENAND_REG_START_ADDRESS2); + boundary = 1; + } else + boundary = 0; + ONENAND_SET_PREV_BUFFERRAM(this); + } + /* While load is going, read from last bufferRAM */ + this->read_bufferram(mtd, ONENAND_DATARAM, buf, column, thislen); + + /* Read oob area if needed */ + if (oobbuf) { + thisooblen = oobsize - oobcolumn; + thisooblen = min_t(int, thisooblen, ooblen - oobread); + + if (ops->mode == MTD_OPS_AUTO_OOB) + onenand_transfer_auto_oob(mtd, oobbuf, oobcolumn, thisooblen); + else + this->read_bufferram(mtd, ONENAND_SPARERAM, oobbuf, oobcolumn, thisooblen); + oobread += thisooblen; + oobbuf += thisooblen; + oobcolumn = 0; + } + + /* See if we are done */ + read += thislen; + if (read == len) + break; + /* Set up for next read from bufferRAM */ + if (unlikely(boundary)) + this->write_word(ONENAND_DDP_CHIP1, this->base + ONENAND_REG_START_ADDRESS2); + ONENAND_SET_NEXT_BUFFERRAM(this); + buf += thislen; + thislen = min_t(int, writesize, len - read); + column = 0; + cond_resched(); + /* Now wait for load */ + ret = this->wait(mtd, FL_READING); + onenand_update_bufferram(mtd, from, !ret); + if (mtd_is_eccerr(ret)) + ret = 0; + } /* * Return success, if no ECC failures, else -EBADMSG * fs driver will take care of that, because * retlen == desired len and result == -EBADMSG */ - *retlen = read; - return ret; + ops->retlen = read; + ops->oobretlen = oobread; + + if (ret) + return ret; + + if (mtd->ecc_stats.failed - stats.failed) + return -EBADMSG; + + /* return max bitflips per ecc step; ONENANDs correct 1 bit only */ + return mtd->ecc_stats.corrected != stats.corrected ? 1 : 0; } /** - * onenand_do_read_oob - [MTD Interface] OneNAND read out-of-band + * onenand_read_oob_nolock - [MTD Interface] OneNAND read out-of-band * @param mtd MTD device structure * @param from offset to read from - * @param len number of bytes to read - * @param retlen pointer to variable to store the number of read bytes - * @param buf the databuffer to put data + * @param ops: oob operation description structure * * OneNAND read out-of-band data from the spare area */ -int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf) +static int onenand_read_oob_nolock(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) { struct onenand_chip *this = mtd->priv; - int read = 0, thislen, column; - int ret = 0; + struct mtd_ecc_stats stats; + int read = 0, thislen, column, oobsize; + size_t len = ops->ooblen; + unsigned int mode = ops->mode; + u_char *buf = ops->oobbuf; + int ret = 0, readcmd; + + from += ops->ooboffs; - DEBUG(MTD_DEBUG_LEVEL3, "onenand_read_oob: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len); + pr_debug("%s: from = 0x%08x, len = %i\n", __func__, (unsigned int)from, + (int)len); /* Initialize return length value */ - *retlen = 0; + ops->oobretlen = 0; + + if (mode == MTD_OPS_AUTO_OOB) + oobsize = this->ecclayout->oobavail; + else + oobsize = mtd->oobsize; + + column = from & (mtd->oobsize - 1); + + if (unlikely(column >= oobsize)) { + printk(KERN_ERR "%s: Attempted to start read outside oob\n", + __func__); + return -EINVAL; + } /* Do not allow reads past end of device */ - if (unlikely((from + len) > mtd->size)) { - DEBUG(MTD_DEBUG_LEVEL0, "onenand_read_oob: Attempt read beyond end of device\n"); + if (unlikely(from >= mtd->size || + column + len > ((mtd->size >> this->page_shift) - + (from >> this->page_shift)) * oobsize)) { + printk(KERN_ERR "%s: Attempted to read beyond end of device\n", + __func__); return -EINVAL; } - /* Grab the lock and see if the device is available */ - onenand_get_device(mtd, FL_READING); + stats = mtd->ecc_stats; - column = from & (mtd->oobsize - 1); + readcmd = ONENAND_IS_4KB_PAGE(this) ? ONENAND_CMD_READ : ONENAND_CMD_READOOB; while (read < len) { - thislen = mtd->oobsize - column; + cond_resched(); + + thislen = oobsize - column; thislen = min_t(int, thislen, len); - this->command(mtd, ONENAND_CMD_READOOB, from, mtd->oobsize); + this->command(mtd, readcmd, from, mtd->oobsize); onenand_update_bufferram(mtd, from, 0); ret = this->wait(mtd, FL_READING); - /* First copy data and check return value for ECC handling */ + if (unlikely(ret)) + ret = onenand_recover_lsb(mtd, from, ret); - this->read_bufferram(mtd, ONENAND_SPARERAM, buf, column, thislen); + if (ret && !mtd_is_eccerr(ret)) { + printk(KERN_ERR "%s: read failed = 0x%x\n", + __func__, ret); + break; + } + + if (mode == MTD_OPS_AUTO_OOB) + onenand_transfer_auto_oob(mtd, buf, column, thislen); + else + this->read_bufferram(mtd, ONENAND_SPARERAM, buf, column, thislen); read += thislen; if (read == len) break; - if (ret) { - DEBUG(MTD_DEBUG_LEVEL0, "onenand_read_oob: read failed = %d\n", ret); - goto out; - } - buf += thislen; /* Read more? */ @@ -736,27 +1430,210 @@ int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len, } } -out: - /* Deselect and wake up anyone waiting on the device */ + ops->oobretlen = read; + + if (ret) + return ret; + + if (mtd->ecc_stats.failed - stats.failed) + return -EBADMSG; + + return 0; +} + +/** + * onenand_read - [MTD Interface] Read data from flash + * @param mtd MTD device structure + * @param from offset to read from + * @param len number of bytes to read + * @param retlen pointer to variable to store the number of read bytes + * @param buf the databuffer to put data + * + * Read with ecc +*/ +static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct onenand_chip *this = mtd->priv; + struct mtd_oob_ops ops = { + .len = len, + .ooblen = 0, + .datbuf = buf, + .oobbuf = NULL, + }; + int ret; + + onenand_get_device(mtd, FL_READING); + ret = ONENAND_IS_4KB_PAGE(this) ? + onenand_mlc_read_ops_nolock(mtd, from, &ops) : + onenand_read_ops_nolock(mtd, from, &ops); onenand_release_device(mtd); - *retlen = read; + *retlen = ops.retlen; return ret; } /** - * onenand_read_oob - [MTD Interface] NAND write data and/or out-of-band - * @mtd: MTD device structure - * @from: offset to read from - * @ops: oob operation description structure + * onenand_read_oob - [MTD Interface] Read main and/or out-of-band + * @param mtd: MTD device structure + * @param from: offset to read from + * @param ops: oob operation description structure + + * Read main and/or out-of-band */ static int onenand_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) { - BUG_ON(ops->mode != MTD_OOB_PLACE); + struct onenand_chip *this = mtd->priv; + int ret; + + switch (ops->mode) { + case MTD_OPS_PLACE_OOB: + case MTD_OPS_AUTO_OOB: + break; + case MTD_OPS_RAW: + /* Not implemented yet */ + default: + return -EINVAL; + } + + onenand_get_device(mtd, FL_READING); + if (ops->datbuf) + ret = ONENAND_IS_4KB_PAGE(this) ? + onenand_mlc_read_ops_nolock(mtd, from, ops) : + onenand_read_ops_nolock(mtd, from, ops); + else + ret = onenand_read_oob_nolock(mtd, from, ops); + onenand_release_device(mtd); + + return ret; +} + +/** + * onenand_bbt_wait - [DEFAULT] wait until the command is done + * @param mtd MTD device structure + * @param state state to select the max. timeout value + * + * Wait for command done. + */ +static int onenand_bbt_wait(struct mtd_info *mtd, int state) +{ + struct onenand_chip *this = mtd->priv; + unsigned long timeout; + unsigned int interrupt, ctrl, ecc, addr1, addr8; + + /* The 20 msec is enough */ + timeout = jiffies + msecs_to_jiffies(20); + while (time_before(jiffies, timeout)) { + interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT); + if (interrupt & ONENAND_INT_MASTER) + break; + } + /* To get correct interrupt status in timeout case */ + interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT); + ctrl = this->read_word(this->base + ONENAND_REG_CTRL_STATUS); + addr1 = this->read_word(this->base + ONENAND_REG_START_ADDRESS1); + addr8 = this->read_word(this->base + ONENAND_REG_START_ADDRESS8); + + if (interrupt & ONENAND_INT_READ) { + ecc = onenand_read_ecc(this); + if (ecc & ONENAND_ECC_2BIT_ALL) { + printk(KERN_DEBUG "%s: ecc 0x%04x ctrl 0x%04x " + "intr 0x%04x addr1 %#x addr8 %#x\n", + __func__, ecc, ctrl, interrupt, addr1, addr8); + return ONENAND_BBT_READ_ECC_ERROR; + } + } else { + printk(KERN_ERR "%s: read timeout! ctrl 0x%04x " + "intr 0x%04x addr1 %#x addr8 %#x\n", + __func__, ctrl, interrupt, addr1, addr8); + return ONENAND_BBT_READ_FATAL_ERROR; + } + + /* Initial bad block case: 0x2400 or 0x0400 */ + if (ctrl & ONENAND_CTRL_ERROR) { + printk(KERN_DEBUG "%s: ctrl 0x%04x intr 0x%04x addr1 %#x " + "addr8 %#x\n", __func__, ctrl, interrupt, addr1, addr8); + return ONENAND_BBT_READ_ERROR; + } + + return 0; +} + +/** + * onenand_bbt_read_oob - [MTD Interface] OneNAND read out-of-band for bbt scan + * @param mtd MTD device structure + * @param from offset to read from + * @param ops oob operation description structure + * + * OneNAND read out-of-band data from the spare area for bbt scan + */ +int onenand_bbt_read_oob(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct onenand_chip *this = mtd->priv; + int read = 0, thislen, column; + int ret = 0, readcmd; + size_t len = ops->ooblen; + u_char *buf = ops->oobbuf; + + pr_debug("%s: from = 0x%08x, len = %zi\n", __func__, (unsigned int)from, + len); + + /* Initialize return value */ + ops->oobretlen = 0; + + /* Do not allow reads past end of device */ + if (unlikely((from + len) > mtd->size)) { + printk(KERN_ERR "%s: Attempt read beyond end of device\n", + __func__); + return ONENAND_BBT_READ_FATAL_ERROR; + } + + /* Grab the lock and see if the device is available */ + onenand_get_device(mtd, FL_READING); + + column = from & (mtd->oobsize - 1); + + readcmd = ONENAND_IS_4KB_PAGE(this) ? ONENAND_CMD_READ : ONENAND_CMD_READOOB; + + while (read < len) { + cond_resched(); + + thislen = mtd->oobsize - column; + thislen = min_t(int, thislen, len); - return onenand_do_read_oob(mtd, from + ops->ooboffs, ops->len, - &ops->retlen, ops->oobbuf); + this->command(mtd, readcmd, from, mtd->oobsize); + + onenand_update_bufferram(mtd, from, 0); + + ret = this->bbt_wait(mtd, FL_READING); + if (unlikely(ret)) + ret = onenand_recover_lsb(mtd, from, ret); + + if (ret) + break; + + this->read_bufferram(mtd, ONENAND_SPARERAM, buf, column, thislen); + read += thislen; + if (read == len) + break; + + buf += thislen; + + /* Read more? */ + if (read < len) { + /* Update Page size */ + from += this->writesize; + column = 0; + } + } + + /* Deselect and wake up anyone waiting on the device */ + onenand_release_device(mtd); + + ops->oobretlen = read; + return ret; } #ifdef CONFIG_MTD_ONENAND_VERIFY_WRITE @@ -765,70 +1642,93 @@ static int onenand_read_oob(struct mtd_info *mtd, loff_t from, * @param mtd MTD device structure * @param buf the databuffer to verify * @param to offset to read from - * @param len number of bytes to read and compare - * */ -static int onenand_verify_oob(struct mtd_info *mtd, const u_char *buf, loff_t to, int len) +static int onenand_verify_oob(struct mtd_info *mtd, const u_char *buf, loff_t to) { struct onenand_chip *this = mtd->priv; - char *readp = this->page_buf; - int column = to & (mtd->oobsize - 1); - int status, i; + u_char *oob_buf = this->oob_buf; + int status, i, readcmd; + + readcmd = ONENAND_IS_4KB_PAGE(this) ? ONENAND_CMD_READ : ONENAND_CMD_READOOB; - this->command(mtd, ONENAND_CMD_READOOB, to, mtd->oobsize); + this->command(mtd, readcmd, to, mtd->oobsize); onenand_update_bufferram(mtd, to, 0); status = this->wait(mtd, FL_READING); if (status) return status; - this->read_bufferram(mtd, ONENAND_SPARERAM, readp, column, len); - - for(i = 0; i < len; i++) - if (buf[i] != 0xFF && buf[i] != readp[i]) + this->read_bufferram(mtd, ONENAND_SPARERAM, oob_buf, 0, mtd->oobsize); + for (i = 0; i < mtd->oobsize; i++) + if (buf[i] != 0xFF && buf[i] != oob_buf[i]) return -EBADMSG; return 0; } /** - * onenand_verify_page - [GENERIC] verify the chip contents after a write - * @param mtd MTD device structure - * @param buf the databuffer to verify - * - * Check DataRAM area directly + * onenand_verify - [GENERIC] verify the chip contents after a write + * @param mtd MTD device structure + * @param buf the databuffer to verify + * @param addr offset to read from + * @param len number of bytes to read and compare */ -static int onenand_verify_page(struct mtd_info *mtd, u_char *buf, loff_t addr) +static int onenand_verify(struct mtd_info *mtd, const u_char *buf, loff_t addr, size_t len) { struct onenand_chip *this = mtd->priv; - void __iomem *dataram0, *dataram1; int ret = 0; + int thislen, column; - this->command(mtd, ONENAND_CMD_READ, addr, mtd->writesize); + column = addr & (this->writesize - 1); - ret = this->wait(mtd, FL_READING); - if (ret) - return ret; + while (len != 0) { + thislen = min_t(int, this->writesize - column, len); - onenand_update_bufferram(mtd, addr, 1); + this->command(mtd, ONENAND_CMD_READ, addr, this->writesize); - /* Check, if the two dataram areas are same */ - dataram0 = this->base + ONENAND_DATARAM; - dataram1 = dataram0 + mtd->writesize; + onenand_update_bufferram(mtd, addr, 0); - if (memcmp(dataram0, dataram1, mtd->writesize)) - return -EBADMSG; + ret = this->wait(mtd, FL_READING); + if (ret) + return ret; + + onenand_update_bufferram(mtd, addr, 1); + + this->read_bufferram(mtd, ONENAND_DATARAM, this->verify_buf, 0, mtd->writesize); + + if (memcmp(buf, this->verify_buf + column, thislen)) + return -EBADMSG; + + len -= thislen; + buf += thislen; + addr += thislen; + column = 0; + } return 0; } #else -#define onenand_verify_page(...) (0) +#define onenand_verify(...) (0) #define onenand_verify_oob(...) (0) #endif -#define NOTALIGNED(x) ((x & (mtd->writesize - 1)) != 0) +#define NOTALIGNED(x) ((x & (this->subpagesize - 1)) != 0) + +static void onenand_panic_wait(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + unsigned int interrupt; + int i; + + for (i = 0; i < 2000; i++) { + interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT); + if (interrupt & ONENAND_INT_MASTER) + break; + udelay(10); + } +} /** - * onenand_write - [MTD Interface] write buffer to FLASH + * onenand_panic_write - [MTD Interface] write buffer to FLASH in a panic context * @param mtd MTD device structure * @param to offset to write to * @param len number of bytes to write @@ -837,182 +1737,499 @@ static int onenand_verify_page(struct mtd_info *mtd, u_char *buf, loff_t addr) * * Write with ECC */ -static int onenand_write(struct mtd_info *mtd, loff_t to, size_t len, - size_t *retlen, const u_char *buf) +static int onenand_panic_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) { struct onenand_chip *this = mtd->priv; + int column, subpage; int written = 0; int ret = 0; - DEBUG(MTD_DEBUG_LEVEL3, "onenand_write: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len); + if (this->state == FL_PM_SUSPENDED) + return -EBUSY; - /* Initialize retlen, in case of early exit */ - *retlen = 0; + /* Wait for any existing operation to clear */ + onenand_panic_wait(mtd); - /* Do not allow writes past end of device */ - if (unlikely((to + len) > mtd->size)) { - DEBUG(MTD_DEBUG_LEVEL0, "onenand_write: Attempt write to past end of device\n"); - return -EINVAL; - } + pr_debug("%s: to = 0x%08x, len = %i\n", __func__, (unsigned int)to, + (int)len); /* Reject writes, which are not page aligned */ - if (unlikely(NOTALIGNED(to)) || unlikely(NOTALIGNED(len))) { - DEBUG(MTD_DEBUG_LEVEL0, "onenand_write: Attempt to write not page aligned data\n"); + if (unlikely(NOTALIGNED(to) || NOTALIGNED(len))) { + printk(KERN_ERR "%s: Attempt to write not page aligned data\n", + __func__); return -EINVAL; } - /* Grab the lock and see if the device is available */ - onenand_get_device(mtd, FL_WRITING); + column = to & (mtd->writesize - 1); /* Loop until all data write */ while (written < len) { - int thislen = min_t(int, mtd->writesize, len - written); + int thislen = min_t(int, mtd->writesize - column, len - written); + u_char *wbuf = (u_char *) buf; - this->command(mtd, ONENAND_CMD_BUFFERRAM, to, mtd->writesize); + this->command(mtd, ONENAND_CMD_BUFFERRAM, to, thislen); - this->write_bufferram(mtd, ONENAND_DATARAM, buf, 0, 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; + } + + this->write_bufferram(mtd, ONENAND_DATARAM, wbuf, 0, mtd->writesize); this->write_bufferram(mtd, ONENAND_SPARERAM, ffchars, 0, mtd->oobsize); this->command(mtd, ONENAND_CMD_PROG, to, mtd->writesize); - onenand_update_bufferram(mtd, to, 1); + onenand_panic_wait(mtd); - ret = this->wait(mtd, FL_WRITING); - if (ret) { - DEBUG(MTD_DEBUG_LEVEL0, "onenand_write: write filaed %d\n", ret); - goto out; + /* 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); } - written += thislen; - - /* Only check verify write turn on */ - ret = onenand_verify_page(mtd, (u_char *) buf, to); if (ret) { - DEBUG(MTD_DEBUG_LEVEL0, "onenand_write: verify failed %d\n", ret); - goto out; + printk(KERN_ERR "%s: write failed %d\n", __func__, ret); + break; } + written += thislen; + if (written == len) break; + column = 0; to += thislen; buf += thislen; } -out: - /* Deselect and wake up anyone waiting on the device */ - onenand_release_device(mtd); - *retlen = written; + return ret; +} + +/** + * onenand_fill_auto_oob - [INTERN] oob auto-placement transfer + * @param mtd MTD device structure + * @param oob_buf oob buffer + * @param buf source address + * @param column oob offset to write to + * @param thislen oob length to write + */ +static int onenand_fill_auto_oob(struct mtd_info *mtd, u_char *oob_buf, + const u_char *buf, int column, int thislen) +{ + struct onenand_chip *this = mtd->priv; + struct nand_oobfree *free; + int writecol = column; + int writeend = column + thislen; + int lastgap = 0; + unsigned int i; + + free = this->ecclayout->oobfree; + for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES && free->length; i++, free++) { + if (writecol >= lastgap) + writecol += free->offset - lastgap; + if (writeend >= lastgap) + writeend += free->offset - lastgap; + lastgap = free->offset + free->length; + } + free = this->ecclayout->oobfree; + for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES && free->length; i++, free++) { + int free_end = free->offset + free->length; + if (free->offset < writeend && free_end > writecol) { + int st = max_t(int,free->offset,writecol); + int ed = min_t(int,free_end,writeend); + int n = ed - st; + memcpy(oob_buf + st, buf, n); + buf += n; + } else if (column == 0) + break; + } + return 0; +} + +/** + * onenand_write_ops_nolock - [OneNAND Interface] write main and/or out-of-band + * @param mtd MTD device structure + * @param to offset to write to + * @param ops oob operation description structure + * + * Write main and/or oob with ECC + */ +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 = 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; + const u_char *buf = ops->datbuf; + const u_char *oob = ops->oobbuf; + u_char *oobbuf; + int ret = 0, cmd; + + pr_debug("%s: to = 0x%08x, len = %i\n", __func__, (unsigned int)to, + (int)len); + + /* Initialize retlen, in case of early exit */ + ops->retlen = 0; + ops->oobretlen = 0; + + /* Reject writes, which are not page aligned */ + if (unlikely(NOTALIGNED(to) || NOTALIGNED(len))) { + printk(KERN_ERR "%s: Attempt to write not page aligned data\n", + __func__); + return -EINVAL; + } + + /* Check zero length */ + if (!len) + return 0; + + if (ops->mode == MTD_OPS_AUTO_OOB) + oobsize = this->ecclayout->oobavail; + else + oobsize = mtd->oobsize; + + oobcolumn = to & (mtd->oobsize - 1); + + column = to & (mtd->writesize - 1); + + /* Loop until all data write */ + 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); + + cond_resched(); + + 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; + } + + this->write_bufferram(mtd, ONENAND_DATARAM, wbuf, 0, mtd->writesize); + + 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_OPS_AUTO_OOB) + onenand_fill_auto_oob(mtd, oobbuf, oob, oobcolumn, thisooblen); + else + memcpy(oobbuf + oobcolumn, oob, thisooblen); + + oobwritten += thisooblen; + oob += thisooblen; + oobcolumn = 0; + } else + oobbuf = (u_char *) ffchars; + + this->write_bufferram(mtd, ONENAND_SPARERAM, oobbuf, 0, mtd->oobsize); + } else + ONENAND_SET_NEXT_BUFFERRAM(this); + + /* + * 2 PLANE, MLC, and Flex-OneNAND do not support + * write-while-program feature. + */ + if (!ONENAND_IS_2PLANE(this) && !ONENAND_IS_4KB_PAGE(this) && !first) { + ONENAND_SET_PREV_BUFFERRAM(this); + + 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 "%s: write failed %d\n", + __func__, ret); + break; + } + + if (written == len) { + /* Only check verify write turn on */ + ret = onenand_verify(mtd, buf - len, to - len, len); + if (ret) + printk(KERN_ERR "%s: verify failed %d\n", + __func__, ret); + break; + } + + ONENAND_SET_NEXT_BUFFERRAM(this); + } + + this->ongoing = 0; + cmd = ONENAND_CMD_PROG; + + /* Exclude 1st OTP and OTP blocks for cache program feature */ + if (ONENAND_IS_CACHE_PROGRAM(this) && + likely(onenand_block(this, to) != 0) && + ONENAND_IS_4KB_PAGE(this) && + ((written + thislen) < len)) { + cmd = ONENAND_CMD_2X_CACHE_PROG; + this->ongoing = 1; + } + + this->command(mtd, cmd, to, mtd->writesize); + + /* + * 2 PLANE, MLC, and Flex-OneNAND wait here + */ + if (ONENAND_IS_2PLANE(this) || ONENAND_IS_4KB_PAGE(this)) { + ret = this->wait(mtd, FL_WRITING); + + /* In partial page write we don't update bufferram */ + onenand_update_bufferram(mtd, to, !ret && !subpage); + if (ret) { + printk(KERN_ERR "%s: write failed %d\n", + __func__, ret); + break; + } + + /* Only check verify write turn on */ + ret = onenand_verify(mtd, buf, to, thislen); + if (ret) { + printk(KERN_ERR "%s: verify failed %d\n", + __func__, 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; } + /** - * onenand_do_write_oob - [Internal] OneNAND write out-of-band + * onenand_write_oob_nolock - [INTERN] OneNAND write out-of-band * @param mtd MTD device structure * @param to offset to write to * @param len number of bytes to write * @param retlen pointer to variable to store the number of written bytes * @param buf the data to write + * @param mode operation mode * * OneNAND write out-of-band */ -static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len, - size_t *retlen, const u_char *buf) +static int onenand_write_oob_nolock(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) { struct onenand_chip *this = mtd->priv; - int column, ret = 0; - int written = 0; + int column, ret = 0, oobsize; + int written = 0, oobcmd; + u_char *oobbuf; + size_t len = ops->ooblen; + const u_char *buf = ops->oobbuf; + unsigned int mode = ops->mode; - DEBUG(MTD_DEBUG_LEVEL3, "onenand_write_oob: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len); + to += ops->ooboffs; + + pr_debug("%s: to = 0x%08x, len = %i\n", __func__, (unsigned int)to, + (int)len); /* Initialize retlen, in case of early exit */ - *retlen = 0; + ops->oobretlen = 0; - /* Do not allow writes past end of device */ - if (unlikely((to + len) > mtd->size)) { - DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_oob: Attempt write to past end of device\n"); + if (mode == MTD_OPS_AUTO_OOB) + oobsize = this->ecclayout->oobavail; + else + oobsize = mtd->oobsize; + + column = to & (mtd->oobsize - 1); + + if (unlikely(column >= oobsize)) { + printk(KERN_ERR "%s: Attempted to start write outside oob\n", + __func__); return -EINVAL; } - /* Grab the lock and see if the device is available */ - onenand_get_device(mtd, FL_WRITING); + /* For compatibility with NAND: Do not allow write past end of page */ + if (unlikely(column + len > oobsize)) { + printk(KERN_ERR "%s: Attempt to write past end of page\n", + __func__); + return -EINVAL; + } + + /* Do not allow reads past end of device */ + if (unlikely(to >= mtd->size || + column + len > ((mtd->size >> this->page_shift) - + (to >> this->page_shift)) * oobsize)) { + printk(KERN_ERR "%s: Attempted to write past end of device\n", + __func__); + return -EINVAL; + } + + oobbuf = this->oob_buf; + + oobcmd = ONENAND_IS_4KB_PAGE(this) ? ONENAND_CMD_PROG : ONENAND_CMD_PROGOOB; /* Loop until all data write */ while (written < len) { - int thislen = min_t(int, mtd->oobsize, len - written); + int thislen = min_t(int, oobsize, len - written); - column = to & (mtd->oobsize - 1); + cond_resched(); this->command(mtd, ONENAND_CMD_BUFFERRAM, to, mtd->oobsize); /* We send data to spare ram with oobsize * to prevent byte access */ - memset(this->page_buf, 0xff, mtd->oobsize); - memcpy(this->page_buf + column, buf, thislen); - this->write_bufferram(mtd, ONENAND_SPARERAM, this->page_buf, 0, mtd->oobsize); + memset(oobbuf, 0xff, mtd->oobsize); + if (mode == MTD_OPS_AUTO_OOB) + onenand_fill_auto_oob(mtd, oobbuf, buf, column, thislen); + else + memcpy(oobbuf + column, buf, thislen); + this->write_bufferram(mtd, ONENAND_SPARERAM, oobbuf, 0, mtd->oobsize); + + if (ONENAND_IS_4KB_PAGE(this)) { + /* Set main area of DataRAM to 0xff*/ + memset(this->page_buf, 0xff, mtd->writesize); + this->write_bufferram(mtd, ONENAND_DATARAM, + this->page_buf, 0, mtd->writesize); + } - this->command(mtd, ONENAND_CMD_PROGOOB, to, mtd->oobsize); + this->command(mtd, oobcmd, to, mtd->oobsize); onenand_update_bufferram(mtd, to, 0); + if (ONENAND_IS_2PLANE(this)) { + ONENAND_SET_BUFFERRAM1(this); + onenand_update_bufferram(mtd, to + this->writesize, 0); + } ret = this->wait(mtd, FL_WRITING); if (ret) { - DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_oob: write filaed %d\n", ret); - goto out; + printk(KERN_ERR "%s: write failed %d\n", __func__, ret); + break; } - ret = onenand_verify_oob(mtd, buf, to, thislen); + ret = onenand_verify_oob(mtd, oobbuf, to); if (ret) { - DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_oob: verify failed %d\n", ret); - goto out; + printk(KERN_ERR "%s: verify failed %d\n", + __func__, ret); + break; } written += thislen; - if (written == len) break; - to += thislen; + to += mtd->writesize; buf += thislen; + column = 0; } -out: - /* Deselect and wake up anyone waiting on the device */ - onenand_release_device(mtd); + ops->oobretlen = written; - *retlen = written; + return ret; +} + +/** + * onenand_write - [MTD Interface] write buffer to FLASH + * @param mtd MTD device structure + * @param to offset to write to + * @param len number of bytes to write + * @param retlen pointer to variable to store the number of written bytes + * @param buf the data to write + * + * Write with ECC + */ +static int onenand_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct mtd_oob_ops ops = { + .len = len, + .ooblen = 0, + .datbuf = (u_char *) buf, + .oobbuf = NULL, + }; + int ret; + onenand_get_device(mtd, FL_WRITING); + ret = onenand_write_ops_nolock(mtd, to, &ops); + onenand_release_device(mtd); + + *retlen = ops.retlen; return ret; } /** * onenand_write_oob - [MTD Interface] NAND write data and/or out-of-band - * @mtd: MTD device structure - * @from: offset to read from - * @ops: oob operation description structure + * @param mtd: MTD device structure + * @param to: offset to write + * @param ops: oob operation description structure */ static int onenand_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops) { - BUG_ON(ops->mode != MTD_OOB_PLACE); + int ret; + + switch (ops->mode) { + case MTD_OPS_PLACE_OOB: + case MTD_OPS_AUTO_OOB: + break; + case MTD_OPS_RAW: + /* Not implemented yet */ + default: + return -EINVAL; + } + + onenand_get_device(mtd, FL_WRITING); + if (ops->datbuf) + ret = onenand_write_ops_nolock(mtd, to, ops); + else + ret = onenand_write_oob_nolock(mtd, to, ops); + onenand_release_device(mtd); - return onenand_do_write_oob(mtd, to + ops->ooboffs, ops->len, - &ops->retlen, ops->oobbuf); + return ret; } /** - * onenand_block_checkbad - [GENERIC] Check if a block is marked bad + * onenand_block_isbad_nolock - [GENERIC] Check if a block is marked bad * @param mtd MTD device structure * @param ofs offset from device start - * @param getchip 0, if the chip is already selected * @param allowbbt 1, if its allowed to access the bbt area * * Check, if the block is bad. Either by reading the bad block table or * calling of the scan function. */ -static int onenand_block_checkbad(struct mtd_info *mtd, loff_t ofs, int getchip, int allowbbt) +static int onenand_block_isbad_nolock(struct mtd_info *mtd, loff_t ofs, int allowbbt) { struct onenand_chip *this = mtd->priv; struct bbm_info *bbm = this->bbm; @@ -1021,93 +2238,292 @@ static int onenand_block_checkbad(struct mtd_info *mtd, loff_t ofs, int getchip, return bbm->isbad_bbt(mtd, ofs, allowbbt); } + +static int onenand_multiblock_erase_verify(struct mtd_info *mtd, + struct erase_info *instr) +{ + struct onenand_chip *this = mtd->priv; + loff_t addr = instr->addr; + int len = instr->len; + unsigned int block_size = (1 << this->erase_shift); + int ret = 0; + + while (len) { + this->command(mtd, ONENAND_CMD_ERASE_VERIFY, addr, block_size); + ret = this->wait(mtd, FL_VERIFYING_ERASE); + if (ret) { + printk(KERN_ERR "%s: Failed verify, block %d\n", + __func__, onenand_block(this, addr)); + instr->state = MTD_ERASE_FAILED; + instr->fail_addr = addr; + return -1; + } + len -= block_size; + addr += block_size; + } + return 0; +} + /** - * onenand_erase - [MTD Interface] erase block(s) + * onenand_multiblock_erase - [INTERN] erase block(s) using multiblock erase * @param mtd MTD device structure * @param instr erase instruction + * @param region erase region * - * Erase one ore more blocks + * Erase one or more blocks up to 64 block at a time */ -static int onenand_erase(struct mtd_info *mtd, struct erase_info *instr) +static int onenand_multiblock_erase(struct mtd_info *mtd, + struct erase_info *instr, + unsigned int block_size) { struct onenand_chip *this = mtd->priv; - unsigned int block_size; - loff_t addr; - int len; + loff_t addr = instr->addr; + int len = instr->len; + int eb_count = 0; int ret = 0; + int bdry_block = 0; - DEBUG(MTD_DEBUG_LEVEL3, "onenand_erase: start = 0x%08x, len = %i\n", (unsigned int) instr->addr, (unsigned int) instr->len); - - block_size = (1 << this->erase_shift); + instr->state = MTD_ERASING; - /* Start address must align on block boundary */ - if (unlikely(instr->addr & (block_size - 1))) { - DEBUG(MTD_DEBUG_LEVEL0, "onenand_erase: Unaligned address\n"); - return -EINVAL; + if (ONENAND_IS_DDP(this)) { + loff_t bdry_addr = this->chipsize >> 1; + if (addr < bdry_addr && (addr + len) > bdry_addr) + bdry_block = bdry_addr >> this->erase_shift; } - /* Length must align on block boundary */ - if (unlikely(instr->len & (block_size - 1))) { - DEBUG(MTD_DEBUG_LEVEL0, "onenand_erase: Length not block aligned\n"); - return -EINVAL; + /* Pre-check bbs */ + while (len) { + /* Check if we have a bad block, we do not erase bad blocks */ + if (onenand_block_isbad_nolock(mtd, addr, 0)) { + printk(KERN_WARNING "%s: attempt to erase a bad block " + "at addr 0x%012llx\n", + __func__, (unsigned long long) addr); + instr->state = MTD_ERASE_FAILED; + return -EIO; + } + len -= block_size; + addr += block_size; } - /* Do not allow erase past end of device */ - if (unlikely((instr->len + instr->addr) > mtd->size)) { - DEBUG(MTD_DEBUG_LEVEL0, "onenand_erase: Erase past end of device\n"); - return -EINVAL; + len = instr->len; + addr = instr->addr; + + /* loop over 64 eb batches */ + while (len) { + struct erase_info verify_instr = *instr; + int max_eb_count = MB_ERASE_MAX_BLK_COUNT; + + verify_instr.addr = addr; + verify_instr.len = 0; + + /* do not cross chip boundary */ + if (bdry_block) { + int this_block = (addr >> this->erase_shift); + + if (this_block < bdry_block) { + max_eb_count = min(max_eb_count, + (bdry_block - this_block)); + } + } + + eb_count = 0; + + while (len > block_size && eb_count < (max_eb_count - 1)) { + this->command(mtd, ONENAND_CMD_MULTIBLOCK_ERASE, + addr, block_size); + onenand_invalidate_bufferram(mtd, addr, block_size); + + ret = this->wait(mtd, FL_PREPARING_ERASE); + if (ret) { + printk(KERN_ERR "%s: Failed multiblock erase, " + "block %d\n", __func__, + onenand_block(this, addr)); + instr->state = MTD_ERASE_FAILED; + instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN; + return -EIO; + } + + len -= block_size; + addr += block_size; + eb_count++; + } + + /* last block of 64-eb series */ + cond_resched(); + this->command(mtd, ONENAND_CMD_ERASE, addr, block_size); + onenand_invalidate_bufferram(mtd, addr, block_size); + + ret = this->wait(mtd, FL_ERASING); + /* Check if it is write protected */ + if (ret) { + printk(KERN_ERR "%s: Failed erase, block %d\n", + __func__, onenand_block(this, addr)); + instr->state = MTD_ERASE_FAILED; + instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN; + return -EIO; + } + + len -= block_size; + addr += block_size; + eb_count++; + + /* verify */ + verify_instr.len = eb_count * block_size; + if (onenand_multiblock_erase_verify(mtd, &verify_instr)) { + instr->state = verify_instr.state; + instr->fail_addr = verify_instr.fail_addr; + return -EIO; + } + } + return 0; +} - instr->fail_addr = 0xffffffff; - /* Grab the lock and see if the device is available */ - onenand_get_device(mtd, FL_ERASING); +/** + * onenand_block_by_block_erase - [INTERN] erase block(s) using regular erase + * @param mtd MTD device structure + * @param instr erase instruction + * @param region erase region + * @param block_size erase block size + * + * Erase one or more blocks one block at a time + */ +static int onenand_block_by_block_erase(struct mtd_info *mtd, + struct erase_info *instr, + struct mtd_erase_region_info *region, + unsigned int block_size) +{ + struct onenand_chip *this = mtd->priv; + loff_t addr = instr->addr; + int len = instr->len; + loff_t region_end = 0; + int ret = 0; - /* Loop throught the pages */ - len = instr->len; - addr = instr->addr; + if (region) { + /* region is set for Flex-OneNAND */ + region_end = region->offset + region->erasesize * region->numblocks; + } instr->state = MTD_ERASING; + /* Loop through the blocks */ while (len) { + cond_resched(); /* Check if we have a bad block, we do not erase bad blocks */ - if (onenand_block_checkbad(mtd, addr, 0, 0)) { - printk (KERN_WARNING "onenand_erase: attempt to erase a bad block at addr 0x%08x\n", (unsigned int) addr); + if (onenand_block_isbad_nolock(mtd, addr, 0)) { + printk(KERN_WARNING "%s: attempt to erase a bad block " + "at addr 0x%012llx\n", + __func__, (unsigned long long) addr); instr->state = MTD_ERASE_FAILED; - goto erase_exit; + return -EIO; } this->command(mtd, ONENAND_CMD_ERASE, addr, block_size); + onenand_invalidate_bufferram(mtd, addr, block_size); + ret = this->wait(mtd, FL_ERASING); /* Check, if it is write protected */ if (ret) { - if (ret == -EPERM) - DEBUG(MTD_DEBUG_LEVEL0, "onenand_erase: Device is write protected!!!\n"); - else - DEBUG(MTD_DEBUG_LEVEL0, "onenand_erase: Failed erase, block %d\n", (unsigned) (addr >> this->erase_shift)); + printk(KERN_ERR "%s: Failed erase, block %d\n", + __func__, onenand_block(this, addr)); instr->state = MTD_ERASE_FAILED; instr->fail_addr = addr; - goto erase_exit; + return -EIO; } len -= block_size; addr += block_size; + + if (region && addr == region_end) { + if (!len) + break; + region++; + + block_size = region->erasesize; + region_end = region->offset + region->erasesize * region->numblocks; + + if (len & (block_size - 1)) { + /* FIXME: This should be handled at MTD partitioning level. */ + printk(KERN_ERR "%s: Unaligned address\n", + __func__); + return -EIO; + } + } } + return 0; +} - instr->state = MTD_ERASE_DONE; +/** + * onenand_erase - [MTD Interface] erase block(s) + * @param mtd MTD device structure + * @param instr erase instruction + * + * Erase one or more blocks + */ +static int onenand_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + struct onenand_chip *this = mtd->priv; + unsigned int block_size; + loff_t addr = instr->addr; + loff_t len = instr->len; + int ret = 0; + struct mtd_erase_region_info *region = NULL; + loff_t region_offset = 0; -erase_exit: + pr_debug("%s: start=0x%012llx, len=%llu\n", __func__, + (unsigned long long)instr->addr, + (unsigned long long)instr->len); - ret = instr->state == MTD_ERASE_DONE ? 0 : -EIO; - /* Do call back function */ - if (!ret) - mtd_erase_callback(instr); + if (FLEXONENAND(this)) { + /* Find the eraseregion of this address */ + int i = flexonenand_region(mtd, addr); + + region = &mtd->eraseregions[i]; + block_size = region->erasesize; + + /* Start address within region must align on block boundary. + * Erase region's start offset is always block start address. + */ + region_offset = region->offset; + } else + block_size = 1 << this->erase_shift; + + /* Start address must align on block boundary */ + if (unlikely((addr - region_offset) & (block_size - 1))) { + printk(KERN_ERR "%s: Unaligned address\n", __func__); + return -EINVAL; + } + + /* Length must align on block boundary */ + if (unlikely(len & (block_size - 1))) { + printk(KERN_ERR "%s: Length not block aligned\n", __func__); + return -EINVAL; + } + + /* Grab the lock and see if the device is available */ + onenand_get_device(mtd, FL_ERASING); + + if (ONENAND_IS_4KB_PAGE(this) || region || + instr->len < MB_ERASE_MIN_BLK_COUNT * block_size) { + /* region is set for Flex-OneNAND (no mb erase) */ + ret = onenand_block_by_block_erase(mtd, instr, + region, block_size); + } else { + ret = onenand_multiblock_erase(mtd, instr, block_size); + } /* Deselect and wake up anyone waiting on the device */ onenand_release_device(mtd); + /* Do call back function */ + if (!ret) { + instr->state = MTD_ERASE_DONE; + mtd_erase_callback(instr); + } + return ret; } @@ -1119,7 +2535,7 @@ erase_exit: */ static void onenand_sync(struct mtd_info *mtd) { - DEBUG(MTD_DEBUG_LEVEL3, "onenand_sync: called\n"); + pr_debug("%s: called\n", __func__); /* Grab the lock and see if the device is available */ onenand_get_device(mtd, FL_SYNCING); @@ -1128,7 +2544,6 @@ static void onenand_sync(struct mtd_info *mtd) onenand_release_device(mtd); } - /** * onenand_block_isbad - [MTD Interface] Check whether the block at the given offset is bad * @param mtd MTD device structure @@ -1138,11 +2553,12 @@ static void onenand_sync(struct mtd_info *mtd) */ static int onenand_block_isbad(struct mtd_info *mtd, loff_t ofs) { - /* Check for invalid offset */ - if (ofs > mtd->size) - return -EINVAL; + int ret; - return onenand_block_checkbad(mtd, ofs, 1, 0); + onenand_get_device(mtd, FL_READING); + ret = onenand_block_isbad_nolock(mtd, ofs, 0); + onenand_release_device(mtd); + return ret; } /** @@ -1158,17 +2574,26 @@ static int onenand_default_block_markbad(struct mtd_info *mtd, loff_t ofs) struct onenand_chip *this = mtd->priv; struct bbm_info *bbm = this->bbm; u_char buf[2] = {0, 0}; - size_t retlen; + struct mtd_oob_ops ops = { + .mode = MTD_OPS_PLACE_OOB, + .ooblen = 2, + .oobbuf = buf, + .ooboffs = 0, + }; int block; /* Get block number */ - block = ((int) ofs) >> bbm->bbt_erase_shift; + block = onenand_block(this, ofs); if (bbm->bbt) bbm->bbt[block >> 2] |= 0x01 << ((block & 0x03) << 1); - /* We write two bytes, so we dont have to mess with 16 bit access */ + /* We write two bytes, so we don't have to mess with 16-bit access */ ofs += mtd->oobsize + (bbm->badblockpos & ~0x01); - return onenand_do_write_oob(mtd, ofs , 2, &retlen, buf); + /* FIXME : What to do when marking SLC block in partition + * with MLC erasesize? For now, it is not advisable to + * create partitions containing both SLC and MLC regions. + */ + return onenand_write_oob_nolock(mtd, ofs, &ops); } /** @@ -1180,7 +2605,6 @@ static int onenand_default_block_markbad(struct mtd_info *mtd, loff_t ofs) */ static int onenand_block_markbad(struct mtd_info *mtd, loff_t ofs) { - struct onenand_chip *this = mtd->priv; int ret; ret = onenand_block_isbad(mtd, ofs); @@ -1191,36 +2615,46 @@ static int onenand_block_markbad(struct mtd_info *mtd, loff_t ofs) return ret; } - return this->block_markbad(mtd, ofs); + onenand_get_device(mtd, FL_WRITING); + ret = mtd_block_markbad(mtd, ofs); + onenand_release_device(mtd); + return ret; } /** - * onenand_unlock - [MTD Interface] Unlock block(s) + * onenand_do_lock_cmd - [OneNAND Interface] Lock or unlock block(s) * @param mtd MTD device structure * @param ofs offset relative to mtd start - * @param len number of bytes to unlock + * @param len number of bytes to lock or unlock + * @param cmd lock or unlock command * - * Unlock one or more blocks + * Lock or unlock one or more blocks */ -static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) +static int onenand_do_lock_cmd(struct mtd_info *mtd, loff_t ofs, size_t len, int cmd) { struct onenand_chip *this = mtd->priv; int start, end, block, value, status; + int wp_status_mask; - start = ofs >> this->erase_shift; - end = len >> this->erase_shift; + start = onenand_block(this, ofs); + end = onenand_block(this, ofs + len) - 1; + + if (cmd == ONENAND_CMD_LOCK) + wp_status_mask = ONENAND_WP_LS; + else + wp_status_mask = ONENAND_WP_US; /* Continuous lock scheme */ - if (this->options & ONENAND_CONT_LOCK) { + if (this->options & ONENAND_HAS_CONT_LOCK) { /* Set start block address */ this->write_word(start, this->base + ONENAND_REG_START_BLOCK_ADDRESS); /* Set end block address */ - this->write_word(end - 1, this->base + ONENAND_REG_END_BLOCK_ADDRESS); - /* Write unlock command */ - this->command(mtd, ONENAND_CMD_UNLOCK, 0, 0); + this->write_word(end, this->base + ONENAND_REG_END_BLOCK_ADDRESS); + /* Write lock command */ + this->command(mtd, cmd, 0, 0); /* There's no return value */ - this->wait(mtd, FL_UNLOCKING); + this->wait(mtd, FL_LOCKING); /* Sanity check */ while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS) @@ -1229,14 +2663,15 @@ static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) /* Check lock status */ status = this->read_word(this->base + ONENAND_REG_WP_STATUS); - if (!(status & ONENAND_WP_US)) - printk(KERN_ERR "wp status = 0x%x\n", status); + if (!(status & wp_status_mask)) + printk(KERN_ERR "%s: wp status = 0x%x\n", + __func__, status); return 0; } /* Block lock scheme */ - for (block = start; block < end; block++) { + for (block = start; block < end + 1; block++) { /* Set block address */ value = onenand_block_address(this, block); this->write_word(value, this->base + ONENAND_REG_START_ADDRESS1); @@ -1245,11 +2680,11 @@ static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); /* Set start block address */ this->write_word(block, this->base + ONENAND_REG_START_BLOCK_ADDRESS); - /* Write unlock command */ - this->command(mtd, ONENAND_CMD_UNLOCK, 0, 0); + /* Write lock command */ + this->command(mtd, cmd, 0, 0); /* There's no return value */ - this->wait(mtd, FL_UNLOCKING); + this->wait(mtd, FL_LOCKING); /* Sanity check */ while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS) @@ -1258,16 +2693,334 @@ static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) /* Check lock status */ status = this->read_word(this->base + ONENAND_REG_WP_STATUS); - if (!(status & ONENAND_WP_US)) - printk(KERN_ERR "block = %d, wp status = 0x%x\n", block, status); + if (!(status & wp_status_mask)) + printk(KERN_ERR "%s: block = %d, wp status = 0x%x\n", + __func__, block, status); } return 0; } +/** + * onenand_lock - [MTD Interface] Lock block(s) + * @param mtd MTD device structure + * @param ofs offset relative to mtd start + * @param len number of bytes to unlock + * + * Lock one or more blocks + */ +static int onenand_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + int ret; + + onenand_get_device(mtd, FL_LOCKING); + ret = onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_LOCK); + onenand_release_device(mtd); + return ret; +} + +/** + * onenand_unlock - [MTD Interface] Unlock block(s) + * @param mtd MTD device structure + * @param ofs offset relative to mtd start + * @param len number of bytes to unlock + * + * Unlock one or more blocks + */ +static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + int ret; + + onenand_get_device(mtd, FL_LOCKING); + ret = onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_UNLOCK); + onenand_release_device(mtd); + return ret; +} + +/** + * onenand_check_lock_status - [OneNAND Interface] Check lock status + * @param this onenand chip data structure + * + * Check lock status + */ +static int onenand_check_lock_status(struct onenand_chip *this) +{ + unsigned int value, block, status; + unsigned int end; + + end = this->chipsize >> this->erase_shift; + for (block = 0; block < end; block++) { + /* Set block address */ + value = onenand_block_address(this, block); + this->write_word(value, this->base + ONENAND_REG_START_ADDRESS1); + /* Select DataRAM for DDP */ + value = onenand_bufferram_address(this, block); + this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); + /* Set start block address */ + this->write_word(block, this->base + ONENAND_REG_START_BLOCK_ADDRESS); + + /* Check lock status */ + status = this->read_word(this->base + ONENAND_REG_WP_STATUS); + if (!(status & ONENAND_WP_US)) { + printk(KERN_ERR "%s: block = %d, wp status = 0x%x\n", + __func__, block, status); + return 0; + } + } + + return 1; +} + +/** + * onenand_unlock_all - [OneNAND Interface] unlock all blocks + * @param mtd MTD device structure + * + * Unlock all blocks + */ +static void onenand_unlock_all(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + loff_t ofs = 0; + loff_t len = mtd->size; + + if (this->options & ONENAND_HAS_UNLOCK_ALL) { + /* Set start block address */ + this->write_word(0, this->base + ONENAND_REG_START_BLOCK_ADDRESS); + /* Write unlock command */ + this->command(mtd, ONENAND_CMD_UNLOCK_ALL, 0, 0); + + /* There's no return value */ + this->wait(mtd, FL_LOCKING); + + /* Sanity check */ + while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS) + & ONENAND_CTRL_ONGO) + continue; + + /* Don't check lock status */ + if (this->options & ONENAND_SKIP_UNLOCK_CHECK) + return; + + /* Check lock status */ + if (onenand_check_lock_status(this)) + return; + + /* Workaround for all block unlock in DDP */ + if (ONENAND_IS_DDP(this) && !FLEXONENAND(this)) { + /* All blocks on another chip */ + ofs = this->chipsize >> 1; + len = this->chipsize >> 1; + } + } + + onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_UNLOCK); +} + #ifdef CONFIG_MTD_ONENAND_OTP -/* Interal OTP operation */ +/** + * onenand_otp_command - Send OTP specific command to OneNAND device + * @param mtd MTD device structure + * @param cmd the command to be sent + * @param addr offset to read from or write to + * @param len number of bytes to read or write + */ +static int onenand_otp_command(struct mtd_info *mtd, int cmd, loff_t addr, + size_t len) +{ + struct onenand_chip *this = mtd->priv; + int value, block, page; + + /* Address translation */ + switch (cmd) { + case ONENAND_CMD_OTP_ACCESS: + block = (int) (addr >> this->erase_shift); + page = -1; + break; + + default: + block = (int) (addr >> this->erase_shift); + page = (int) (addr >> this->page_shift); + + if (ONENAND_IS_2PLANE(this)) { + /* Make the even block number */ + block &= ~1; + /* Is it the odd plane? */ + if (addr & this->writesize) + block++; + page >>= 1; + } + page &= this->page_mask; + break; + } + + if (block != -1) { + /* Write 'DFS, FBA' of Flash */ + value = onenand_block_address(this, block); + this->write_word(value, this->base + + ONENAND_REG_START_ADDRESS1); + } + + if (page != -1) { + /* Now we use page size operation */ + int sectors = 4, count = 4; + int dataram; + + switch (cmd) { + default: + if (ONENAND_IS_2PLANE(this) && cmd == ONENAND_CMD_PROG) + cmd = ONENAND_CMD_2X_PROG; + dataram = ONENAND_CURRENT_BUFFERRAM(this); + break; + } + + /* Write 'FPA, FSA' of Flash */ + value = onenand_page_address(page, sectors); + this->write_word(value, this->base + + ONENAND_REG_START_ADDRESS8); + + /* Write 'BSA, BSC' of DataRAM */ + value = onenand_buffer_address(dataram, sectors, count); + this->write_word(value, this->base + ONENAND_REG_START_BUFFER); + } + + /* Interrupt clear */ + this->write_word(ONENAND_INT_CLEAR, this->base + ONENAND_REG_INTERRUPT); + + /* Write command */ + this->write_word(cmd, this->base + ONENAND_REG_COMMAND); + + return 0; +} + +/** + * onenand_otp_write_oob_nolock - [INTERN] OneNAND write out-of-band, specific to OTP + * @param mtd MTD device structure + * @param to offset to write to + * @param len number of bytes to write + * @param retlen pointer to variable to store the number of written bytes + * @param buf the data to write + * + * OneNAND write out-of-band only for OTP + */ +static int onenand_otp_write_oob_nolock(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + struct onenand_chip *this = mtd->priv; + int column, ret = 0, oobsize; + int written = 0; + u_char *oobbuf; + size_t len = ops->ooblen; + const u_char *buf = ops->oobbuf; + int block, value, status; + + to += ops->ooboffs; + + /* Initialize retlen, in case of early exit */ + ops->oobretlen = 0; + + oobsize = mtd->oobsize; + + column = to & (mtd->oobsize - 1); + + oobbuf = this->oob_buf; + + /* Loop until all data write */ + while (written < len) { + int thislen = min_t(int, oobsize, len - written); + + cond_resched(); + + block = (int) (to >> this->erase_shift); + /* + * Write 'DFS, FBA' of Flash + * Add: F100h DQ=DFS, FBA + */ + + value = onenand_block_address(this, block); + this->write_word(value, this->base + + ONENAND_REG_START_ADDRESS1); + + /* + * Select DataRAM for DDP + * Add: F101h DQ=DBS + */ + + value = onenand_bufferram_address(this, block); + this->write_word(value, this->base + + ONENAND_REG_START_ADDRESS2); + ONENAND_SET_NEXT_BUFFERRAM(this); + + /* + * Enter OTP access mode + */ + this->command(mtd, ONENAND_CMD_OTP_ACCESS, 0, 0); + this->wait(mtd, FL_OTPING); + + /* We send data to spare ram with oobsize + * to prevent byte access */ + memcpy(oobbuf + column, buf, thislen); + + /* + * Write Data into DataRAM + * Add: 8th Word + * in sector0/spare/page0 + * DQ=XXFCh + */ + this->write_bufferram(mtd, ONENAND_SPARERAM, + oobbuf, 0, mtd->oobsize); + + onenand_otp_command(mtd, ONENAND_CMD_PROGOOB, to, mtd->oobsize); + onenand_update_bufferram(mtd, to, 0); + if (ONENAND_IS_2PLANE(this)) { + ONENAND_SET_BUFFERRAM1(this); + onenand_update_bufferram(mtd, to + this->writesize, 0); + } + + ret = this->wait(mtd, FL_WRITING); + if (ret) { + printk(KERN_ERR "%s: write failed %d\n", __func__, ret); + break; + } + + /* Exit OTP access mode */ + this->command(mtd, ONENAND_CMD_RESET, 0, 0); + this->wait(mtd, FL_RESETING); + + status = this->read_word(this->base + ONENAND_REG_CTRL_STATUS); + status &= 0x60; + + if (status == 0x60) { + printk(KERN_DEBUG "\nBLOCK\tSTATUS\n"); + printk(KERN_DEBUG "1st Block\tLOCKED\n"); + printk(KERN_DEBUG "OTP Block\tLOCKED\n"); + } else if (status == 0x20) { + printk(KERN_DEBUG "\nBLOCK\tSTATUS\n"); + printk(KERN_DEBUG "1st Block\tLOCKED\n"); + printk(KERN_DEBUG "OTP Block\tUN-LOCKED\n"); + } else if (status == 0x40) { + printk(KERN_DEBUG "\nBLOCK\tSTATUS\n"); + printk(KERN_DEBUG "1st Block\tUN-LOCKED\n"); + printk(KERN_DEBUG "OTP Block\tLOCKED\n"); + } else { + printk(KERN_DEBUG "Reboot to check\n"); + } + + written += thislen; + if (written == len) + break; + + to += mtd->writesize; + buf += thislen; + column = 0; + } + + ops->oobretlen = written; + + return ret; +} + +/* Internal OTP operation */ typedef int (*otp_op_t)(struct mtd_info *mtd, loff_t form, size_t len, size_t *retlen, u_char *buf); @@ -1285,13 +3038,21 @@ static int do_otp_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { struct onenand_chip *this = mtd->priv; + struct mtd_oob_ops ops = { + .len = len, + .ooblen = 0, + .datbuf = buf, + .oobbuf = NULL, + }; int ret; /* Enter OTP access mode */ this->command(mtd, ONENAND_CMD_OTP_ACCESS, 0, 0); this->wait(mtd, FL_OTPING); - ret = mtd->read(mtd, from, len, retlen, buf); + ret = ONENAND_IS_4KB_PAGE(this) ? + onenand_mlc_read_ops_nolock(mtd, from, &ops) : + onenand_read_ops_nolock(mtd, from, &ops); /* Exit OTP access mode */ this->command(mtd, ONENAND_CMD_RESET, 0, 0); @@ -1303,19 +3064,20 @@ static int do_otp_read(struct mtd_info *mtd, loff_t from, size_t len, /** * do_otp_write - [DEFAULT] Write OTP block area * @param mtd MTD device structure - * @param from The offset to write + * @param to The offset to write * @param len number of bytes to write * @param retlen pointer to variable to store the number of write bytes * @param buf the databuffer to put/get data * * Write OTP block area. */ -static int do_otp_write(struct mtd_info *mtd, loff_t from, size_t len, +static int do_otp_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, u_char *buf) { struct onenand_chip *this = mtd->priv; unsigned char *pbuf = buf; int ret; + struct mtd_oob_ops ops; /* Force buffer page aligned */ if (len < mtd->writesize) { @@ -1329,7 +3091,12 @@ static int do_otp_write(struct mtd_info *mtd, loff_t from, size_t len, this->command(mtd, ONENAND_CMD_OTP_ACCESS, 0, 0); this->wait(mtd, FL_OTPING); - ret = mtd->write(mtd, from, len, retlen, pbuf); + ops.len = len; + ops.ooblen = 0; + ops.datbuf = pbuf; + ops.oobbuf = NULL; + ret = onenand_write_ops_nolock(mtd, to, &ops); + *retlen = ops.retlen; /* Exit OTP access mode */ this->command(mtd, ONENAND_CMD_RESET, 0, 0); @@ -1352,17 +3119,36 @@ static int do_otp_lock(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { struct onenand_chip *this = mtd->priv; + struct mtd_oob_ops ops; int ret; - /* Enter OTP access mode */ - this->command(mtd, ONENAND_CMD_OTP_ACCESS, 0, 0); - this->wait(mtd, FL_OTPING); - - ret = onenand_do_write_oob(mtd, from, len, retlen, buf); - - /* Exit OTP access mode */ - this->command(mtd, ONENAND_CMD_RESET, 0, 0); - this->wait(mtd, FL_RESETING); + if (FLEXONENAND(this)) { + + /* Enter OTP access mode */ + this->command(mtd, ONENAND_CMD_OTP_ACCESS, 0, 0); + this->wait(mtd, FL_OTPING); + /* + * For Flex-OneNAND, we write lock mark to 1st word of sector 4 of + * main area of page 49. + */ + ops.len = mtd->writesize; + ops.ooblen = 0; + ops.datbuf = buf; + ops.oobbuf = NULL; + ret = onenand_write_ops_nolock(mtd, mtd->writesize * 49, &ops); + *retlen = ops.retlen; + + /* Exit OTP access mode */ + this->command(mtd, ONENAND_CMD_RESET, 0, 0); + this->wait(mtd, FL_RESETING); + } else { + ops.mode = MTD_OPS_PLACE_OOB; + ops.ooblen = len; + ops.oobbuf = buf; + ops.ooboffs = 0; + ret = onenand_otp_write_oob_nolock(mtd, from, &ops); + *retlen = ops.oobretlen; + } return ret; } @@ -1390,28 +3176,36 @@ static int onenand_otp_walk(struct mtd_info *mtd, loff_t from, size_t len, *retlen = 0; - density = this->device_id >> ONENAND_DEVICE_DENSITY_SHIFT; + density = onenand_get_density(this->device_id); if (density < ONENAND_DEVICE_DENSITY_512Mb) otp_pages = 20; else - otp_pages = 10; + otp_pages = 50; if (mode == MTD_OTP_FACTORY) { from += mtd->writesize * otp_pages; - otp_pages = 64 - otp_pages; + otp_pages = ONENAND_PAGES_PER_BLOCK - otp_pages; } /* Check User/Factory boundary */ - if (((mtd->writesize * otp_pages) - (from + len)) < 0) - return 0; + if (mode == MTD_OTP_USER) { + if (mtd->writesize * otp_pages < from + len) + return 0; + } else { + if (mtd->writesize * otp_pages < len) + return 0; + } + onenand_get_device(mtd, FL_OTPING); while (len > 0 && otp_pages > 0) { if (!action) { /* OTP Info functions */ struct otp_info *otpinfo; len -= sizeof(struct otp_info); - if (len <= 0) - return -ENOSPC; + if (len <= 0) { + ret = -ENOSPC; + break; + } otpinfo = (struct otp_info *) buf; otpinfo->start = from; @@ -1423,40 +3217,37 @@ static int onenand_otp_walk(struct mtd_info *mtd, loff_t from, size_t len, *retlen += sizeof(struct otp_info); } else { size_t tmp_retlen; - int size = len; ret = action(mtd, from, len, &tmp_retlen, buf); - buf += size; - len -= size; - *retlen += size; + buf += tmp_retlen; + len -= tmp_retlen; + *retlen += tmp_retlen; - if (ret < 0) - return ret; + if (ret) + break; } otp_pages--; } + onenand_release_device(mtd); - return 0; + return ret; } /** * onenand_get_fact_prot_info - [MTD Interface] Read factory OTP info * @param mtd MTD device structure - * @param buf the databuffer to put/get data * @param len number of bytes to read + * @param retlen pointer to variable to store the number of read bytes + * @param buf the databuffer to put/get data * * Read factory OTP info. */ -static int onenand_get_fact_prot_info(struct mtd_info *mtd, - struct otp_info *buf, size_t len) +static int onenand_get_fact_prot_info(struct mtd_info *mtd, size_t len, + size_t *retlen, struct otp_info *buf) { - size_t retlen; - int ret; - - ret = onenand_otp_walk(mtd, 0, len, &retlen, (u_char *) buf, NULL, MTD_OTP_FACTORY); - - return ret ? : retlen; + return onenand_otp_walk(mtd, 0, len, retlen, (u_char *) buf, NULL, + MTD_OTP_FACTORY); } /** @@ -1478,20 +3269,17 @@ static int onenand_read_fact_prot_reg(struct mtd_info *mtd, loff_t from, /** * onenand_get_user_prot_info - [MTD Interface] Read user OTP info * @param mtd MTD device structure - * @param buf the databuffer to put/get data + * @param retlen pointer to variable to store the number of read bytes * @param len number of bytes to read + * @param buf the databuffer to put/get data * * Read user OTP info. */ -static int onenand_get_user_prot_info(struct mtd_info *mtd, - struct otp_info *buf, size_t len) +static int onenand_get_user_prot_info(struct mtd_info *mtd, size_t len, + size_t *retlen, struct otp_info *buf) { - size_t retlen; - int ret; - - ret = onenand_otp_walk(mtd, 0, len, &retlen, (u_char *) buf, NULL, MTD_OTP_USER); - - return ret ? : retlen; + return onenand_otp_walk(mtd, 0, len, retlen, (u_char *) buf, NULL, + MTD_OTP_USER); } /** @@ -1537,56 +3325,163 @@ static int onenand_write_user_prot_reg(struct mtd_info *mtd, loff_t from, static int onenand_lock_user_prot_reg(struct mtd_info *mtd, loff_t from, size_t len) { - unsigned char oob_buf[64]; + struct onenand_chip *this = mtd->priv; + u_char *buf = FLEXONENAND(this) ? this->page_buf : this->oob_buf; size_t retlen; int ret; + unsigned int otp_lock_offset = ONENAND_OTP_LOCK_OFFSET; - memset(oob_buf, 0xff, mtd->oobsize); - /* - * Note: OTP lock operation - * OTP block : 0xXXFC - * 1st block : 0xXXF3 (If chip support) - * Both : 0xXXF0 (If chip support) - */ - oob_buf[ONENAND_OTP_LOCK_OFFSET] = 0xFC; - + memset(buf, 0xff, FLEXONENAND(this) ? this->writesize + : mtd->oobsize); /* * Write lock mark to 8th word of sector0 of page0 of the spare0. * We write 16 bytes spare area instead of 2 bytes. + * For Flex-OneNAND, we write lock mark to 1st word of sector 4 of + * main area of page 49. */ + from = 0; - len = 16; + len = FLEXONENAND(this) ? mtd->writesize : 16; + + /* + * Note: OTP lock operation + * OTP block : 0xXXFC XX 1111 1100 + * 1st block : 0xXXF3 (If chip support) XX 1111 0011 + * Both : 0xXXF0 (If chip support) XX 1111 0000 + */ + if (FLEXONENAND(this)) + otp_lock_offset = FLEXONENAND_OTP_LOCK_OFFSET; - ret = onenand_otp_walk(mtd, from, len, &retlen, oob_buf, do_otp_lock, MTD_OTP_USER); + /* ONENAND_OTP_AREA | ONENAND_OTP_BLOCK0 | ONENAND_OTP_AREA_BLOCK0 */ + if (otp == 1) + buf[otp_lock_offset] = 0xFC; + else if (otp == 2) + buf[otp_lock_offset] = 0xF3; + else if (otp == 3) + buf[otp_lock_offset] = 0xF0; + else if (otp != 0) + printk(KERN_DEBUG "[OneNAND] Invalid option selected for OTP\n"); + + ret = onenand_otp_walk(mtd, from, len, &retlen, buf, do_otp_lock, MTD_OTP_USER); return ret ? : retlen; } + #endif /* CONFIG_MTD_ONENAND_OTP */ /** - * onenand_print_device_info - Print device ID + * onenand_check_features - Check and set OneNAND features + * @param mtd MTD data structure + * + * Check and set OneNAND features + * - lock scheme + * - two plane + */ +static void onenand_check_features(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + unsigned int density, process, numbufs; + + /* Lock scheme depends on density and process */ + density = onenand_get_density(this->device_id); + process = this->version_id >> ONENAND_VERSION_PROCESS_SHIFT; + numbufs = this->read_word(this->base + ONENAND_REG_NUM_BUFFERS) >> 8; + + /* Lock scheme */ + switch (density) { + case ONENAND_DEVICE_DENSITY_4Gb: + if (ONENAND_IS_DDP(this)) + this->options |= ONENAND_HAS_2PLANE; + else if (numbufs == 1) { + this->options |= ONENAND_HAS_4KB_PAGE; + this->options |= ONENAND_HAS_CACHE_PROGRAM; + /* + * There are two different 4KiB pagesize chips + * and no way to detect it by H/W config values. + * + * To detect the correct NOP for each chips, + * It should check the version ID as workaround. + * + * Now it has as following + * KFM4G16Q4M has NOP 4 with version ID 0x0131 + * KFM4G16Q5M has NOP 1 with versoin ID 0x013e + */ + if ((this->version_id & 0xf) == 0xe) + this->options |= ONENAND_HAS_NOP_1; + } + + case ONENAND_DEVICE_DENSITY_2Gb: + /* 2Gb DDP does not have 2 plane */ + if (!ONENAND_IS_DDP(this)) + this->options |= ONENAND_HAS_2PLANE; + this->options |= ONENAND_HAS_UNLOCK_ALL; + + case ONENAND_DEVICE_DENSITY_1Gb: + /* A-Die has all block unlock */ + if (process) + this->options |= ONENAND_HAS_UNLOCK_ALL; + break; + + default: + /* Some OneNAND has continuous lock scheme */ + if (!process) + this->options |= ONENAND_HAS_CONT_LOCK; + break; + } + + /* The MLC has 4KiB pagesize. */ + if (ONENAND_IS_MLC(this)) + this->options |= ONENAND_HAS_4KB_PAGE; + + if (ONENAND_IS_4KB_PAGE(this)) + this->options &= ~ONENAND_HAS_2PLANE; + + if (FLEXONENAND(this)) { + this->options &= ~ONENAND_HAS_CONT_LOCK; + this->options |= ONENAND_HAS_UNLOCK_ALL; + } + + if (this->options & ONENAND_HAS_CONT_LOCK) + printk(KERN_DEBUG "Lock scheme is Continuous Lock\n"); + if (this->options & ONENAND_HAS_UNLOCK_ALL) + printk(KERN_DEBUG "Chip support all block unlock\n"); + if (this->options & ONENAND_HAS_2PLANE) + printk(KERN_DEBUG "Chip has 2 plane\n"); + if (this->options & ONENAND_HAS_4KB_PAGE) + printk(KERN_DEBUG "Chip has 4KiB pagesize\n"); + if (this->options & ONENAND_HAS_CACHE_PROGRAM) + printk(KERN_DEBUG "Chip has cache program feature\n"); +} + +/** + * onenand_print_device_info - Print device & version ID * @param device device ID + * @param version version ID * - * Print device ID + * Print device & version ID */ -static void onenand_print_device_info(int device) +static void onenand_print_device_info(int device, int version) { - int vcc, demuxed, ddp, density; + int vcc, demuxed, ddp, density, flexonenand; vcc = device & ONENAND_DEVICE_VCC_MASK; demuxed = device & ONENAND_DEVICE_IS_DEMUX; ddp = device & ONENAND_DEVICE_IS_DDP; - density = device >> ONENAND_DEVICE_DENSITY_SHIFT; - printk(KERN_INFO "%sOneNAND%s %dMB %sV 16-bit (0x%02x)\n", - demuxed ? "" : "Muxed ", + density = onenand_get_density(device); + flexonenand = device & DEVICE_IS_FLEXONENAND; + printk(KERN_INFO "%s%sOneNAND%s %dMB %sV 16-bit (0x%02x)\n", + demuxed ? "" : "Muxed ", + flexonenand ? "Flex-" : "", ddp ? "(DDP)" : "", (16 << density), vcc ? "2.65/3.3" : "1.8", device); + printk(KERN_INFO "OneNAND version = 0x%04x\n", version); } static const struct onenand_manufacturers onenand_manuf_ids[] = { {ONENAND_MFR_SAMSUNG, "Samsung"}, + {ONENAND_MFR_NUMONYX, "Numonyx"}, }; /** @@ -1616,18 +3511,281 @@ static int onenand_check_maf(int manuf) } /** - * onenand_probe - [OneNAND Interface] Probe the OneNAND device +* flexonenand_get_boundary - Reads the SLC boundary +* @param onenand_info - onenand info structure +**/ +static int flexonenand_get_boundary(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + unsigned die, bdry; + int syscfg, locked; + + /* Disable ECC */ + syscfg = this->read_word(this->base + ONENAND_REG_SYS_CFG1); + this->write_word((syscfg | 0x0100), this->base + ONENAND_REG_SYS_CFG1); + + for (die = 0; die < this->dies; die++) { + this->command(mtd, FLEXONENAND_CMD_PI_ACCESS, die, 0); + this->wait(mtd, FL_SYNCING); + + this->command(mtd, FLEXONENAND_CMD_READ_PI, die, 0); + this->wait(mtd, FL_READING); + + bdry = this->read_word(this->base + ONENAND_DATARAM); + if ((bdry >> FLEXONENAND_PI_UNLOCK_SHIFT) == 3) + locked = 0; + else + locked = 1; + this->boundary[die] = bdry & FLEXONENAND_PI_MASK; + + this->command(mtd, ONENAND_CMD_RESET, 0, 0); + this->wait(mtd, FL_RESETING); + + printk(KERN_INFO "Die %d boundary: %d%s\n", die, + this->boundary[die], locked ? "(Locked)" : "(Unlocked)"); + } + + /* Enable ECC */ + this->write_word(syscfg, this->base + ONENAND_REG_SYS_CFG1); + return 0; +} + +/** + * flexonenand_get_size - Fill up fields in onenand_chip and mtd_info + * boundary[], diesize[], mtd->size, mtd->erasesize + * @param mtd - MTD device structure + */ +static void flexonenand_get_size(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + int die, i, eraseshift, density; + int blksperdie, maxbdry; + loff_t ofs; + + density = onenand_get_density(this->device_id); + blksperdie = ((loff_t)(16 << density) << 20) >> (this->erase_shift); + blksperdie >>= ONENAND_IS_DDP(this) ? 1 : 0; + maxbdry = blksperdie - 1; + eraseshift = this->erase_shift - 1; + + mtd->numeraseregions = this->dies << 1; + + /* This fills up the device boundary */ + flexonenand_get_boundary(mtd); + die = ofs = 0; + i = -1; + for (; die < this->dies; die++) { + if (!die || this->boundary[die-1] != maxbdry) { + i++; + mtd->eraseregions[i].offset = ofs; + mtd->eraseregions[i].erasesize = 1 << eraseshift; + mtd->eraseregions[i].numblocks = + this->boundary[die] + 1; + ofs += mtd->eraseregions[i].numblocks << eraseshift; + eraseshift++; + } else { + mtd->numeraseregions -= 1; + mtd->eraseregions[i].numblocks += + this->boundary[die] + 1; + ofs += (this->boundary[die] + 1) << (eraseshift - 1); + } + if (this->boundary[die] != maxbdry) { + i++; + mtd->eraseregions[i].offset = ofs; + mtd->eraseregions[i].erasesize = 1 << eraseshift; + mtd->eraseregions[i].numblocks = maxbdry ^ + this->boundary[die]; + ofs += mtd->eraseregions[i].numblocks << eraseshift; + eraseshift--; + } else + mtd->numeraseregions -= 1; + } + + /* Expose MLC erase size except when all blocks are SLC */ + mtd->erasesize = 1 << this->erase_shift; + if (mtd->numeraseregions == 1) + mtd->erasesize >>= 1; + + printk(KERN_INFO "Device has %d eraseregions\n", mtd->numeraseregions); + for (i = 0; i < mtd->numeraseregions; i++) + printk(KERN_INFO "[offset: 0x%08x, erasesize: 0x%05x," + " numblocks: %04u]\n", + (unsigned int) mtd->eraseregions[i].offset, + mtd->eraseregions[i].erasesize, + mtd->eraseregions[i].numblocks); + + for (die = 0, mtd->size = 0; die < this->dies; die++) { + this->diesize[die] = (loff_t)blksperdie << this->erase_shift; + this->diesize[die] -= (loff_t)(this->boundary[die] + 1) + << (this->erase_shift - 1); + mtd->size += this->diesize[die]; + } +} + +/** + * flexonenand_check_blocks_erased - Check if blocks are erased + * @param mtd_info - mtd info structure + * @param start - first erase block to check + * @param end - last erase block to check + * + * Converting an unerased block from MLC to SLC + * causes byte values to change. Since both data and its ECC + * have changed, reads on the block give uncorrectable error. + * This might lead to the block being detected as bad. + * + * Avoid this by ensuring that the block to be converted is + * erased. + */ +static int flexonenand_check_blocks_erased(struct mtd_info *mtd, int start, int end) +{ + struct onenand_chip *this = mtd->priv; + int i, ret; + int block; + struct mtd_oob_ops ops = { + .mode = MTD_OPS_PLACE_OOB, + .ooboffs = 0, + .ooblen = mtd->oobsize, + .datbuf = NULL, + .oobbuf = this->oob_buf, + }; + loff_t addr; + + printk(KERN_DEBUG "Check blocks from %d to %d\n", start, end); + + for (block = start; block <= end; block++) { + addr = flexonenand_addr(this, block); + if (onenand_block_isbad_nolock(mtd, addr, 0)) + continue; + + /* + * Since main area write results in ECC write to spare, + * it is sufficient to check only ECC bytes for change. + */ + ret = onenand_read_oob_nolock(mtd, addr, &ops); + if (ret) + return ret; + + for (i = 0; i < mtd->oobsize; i++) + if (this->oob_buf[i] != 0xff) + break; + + if (i != mtd->oobsize) { + printk(KERN_WARNING "%s: Block %d not erased.\n", + __func__, block); + return 1; + } + } + + return 0; +} + +/** + * flexonenand_set_boundary - Writes the SLC boundary + * @param mtd - mtd info structure + */ +static int flexonenand_set_boundary(struct mtd_info *mtd, int die, + int boundary, int lock) +{ + struct onenand_chip *this = mtd->priv; + int ret, density, blksperdie, old, new, thisboundary; + loff_t addr; + + /* Change only once for SDP Flex-OneNAND */ + if (die && (!ONENAND_IS_DDP(this))) + return 0; + + /* boundary value of -1 indicates no required change */ + if (boundary < 0 || boundary == this->boundary[die]) + return 0; + + density = onenand_get_density(this->device_id); + blksperdie = ((16 << density) << 20) >> this->erase_shift; + blksperdie >>= ONENAND_IS_DDP(this) ? 1 : 0; + + if (boundary >= blksperdie) { + printk(KERN_ERR "%s: Invalid boundary value. " + "Boundary not changed.\n", __func__); + return -EINVAL; + } + + /* Check if converting blocks are erased */ + old = this->boundary[die] + (die * this->density_mask); + new = boundary + (die * this->density_mask); + ret = flexonenand_check_blocks_erased(mtd, min(old, new) + 1, max(old, new)); + if (ret) { + printk(KERN_ERR "%s: Please erase blocks " + "before boundary change\n", __func__); + return ret; + } + + this->command(mtd, FLEXONENAND_CMD_PI_ACCESS, die, 0); + this->wait(mtd, FL_SYNCING); + + /* Check is boundary is locked */ + this->command(mtd, FLEXONENAND_CMD_READ_PI, die, 0); + this->wait(mtd, FL_READING); + + thisboundary = this->read_word(this->base + ONENAND_DATARAM); + if ((thisboundary >> FLEXONENAND_PI_UNLOCK_SHIFT) != 3) { + printk(KERN_ERR "%s: boundary locked\n", __func__); + ret = 1; + goto out; + } + + printk(KERN_INFO "Changing die %d boundary: %d%s\n", + die, boundary, lock ? "(Locked)" : "(Unlocked)"); + + addr = die ? this->diesize[0] : 0; + + boundary &= FLEXONENAND_PI_MASK; + boundary |= lock ? 0 : (3 << FLEXONENAND_PI_UNLOCK_SHIFT); + + this->command(mtd, ONENAND_CMD_ERASE, addr, 0); + ret = this->wait(mtd, FL_ERASING); + if (ret) { + printk(KERN_ERR "%s: Failed PI erase for Die %d\n", + __func__, die); + goto out; + } + + this->write_word(boundary, this->base + ONENAND_DATARAM); + this->command(mtd, ONENAND_CMD_PROG, addr, 0); + ret = this->wait(mtd, FL_WRITING); + if (ret) { + printk(KERN_ERR "%s: Failed PI write for Die %d\n", + __func__, die); + goto out; + } + + this->command(mtd, FLEXONENAND_CMD_PI_UPDATE, die, 0); + ret = this->wait(mtd, FL_WRITING); +out: + this->write_word(ONENAND_CMD_RESET, this->base + ONENAND_REG_COMMAND); + this->wait(mtd, FL_RESETING); + if (!ret) + /* Recalculate device size on boundary change*/ + flexonenand_get_size(mtd); + + return ret; +} + +/** + * onenand_chip_probe - [OneNAND Interface] The generic chip probe * @param mtd MTD device structure * * OneNAND detection method: - * Compare the the values from command with ones from register + * Compare the values from command with ones from register */ -static int onenand_probe(struct mtd_info *mtd) +static int onenand_chip_probe(struct mtd_info *mtd) { struct onenand_chip *this = mtd->priv; int bram_maf_id, bram_dev_id, maf_id, dev_id; - int version_id; - int density; + int syscfg; + + /* Save system configuration 1 */ + syscfg = this->read_word(this->base + ONENAND_REG_SYS_CFG1); + /* Clear Sync. Burst Read mode to read BootRAM */ + this->write_word((syscfg & ~ONENAND_SYS_CFG1_SYNC_READ & ~ONENAND_SYS_CFG1_SYNC_WRITE), this->base + ONENAND_REG_SYS_CFG1); /* Send the command for reading device ID from BootRAM */ this->write_word(ONENAND_CMD_READID, this->base + ONENAND_BOOTRAM); @@ -1636,13 +3794,18 @@ static int onenand_probe(struct mtd_info *mtd) bram_maf_id = this->read_word(this->base + ONENAND_BOOTRAM + 0x0); bram_dev_id = this->read_word(this->base + ONENAND_BOOTRAM + 0x2); + /* Reset OneNAND to read default register values */ + this->write_word(ONENAND_CMD_RESET, this->base + ONENAND_BOOTRAM); + /* Wait reset */ + this->wait(mtd, FL_RESETING); + + /* Restore system configuration 1 */ + this->write_word(syscfg, this->base + ONENAND_REG_SYS_CFG1); + /* Check manufacturer ID */ if (onenand_check_maf(bram_maf_id)) return -ENXIO; - /* Reset OneNAND to read default register values */ - this->write_word(ONENAND_CMD_RESET, this->base + ONENAND_BOOTRAM); - /* Read manufacturer and device IDs from Register */ maf_id = this->read_word(this->base + ONENAND_REG_MANUFACTURER_ID); dev_id = this->read_word(this->base + ONENAND_REG_DEVICE_ID); @@ -1651,40 +3814,97 @@ static int onenand_probe(struct mtd_info *mtd) if (maf_id != bram_maf_id || dev_id != bram_dev_id) return -ENXIO; + return 0; +} + +/** + * onenand_probe - [OneNAND Interface] Probe the OneNAND device + * @param mtd MTD device structure + */ +static int onenand_probe(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + int dev_id, ver_id; + int density; + int ret; + + ret = this->chip_probe(mtd); + if (ret) + return ret; + + /* Device and version IDs from Register */ + dev_id = this->read_word(this->base + ONENAND_REG_DEVICE_ID); + ver_id = this->read_word(this->base + ONENAND_REG_VERSION_ID); + this->technology = this->read_word(this->base + ONENAND_REG_TECHNOLOGY); + /* Flash device information */ - onenand_print_device_info(dev_id); + onenand_print_device_info(dev_id, ver_id); this->device_id = dev_id; + this->version_id = ver_id; + + /* Check OneNAND features */ + onenand_check_features(mtd); + + density = onenand_get_density(dev_id); + if (FLEXONENAND(this)) { + this->dies = ONENAND_IS_DDP(this) ? 2 : 1; + /* Maximum possible erase regions */ + mtd->numeraseregions = this->dies << 1; + mtd->eraseregions = kzalloc(sizeof(struct mtd_erase_region_info) + * (this->dies << 1), GFP_KERNEL); + if (!mtd->eraseregions) + return -ENOMEM; + } - density = dev_id >> ONENAND_DEVICE_DENSITY_SHIFT; + /* + * For Flex-OneNAND, chipsize represents maximum possible device size. + * mtd->size represents the actual device size. + */ this->chipsize = (16 << density) << 20; - /* Set density mask. it is used for DDP */ - this->density_mask = (1 << (density + 6)); /* OneNAND page size & block size */ /* The data buffer size is equal to page size */ mtd->writesize = this->read_word(this->base + ONENAND_REG_DATA_BUFFER_SIZE); + /* We use the full BufferRAM */ + if (ONENAND_IS_4KB_PAGE(this)) + mtd->writesize <<= 1; + mtd->oobsize = mtd->writesize >> 5; - /* Pagers per block is always 64 in OneNAND */ + /* Pages per a block are always 64 in OneNAND */ mtd->erasesize = mtd->writesize << 6; + /* + * Flex-OneNAND SLC area has 64 pages per block. + * Flex-OneNAND MLC area has 128 pages per block. + * Expose MLC erase size to find erase_shift and page_mask. + */ + if (FLEXONENAND(this)) + mtd->erasesize <<= 1; this->erase_shift = ffs(mtd->erasesize) - 1; this->page_shift = ffs(mtd->writesize) - 1; - this->ppb_shift = (this->erase_shift - this->page_shift); - this->page_mask = (mtd->erasesize / mtd->writesize) - 1; - - /* REVIST: Multichip handling */ + this->page_mask = (1 << (this->erase_shift - this->page_shift)) - 1; + /* Set density mask. it is used for DDP */ + if (ONENAND_IS_DDP(this)) + this->density_mask = this->chipsize >> (this->erase_shift + 1); + /* It's real page size */ + this->writesize = mtd->writesize; - mtd->size = this->chipsize; + /* REVISIT: Multichip handling */ - /* Version ID */ - version_id = this->read_word(this->base + ONENAND_REG_VERSION_ID); - printk(KERN_DEBUG "OneNAND version = 0x%04x\n", version_id); + if (FLEXONENAND(this)) + flexonenand_get_size(mtd); + else + mtd->size = this->chipsize; - /* Lock scheme */ - if (density <= ONENAND_DEVICE_DENSITY_512Mb && - !(version_id >> ONENAND_VERSION_PROCESS_SHIFT)) { - printk(KERN_INFO "Lock scheme is Continues Lock\n"); - this->options |= ONENAND_CONT_LOCK; + /* + * We emulate the 4KiB page and 256KiB erase block size + * But oobsize is still 64 bytes. + * It is only valid if you turn on 2X program support, + * Otherwise it will be ignored by compiler. + */ + if (ONENAND_IS_2PLANE(this)) { + mtd->writesize <<= 1; + mtd->erasesize <<= 1; } return 0; @@ -1710,8 +3930,8 @@ static void onenand_resume(struct mtd_info *mtd) if (this->state == FL_PM_SUSPENDED) onenand_release_device(mtd); else - printk(KERN_ERR "resume() called for the chip which is not" - "in suspended state\n"); + printk(KERN_ERR "%s: resume() called for the chip which is not " + "in suspended state\n", __func__); } /** @@ -1726,6 +3946,7 @@ static void onenand_resume(struct mtd_info *mtd) */ int onenand_scan(struct mtd_info *mtd, int maxchips) { + int i, ret; struct onenand_chip *this = mtd->priv; if (!this->read_word) @@ -1736,7 +3957,14 @@ int onenand_scan(struct mtd_info *mtd, int maxchips) if (!this->command) this->command = onenand_command; if (!this->wait) - this->wait = onenand_wait; + onenand_setup_wait(mtd); + if (!this->bbt_wait) + this->bbt_wait = onenand_bbt_wait; + if (!this->unlock_all) + this->unlock_all = onenand_unlock_all; + + if (!this->chip_probe) + this->chip_probe = onenand_chip_probe; if (!this->read_bufferram) this->read_bufferram = onenand_read_bufferram; @@ -1759,71 +3987,127 @@ int onenand_scan(struct mtd_info *mtd, int maxchips) /* Allocate buffers, if necessary */ if (!this->page_buf) { - size_t len; - len = mtd->writesize + mtd->oobsize; - this->page_buf = kmalloc(len, GFP_KERNEL); - if (!this->page_buf) { - printk(KERN_ERR "onenand_scan(): Can't allocate page_buf\n"); + this->page_buf = kzalloc(mtd->writesize, GFP_KERNEL); + if (!this->page_buf) + return -ENOMEM; +#ifdef CONFIG_MTD_ONENAND_VERIFY_WRITE + this->verify_buf = kzalloc(mtd->writesize, GFP_KERNEL); + if (!this->verify_buf) { + kfree(this->page_buf); return -ENOMEM; } +#endif this->options |= ONENAND_PAGEBUF_ALLOC; } + if (!this->oob_buf) { + this->oob_buf = kzalloc(mtd->oobsize, GFP_KERNEL); + if (!this->oob_buf) { + if (this->options & ONENAND_PAGEBUF_ALLOC) { + this->options &= ~ONENAND_PAGEBUF_ALLOC; + kfree(this->page_buf); + } + return -ENOMEM; + } + this->options |= ONENAND_OOBBUF_ALLOC; + } this->state = FL_READY; init_waitqueue_head(&this->wq); spin_lock_init(&this->chip_lock); + /* + * Allow subpage writes up to oobsize. + */ switch (mtd->oobsize) { + case 128: + if (FLEXONENAND(this)) { + this->ecclayout = &flexonenand_oob_128; + mtd->subpage_sft = 0; + } else { + this->ecclayout = &onenand_oob_128; + mtd->subpage_sft = 2; + } + if (ONENAND_IS_NOP_1(this)) + mtd->subpage_sft = 0; + break; case 64: this->ecclayout = &onenand_oob_64; + mtd->subpage_sft = 2; break; case 32: this->ecclayout = &onenand_oob_32; + mtd->subpage_sft = 1; break; default: - printk(KERN_WARNING "No OOB scheme defined for oobsize %d\n", - mtd->oobsize); + printk(KERN_WARNING "%s: No OOB scheme defined for oobsize %d\n", + __func__, mtd->oobsize); + mtd->subpage_sft = 0; /* To prevent kernel oops */ this->ecclayout = &onenand_oob_32; break; } + this->subpagesize = mtd->writesize >> mtd->subpage_sft; + + /* + * The number of bytes available for a client to place data into + * the out of band area + */ + this->ecclayout->oobavail = 0; + for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES && + this->ecclayout->oobfree[i].length; i++) + this->ecclayout->oobavail += + this->ecclayout->oobfree[i].length; + mtd->oobavail = this->ecclayout->oobavail; + mtd->ecclayout = this->ecclayout; + mtd->ecc_strength = 1; /* Fill in remaining MTD driver data */ - mtd->type = MTD_NANDFLASH; + mtd->type = ONENAND_IS_MLC(this) ? MTD_MLCNANDFLASH : MTD_NANDFLASH; mtd->flags = MTD_CAP_NANDFLASH; - mtd->ecctype = MTD_ECC_SW; - mtd->erase = onenand_erase; - mtd->point = NULL; - mtd->unpoint = NULL; - mtd->read = onenand_read; - mtd->write = onenand_write; - mtd->read_oob = onenand_read_oob; - mtd->write_oob = onenand_write_oob; + mtd->_erase = onenand_erase; + mtd->_point = NULL; + mtd->_unpoint = NULL; + mtd->_read = onenand_read; + mtd->_write = onenand_write; + mtd->_read_oob = onenand_read_oob; + mtd->_write_oob = onenand_write_oob; + mtd->_panic_write = onenand_panic_write; #ifdef CONFIG_MTD_ONENAND_OTP - mtd->get_fact_prot_info = onenand_get_fact_prot_info; - mtd->read_fact_prot_reg = onenand_read_fact_prot_reg; - mtd->get_user_prot_info = onenand_get_user_prot_info; - mtd->read_user_prot_reg = onenand_read_user_prot_reg; - mtd->write_user_prot_reg = onenand_write_user_prot_reg; - mtd->lock_user_prot_reg = onenand_lock_user_prot_reg; + mtd->_get_fact_prot_info = onenand_get_fact_prot_info; + mtd->_read_fact_prot_reg = onenand_read_fact_prot_reg; + mtd->_get_user_prot_info = onenand_get_user_prot_info; + mtd->_read_user_prot_reg = onenand_read_user_prot_reg; + mtd->_write_user_prot_reg = onenand_write_user_prot_reg; + mtd->_lock_user_prot_reg = onenand_lock_user_prot_reg; #endif - mtd->sync = onenand_sync; - mtd->lock = NULL; - mtd->unlock = onenand_unlock; - mtd->suspend = onenand_suspend; - mtd->resume = onenand_resume; - mtd->block_isbad = onenand_block_isbad; - mtd->block_markbad = onenand_block_markbad; + mtd->_sync = onenand_sync; + mtd->_lock = onenand_lock; + mtd->_unlock = onenand_unlock; + mtd->_suspend = onenand_suspend; + mtd->_resume = onenand_resume; + mtd->_block_isbad = onenand_block_isbad; + mtd->_block_markbad = onenand_block_markbad; mtd->owner = THIS_MODULE; + mtd->writebufsize = mtd->writesize; /* Unlock whole block */ - mtd->unlock(mtd, 0x0, this->chipsize); + if (!(this->options & ONENAND_SKIP_INITIAL_UNLOCKING)) + this->unlock_all(mtd); - return this->scan_bbt(mtd); + ret = this->scan_bbt(mtd); + if ((!FLEXONENAND(this)) || ret) + return ret; + + /* Change Flex-OneNAND boundaries if required */ + for (i = 0; i < MAX_DIES; i++) + flexonenand_set_boundary(mtd, i, flex_bdry[2 * i], + flex_bdry[(2 * i) + 1]); + + return 0; } /** @@ -1834,19 +4118,25 @@ void onenand_release(struct mtd_info *mtd) { struct onenand_chip *this = mtd->priv; -#ifdef CONFIG_MTD_PARTITIONS /* Deregister partitions */ - del_mtd_partitions (mtd); -#endif - /* Deregister the device */ - del_mtd_device (mtd); + mtd_device_unregister(mtd); /* Free bad block table memory, if allocated */ - if (this->bbm) + if (this->bbm) { + struct bbm_info *bbm = this->bbm; + kfree(bbm->bbt); kfree(this->bbm); - /* Buffer allocated by onenand_scan */ - if (this->options & ONENAND_PAGEBUF_ALLOC) + } + /* Buffers allocated by onenand_scan */ + if (this->options & ONENAND_PAGEBUF_ALLOC) { kfree(this->page_buf); +#ifdef CONFIG_MTD_ONENAND_VERIFY_WRITE + kfree(this->verify_buf); +#endif + } + if (this->options & ONENAND_OOBBUF_ALLOC) + kfree(this->oob_buf); + kfree(mtd->eraseregions); } EXPORT_SYMBOL_GPL(onenand_scan); diff --git a/drivers/mtd/onenand/onenand_bbt.c b/drivers/mtd/onenand/onenand_bbt.c index 1b00dac3d7d..08d0085f3e9 100644 --- a/drivers/mtd/onenand/onenand_bbt.c +++ b/drivers/mtd/onenand/onenand_bbt.c @@ -15,10 +15,7 @@ #include <linux/slab.h> #include <linux/mtd/mtd.h> #include <linux/mtd/onenand.h> -#include <linux/mtd/compatmac.h> - -extern int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf); +#include <linux/export.h> /** * check_short_pattern - [GENERIC] check if a pattern is in the buffer @@ -65,10 +62,12 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr int startblock; loff_t from; size_t readlen, ooblen; + struct mtd_oob_ops ops; + int rgn; printk(KERN_INFO "Scanning device for bad blocks\n"); - len = 1; + len = 2; /* We need only read few bytes from the OOB area */ scanlen = ooblen = 0; @@ -78,33 +77,44 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr /* Note that numblocks is 2 * (real numblocks) here; * see i += 2 below as it makses shifting and masking less painful */ - numblocks = mtd->size >> (bbm->bbt_erase_shift - 1); + numblocks = this->chipsize >> (bbm->bbt_erase_shift - 1); startblock = 0; from = 0; + ops.mode = MTD_OPS_PLACE_OOB; + ops.ooblen = readlen; + ops.oobbuf = buf; + ops.len = ops.ooboffs = ops.retlen = ops.oobretlen = 0; + for (i = startblock; i < numblocks; ) { int ret; for (j = 0; j < len; j++) { - size_t retlen; - /* No need to read pages fully, * just read required OOB bytes */ - ret = onenand_do_read_oob(mtd, from + j * mtd->writesize + bd->offs, - readlen, &retlen, &buf[0]); + ret = onenand_bbt_read_oob(mtd, + from + j * this->writesize + bd->offs, &ops); - if (ret) - return ret; + /* If it is a initial bad block, just ignore it */ + if (ret == ONENAND_BBT_READ_FATAL_ERROR) + return -EIO; - if (check_short_pattern(&buf[j * scanlen], scanlen, mtd->writesize, bd)) { + if (ret || check_short_pattern(&buf[j * scanlen], + scanlen, this->writesize, bd)) { bbm->bbt[i >> 3] |= 0x03 << (i & 0x6); - printk(KERN_WARNING "Bad eraseblock %d at 0x%08x\n", - i >> 1, (unsigned int) from); + printk(KERN_INFO "OneNAND eraseblock %d is an " + "initial bad block\n", i >> 1); + mtd->ecc_stats.badblocks++; break; } } i += 2; - from += (1 << bbm->bbt_erase_shift); + + if (FLEXONENAND(this)) { + rgn = flexonenand_region(mtd, from); + from += mtd->eraseregions[rgn].erasesize; + } else + from += (1 << bbm->bbt_erase_shift); } return 0; @@ -123,7 +133,6 @@ static inline int onenand_memory_bbt (struct mtd_info *mtd, struct nand_bbt_desc { struct onenand_chip *this = mtd->priv; - bd->options &= ~NAND_BBT_SCANEMPTY; return create_bbt(mtd, this->page_buf, bd, -1); } @@ -141,10 +150,10 @@ static int onenand_isbad_bbt(struct mtd_info *mtd, loff_t offs, int allowbbt) uint8_t res; /* Get block number * 2 */ - block = (int) (offs >> (bbm->bbt_erase_shift - 1)); + block = (int) (onenand_block(this, offs) << 1); res = (bbm->bbt[block >> 3] >> (block & 0x06)) & 0x03; - DEBUG(MTD_DEBUG_LEVEL2, "onenand_isbad_bbt: bbt info for offs 0x%08x: (block %d) 0x%02x\n", + pr_debug("onenand_isbad_bbt: bbt info for offs 0x%08x: (block %d) 0x%02x\n", (unsigned int) offs, block >> 1, res); switch ((int) res) { @@ -166,8 +175,8 @@ static int onenand_isbad_bbt(struct mtd_info *mtd, loff_t offs, int allowbbt) * marked good / bad blocks and writes the bad block table(s) to * the selected place. * - * The bad block table memory is allocated here. It must be freed - * by calling the onenand_free_bbt function. + * The bad block table memory is allocated here. It is freed + * by the onenand_release function. * */ int onenand_scan_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd) @@ -176,15 +185,11 @@ int onenand_scan_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd) struct bbm_info *bbm = this->bbm; int len, ret = 0; - len = mtd->size >> (this->erase_shift + 2); - /* Allocate memory (2bit per block) */ - bbm->bbt = kmalloc(len, GFP_KERNEL); - if (!bbm->bbt) { - printk(KERN_ERR "onenand_scan_bbt: Out of memory\n"); + len = this->chipsize >> (this->erase_shift + 2); + /* Allocate memory (2bit per block) and clear the memory bad block table */ + bbm->bbt = kzalloc(len, GFP_KERNEL); + if (!bbm->bbt) return -ENOMEM; - } - /* Clear the memory bad block table */ - memset(bbm->bbt, 0x00, len); /* Set the bad block position */ bbm->badblockpos = ONENAND_BADBLOCK_POS; @@ -230,14 +235,12 @@ int onenand_default_bbt(struct mtd_info *mtd) struct onenand_chip *this = mtd->priv; struct bbm_info *bbm; - this->bbm = kmalloc(sizeof(struct bbm_info), GFP_KERNEL); + this->bbm = kzalloc(sizeof(struct bbm_info), GFP_KERNEL); if (!this->bbm) return -ENOMEM; bbm = this->bbm; - memset(bbm, 0, sizeof(struct bbm_info)); - /* 1KB page has same configuration as 2KB page */ if (!bbm->badblock_pattern) bbm->badblock_pattern = &largepage_memorybased; diff --git a/drivers/mtd/onenand/samsung.c b/drivers/mtd/onenand/samsung.c new file mode 100644 index 00000000000..efb819c3df2 --- /dev/null +++ b/drivers/mtd/onenand/samsung.c @@ -0,0 +1,1137 @@ +/* + * Samsung S3C64XX/S5PC1XX OneNAND driver + * + * Copyright © 2008-2010 Samsung Electronics + * Kyungmin Park <kyungmin.park@samsung.com> + * Marek Szyprowski <m.szyprowski@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Implementation: + * S3C64XX and S5PC100: emulate the pseudo BufferRAM + * S5PC110: use DMA + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/onenand.h> +#include <linux/mtd/partitions.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/io.h> + +#include <asm/mach/flash.h> + +#include "samsung.h" + +enum soc_type { + TYPE_S3C6400, + TYPE_S3C6410, + TYPE_S5PC100, + TYPE_S5PC110, +}; + +#define ONENAND_ERASE_STATUS 0x00 +#define ONENAND_MULTI_ERASE_SET 0x01 +#define ONENAND_ERASE_START 0x03 +#define ONENAND_UNLOCK_START 0x08 +#define ONENAND_UNLOCK_END 0x09 +#define ONENAND_LOCK_START 0x0A +#define ONENAND_LOCK_END 0x0B +#define ONENAND_LOCK_TIGHT_START 0x0C +#define ONENAND_LOCK_TIGHT_END 0x0D +#define ONENAND_UNLOCK_ALL 0x0E +#define ONENAND_OTP_ACCESS 0x12 +#define ONENAND_SPARE_ACCESS_ONLY 0x13 +#define ONENAND_MAIN_ACCESS_ONLY 0x14 +#define ONENAND_ERASE_VERIFY 0x15 +#define ONENAND_MAIN_SPARE_ACCESS 0x16 +#define ONENAND_PIPELINE_READ 0x4000 + +#define MAP_00 (0x0) +#define MAP_01 (0x1) +#define MAP_10 (0x2) +#define MAP_11 (0x3) + +#define S3C64XX_CMD_MAP_SHIFT 24 +#define S5PC100_CMD_MAP_SHIFT 26 + +#define S3C6400_FBA_SHIFT 10 +#define S3C6400_FPA_SHIFT 4 +#define S3C6400_FSA_SHIFT 2 + +#define S3C6410_FBA_SHIFT 12 +#define S3C6410_FPA_SHIFT 6 +#define S3C6410_FSA_SHIFT 4 + +#define S5PC100_FBA_SHIFT 13 +#define S5PC100_FPA_SHIFT 7 +#define S5PC100_FSA_SHIFT 5 + +/* S5PC110 specific definitions */ +#define S5PC110_DMA_SRC_ADDR 0x400 +#define S5PC110_DMA_SRC_CFG 0x404 +#define S5PC110_DMA_DST_ADDR 0x408 +#define S5PC110_DMA_DST_CFG 0x40C +#define S5PC110_DMA_TRANS_SIZE 0x414 +#define S5PC110_DMA_TRANS_CMD 0x418 +#define S5PC110_DMA_TRANS_STATUS 0x41C +#define S5PC110_DMA_TRANS_DIR 0x420 +#define S5PC110_INTC_DMA_CLR 0x1004 +#define S5PC110_INTC_ONENAND_CLR 0x1008 +#define S5PC110_INTC_DMA_MASK 0x1024 +#define S5PC110_INTC_ONENAND_MASK 0x1028 +#define S5PC110_INTC_DMA_PEND 0x1044 +#define S5PC110_INTC_ONENAND_PEND 0x1048 +#define S5PC110_INTC_DMA_STATUS 0x1064 +#define S5PC110_INTC_ONENAND_STATUS 0x1068 + +#define S5PC110_INTC_DMA_TD (1 << 24) +#define S5PC110_INTC_DMA_TE (1 << 16) + +#define S5PC110_DMA_CFG_SINGLE (0x0 << 16) +#define S5PC110_DMA_CFG_4BURST (0x2 << 16) +#define S5PC110_DMA_CFG_8BURST (0x3 << 16) +#define S5PC110_DMA_CFG_16BURST (0x4 << 16) + +#define S5PC110_DMA_CFG_INC (0x0 << 8) +#define S5PC110_DMA_CFG_CNT (0x1 << 8) + +#define S5PC110_DMA_CFG_8BIT (0x0 << 0) +#define S5PC110_DMA_CFG_16BIT (0x1 << 0) +#define S5PC110_DMA_CFG_32BIT (0x2 << 0) + +#define S5PC110_DMA_SRC_CFG_READ (S5PC110_DMA_CFG_16BURST | \ + S5PC110_DMA_CFG_INC | \ + S5PC110_DMA_CFG_16BIT) +#define S5PC110_DMA_DST_CFG_READ (S5PC110_DMA_CFG_16BURST | \ + S5PC110_DMA_CFG_INC | \ + S5PC110_DMA_CFG_32BIT) +#define S5PC110_DMA_SRC_CFG_WRITE (S5PC110_DMA_CFG_16BURST | \ + S5PC110_DMA_CFG_INC | \ + S5PC110_DMA_CFG_32BIT) +#define S5PC110_DMA_DST_CFG_WRITE (S5PC110_DMA_CFG_16BURST | \ + S5PC110_DMA_CFG_INC | \ + S5PC110_DMA_CFG_16BIT) + +#define S5PC110_DMA_TRANS_CMD_TDC (0x1 << 18) +#define S5PC110_DMA_TRANS_CMD_TEC (0x1 << 16) +#define S5PC110_DMA_TRANS_CMD_TR (0x1 << 0) + +#define S5PC110_DMA_TRANS_STATUS_TD (0x1 << 18) +#define S5PC110_DMA_TRANS_STATUS_TB (0x1 << 17) +#define S5PC110_DMA_TRANS_STATUS_TE (0x1 << 16) + +#define S5PC110_DMA_DIR_READ 0x0 +#define S5PC110_DMA_DIR_WRITE 0x1 + +struct s3c_onenand { + struct mtd_info *mtd; + struct platform_device *pdev; + enum soc_type type; + void __iomem *base; + struct resource *base_res; + void __iomem *ahb_addr; + struct resource *ahb_res; + int bootram_command; + void __iomem *page_buf; + void __iomem *oob_buf; + unsigned int (*mem_addr)(int fba, int fpa, int fsa); + unsigned int (*cmd_map)(unsigned int type, unsigned int val); + void __iomem *dma_addr; + struct resource *dma_res; + unsigned long phys_base; + struct completion complete; +}; + +#define CMD_MAP_00(dev, addr) (dev->cmd_map(MAP_00, ((addr) << 1))) +#define CMD_MAP_01(dev, mem_addr) (dev->cmd_map(MAP_01, (mem_addr))) +#define CMD_MAP_10(dev, mem_addr) (dev->cmd_map(MAP_10, (mem_addr))) +#define CMD_MAP_11(dev, addr) (dev->cmd_map(MAP_11, ((addr) << 2))) + +static struct s3c_onenand *onenand; + +static inline int s3c_read_reg(int offset) +{ + return readl(onenand->base + offset); +} + +static inline void s3c_write_reg(int value, int offset) +{ + writel(value, onenand->base + offset); +} + +static inline int s3c_read_cmd(unsigned int cmd) +{ + return readl(onenand->ahb_addr + cmd); +} + +static inline void s3c_write_cmd(int value, unsigned int cmd) +{ + writel(value, onenand->ahb_addr + cmd); +} + +#ifdef SAMSUNG_DEBUG +static void s3c_dump_reg(void) +{ + int i; + + for (i = 0; i < 0x400; i += 0x40) { + printk(KERN_INFO "0x%08X: 0x%08x 0x%08x 0x%08x 0x%08x\n", + (unsigned int) onenand->base + i, + s3c_read_reg(i), s3c_read_reg(i + 0x10), + s3c_read_reg(i + 0x20), s3c_read_reg(i + 0x30)); + } +} +#endif + +static unsigned int s3c64xx_cmd_map(unsigned type, unsigned val) +{ + return (type << S3C64XX_CMD_MAP_SHIFT) | val; +} + +static unsigned int s5pc1xx_cmd_map(unsigned type, unsigned val) +{ + return (type << S5PC100_CMD_MAP_SHIFT) | val; +} + +static unsigned int s3c6400_mem_addr(int fba, int fpa, int fsa) +{ + return (fba << S3C6400_FBA_SHIFT) | (fpa << S3C6400_FPA_SHIFT) | + (fsa << S3C6400_FSA_SHIFT); +} + +static unsigned int s3c6410_mem_addr(int fba, int fpa, int fsa) +{ + return (fba << S3C6410_FBA_SHIFT) | (fpa << S3C6410_FPA_SHIFT) | + (fsa << S3C6410_FSA_SHIFT); +} + +static unsigned int s5pc100_mem_addr(int fba, int fpa, int fsa) +{ + return (fba << S5PC100_FBA_SHIFT) | (fpa << S5PC100_FPA_SHIFT) | + (fsa << S5PC100_FSA_SHIFT); +} + +static void s3c_onenand_reset(void) +{ + unsigned long timeout = 0x10000; + int stat; + + s3c_write_reg(ONENAND_MEM_RESET_COLD, MEM_RESET_OFFSET); + while (1 && timeout--) { + stat = s3c_read_reg(INT_ERR_STAT_OFFSET); + if (stat & RST_CMP) + break; + } + stat = s3c_read_reg(INT_ERR_STAT_OFFSET); + s3c_write_reg(stat, INT_ERR_ACK_OFFSET); + + /* Clear interrupt */ + s3c_write_reg(0x0, INT_ERR_ACK_OFFSET); + /* Clear the ECC status */ + s3c_write_reg(0x0, ECC_ERR_STAT_OFFSET); +} + +static unsigned short s3c_onenand_readw(void __iomem *addr) +{ + struct onenand_chip *this = onenand->mtd->priv; + struct device *dev = &onenand->pdev->dev; + int reg = addr - this->base; + int word_addr = reg >> 1; + int value; + + /* It's used for probing time */ + switch (reg) { + case ONENAND_REG_MANUFACTURER_ID: + return s3c_read_reg(MANUFACT_ID_OFFSET); + case ONENAND_REG_DEVICE_ID: + return s3c_read_reg(DEVICE_ID_OFFSET); + case ONENAND_REG_VERSION_ID: + return s3c_read_reg(FLASH_VER_ID_OFFSET); + case ONENAND_REG_DATA_BUFFER_SIZE: + return s3c_read_reg(DATA_BUF_SIZE_OFFSET); + case ONENAND_REG_TECHNOLOGY: + return s3c_read_reg(TECH_OFFSET); + case ONENAND_REG_SYS_CFG1: + return s3c_read_reg(MEM_CFG_OFFSET); + + /* Used at unlock all status */ + case ONENAND_REG_CTRL_STATUS: + return 0; + + case ONENAND_REG_WP_STATUS: + return ONENAND_WP_US; + + default: + break; + } + + /* BootRAM access control */ + if ((unsigned int) addr < ONENAND_DATARAM && onenand->bootram_command) { + if (word_addr == 0) + return s3c_read_reg(MANUFACT_ID_OFFSET); + if (word_addr == 1) + return s3c_read_reg(DEVICE_ID_OFFSET); + if (word_addr == 2) + return s3c_read_reg(FLASH_VER_ID_OFFSET); + } + + value = s3c_read_cmd(CMD_MAP_11(onenand, word_addr)) & 0xffff; + dev_info(dev, "%s: Illegal access at reg 0x%x, value 0x%x\n", __func__, + word_addr, value); + return value; +} + +static void s3c_onenand_writew(unsigned short value, void __iomem *addr) +{ + struct onenand_chip *this = onenand->mtd->priv; + struct device *dev = &onenand->pdev->dev; + unsigned int reg = addr - this->base; + unsigned int word_addr = reg >> 1; + + /* It's used for probing time */ + switch (reg) { + case ONENAND_REG_SYS_CFG1: + s3c_write_reg(value, MEM_CFG_OFFSET); + return; + + case ONENAND_REG_START_ADDRESS1: + case ONENAND_REG_START_ADDRESS2: + return; + + /* Lock/lock-tight/unlock/unlock_all */ + case ONENAND_REG_START_BLOCK_ADDRESS: + return; + + default: + break; + } + + /* BootRAM access control */ + if ((unsigned int)addr < ONENAND_DATARAM) { + if (value == ONENAND_CMD_READID) { + onenand->bootram_command = 1; + return; + } + if (value == ONENAND_CMD_RESET) { + s3c_write_reg(ONENAND_MEM_RESET_COLD, MEM_RESET_OFFSET); + onenand->bootram_command = 0; + return; + } + } + + dev_info(dev, "%s: Illegal access at reg 0x%x, value 0x%x\n", __func__, + word_addr, value); + + s3c_write_cmd(value, CMD_MAP_11(onenand, word_addr)); +} + +static int s3c_onenand_wait(struct mtd_info *mtd, int state) +{ + struct device *dev = &onenand->pdev->dev; + unsigned int flags = INT_ACT; + unsigned int stat, ecc; + unsigned long timeout; + + switch (state) { + case FL_READING: + flags |= BLK_RW_CMP | LOAD_CMP; + break; + case FL_WRITING: + flags |= BLK_RW_CMP | PGM_CMP; + break; + case FL_ERASING: + flags |= BLK_RW_CMP | ERS_CMP; + break; + case FL_LOCKING: + flags |= BLK_RW_CMP; + break; + default: + break; + } + + /* The 20 msec is enough */ + timeout = jiffies + msecs_to_jiffies(20); + while (time_before(jiffies, timeout)) { + stat = s3c_read_reg(INT_ERR_STAT_OFFSET); + if (stat & flags) + break; + + if (state != FL_READING) + cond_resched(); + } + /* To get correct interrupt status in timeout case */ + stat = s3c_read_reg(INT_ERR_STAT_OFFSET); + s3c_write_reg(stat, INT_ERR_ACK_OFFSET); + + /* + * In the Spec. it checks the controller status first + * However if you get the correct information in case of + * power off recovery (POR) test, it should read ECC status first + */ + if (stat & LOAD_CMP) { + ecc = s3c_read_reg(ECC_ERR_STAT_OFFSET); + if (ecc & ONENAND_ECC_4BIT_UNCORRECTABLE) { + dev_info(dev, "%s: ECC error = 0x%04x\n", __func__, + ecc); + mtd->ecc_stats.failed++; + return -EBADMSG; + } + } + + if (stat & (LOCKED_BLK | ERS_FAIL | PGM_FAIL | LD_FAIL_ECC_ERR)) { + dev_info(dev, "%s: controller error = 0x%04x\n", __func__, + stat); + if (stat & LOCKED_BLK) + dev_info(dev, "%s: it's locked error = 0x%04x\n", + __func__, stat); + + return -EIO; + } + + return 0; +} + +static int s3c_onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, + size_t len) +{ + struct onenand_chip *this = mtd->priv; + unsigned int *m, *s; + int fba, fpa, fsa = 0; + unsigned int mem_addr, cmd_map_01, cmd_map_10; + int i, mcount, scount; + int index; + + fba = (int) (addr >> this->erase_shift); + fpa = (int) (addr >> this->page_shift); + fpa &= this->page_mask; + + mem_addr = onenand->mem_addr(fba, fpa, fsa); + cmd_map_01 = CMD_MAP_01(onenand, mem_addr); + cmd_map_10 = CMD_MAP_10(onenand, mem_addr); + + switch (cmd) { + case ONENAND_CMD_READ: + case ONENAND_CMD_READOOB: + case ONENAND_CMD_BUFFERRAM: + ONENAND_SET_NEXT_BUFFERRAM(this); + default: + break; + } + + index = ONENAND_CURRENT_BUFFERRAM(this); + + /* + * Emulate Two BufferRAMs and access with 4 bytes pointer + */ + m = (unsigned int *) onenand->page_buf; + s = (unsigned int *) onenand->oob_buf; + + if (index) { + m += (this->writesize >> 2); + s += (mtd->oobsize >> 2); + } + + mcount = mtd->writesize >> 2; + scount = mtd->oobsize >> 2; + + switch (cmd) { + case ONENAND_CMD_READ: + /* Main */ + for (i = 0; i < mcount; i++) + *m++ = s3c_read_cmd(cmd_map_01); + return 0; + + case ONENAND_CMD_READOOB: + s3c_write_reg(TSRF, TRANS_SPARE_OFFSET); + /* Main */ + for (i = 0; i < mcount; i++) + *m++ = s3c_read_cmd(cmd_map_01); + + /* Spare */ + for (i = 0; i < scount; i++) + *s++ = s3c_read_cmd(cmd_map_01); + + s3c_write_reg(0, TRANS_SPARE_OFFSET); + return 0; + + case ONENAND_CMD_PROG: + /* Main */ + for (i = 0; i < mcount; i++) + s3c_write_cmd(*m++, cmd_map_01); + return 0; + + case ONENAND_CMD_PROGOOB: + s3c_write_reg(TSRF, TRANS_SPARE_OFFSET); + + /* Main - dummy write */ + for (i = 0; i < mcount; i++) + s3c_write_cmd(0xffffffff, cmd_map_01); + + /* Spare */ + for (i = 0; i < scount; i++) + s3c_write_cmd(*s++, cmd_map_01); + + s3c_write_reg(0, TRANS_SPARE_OFFSET); + return 0; + + case ONENAND_CMD_UNLOCK_ALL: + s3c_write_cmd(ONENAND_UNLOCK_ALL, cmd_map_10); + return 0; + + case ONENAND_CMD_ERASE: + s3c_write_cmd(ONENAND_ERASE_START, cmd_map_10); + return 0; + + default: + break; + } + + return 0; +} + +static unsigned char *s3c_get_bufferram(struct mtd_info *mtd, int area) +{ + struct onenand_chip *this = mtd->priv; + int index = ONENAND_CURRENT_BUFFERRAM(this); + unsigned char *p; + + if (area == ONENAND_DATARAM) { + p = (unsigned char *) onenand->page_buf; + if (index == 1) + p += this->writesize; + } else { + p = (unsigned char *) onenand->oob_buf; + if (index == 1) + p += mtd->oobsize; + } + + return p; +} + +static int onenand_read_bufferram(struct mtd_info *mtd, int area, + unsigned char *buffer, int offset, + size_t count) +{ + unsigned char *p; + + p = s3c_get_bufferram(mtd, area); + memcpy(buffer, p + offset, count); + return 0; +} + +static int onenand_write_bufferram(struct mtd_info *mtd, int area, + const unsigned char *buffer, int offset, + size_t count) +{ + unsigned char *p; + + p = s3c_get_bufferram(mtd, area); + memcpy(p + offset, buffer, count); + return 0; +} + +static int (*s5pc110_dma_ops)(dma_addr_t dst, dma_addr_t src, size_t count, int direction); + +static int s5pc110_dma_poll(dma_addr_t dst, dma_addr_t src, size_t count, int direction) +{ + void __iomem *base = onenand->dma_addr; + int status; + unsigned long timeout; + + writel(src, base + S5PC110_DMA_SRC_ADDR); + writel(dst, base + S5PC110_DMA_DST_ADDR); + + if (direction == S5PC110_DMA_DIR_READ) { + writel(S5PC110_DMA_SRC_CFG_READ, base + S5PC110_DMA_SRC_CFG); + writel(S5PC110_DMA_DST_CFG_READ, base + S5PC110_DMA_DST_CFG); + } else { + writel(S5PC110_DMA_SRC_CFG_WRITE, base + S5PC110_DMA_SRC_CFG); + writel(S5PC110_DMA_DST_CFG_WRITE, base + S5PC110_DMA_DST_CFG); + } + + writel(count, base + S5PC110_DMA_TRANS_SIZE); + writel(direction, base + S5PC110_DMA_TRANS_DIR); + + writel(S5PC110_DMA_TRANS_CMD_TR, base + S5PC110_DMA_TRANS_CMD); + + /* + * There's no exact timeout values at Spec. + * In real case it takes under 1 msec. + * So 20 msecs are enough. + */ + timeout = jiffies + msecs_to_jiffies(20); + + do { + status = readl(base + S5PC110_DMA_TRANS_STATUS); + if (status & S5PC110_DMA_TRANS_STATUS_TE) { + writel(S5PC110_DMA_TRANS_CMD_TEC, + base + S5PC110_DMA_TRANS_CMD); + return -EIO; + } + } while (!(status & S5PC110_DMA_TRANS_STATUS_TD) && + time_before(jiffies, timeout)); + + writel(S5PC110_DMA_TRANS_CMD_TDC, base + S5PC110_DMA_TRANS_CMD); + + return 0; +} + +static irqreturn_t s5pc110_onenand_irq(int irq, void *data) +{ + void __iomem *base = onenand->dma_addr; + int status, cmd = 0; + + status = readl(base + S5PC110_INTC_DMA_STATUS); + + if (likely(status & S5PC110_INTC_DMA_TD)) + cmd = S5PC110_DMA_TRANS_CMD_TDC; + + if (unlikely(status & S5PC110_INTC_DMA_TE)) + cmd = S5PC110_DMA_TRANS_CMD_TEC; + + writel(cmd, base + S5PC110_DMA_TRANS_CMD); + writel(status, base + S5PC110_INTC_DMA_CLR); + + if (!onenand->complete.done) + complete(&onenand->complete); + + return IRQ_HANDLED; +} + +static int s5pc110_dma_irq(dma_addr_t dst, dma_addr_t src, size_t count, int direction) +{ + void __iomem *base = onenand->dma_addr; + int status; + + status = readl(base + S5PC110_INTC_DMA_MASK); + if (status) { + status &= ~(S5PC110_INTC_DMA_TD | S5PC110_INTC_DMA_TE); + writel(status, base + S5PC110_INTC_DMA_MASK); + } + + writel(src, base + S5PC110_DMA_SRC_ADDR); + writel(dst, base + S5PC110_DMA_DST_ADDR); + + if (direction == S5PC110_DMA_DIR_READ) { + writel(S5PC110_DMA_SRC_CFG_READ, base + S5PC110_DMA_SRC_CFG); + writel(S5PC110_DMA_DST_CFG_READ, base + S5PC110_DMA_DST_CFG); + } else { + writel(S5PC110_DMA_SRC_CFG_WRITE, base + S5PC110_DMA_SRC_CFG); + writel(S5PC110_DMA_DST_CFG_WRITE, base + S5PC110_DMA_DST_CFG); + } + + writel(count, base + S5PC110_DMA_TRANS_SIZE); + writel(direction, base + S5PC110_DMA_TRANS_DIR); + + writel(S5PC110_DMA_TRANS_CMD_TR, base + S5PC110_DMA_TRANS_CMD); + + wait_for_completion_timeout(&onenand->complete, msecs_to_jiffies(20)); + + return 0; +} + +static int s5pc110_read_bufferram(struct mtd_info *mtd, int area, + unsigned char *buffer, int offset, size_t count) +{ + struct onenand_chip *this = mtd->priv; + void __iomem *p; + void *buf = (void *) buffer; + dma_addr_t dma_src, dma_dst; + int err, ofs, page_dma = 0; + struct device *dev = &onenand->pdev->dev; + + p = this->base + area; + if (ONENAND_CURRENT_BUFFERRAM(this)) { + if (area == ONENAND_DATARAM) + p += this->writesize; + else + p += mtd->oobsize; + } + + if (offset & 3 || (size_t) buf & 3 || + !onenand->dma_addr || count != mtd->writesize) + goto normal; + + /* Handle vmalloc address */ + if (buf >= high_memory) { + struct page *page; + + if (((size_t) buf & PAGE_MASK) != + ((size_t) (buf + count - 1) & PAGE_MASK)) + goto normal; + page = vmalloc_to_page(buf); + if (!page) + goto normal; + + /* Page offset */ + ofs = ((size_t) buf & ~PAGE_MASK); + page_dma = 1; + + /* DMA routine */ + dma_src = onenand->phys_base + (p - this->base); + dma_dst = dma_map_page(dev, page, ofs, count, DMA_FROM_DEVICE); + } else { + /* DMA routine */ + dma_src = onenand->phys_base + (p - this->base); + dma_dst = dma_map_single(dev, buf, count, DMA_FROM_DEVICE); + } + if (dma_mapping_error(dev, dma_dst)) { + dev_err(dev, "Couldn't map a %d byte buffer for DMA\n", count); + goto normal; + } + err = s5pc110_dma_ops(dma_dst, dma_src, + count, S5PC110_DMA_DIR_READ); + + if (page_dma) + dma_unmap_page(dev, dma_dst, count, DMA_FROM_DEVICE); + else + dma_unmap_single(dev, dma_dst, count, DMA_FROM_DEVICE); + + if (!err) + return 0; + +normal: + if (count != mtd->writesize) { + /* Copy the bufferram to memory to prevent unaligned access */ + memcpy(this->page_buf, p, mtd->writesize); + p = this->page_buf + offset; + } + + memcpy(buffer, p, count); + + return 0; +} + +static int s5pc110_chip_probe(struct mtd_info *mtd) +{ + /* Now just return 0 */ + return 0; +} + +static int s3c_onenand_bbt_wait(struct mtd_info *mtd, int state) +{ + unsigned int flags = INT_ACT | LOAD_CMP; + unsigned int stat; + unsigned long timeout; + + /* The 20 msec is enough */ + timeout = jiffies + msecs_to_jiffies(20); + while (time_before(jiffies, timeout)) { + stat = s3c_read_reg(INT_ERR_STAT_OFFSET); + if (stat & flags) + break; + } + /* To get correct interrupt status in timeout case */ + stat = s3c_read_reg(INT_ERR_STAT_OFFSET); + s3c_write_reg(stat, INT_ERR_ACK_OFFSET); + + if (stat & LD_FAIL_ECC_ERR) { + s3c_onenand_reset(); + return ONENAND_BBT_READ_ERROR; + } + + if (stat & LOAD_CMP) { + int ecc = s3c_read_reg(ECC_ERR_STAT_OFFSET); + if (ecc & ONENAND_ECC_4BIT_UNCORRECTABLE) { + s3c_onenand_reset(); + return ONENAND_BBT_READ_ERROR; + } + } + + return 0; +} + +static void s3c_onenand_check_lock_status(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + struct device *dev = &onenand->pdev->dev; + unsigned int block, end; + int tmp; + + end = this->chipsize >> this->erase_shift; + + for (block = 0; block < end; block++) { + unsigned int mem_addr = onenand->mem_addr(block, 0, 0); + tmp = s3c_read_cmd(CMD_MAP_01(onenand, mem_addr)); + + if (s3c_read_reg(INT_ERR_STAT_OFFSET) & LOCKED_BLK) { + dev_err(dev, "block %d is write-protected!\n", block); + s3c_write_reg(LOCKED_BLK, INT_ERR_ACK_OFFSET); + } + } +} + +static void s3c_onenand_do_lock_cmd(struct mtd_info *mtd, loff_t ofs, + size_t len, int cmd) +{ + struct onenand_chip *this = mtd->priv; + int start, end, start_mem_addr, end_mem_addr; + + start = ofs >> this->erase_shift; + start_mem_addr = onenand->mem_addr(start, 0, 0); + end = start + (len >> this->erase_shift) - 1; + end_mem_addr = onenand->mem_addr(end, 0, 0); + + if (cmd == ONENAND_CMD_LOCK) { + s3c_write_cmd(ONENAND_LOCK_START, CMD_MAP_10(onenand, + start_mem_addr)); + s3c_write_cmd(ONENAND_LOCK_END, CMD_MAP_10(onenand, + end_mem_addr)); + } else { + s3c_write_cmd(ONENAND_UNLOCK_START, CMD_MAP_10(onenand, + start_mem_addr)); + s3c_write_cmd(ONENAND_UNLOCK_END, CMD_MAP_10(onenand, + end_mem_addr)); + } + + this->wait(mtd, FL_LOCKING); +} + +static void s3c_unlock_all(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + loff_t ofs = 0; + size_t len = this->chipsize; + + if (this->options & ONENAND_HAS_UNLOCK_ALL) { + /* Write unlock command */ + this->command(mtd, ONENAND_CMD_UNLOCK_ALL, 0, 0); + + /* No need to check return value */ + this->wait(mtd, FL_LOCKING); + + /* Workaround for all block unlock in DDP */ + if (!ONENAND_IS_DDP(this)) { + s3c_onenand_check_lock_status(mtd); + return; + } + + /* All blocks on another chip */ + ofs = this->chipsize >> 1; + len = this->chipsize >> 1; + } + + s3c_onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_UNLOCK); + + s3c_onenand_check_lock_status(mtd); +} + +static void s3c_onenand_setup(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + + onenand->mtd = mtd; + + if (onenand->type == TYPE_S3C6400) { + onenand->mem_addr = s3c6400_mem_addr; + onenand->cmd_map = s3c64xx_cmd_map; + } else if (onenand->type == TYPE_S3C6410) { + onenand->mem_addr = s3c6410_mem_addr; + onenand->cmd_map = s3c64xx_cmd_map; + } else if (onenand->type == TYPE_S5PC100) { + onenand->mem_addr = s5pc100_mem_addr; + onenand->cmd_map = s5pc1xx_cmd_map; + } else if (onenand->type == TYPE_S5PC110) { + /* Use generic onenand functions */ + this->read_bufferram = s5pc110_read_bufferram; + this->chip_probe = s5pc110_chip_probe; + return; + } else { + BUG(); + } + + this->read_word = s3c_onenand_readw; + this->write_word = s3c_onenand_writew; + + this->wait = s3c_onenand_wait; + this->bbt_wait = s3c_onenand_bbt_wait; + this->unlock_all = s3c_unlock_all; + this->command = s3c_onenand_command; + + this->read_bufferram = onenand_read_bufferram; + this->write_bufferram = onenand_write_bufferram; +} + +static int s3c_onenand_probe(struct platform_device *pdev) +{ + struct onenand_platform_data *pdata; + struct onenand_chip *this; + struct mtd_info *mtd; + struct resource *r; + int size, err; + + pdata = dev_get_platdata(&pdev->dev); + /* No need to check pdata. the platform data is optional */ + + size = sizeof(struct mtd_info) + sizeof(struct onenand_chip); + mtd = kzalloc(size, GFP_KERNEL); + if (!mtd) + return -ENOMEM; + + onenand = kzalloc(sizeof(struct s3c_onenand), GFP_KERNEL); + if (!onenand) { + err = -ENOMEM; + goto onenand_fail; + } + + this = (struct onenand_chip *) &mtd[1]; + mtd->priv = this; + mtd->dev.parent = &pdev->dev; + mtd->owner = THIS_MODULE; + onenand->pdev = pdev; + onenand->type = platform_get_device_id(pdev)->driver_data; + + s3c_onenand_setup(mtd); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "no memory resource defined\n"); + return -ENOENT; + goto ahb_resource_failed; + } + + onenand->base_res = request_mem_region(r->start, resource_size(r), + pdev->name); + if (!onenand->base_res) { + dev_err(&pdev->dev, "failed to request memory resource\n"); + err = -EBUSY; + goto resource_failed; + } + + onenand->base = ioremap(r->start, resource_size(r)); + if (!onenand->base) { + dev_err(&pdev->dev, "failed to map memory resource\n"); + err = -EFAULT; + goto ioremap_failed; + } + /* Set onenand_chip also */ + this->base = onenand->base; + + /* Use runtime badblock check */ + this->options |= ONENAND_SKIP_UNLOCK_CHECK; + + if (onenand->type != TYPE_S5PC110) { + r = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!r) { + dev_err(&pdev->dev, "no buffer memory resource defined\n"); + err = -ENOENT; + goto ahb_resource_failed; + } + + onenand->ahb_res = request_mem_region(r->start, resource_size(r), + pdev->name); + if (!onenand->ahb_res) { + dev_err(&pdev->dev, "failed to request buffer memory resource\n"); + err = -EBUSY; + goto ahb_resource_failed; + } + + onenand->ahb_addr = ioremap(r->start, resource_size(r)); + if (!onenand->ahb_addr) { + dev_err(&pdev->dev, "failed to map buffer memory resource\n"); + err = -EINVAL; + goto ahb_ioremap_failed; + } + + /* Allocate 4KiB BufferRAM */ + onenand->page_buf = kzalloc(SZ_4K, GFP_KERNEL); + if (!onenand->page_buf) { + err = -ENOMEM; + goto page_buf_fail; + } + + /* Allocate 128 SpareRAM */ + onenand->oob_buf = kzalloc(128, GFP_KERNEL); + if (!onenand->oob_buf) { + err = -ENOMEM; + goto oob_buf_fail; + } + + /* S3C doesn't handle subpage write */ + mtd->subpage_sft = 0; + this->subpagesize = mtd->writesize; + + } else { /* S5PC110 */ + r = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!r) { + dev_err(&pdev->dev, "no dma memory resource defined\n"); + err = -ENOENT; + goto dma_resource_failed; + } + + onenand->dma_res = request_mem_region(r->start, resource_size(r), + pdev->name); + if (!onenand->dma_res) { + dev_err(&pdev->dev, "failed to request dma memory resource\n"); + err = -EBUSY; + goto dma_resource_failed; + } + + onenand->dma_addr = ioremap(r->start, resource_size(r)); + if (!onenand->dma_addr) { + dev_err(&pdev->dev, "failed to map dma memory resource\n"); + err = -EINVAL; + goto dma_ioremap_failed; + } + + onenand->phys_base = onenand->base_res->start; + + s5pc110_dma_ops = s5pc110_dma_poll; + /* Interrupt support */ + r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (r) { + init_completion(&onenand->complete); + s5pc110_dma_ops = s5pc110_dma_irq; + err = request_irq(r->start, s5pc110_onenand_irq, + IRQF_SHARED, "onenand", &onenand); + if (err) { + dev_err(&pdev->dev, "failed to get irq\n"); + goto scan_failed; + } + } + } + + if (onenand_scan(mtd, 1)) { + err = -EFAULT; + goto scan_failed; + } + + if (onenand->type != TYPE_S5PC110) { + /* S3C doesn't handle subpage write */ + mtd->subpage_sft = 0; + this->subpagesize = mtd->writesize; + } + + if (s3c_read_reg(MEM_CFG_OFFSET) & ONENAND_SYS_CFG1_SYNC_READ) + dev_info(&onenand->pdev->dev, "OneNAND Sync. Burst Read enabled\n"); + + err = mtd_device_parse_register(mtd, NULL, NULL, + pdata ? pdata->parts : NULL, + pdata ? pdata->nr_parts : 0); + + platform_set_drvdata(pdev, mtd); + + return 0; + +scan_failed: + if (onenand->dma_addr) + iounmap(onenand->dma_addr); +dma_ioremap_failed: + if (onenand->dma_res) + release_mem_region(onenand->dma_res->start, + resource_size(onenand->dma_res)); + kfree(onenand->oob_buf); +oob_buf_fail: + kfree(onenand->page_buf); +page_buf_fail: + if (onenand->ahb_addr) + iounmap(onenand->ahb_addr); +ahb_ioremap_failed: + if (onenand->ahb_res) + release_mem_region(onenand->ahb_res->start, + resource_size(onenand->ahb_res)); +dma_resource_failed: +ahb_resource_failed: + iounmap(onenand->base); +ioremap_failed: + if (onenand->base_res) + release_mem_region(onenand->base_res->start, + resource_size(onenand->base_res)); +resource_failed: + kfree(onenand); +onenand_fail: + kfree(mtd); + return err; +} + +static int s3c_onenand_remove(struct platform_device *pdev) +{ + struct mtd_info *mtd = platform_get_drvdata(pdev); + + onenand_release(mtd); + if (onenand->ahb_addr) + iounmap(onenand->ahb_addr); + if (onenand->ahb_res) + release_mem_region(onenand->ahb_res->start, + resource_size(onenand->ahb_res)); + if (onenand->dma_addr) + iounmap(onenand->dma_addr); + if (onenand->dma_res) + release_mem_region(onenand->dma_res->start, + resource_size(onenand->dma_res)); + + iounmap(onenand->base); + release_mem_region(onenand->base_res->start, + resource_size(onenand->base_res)); + + kfree(onenand->oob_buf); + kfree(onenand->page_buf); + kfree(onenand); + kfree(mtd); + return 0; +} + +static int s3c_pm_ops_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mtd_info *mtd = platform_get_drvdata(pdev); + struct onenand_chip *this = mtd->priv; + + this->wait(mtd, FL_PM_SUSPENDED); + return 0; +} + +static int s3c_pm_ops_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mtd_info *mtd = platform_get_drvdata(pdev); + struct onenand_chip *this = mtd->priv; + + this->unlock_all(mtd); + return 0; +} + +static const struct dev_pm_ops s3c_pm_ops = { + .suspend = s3c_pm_ops_suspend, + .resume = s3c_pm_ops_resume, +}; + +static struct platform_device_id s3c_onenand_driver_ids[] = { + { + .name = "s3c6400-onenand", + .driver_data = TYPE_S3C6400, + }, { + .name = "s3c6410-onenand", + .driver_data = TYPE_S3C6410, + }, { + .name = "s5pc100-onenand", + .driver_data = TYPE_S5PC100, + }, { + .name = "s5pc110-onenand", + .driver_data = TYPE_S5PC110, + }, { }, +}; +MODULE_DEVICE_TABLE(platform, s3c_onenand_driver_ids); + +static struct platform_driver s3c_onenand_driver = { + .driver = { + .name = "samsung-onenand", + .pm = &s3c_pm_ops, + }, + .id_table = s3c_onenand_driver_ids, + .probe = s3c_onenand_probe, + .remove = s3c_onenand_remove, +}; + +module_platform_driver(s3c_onenand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>"); +MODULE_DESCRIPTION("Samsung OneNAND controller support"); diff --git a/drivers/mtd/onenand/samsung.h b/drivers/mtd/onenand/samsung.h new file mode 100644 index 00000000000..9016dc0136a --- /dev/null +++ b/drivers/mtd/onenand/samsung.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2008-2010 Samsung Electronics + * Kyungmin Park <kyungmin.park@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __SAMSUNG_ONENAND_H__ +#define __SAMSUNG_ONENAND_H__ + +/* + * OneNAND Controller + */ +#define MEM_CFG_OFFSET 0x0000 +#define BURST_LEN_OFFSET 0x0010 +#define MEM_RESET_OFFSET 0x0020 +#define INT_ERR_STAT_OFFSET 0x0030 +#define INT_ERR_MASK_OFFSET 0x0040 +#define INT_ERR_ACK_OFFSET 0x0050 +#define ECC_ERR_STAT_OFFSET 0x0060 +#define MANUFACT_ID_OFFSET 0x0070 +#define DEVICE_ID_OFFSET 0x0080 +#define DATA_BUF_SIZE_OFFSET 0x0090 +#define BOOT_BUF_SIZE_OFFSET 0x00A0 +#define BUF_AMOUNT_OFFSET 0x00B0 +#define TECH_OFFSET 0x00C0 +#define FBA_WIDTH_OFFSET 0x00D0 +#define FPA_WIDTH_OFFSET 0x00E0 +#define FSA_WIDTH_OFFSET 0x00F0 +#define TRANS_SPARE_OFFSET 0x0140 +#define DBS_DFS_WIDTH_OFFSET 0x0160 +#define INT_PIN_ENABLE_OFFSET 0x01A0 +#define ACC_CLOCK_OFFSET 0x01C0 +#define FLASH_VER_ID_OFFSET 0x01F0 +#define FLASH_AUX_CNTRL_OFFSET 0x0300 /* s3c64xx only */ + +#define ONENAND_MEM_RESET_HOT 0x3 +#define ONENAND_MEM_RESET_COLD 0x2 +#define ONENAND_MEM_RESET_WARM 0x1 + +#define CACHE_OP_ERR (1 << 13) +#define RST_CMP (1 << 12) +#define RDY_ACT (1 << 11) +#define INT_ACT (1 << 10) +#define UNSUP_CMD (1 << 9) +#define LOCKED_BLK (1 << 8) +#define BLK_RW_CMP (1 << 7) +#define ERS_CMP (1 << 6) +#define PGM_CMP (1 << 5) +#define LOAD_CMP (1 << 4) +#define ERS_FAIL (1 << 3) +#define PGM_FAIL (1 << 2) +#define INT_TO (1 << 1) +#define LD_FAIL_ECC_ERR (1 << 0) + +#define TSRF (1 << 0) + +#endif diff --git a/drivers/mtd/redboot.c b/drivers/mtd/redboot.c index 5b58523e4d4..5da911ebdf4 100644 --- a/drivers/mtd/redboot.c +++ b/drivers/mtd/redboot.c @@ -1,8 +1,24 @@ /* - * $Id: redboot.c,v 1.21 2006/03/30 18:34:37 bjd Exp $ - * * Parse RedBoot-style Flash Image System (FIS) tables and * produce a Linux partition array to match. + * + * Copyright © 2001 Red Hat UK Limited + * Copyright © 2001-2010 David Woodhouse <dwmw2@infradead.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * */ #include <linux/kernel.h> @@ -12,6 +28,7 @@ #include <linux/mtd/mtd.h> #include <linux/mtd/partitions.h> +#include <linux/module.h> struct fis_image_desc { unsigned char name[16]; // Null terminated name @@ -40,8 +57,8 @@ static inline int redboot_checksum(struct fis_image_desc *img) } static int parse_redboot_partitions(struct mtd_info *master, - struct mtd_partition **pparts, - unsigned long fis_origin) + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) { int nrparts = 0; struct fis_image_desc *buf; @@ -59,21 +76,34 @@ static int parse_redboot_partitions(struct mtd_info *master, static char nullstring[] = "unallocated"; #endif + if ( directory < 0 ) { + offset = master->size + directory * master->erasesize; + while (mtd_block_isbad(master, offset)) { + if (!offset) { + nogood: + printk(KERN_NOTICE "Failed to find a non-bad block to check for RedBoot partition table\n"); + return -EIO; + } + offset -= master->erasesize; + } + } else { + offset = directory * master->erasesize; + while (mtd_block_isbad(master, offset)) { + offset += master->erasesize; + if (offset == master->size) + goto nogood; + } + } buf = vmalloc(master->erasesize); if (!buf) return -ENOMEM; - if ( directory < 0 ) - offset = master->size + directory*master->erasesize; - else - offset = directory*master->erasesize; - printk(KERN_NOTICE "Searching for RedBoot partition table in %s at offset 0x%lx\n", master->name, offset); - ret = master->read(master, offset, - master->erasesize, &retlen, (void *)buf); + ret = mtd_read(master, offset, master->erasesize, &retlen, + (void *)buf); if (ret) goto out; @@ -94,9 +124,32 @@ static int parse_redboot_partitions(struct mtd_info *master, * (NOTE: this is 'size' not 'data_length'; size is * the full size of the entry.) */ - if (swab32(buf[i].size) == master->erasesize) { + + /* RedBoot can combine the FIS directory and + config partitions into a single eraseblock; + we assume wrong-endian if either the swapped + 'size' matches the eraseblock size precisely, + or if the swapped size actually fits in an + eraseblock while the unswapped size doesn't. */ + if (swab32(buf[i].size) == master->erasesize || + (buf[i].size > master->erasesize + && swab32(buf[i].size) < master->erasesize)) { int j; - for (j = 0; j < numslots && buf[j].name[0] != 0xff; ++j) { + /* Update numslots based on actual FIS directory size */ + numslots = swab32(buf[i].size) / sizeof (struct fis_image_desc); + for (j = 0; j < numslots; ++j) { + + /* A single 0xff denotes a deleted entry. + * Two of them in a row is the end of the table. + */ + if (buf[j].name[0] == 0xff) { + if (buf[j].name[1] == 0xff) { + break; + } else { + continue; + } + } + /* The unsigned long fields were written with the * wrong byte sex, name and pad have no byte sex. */ @@ -108,6 +161,9 @@ static int parse_redboot_partitions(struct mtd_info *master, swab32s(&buf[j].desc_cksum); swab32s(&buf[j].file_cksum); } + } else if (buf[i].size < master->erasesize) { + /* Update numslots based on actual FIS directory size */ + numslots = buf[i].size / sizeof(struct fis_image_desc); } break; } @@ -123,8 +179,13 @@ static int parse_redboot_partitions(struct mtd_info *master, for (i = 0; i < numslots; i++) { struct fis_list *new_fl, **prev; - if (buf[i].name[0] == 0xff) - continue; + if (buf[i].name[0] == 0xff) { + if (buf[i].name[1] == 0xff) { + break; + } else { + continue; + } + } if (!redboot_checksum(&buf[i])) break; @@ -135,11 +196,10 @@ static int parse_redboot_partitions(struct mtd_info *master, goto out; } new_fl->img = &buf[i]; - if (fis_origin) { - buf[i].flash_base -= fis_origin; - } else { - buf[i].flash_base &= master->size-1; - } + if (data && data->origin) + buf[i].flash_base -= data->origin; + else + buf[i].flash_base &= master->size-1; /* I'm sure the JFFS2 code has done me permanent damage. * I now think the following is _normal_ @@ -165,15 +225,13 @@ static int parse_redboot_partitions(struct mtd_info *master, } } #endif - parts = kmalloc(sizeof(*parts)*nrparts + nulllen + namelen, GFP_KERNEL); + parts = kzalloc(sizeof(*parts)*nrparts + nulllen + namelen, GFP_KERNEL); if (!parts) { ret = -ENOMEM; goto out; } - memset(parts, 0, sizeof(*parts)*nrparts + nulllen + namelen); - nullname = (char *)&parts[nrparts]; #ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED if (nulllen > 0) { @@ -237,9 +295,13 @@ static struct mtd_part_parser redboot_parser = { .name = "RedBoot", }; +/* mtd parsers will request the module by parser name */ +MODULE_ALIAS("RedBoot"); + static int __init redboot_parser_init(void) { - return register_mtd_parser(&redboot_parser); + register_mtd_parser(&redboot_parser); + return 0; } static void __exit redboot_parser_exit(void) @@ -251,5 +313,5 @@ module_init(redboot_parser_init); module_exit(redboot_parser_exit); MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Red Hat, Inc. - David Woodhouse <dwmw2@cambridge.redhat.com>"); +MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); MODULE_DESCRIPTION("Parsing code for RedBoot Flash Image System (FIS) tables"); diff --git a/drivers/mtd/rfd_ftl.c b/drivers/mtd/rfd_ftl.c index fa4362fb4dd..d1cbf26db2c 100644 --- a/drivers/mtd/rfd_ftl.c +++ b/drivers/mtd/rfd_ftl.c @@ -1,9 +1,7 @@ /* * rfd_ftl.c -- resident flash disk (flash translation layer) * - * Copyright (C) 2005 Sean Young <sean@mess.org> - * - * $Id: rfd_ftl.c,v 1.8 2006/01/15 12:51:44 sean Exp $ + * Copyright © 2005 Sean Young <sean@mess.org> * * This type of flash translation layer (FTL) is used by the Embedded BIOS * by General Software. It is known as the Resident Flash Disk (RFD), see: @@ -20,11 +18,10 @@ #include <linux/vmalloc.h> #include <linux/slab.h> #include <linux/jiffies.h> +#include <linux/module.h> #include <asm/types.h> -#define const_cpu_to_le16 __constant_cpu_to_le16 - static int block_size = 0; module_param(block_size, int, 0); MODULE_PARM_DESC(block_size, "Block size to use by RFD, defaults to erase unit size"); @@ -158,7 +155,7 @@ static int scan_header(struct partition *part) size_t retlen; sectors_per_block = part->block_size / SECTOR_SIZE; - part->total_blocks = part->mbd.mtd->size / part->block_size; + part->total_blocks = (u32)part->mbd.mtd->size / part->block_size; if (part->total_blocks < 2) return -ENOENT; @@ -203,9 +200,9 @@ static int scan_header(struct partition *part) part->sector_map[i] = -1; for (i=0, blocks_found=0; i<part->total_blocks; i++) { - rc = part->mbd.mtd->read(part->mbd.mtd, - i * part->block_size, part->header_size, - &retlen, (u_char*)part->header_cache); + rc = mtd_read(part->mbd.mtd, i * part->block_size, + part->header_size, &retlen, + (u_char *)part->header_cache); if (!rc && retlen != part->header_size) rc = -EIO; @@ -253,8 +250,8 @@ static int rfd_ftl_readsect(struct mtd_blktrans_dev *dev, u_long sector, char *b addr = part->sector_map[sector]; if (addr != -1) { - rc = part->mbd.mtd->read(part->mbd.mtd, addr, SECTOR_SIZE, - &retlen, (u_char*)buf); + rc = mtd_read(part->mbd.mtd, addr, SECTOR_SIZE, &retlen, + (u_char *)buf); if (!rc && retlen != SECTOR_SIZE) rc = -EIO; @@ -278,16 +275,17 @@ static void erase_callback(struct erase_info *erase) part = (struct partition*)erase->priv; - i = erase->addr / part->block_size; - if (i >= part->total_blocks || part->blocks[i].offset != erase->addr) { - printk(KERN_ERR PREFIX "erase callback for unknown offset %x " - "on '%s'\n", erase->addr, part->mbd.mtd->name); + i = (u32)erase->addr / part->block_size; + if (i >= part->total_blocks || part->blocks[i].offset != erase->addr || + erase->addr > UINT_MAX) { + printk(KERN_ERR PREFIX "erase callback for unknown offset %llx " + "on '%s'\n", (unsigned long long)erase->addr, part->mbd.mtd->name); return; } if (erase->state != MTD_ERASE_DONE) { - printk(KERN_WARNING PREFIX "erase failed at 0x%x on '%s', " - "state %d\n", erase->addr, + printk(KERN_WARNING PREFIX "erase failed at 0x%llx on '%s', " + "state %d\n", (unsigned long long)erase->addr, part->mbd.mtd->name, erase->state); part->blocks[i].state = BLOCK_FAILED; @@ -299,16 +297,15 @@ static void erase_callback(struct erase_info *erase) return; } - magic = const_cpu_to_le16(RFD_MAGIC); + magic = cpu_to_le16(RFD_MAGIC); part->blocks[i].state = BLOCK_ERASED; part->blocks[i].free_sectors = part->data_sectors_per_block; part->blocks[i].used_sectors = 0; part->blocks[i].erases++; - rc = part->mbd.mtd->write(part->mbd.mtd, - part->blocks[i].offset, sizeof(magic), &retlen, - (u_char*)&magic); + rc = mtd_write(part->mbd.mtd, part->blocks[i].offset, sizeof(magic), + &retlen, (u_char *)&magic); if (!rc && retlen != sizeof(magic)) rc = -EIO; @@ -344,12 +341,12 @@ static int erase_block(struct partition *part, int block) part->blocks[block].state = BLOCK_ERASING; part->blocks[block].free_sectors = 0; - rc = part->mbd.mtd->erase(part->mbd.mtd, erase); + rc = mtd_erase(part->mbd.mtd, erase); if (rc) { - printk(KERN_ERR PREFIX "erase of region %x,%x on '%s' " - "failed\n", erase->addr, erase->len, - part->mbd.mtd->name); + printk(KERN_ERR PREFIX "erase of region %llx,%llx on '%s' " + "failed\n", (unsigned long long)erase->addr, + (unsigned long long)erase->len, part->mbd.mtd->name); kfree(erase); } @@ -374,9 +371,8 @@ static int move_block_contents(struct partition *part, int block_no, u_long *old if (!map) goto err2; - rc = part->mbd.mtd->read(part->mbd.mtd, - part->blocks[block_no].offset, part->header_size, - &retlen, (u_char*)map); + rc = mtd_read(part->mbd.mtd, part->blocks[block_no].offset, + part->header_size, &retlen, (u_char *)map); if (!rc && retlen != part->header_size) rc = -EIO; @@ -415,8 +411,8 @@ static int move_block_contents(struct partition *part, int block_no, u_long *old } continue; } - rc = part->mbd.mtd->read(part->mbd.mtd, addr, - SECTOR_SIZE, &retlen, sector_data); + rc = mtd_read(part->mbd.mtd, addr, SECTOR_SIZE, &retlen, + sector_data); if (!rc && retlen != SECTOR_SIZE) rc = -EIO; @@ -452,8 +448,7 @@ static int reclaim_block(struct partition *part, u_long *old_sector) int rc; /* we have a race if sync doesn't exist */ - if (part->mbd.mtd->sync) - part->mbd.mtd->sync(part->mbd.mtd); + mtd_sync(part->mbd.mtd); score = 0x7fffffff; /* MAX_INT */ best_block = -1; @@ -565,8 +560,9 @@ static int find_writable_block(struct partition *part, u_long *old_sector) } } - rc = part->mbd.mtd->read(part->mbd.mtd, part->blocks[block].offset, - part->header_size, &retlen, (u_char*)part->header_cache); + rc = mtd_read(part->mbd.mtd, part->blocks[block].offset, + part->header_size, &retlen, + (u_char *)part->header_cache); if (!rc && retlen != part->header_size) rc = -EIO; @@ -589,7 +585,7 @@ static int mark_sector_deleted(struct partition *part, u_long old_addr) int block, offset, rc; u_long addr; size_t retlen; - u16 del = const_cpu_to_le16(SECTOR_DELETED); + u16 del = cpu_to_le16(SECTOR_DELETED); block = old_addr / part->block_size; offset = (old_addr % part->block_size) / SECTOR_SIZE - @@ -597,8 +593,8 @@ static int mark_sector_deleted(struct partition *part, u_long old_addr) addr = part->blocks[block].offset + (HEADER_MAP_OFFSET + offset) * sizeof(u16); - rc = part->mbd.mtd->write(part->mbd.mtd, addr, - sizeof(del), &retlen, (u_char*)&del); + rc = mtd_write(part->mbd.mtd, addr, sizeof(del), &retlen, + (u_char *)&del); if (!rc && retlen != sizeof(del)) rc = -EIO; @@ -606,8 +602,7 @@ static int mark_sector_deleted(struct partition *part, u_long old_addr) if (rc) { printk(KERN_ERR PREFIX "error writing '%s' at " "0x%lx\n", part->mbd.mtd->name, addr); - if (rc) - goto err; + goto err; } if (block == part->current_block) part->header_cache[offset + HEADER_MAP_OFFSET] = del; @@ -670,8 +665,8 @@ static int do_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf, addr = (i + part->header_sectors_per_block) * SECTOR_SIZE + block->offset; - rc = part->mbd.mtd->write(part->mbd.mtd, - addr, SECTOR_SIZE, &retlen, (u_char*)buf); + rc = mtd_write(part->mbd.mtd, addr, SECTOR_SIZE, &retlen, + (u_char *)buf); if (!rc && retlen != SECTOR_SIZE) rc = -EIO; @@ -679,8 +674,7 @@ static int do_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf, if (rc) { printk(KERN_ERR PREFIX "error writing '%s' at 0x%lx\n", part->mbd.mtd->name, addr); - if (rc) - goto err; + goto err; } part->sector_map[sector] = addr; @@ -690,8 +684,8 @@ static int do_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf, part->header_cache[i + HEADER_MAP_OFFSET] = entry; addr = block->offset + (HEADER_MAP_OFFSET + i) * sizeof(u16); - rc = part->mbd.mtd->write(part->mbd.mtd, addr, - sizeof(entry), &retlen, (u_char*)&entry); + rc = mtd_write(part->mbd.mtd, addr, sizeof(entry), &retlen, + (u_char *)&entry); if (!rc && retlen != sizeof(entry)) rc = -EIO; @@ -699,8 +693,7 @@ static int do_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf, if (rc) { printk(KERN_ERR PREFIX "error writing '%s' at 0x%lx\n", part->mbd.mtd->name, addr); - if (rc) - goto err; + goto err; } block->used_sectors++; block->free_sectors--; @@ -765,10 +758,10 @@ static void rfd_ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) { struct partition *part; - if (mtd->type != MTD_NORFLASH) + if (mtd->type != MTD_NORFLASH || mtd->size > UINT_MAX) return; - part = kcalloc(1, sizeof(struct partition), GFP_KERNEL); + part = kzalloc(sizeof(struct partition), GFP_KERNEL); if (!part) return; @@ -779,15 +772,13 @@ static void rfd_ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) else { if (!mtd->erasesize) { printk(KERN_WARNING PREFIX "please provide block_size"); - return; - } - else + goto out; + } else part->block_size = mtd->erasesize; } if (scan_header(part) == 0) { part->mbd.size = part->sector_count; - part->mbd.blksize = SECTOR_SIZE; part->mbd.tr = tr; part->mbd.devnum = -1; if (!(mtd->flags & MTD_WRITEABLE)) @@ -804,7 +795,7 @@ static void rfd_ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) if (!add_mtd_blktrans_dev((void*)part)) return; } - +out: kfree(part); } @@ -822,13 +813,14 @@ static void rfd_ftl_remove_dev(struct mtd_blktrans_dev *dev) vfree(part->sector_map); kfree(part->header_cache); kfree(part->blocks); - kfree(part); } -struct mtd_blktrans_ops rfd_ftl_tr = { +static struct mtd_blktrans_ops rfd_ftl_tr = { .name = "rfd", .major = RFD_FTL_MAJOR, .part_bits = PART_BITS, + .blksize = SECTOR_SIZE, + .readsect = rfd_ftl_readsect, .writesect = rfd_ftl_writesect, .getgeo = rfd_ftl_getgeo, diff --git a/drivers/mtd/sm_ftl.c b/drivers/mtd/sm_ftl.c new file mode 100644 index 00000000000..cf49c22673b --- /dev/null +++ b/drivers/mtd/sm_ftl.c @@ -0,0 +1,1297 @@ +/* + * Copyright © 2009 - Maxim Levitsky + * SmartMedia/xD translation layer + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/random.h> +#include <linux/hdreg.h> +#include <linux/kthread.h> +#include <linux/freezer.h> +#include <linux/sysfs.h> +#include <linux/bitops.h> +#include <linux/slab.h> +#include <linux/mtd/nand_ecc.h> +#include "nand/sm_common.h" +#include "sm_ftl.h" + + + +static struct workqueue_struct *cache_flush_workqueue; + +static int cache_timeout = 1000; +module_param(cache_timeout, int, S_IRUGO); +MODULE_PARM_DESC(cache_timeout, + "Timeout (in ms) for cache flush (1000 ms default"); + +static int debug; +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug level (0-2)"); + + +/* ------------------- sysfs attributes ---------------------------------- */ +struct sm_sysfs_attribute { + struct device_attribute dev_attr; + char *data; + int len; +}; + +static ssize_t sm_attr_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sm_sysfs_attribute *sm_attr = + container_of(attr, struct sm_sysfs_attribute, dev_attr); + + strncpy(buf, sm_attr->data, sm_attr->len); + return sm_attr->len; +} + + +#define NUM_ATTRIBUTES 1 +#define SM_CIS_VENDOR_OFFSET 0x59 +static struct attribute_group *sm_create_sysfs_attributes(struct sm_ftl *ftl) +{ + struct attribute_group *attr_group; + struct attribute **attributes; + struct sm_sysfs_attribute *vendor_attribute; + char *vendor; + + vendor = kstrndup(ftl->cis_buffer + SM_CIS_VENDOR_OFFSET, + SM_SMALL_PAGE - SM_CIS_VENDOR_OFFSET, GFP_KERNEL); + if (!vendor) + goto error1; + + /* Initialize sysfs attributes */ + vendor_attribute = + kzalloc(sizeof(struct sm_sysfs_attribute), GFP_KERNEL); + if (!vendor_attribute) + goto error2; + + sysfs_attr_init(&vendor_attribute->dev_attr.attr); + + vendor_attribute->data = vendor; + vendor_attribute->len = strlen(vendor); + vendor_attribute->dev_attr.attr.name = "vendor"; + vendor_attribute->dev_attr.attr.mode = S_IRUGO; + vendor_attribute->dev_attr.show = sm_attr_show; + + + /* Create array of pointers to the attributes */ + attributes = kzalloc(sizeof(struct attribute *) * (NUM_ATTRIBUTES + 1), + GFP_KERNEL); + if (!attributes) + goto error3; + attributes[0] = &vendor_attribute->dev_attr.attr; + + /* Finally create the attribute group */ + attr_group = kzalloc(sizeof(struct attribute_group), GFP_KERNEL); + if (!attr_group) + goto error4; + attr_group->attrs = attributes; + return attr_group; +error4: + kfree(attributes); +error3: + kfree(vendor_attribute); +error2: + kfree(vendor); +error1: + return NULL; +} + +static void sm_delete_sysfs_attributes(struct sm_ftl *ftl) +{ + struct attribute **attributes = ftl->disk_attributes->attrs; + int i; + + for (i = 0; attributes[i] ; i++) { + + struct device_attribute *dev_attr = container_of(attributes[i], + struct device_attribute, attr); + + struct sm_sysfs_attribute *sm_attr = + container_of(dev_attr, + struct sm_sysfs_attribute, dev_attr); + + kfree(sm_attr->data); + kfree(sm_attr); + } + + kfree(ftl->disk_attributes->attrs); + kfree(ftl->disk_attributes); +} + + +/* ----------------------- oob helpers -------------------------------------- */ + +static int sm_get_lba(uint8_t *lba) +{ + /* check fixed bits */ + if ((lba[0] & 0xF8) != 0x10) + return -2; + + /* check parity - endianness doesn't matter */ + if (hweight16(*(uint16_t *)lba) & 1) + return -2; + + return (lba[1] >> 1) | ((lba[0] & 0x07) << 7); +} + + +/* + * Read LBA associated with block + * returns -1, if block is erased + * returns -2 if error happens + */ +static int sm_read_lba(struct sm_oob *oob) +{ + static const uint32_t erased_pattern[4] = { + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + + uint16_t lba_test; + int lba; + + /* First test for erased block */ + if (!memcmp(oob, erased_pattern, SM_OOB_SIZE)) + return -1; + + /* Now check is both copies of the LBA differ too much */ + lba_test = *(uint16_t *)oob->lba_copy1 ^ *(uint16_t*)oob->lba_copy2; + if (lba_test && !is_power_of_2(lba_test)) + return -2; + + /* And read it */ + lba = sm_get_lba(oob->lba_copy1); + + if (lba == -2) + lba = sm_get_lba(oob->lba_copy2); + + return lba; +} + +static void sm_write_lba(struct sm_oob *oob, uint16_t lba) +{ + uint8_t tmp[2]; + + WARN_ON(lba >= 1000); + + tmp[0] = 0x10 | ((lba >> 7) & 0x07); + tmp[1] = (lba << 1) & 0xFF; + + if (hweight16(*(uint16_t *)tmp) & 0x01) + tmp[1] |= 1; + + oob->lba_copy1[0] = oob->lba_copy2[0] = tmp[0]; + oob->lba_copy1[1] = oob->lba_copy2[1] = tmp[1]; +} + + +/* Make offset from parts */ +static loff_t sm_mkoffset(struct sm_ftl *ftl, int zone, int block, int boffset) +{ + WARN_ON(boffset & (SM_SECTOR_SIZE - 1)); + WARN_ON(zone < 0 || zone >= ftl->zone_count); + WARN_ON(block >= ftl->zone_size); + WARN_ON(boffset >= ftl->block_size); + + if (block == -1) + return -1; + + return (zone * SM_MAX_ZONE_SIZE + block) * ftl->block_size + boffset; +} + +/* Breaks offset into parts */ +static void sm_break_offset(struct sm_ftl *ftl, loff_t offset, + int *zone, int *block, int *boffset) +{ + *boffset = do_div(offset, ftl->block_size); + *block = do_div(offset, ftl->max_lba); + *zone = offset >= ftl->zone_count ? -1 : offset; +} + +/* ---------------------- low level IO ------------------------------------- */ + +static int sm_correct_sector(uint8_t *buffer, struct sm_oob *oob) +{ + uint8_t ecc[3]; + + __nand_calculate_ecc(buffer, SM_SMALL_PAGE, ecc); + if (__nand_correct_data(buffer, ecc, oob->ecc1, SM_SMALL_PAGE) < 0) + return -EIO; + + buffer += SM_SMALL_PAGE; + + __nand_calculate_ecc(buffer, SM_SMALL_PAGE, ecc); + if (__nand_correct_data(buffer, ecc, oob->ecc2, SM_SMALL_PAGE) < 0) + return -EIO; + return 0; +} + +/* Reads a sector + oob*/ +static int sm_read_sector(struct sm_ftl *ftl, + int zone, int block, int boffset, + uint8_t *buffer, struct sm_oob *oob) +{ + struct mtd_info *mtd = ftl->trans->mtd; + struct mtd_oob_ops ops; + struct sm_oob tmp_oob; + int ret = -EIO; + int try = 0; + + /* FTL can contain -1 entries that are by default filled with bits */ + if (block == -1) { + memset(buffer, 0xFF, SM_SECTOR_SIZE); + return 0; + } + + /* User might not need the oob, but we do for data verification */ + if (!oob) + oob = &tmp_oob; + + ops.mode = ftl->smallpagenand ? MTD_OPS_RAW : MTD_OPS_PLACE_OOB; + ops.ooboffs = 0; + ops.ooblen = SM_OOB_SIZE; + ops.oobbuf = (void *)oob; + ops.len = SM_SECTOR_SIZE; + ops.datbuf = buffer; + +again: + if (try++) { + /* Avoid infinite recursion on CIS reads, sm_recheck_media + won't help anyway */ + if (zone == 0 && block == ftl->cis_block && boffset == + ftl->cis_boffset) + return ret; + + /* Test if media is stable */ + if (try == 3 || sm_recheck_media(ftl)) + return ret; + } + + /* Unfortunately, oob read will _always_ succeed, + despite card removal..... */ + ret = mtd_read_oob(mtd, sm_mkoffset(ftl, zone, block, boffset), &ops); + + /* Test for unknown errors */ + if (ret != 0 && !mtd_is_bitflip_or_eccerr(ret)) { + dbg("read of block %d at zone %d, failed due to error (%d)", + block, zone, ret); + goto again; + } + + /* Do a basic test on the oob, to guard against returned garbage */ + if (oob->reserved != 0xFFFFFFFF && !is_power_of_2(~oob->reserved)) + goto again; + + /* This should never happen, unless there is a bug in the mtd driver */ + WARN_ON(ops.oobretlen != SM_OOB_SIZE); + WARN_ON(buffer && ops.retlen != SM_SECTOR_SIZE); + + if (!buffer) + return 0; + + /* Test if sector marked as bad */ + if (!sm_sector_valid(oob)) { + dbg("read of block %d at zone %d, failed because it is marked" + " as bad" , block, zone); + goto again; + } + + /* Test ECC*/ + if (mtd_is_eccerr(ret) || + (ftl->smallpagenand && sm_correct_sector(buffer, oob))) { + + dbg("read of block %d at zone %d, failed due to ECC error", + block, zone); + goto again; + } + + return 0; +} + +/* Writes a sector to media */ +static int sm_write_sector(struct sm_ftl *ftl, + int zone, int block, int boffset, + uint8_t *buffer, struct sm_oob *oob) +{ + struct mtd_oob_ops ops; + struct mtd_info *mtd = ftl->trans->mtd; + int ret; + + BUG_ON(ftl->readonly); + + if (zone == 0 && (block == ftl->cis_block || block == 0)) { + dbg("attempted to write the CIS!"); + return -EIO; + } + + if (ftl->unstable) + return -EIO; + + ops.mode = ftl->smallpagenand ? MTD_OPS_RAW : MTD_OPS_PLACE_OOB; + ops.len = SM_SECTOR_SIZE; + ops.datbuf = buffer; + ops.ooboffs = 0; + ops.ooblen = SM_OOB_SIZE; + ops.oobbuf = (void *)oob; + + ret = mtd_write_oob(mtd, sm_mkoffset(ftl, zone, block, boffset), &ops); + + /* Now we assume that hardware will catch write bitflip errors */ + + if (ret) { + dbg("write to block %d at zone %d, failed with error %d", + block, zone, ret); + + sm_recheck_media(ftl); + return ret; + } + + /* This should never happen, unless there is a bug in the driver */ + WARN_ON(ops.oobretlen != SM_OOB_SIZE); + WARN_ON(buffer && ops.retlen != SM_SECTOR_SIZE); + + return 0; +} + +/* ------------------------ block IO ------------------------------------- */ + +/* Write a block using data and lba, and invalid sector bitmap */ +static int sm_write_block(struct sm_ftl *ftl, uint8_t *buf, + int zone, int block, int lba, + unsigned long invalid_bitmap) +{ + struct sm_oob oob; + int boffset; + int retry = 0; + + /* Initialize the oob with requested values */ + memset(&oob, 0xFF, SM_OOB_SIZE); + sm_write_lba(&oob, lba); +restart: + if (ftl->unstable) + return -EIO; + + for (boffset = 0; boffset < ftl->block_size; + boffset += SM_SECTOR_SIZE) { + + oob.data_status = 0xFF; + + if (test_bit(boffset / SM_SECTOR_SIZE, &invalid_bitmap)) { + + sm_printk("sector %d of block at LBA %d of zone %d" + " coudn't be read, marking it as invalid", + boffset / SM_SECTOR_SIZE, lba, zone); + + oob.data_status = 0; + } + + if (ftl->smallpagenand) { + __nand_calculate_ecc(buf + boffset, + SM_SMALL_PAGE, oob.ecc1); + + __nand_calculate_ecc(buf + boffset + SM_SMALL_PAGE, + SM_SMALL_PAGE, oob.ecc2); + } + if (!sm_write_sector(ftl, zone, block, boffset, + buf + boffset, &oob)) + continue; + + if (!retry) { + + /* If write fails. try to erase the block */ + /* This is safe, because we never write in blocks + that contain valuable data. + This is intended to repair block that are marked + as erased, but that isn't fully erased*/ + + if (sm_erase_block(ftl, zone, block, 0)) + return -EIO; + + retry = 1; + goto restart; + } else { + sm_mark_block_bad(ftl, zone, block); + return -EIO; + } + } + return 0; +} + + +/* Mark whole block at offset 'offs' as bad. */ +static void sm_mark_block_bad(struct sm_ftl *ftl, int zone, int block) +{ + struct sm_oob oob; + int boffset; + + memset(&oob, 0xFF, SM_OOB_SIZE); + oob.block_status = 0xF0; + + if (ftl->unstable) + return; + + if (sm_recheck_media(ftl)) + return; + + sm_printk("marking block %d of zone %d as bad", block, zone); + + /* We aren't checking the return value, because we don't care */ + /* This also fails on fake xD cards, but I guess these won't expose + any bad blocks till fail completely */ + for (boffset = 0; boffset < ftl->block_size; boffset += SM_SECTOR_SIZE) + sm_write_sector(ftl, zone, block, boffset, NULL, &oob); +} + +/* + * Erase a block within a zone + * If erase succeeds, it updates free block fifo, otherwise marks block as bad + */ +static int sm_erase_block(struct sm_ftl *ftl, int zone_num, uint16_t block, + int put_free) +{ + struct ftl_zone *zone = &ftl->zones[zone_num]; + struct mtd_info *mtd = ftl->trans->mtd; + struct erase_info erase; + + erase.mtd = mtd; + erase.callback = sm_erase_callback; + erase.addr = sm_mkoffset(ftl, zone_num, block, 0); + erase.len = ftl->block_size; + erase.priv = (u_long)ftl; + + if (ftl->unstable) + return -EIO; + + BUG_ON(ftl->readonly); + + if (zone_num == 0 && (block == ftl->cis_block || block == 0)) { + sm_printk("attempted to erase the CIS!"); + return -EIO; + } + + if (mtd_erase(mtd, &erase)) { + sm_printk("erase of block %d in zone %d failed", + block, zone_num); + goto error; + } + + if (erase.state == MTD_ERASE_PENDING) + wait_for_completion(&ftl->erase_completion); + + if (erase.state != MTD_ERASE_DONE) { + sm_printk("erase of block %d in zone %d failed after wait", + block, zone_num); + goto error; + } + + if (put_free) + kfifo_in(&zone->free_sectors, + (const unsigned char *)&block, sizeof(block)); + + return 0; +error: + sm_mark_block_bad(ftl, zone_num, block); + return -EIO; +} + +static void sm_erase_callback(struct erase_info *self) +{ + struct sm_ftl *ftl = (struct sm_ftl *)self->priv; + complete(&ftl->erase_completion); +} + +/* Thoroughly test that block is valid. */ +static int sm_check_block(struct sm_ftl *ftl, int zone, int block) +{ + int boffset; + struct sm_oob oob; + int lbas[] = { -3, 0, 0, 0 }; + int i = 0; + int test_lba; + + + /* First just check that block doesn't look fishy */ + /* Only blocks that are valid or are sliced in two parts, are + accepted */ + for (boffset = 0; boffset < ftl->block_size; + boffset += SM_SECTOR_SIZE) { + + /* This shouldn't happen anyway */ + if (sm_read_sector(ftl, zone, block, boffset, NULL, &oob)) + return -2; + + test_lba = sm_read_lba(&oob); + + if (lbas[i] != test_lba) + lbas[++i] = test_lba; + + /* If we found three different LBAs, something is fishy */ + if (i == 3) + return -EIO; + } + + /* If the block is sliced (partially erased usually) erase it */ + if (i == 2) { + sm_erase_block(ftl, zone, block, 1); + return 1; + } + + return 0; +} + +/* ----------------- media scanning --------------------------------- */ +static const struct chs_entry chs_table[] = { + { 1, 125, 4, 4 }, + { 2, 125, 4, 8 }, + { 4, 250, 4, 8 }, + { 8, 250, 4, 16 }, + { 16, 500, 4, 16 }, + { 32, 500, 8, 16 }, + { 64, 500, 8, 32 }, + { 128, 500, 16, 32 }, + { 256, 1000, 16, 32 }, + { 512, 1015, 32, 63 }, + { 1024, 985, 33, 63 }, + { 2048, 985, 33, 63 }, + { 0 }, +}; + + +static const uint8_t cis_signature[] = { + 0x01, 0x03, 0xD9, 0x01, 0xFF, 0x18, 0x02, 0xDF, 0x01, 0x20 +}; +/* Find out media parameters. + * This ideally has to be based on nand id, but for now device size is enough */ +static int sm_get_media_info(struct sm_ftl *ftl, struct mtd_info *mtd) +{ + int i; + int size_in_megs = mtd->size / (1024 * 1024); + + ftl->readonly = mtd->type == MTD_ROM; + + /* Manual settings for very old devices */ + ftl->zone_count = 1; + ftl->smallpagenand = 0; + + switch (size_in_megs) { + case 1: + /* 1 MiB flash/rom SmartMedia card (256 byte pages)*/ + ftl->zone_size = 256; + ftl->max_lba = 250; + ftl->block_size = 8 * SM_SECTOR_SIZE; + ftl->smallpagenand = 1; + + break; + case 2: + /* 2 MiB flash SmartMedia (256 byte pages)*/ + if (mtd->writesize == SM_SMALL_PAGE) { + ftl->zone_size = 512; + ftl->max_lba = 500; + ftl->block_size = 8 * SM_SECTOR_SIZE; + ftl->smallpagenand = 1; + /* 2 MiB rom SmartMedia */ + } else { + + if (!ftl->readonly) + return -ENODEV; + + ftl->zone_size = 256; + ftl->max_lba = 250; + ftl->block_size = 16 * SM_SECTOR_SIZE; + } + break; + case 4: + /* 4 MiB flash/rom SmartMedia device */ + ftl->zone_size = 512; + ftl->max_lba = 500; + ftl->block_size = 16 * SM_SECTOR_SIZE; + break; + case 8: + /* 8 MiB flash/rom SmartMedia device */ + ftl->zone_size = 1024; + ftl->max_lba = 1000; + ftl->block_size = 16 * SM_SECTOR_SIZE; + } + + /* Minimum xD size is 16MiB. Also, all xD cards have standard zone + sizes. SmartMedia cards exist up to 128 MiB and have same layout*/ + if (size_in_megs >= 16) { + ftl->zone_count = size_in_megs / 16; + ftl->zone_size = 1024; + ftl->max_lba = 1000; + ftl->block_size = 32 * SM_SECTOR_SIZE; + } + + /* Test for proper write,erase and oob sizes */ + if (mtd->erasesize > ftl->block_size) + return -ENODEV; + + if (mtd->writesize > SM_SECTOR_SIZE) + return -ENODEV; + + if (ftl->smallpagenand && mtd->oobsize < SM_SMALL_OOB_SIZE) + return -ENODEV; + + if (!ftl->smallpagenand && mtd->oobsize < SM_OOB_SIZE) + return -ENODEV; + + /* We use OOB */ + if (!mtd_has_oob(mtd)) + return -ENODEV; + + /* Find geometry information */ + for (i = 0 ; i < ARRAY_SIZE(chs_table) ; i++) { + if (chs_table[i].size == size_in_megs) { + ftl->cylinders = chs_table[i].cyl; + ftl->heads = chs_table[i].head; + ftl->sectors = chs_table[i].sec; + return 0; + } + } + + sm_printk("media has unknown size : %dMiB", size_in_megs); + ftl->cylinders = 985; + ftl->heads = 33; + ftl->sectors = 63; + return 0; +} + +/* Validate the CIS */ +static int sm_read_cis(struct sm_ftl *ftl) +{ + struct sm_oob oob; + + if (sm_read_sector(ftl, + 0, ftl->cis_block, ftl->cis_boffset, ftl->cis_buffer, &oob)) + return -EIO; + + if (!sm_sector_valid(&oob) || !sm_block_valid(&oob)) + return -EIO; + + if (!memcmp(ftl->cis_buffer + ftl->cis_page_offset, + cis_signature, sizeof(cis_signature))) { + return 0; + } + + return -EIO; +} + +/* Scan the media for the CIS */ +static int sm_find_cis(struct sm_ftl *ftl) +{ + struct sm_oob oob; + int block, boffset; + int block_found = 0; + int cis_found = 0; + + /* Search for first valid block */ + for (block = 0 ; block < ftl->zone_size - ftl->max_lba ; block++) { + + if (sm_read_sector(ftl, 0, block, 0, NULL, &oob)) + continue; + + if (!sm_block_valid(&oob)) + continue; + block_found = 1; + break; + } + + if (!block_found) + return -EIO; + + /* Search for first valid sector in this block */ + for (boffset = 0 ; boffset < ftl->block_size; + boffset += SM_SECTOR_SIZE) { + + if (sm_read_sector(ftl, 0, block, boffset, NULL, &oob)) + continue; + + if (!sm_sector_valid(&oob)) + continue; + break; + } + + if (boffset == ftl->block_size) + return -EIO; + + ftl->cis_block = block; + ftl->cis_boffset = boffset; + ftl->cis_page_offset = 0; + + cis_found = !sm_read_cis(ftl); + + if (!cis_found) { + ftl->cis_page_offset = SM_SMALL_PAGE; + cis_found = !sm_read_cis(ftl); + } + + if (cis_found) { + dbg("CIS block found at offset %x", + block * ftl->block_size + + boffset + ftl->cis_page_offset); + return 0; + } + return -EIO; +} + +/* Basic test to determine if underlying mtd device if functional */ +static int sm_recheck_media(struct sm_ftl *ftl) +{ + if (sm_read_cis(ftl)) { + + if (!ftl->unstable) { + sm_printk("media unstable, not allowing writes"); + ftl->unstable = 1; + } + return -EIO; + } + return 0; +} + +/* Initialize a FTL zone */ +static int sm_init_zone(struct sm_ftl *ftl, int zone_num) +{ + struct ftl_zone *zone = &ftl->zones[zone_num]; + struct sm_oob oob; + uint16_t block; + int lba; + int i = 0; + int len; + + dbg("initializing zone %d", zone_num); + + /* Allocate memory for FTL table */ + zone->lba_to_phys_table = kmalloc(ftl->max_lba * 2, GFP_KERNEL); + + if (!zone->lba_to_phys_table) + return -ENOMEM; + memset(zone->lba_to_phys_table, -1, ftl->max_lba * 2); + + + /* Allocate memory for free sectors FIFO */ + if (kfifo_alloc(&zone->free_sectors, ftl->zone_size * 2, GFP_KERNEL)) { + kfree(zone->lba_to_phys_table); + return -ENOMEM; + } + + /* Now scan the zone */ + for (block = 0 ; block < ftl->zone_size ; block++) { + + /* Skip blocks till the CIS (including) */ + if (zone_num == 0 && block <= ftl->cis_block) + continue; + + /* Read the oob of first sector */ + if (sm_read_sector(ftl, zone_num, block, 0, NULL, &oob)) + return -EIO; + + /* Test to see if block is erased. It is enough to test + first sector, because erase happens in one shot */ + if (sm_block_erased(&oob)) { + kfifo_in(&zone->free_sectors, + (unsigned char *)&block, 2); + continue; + } + + /* If block is marked as bad, skip it */ + /* This assumes we can trust first sector*/ + /* However the way the block valid status is defined, ensures + very low probability of failure here */ + if (!sm_block_valid(&oob)) { + dbg("PH %04d <-> <marked bad>", block); + continue; + } + + + lba = sm_read_lba(&oob); + + /* Invalid LBA means that block is damaged. */ + /* We can try to erase it, or mark it as bad, but + lets leave that to recovery application */ + if (lba == -2 || lba >= ftl->max_lba) { + dbg("PH %04d <-> LBA %04d(bad)", block, lba); + continue; + } + + + /* If there is no collision, + just put the sector in the FTL table */ + if (zone->lba_to_phys_table[lba] < 0) { + dbg_verbose("PH %04d <-> LBA %04d", block, lba); + zone->lba_to_phys_table[lba] = block; + continue; + } + + sm_printk("collision" + " of LBA %d between blocks %d and %d in zone %d", + lba, zone->lba_to_phys_table[lba], block, zone_num); + + /* Test that this block is valid*/ + if (sm_check_block(ftl, zone_num, block)) + continue; + + /* Test now the old block */ + if (sm_check_block(ftl, zone_num, + zone->lba_to_phys_table[lba])) { + zone->lba_to_phys_table[lba] = block; + continue; + } + + /* If both blocks are valid and share same LBA, it means that + they hold different versions of same data. It not + known which is more recent, thus just erase one of them + */ + sm_printk("both blocks are valid, erasing the later"); + sm_erase_block(ftl, zone_num, block, 1); + } + + dbg("zone initialized"); + zone->initialized = 1; + + /* No free sectors, means that the zone is heavily damaged, write won't + work, but it can still can be (partially) read */ + if (!kfifo_len(&zone->free_sectors)) { + sm_printk("no free blocks in zone %d", zone_num); + return 0; + } + + /* Randomize first block we write to */ + get_random_bytes(&i, 2); + i %= (kfifo_len(&zone->free_sectors) / 2); + + while (i--) { + len = kfifo_out(&zone->free_sectors, + (unsigned char *)&block, 2); + WARN_ON(len != 2); + kfifo_in(&zone->free_sectors, (const unsigned char *)&block, 2); + } + return 0; +} + +/* Get and automatically initialize an FTL mapping for one zone */ +static struct ftl_zone *sm_get_zone(struct sm_ftl *ftl, int zone_num) +{ + struct ftl_zone *zone; + int error; + + BUG_ON(zone_num >= ftl->zone_count); + zone = &ftl->zones[zone_num]; + + if (!zone->initialized) { + error = sm_init_zone(ftl, zone_num); + + if (error) + return ERR_PTR(error); + } + return zone; +} + + +/* ----------------- cache handling ------------------------------------------*/ + +/* Initialize the one block cache */ +static void sm_cache_init(struct sm_ftl *ftl) +{ + ftl->cache_data_invalid_bitmap = 0xFFFFFFFF; + ftl->cache_clean = 1; + ftl->cache_zone = -1; + ftl->cache_block = -1; + /*memset(ftl->cache_data, 0xAA, ftl->block_size);*/ +} + +/* Put sector in one block cache */ +static void sm_cache_put(struct sm_ftl *ftl, char *buffer, int boffset) +{ + memcpy(ftl->cache_data + boffset, buffer, SM_SECTOR_SIZE); + clear_bit(boffset / SM_SECTOR_SIZE, &ftl->cache_data_invalid_bitmap); + ftl->cache_clean = 0; +} + +/* Read a sector from the cache */ +static int sm_cache_get(struct sm_ftl *ftl, char *buffer, int boffset) +{ + if (test_bit(boffset / SM_SECTOR_SIZE, + &ftl->cache_data_invalid_bitmap)) + return -1; + + memcpy(buffer, ftl->cache_data + boffset, SM_SECTOR_SIZE); + return 0; +} + +/* Write the cache to hardware */ +static int sm_cache_flush(struct sm_ftl *ftl) +{ + struct ftl_zone *zone; + + int sector_num; + uint16_t write_sector; + int zone_num = ftl->cache_zone; + int block_num; + + if (ftl->cache_clean) + return 0; + + if (ftl->unstable) + return -EIO; + + BUG_ON(zone_num < 0); + zone = &ftl->zones[zone_num]; + block_num = zone->lba_to_phys_table[ftl->cache_block]; + + + /* Try to read all unread areas of the cache block*/ + for_each_set_bit(sector_num, &ftl->cache_data_invalid_bitmap, + ftl->block_size / SM_SECTOR_SIZE) { + + if (!sm_read_sector(ftl, + zone_num, block_num, sector_num * SM_SECTOR_SIZE, + ftl->cache_data + sector_num * SM_SECTOR_SIZE, NULL)) + clear_bit(sector_num, + &ftl->cache_data_invalid_bitmap); + } +restart: + + if (ftl->unstable) + return -EIO; + + /* If there are no spare blocks, */ + /* we could still continue by erasing/writing the current block, + but for such worn out media it doesn't worth the trouble, + and the dangers */ + if (kfifo_out(&zone->free_sectors, + (unsigned char *)&write_sector, 2) != 2) { + dbg("no free sectors for write!"); + return -EIO; + } + + + if (sm_write_block(ftl, ftl->cache_data, zone_num, write_sector, + ftl->cache_block, ftl->cache_data_invalid_bitmap)) + goto restart; + + /* Update the FTL table */ + zone->lba_to_phys_table[ftl->cache_block] = write_sector; + + /* Write succesfull, so erase and free the old block */ + if (block_num > 0) + sm_erase_block(ftl, zone_num, block_num, 1); + + sm_cache_init(ftl); + return 0; +} + + +/* flush timer, runs a second after last write */ +static void sm_cache_flush_timer(unsigned long data) +{ + struct sm_ftl *ftl = (struct sm_ftl *)data; + queue_work(cache_flush_workqueue, &ftl->flush_work); +} + +/* cache flush work, kicked by timer */ +static void sm_cache_flush_work(struct work_struct *work) +{ + struct sm_ftl *ftl = container_of(work, struct sm_ftl, flush_work); + mutex_lock(&ftl->mutex); + sm_cache_flush(ftl); + mutex_unlock(&ftl->mutex); + return; +} + +/* ---------------- outside interface -------------------------------------- */ + +/* outside interface: read a sector */ +static int sm_read(struct mtd_blktrans_dev *dev, + unsigned long sect_no, char *buf) +{ + struct sm_ftl *ftl = dev->priv; + struct ftl_zone *zone; + int error = 0, in_cache = 0; + int zone_num, block, boffset; + + sm_break_offset(ftl, sect_no << 9, &zone_num, &block, &boffset); + mutex_lock(&ftl->mutex); + + + zone = sm_get_zone(ftl, zone_num); + if (IS_ERR(zone)) { + error = PTR_ERR(zone); + goto unlock; + } + + /* Have to look at cache first */ + if (ftl->cache_zone == zone_num && ftl->cache_block == block) { + in_cache = 1; + if (!sm_cache_get(ftl, buf, boffset)) + goto unlock; + } + + /* Translate the block and return if doesn't exist in the table */ + block = zone->lba_to_phys_table[block]; + + if (block == -1) { + memset(buf, 0xFF, SM_SECTOR_SIZE); + goto unlock; + } + + if (sm_read_sector(ftl, zone_num, block, boffset, buf, NULL)) { + error = -EIO; + goto unlock; + } + + if (in_cache) + sm_cache_put(ftl, buf, boffset); +unlock: + mutex_unlock(&ftl->mutex); + return error; +} + +/* outside interface: write a sector */ +static int sm_write(struct mtd_blktrans_dev *dev, + unsigned long sec_no, char *buf) +{ + struct sm_ftl *ftl = dev->priv; + struct ftl_zone *zone; + int error, zone_num, block, boffset; + + BUG_ON(ftl->readonly); + sm_break_offset(ftl, sec_no << 9, &zone_num, &block, &boffset); + + /* No need in flush thread running now */ + del_timer(&ftl->timer); + mutex_lock(&ftl->mutex); + + zone = sm_get_zone(ftl, zone_num); + if (IS_ERR(zone)) { + error = PTR_ERR(zone); + goto unlock; + } + + /* If entry is not in cache, flush it */ + if (ftl->cache_block != block || ftl->cache_zone != zone_num) { + + error = sm_cache_flush(ftl); + if (error) + goto unlock; + + ftl->cache_block = block; + ftl->cache_zone = zone_num; + } + + sm_cache_put(ftl, buf, boffset); +unlock: + mod_timer(&ftl->timer, jiffies + msecs_to_jiffies(cache_timeout)); + mutex_unlock(&ftl->mutex); + return error; +} + +/* outside interface: flush everything */ +static int sm_flush(struct mtd_blktrans_dev *dev) +{ + struct sm_ftl *ftl = dev->priv; + int retval; + + mutex_lock(&ftl->mutex); + retval = sm_cache_flush(ftl); + mutex_unlock(&ftl->mutex); + return retval; +} + +/* outside interface: device is released */ +static void sm_release(struct mtd_blktrans_dev *dev) +{ + struct sm_ftl *ftl = dev->priv; + + mutex_lock(&ftl->mutex); + del_timer_sync(&ftl->timer); + cancel_work_sync(&ftl->flush_work); + sm_cache_flush(ftl); + mutex_unlock(&ftl->mutex); +} + +/* outside interface: get geometry */ +static int sm_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo) +{ + struct sm_ftl *ftl = dev->priv; + geo->heads = ftl->heads; + geo->sectors = ftl->sectors; + geo->cylinders = ftl->cylinders; + return 0; +} + +/* external interface: main initialization function */ +static void sm_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) +{ + struct mtd_blktrans_dev *trans; + struct sm_ftl *ftl; + + /* Allocate & initialize our private structure */ + ftl = kzalloc(sizeof(struct sm_ftl), GFP_KERNEL); + if (!ftl) + goto error1; + + + mutex_init(&ftl->mutex); + setup_timer(&ftl->timer, sm_cache_flush_timer, (unsigned long)ftl); + INIT_WORK(&ftl->flush_work, sm_cache_flush_work); + init_completion(&ftl->erase_completion); + + /* Read media information */ + if (sm_get_media_info(ftl, mtd)) { + dbg("found unsupported mtd device, aborting"); + goto error2; + } + + + /* Allocate temporary CIS buffer for read retry support */ + ftl->cis_buffer = kzalloc(SM_SECTOR_SIZE, GFP_KERNEL); + if (!ftl->cis_buffer) + goto error2; + + /* Allocate zone array, it will be initialized on demand */ + ftl->zones = kzalloc(sizeof(struct ftl_zone) * ftl->zone_count, + GFP_KERNEL); + if (!ftl->zones) + goto error3; + + /* Allocate the cache*/ + ftl->cache_data = kzalloc(ftl->block_size, GFP_KERNEL); + + if (!ftl->cache_data) + goto error4; + + sm_cache_init(ftl); + + + /* Allocate upper layer structure and initialize it */ + trans = kzalloc(sizeof(struct mtd_blktrans_dev), GFP_KERNEL); + if (!trans) + goto error5; + + ftl->trans = trans; + trans->priv = ftl; + + trans->tr = tr; + trans->mtd = mtd; + trans->devnum = -1; + trans->size = (ftl->block_size * ftl->max_lba * ftl->zone_count) >> 9; + trans->readonly = ftl->readonly; + + if (sm_find_cis(ftl)) { + dbg("CIS not found on mtd device, aborting"); + goto error6; + } + + ftl->disk_attributes = sm_create_sysfs_attributes(ftl); + if (!ftl->disk_attributes) + goto error6; + trans->disk_attributes = ftl->disk_attributes; + + sm_printk("Found %d MiB xD/SmartMedia FTL on mtd%d", + (int)(mtd->size / (1024 * 1024)), mtd->index); + + dbg("FTL layout:"); + dbg("%d zone(s), each consists of %d blocks (+%d spares)", + ftl->zone_count, ftl->max_lba, + ftl->zone_size - ftl->max_lba); + dbg("each block consists of %d bytes", + ftl->block_size); + + + /* Register device*/ + if (add_mtd_blktrans_dev(trans)) { + dbg("error in mtdblktrans layer"); + goto error6; + } + return; +error6: + kfree(trans); +error5: + kfree(ftl->cache_data); +error4: + kfree(ftl->zones); +error3: + kfree(ftl->cis_buffer); +error2: + kfree(ftl); +error1: + return; +} + +/* main interface: device {surprise,} removal */ +static void sm_remove_dev(struct mtd_blktrans_dev *dev) +{ + struct sm_ftl *ftl = dev->priv; + int i; + + del_mtd_blktrans_dev(dev); + ftl->trans = NULL; + + for (i = 0 ; i < ftl->zone_count; i++) { + + if (!ftl->zones[i].initialized) + continue; + + kfree(ftl->zones[i].lba_to_phys_table); + kfifo_free(&ftl->zones[i].free_sectors); + } + + sm_delete_sysfs_attributes(ftl); + kfree(ftl->cis_buffer); + kfree(ftl->zones); + kfree(ftl->cache_data); + kfree(ftl); +} + +static struct mtd_blktrans_ops sm_ftl_ops = { + .name = "smblk", + .major = 0, + .part_bits = SM_FTL_PARTN_BITS, + .blksize = SM_SECTOR_SIZE, + .getgeo = sm_getgeo, + + .add_mtd = sm_add_mtd, + .remove_dev = sm_remove_dev, + + .readsect = sm_read, + .writesect = sm_write, + + .flush = sm_flush, + .release = sm_release, + + .owner = THIS_MODULE, +}; + +static __init int sm_module_init(void) +{ + int error = 0; + + cache_flush_workqueue = create_freezable_workqueue("smflush"); + if (!cache_flush_workqueue) + return -ENOMEM; + + error = register_mtd_blktrans(&sm_ftl_ops); + if (error) + destroy_workqueue(cache_flush_workqueue); + return error; + +} + +static void __exit sm_module_exit(void) +{ + destroy_workqueue(cache_flush_workqueue); + deregister_mtd_blktrans(&sm_ftl_ops); +} + +module_init(sm_module_init); +module_exit(sm_module_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Maxim Levitsky <maximlevitsky@gmail.com>"); +MODULE_DESCRIPTION("Smartmedia/xD mtd translation layer"); diff --git a/drivers/mtd/sm_ftl.h b/drivers/mtd/sm_ftl.h new file mode 100644 index 00000000000..43bb7300785 --- /dev/null +++ b/drivers/mtd/sm_ftl.h @@ -0,0 +1,94 @@ +/* + * Copyright © 2009 - Maxim Levitsky + * SmartMedia/xD translation layer + * + * Based loosly on ssfdc.c which is + * © 2005 Eptar srl + * Author: Claudio Lanconelli <lanconelli.claudio@eptar.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/mtd/blktrans.h> +#include <linux/kfifo.h> +#include <linux/sched.h> +#include <linux/completion.h> +#include <linux/mtd/mtd.h> + + + +struct ftl_zone { + bool initialized; + int16_t *lba_to_phys_table; /* LBA to physical table */ + struct kfifo free_sectors; /* queue of free sectors */ +}; + +struct sm_ftl { + struct mtd_blktrans_dev *trans; + + struct mutex mutex; /* protects the structure */ + struct ftl_zone *zones; /* FTL tables for each zone */ + + /* Media information */ + int block_size; /* block size in bytes */ + int zone_size; /* zone size in blocks */ + int zone_count; /* number of zones */ + int max_lba; /* maximum lba in a zone */ + int smallpagenand; /* 256 bytes/page nand */ + bool readonly; /* is FS readonly */ + bool unstable; + int cis_block; /* CIS block location */ + int cis_boffset; /* CIS offset in the block */ + int cis_page_offset; /* CIS offset in the page */ + void *cis_buffer; /* tmp buffer for cis reads */ + + /* Cache */ + int cache_block; /* block number of cached block */ + int cache_zone; /* zone of cached block */ + unsigned char *cache_data; /* cached block data */ + long unsigned int cache_data_invalid_bitmap; + bool cache_clean; + struct work_struct flush_work; + struct timer_list timer; + + /* Async erase stuff */ + struct completion erase_completion; + + /* Geometry stuff */ + int heads; + int sectors; + int cylinders; + + struct attribute_group *disk_attributes; +}; + +struct chs_entry { + unsigned long size; + unsigned short cyl; + unsigned char head; + unsigned char sec; +}; + + +#define SM_FTL_PARTN_BITS 3 + +#define sm_printk(format, ...) \ + printk(KERN_WARNING "sm_ftl" ": " format "\n", ## __VA_ARGS__) + +#define dbg(format, ...) \ + if (debug) \ + printk(KERN_DEBUG "sm_ftl" ": " format "\n", ## __VA_ARGS__) + +#define dbg_verbose(format, ...) \ + if (debug > 1) \ + printk(KERN_DEBUG "sm_ftl" ": " format "\n", ## __VA_ARGS__) + + +static void sm_erase_callback(struct erase_info *self); +static int sm_erase_block(struct sm_ftl *ftl, int zone_num, uint16_t block, + int put_free); +static void sm_mark_block_bad(struct sm_ftl *ftl, int zone_num, int block); + +static int sm_recheck_media(struct sm_ftl *ftl); diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig new file mode 100644 index 00000000000..f8acfa4310e --- /dev/null +++ b/drivers/mtd/spi-nor/Kconfig @@ -0,0 +1,17 @@ +menuconfig MTD_SPI_NOR + tristate "SPI-NOR device support" + depends on MTD + help + This is the framework for the SPI NOR which can be used by the SPI + device drivers and the SPI-NOR device driver. + +if MTD_SPI_NOR + +config SPI_FSL_QUADSPI + tristate "Freescale Quad SPI controller" + depends on ARCH_MXC + help + This enables support for the Quad SPI controller in master mode. + We only connect the NOR to this controller now. + +endif # MTD_SPI_NOR diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile new file mode 100644 index 00000000000..6a7ce146224 --- /dev/null +++ b/drivers/mtd/spi-nor/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o +obj-$(CONFIG_SPI_FSL_QUADSPI) += fsl-quadspi.o diff --git a/drivers/mtd/spi-nor/fsl-quadspi.c b/drivers/mtd/spi-nor/fsl-quadspi.c new file mode 100644 index 00000000000..8d659a2888d --- /dev/null +++ b/drivers/mtd/spi-nor/fsl-quadspi.c @@ -0,0 +1,1009 @@ +/* + * Freescale QuadSPI driver. + * + * Copyright (C) 2013 Freescale Semiconductor, Inc. + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/completion.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/spi-nor.h> + +/* The registers */ +#define QUADSPI_MCR 0x00 +#define QUADSPI_MCR_RESERVED_SHIFT 16 +#define QUADSPI_MCR_RESERVED_MASK (0xF << QUADSPI_MCR_RESERVED_SHIFT) +#define QUADSPI_MCR_MDIS_SHIFT 14 +#define QUADSPI_MCR_MDIS_MASK (1 << QUADSPI_MCR_MDIS_SHIFT) +#define QUADSPI_MCR_CLR_TXF_SHIFT 11 +#define QUADSPI_MCR_CLR_TXF_MASK (1 << QUADSPI_MCR_CLR_TXF_SHIFT) +#define QUADSPI_MCR_CLR_RXF_SHIFT 10 +#define QUADSPI_MCR_CLR_RXF_MASK (1 << QUADSPI_MCR_CLR_RXF_SHIFT) +#define QUADSPI_MCR_DDR_EN_SHIFT 7 +#define QUADSPI_MCR_DDR_EN_MASK (1 << QUADSPI_MCR_DDR_EN_SHIFT) +#define QUADSPI_MCR_END_CFG_SHIFT 2 +#define QUADSPI_MCR_END_CFG_MASK (3 << QUADSPI_MCR_END_CFG_SHIFT) +#define QUADSPI_MCR_SWRSTHD_SHIFT 1 +#define QUADSPI_MCR_SWRSTHD_MASK (1 << QUADSPI_MCR_SWRSTHD_SHIFT) +#define QUADSPI_MCR_SWRSTSD_SHIFT 0 +#define QUADSPI_MCR_SWRSTSD_MASK (1 << QUADSPI_MCR_SWRSTSD_SHIFT) + +#define QUADSPI_IPCR 0x08 +#define QUADSPI_IPCR_SEQID_SHIFT 24 +#define QUADSPI_IPCR_SEQID_MASK (0xF << QUADSPI_IPCR_SEQID_SHIFT) + +#define QUADSPI_BUF0CR 0x10 +#define QUADSPI_BUF1CR 0x14 +#define QUADSPI_BUF2CR 0x18 +#define QUADSPI_BUFXCR_INVALID_MSTRID 0xe + +#define QUADSPI_BUF3CR 0x1c +#define QUADSPI_BUF3CR_ALLMST_SHIFT 31 +#define QUADSPI_BUF3CR_ALLMST (1 << QUADSPI_BUF3CR_ALLMST_SHIFT) + +#define QUADSPI_BFGENCR 0x20 +#define QUADSPI_BFGENCR_PAR_EN_SHIFT 16 +#define QUADSPI_BFGENCR_PAR_EN_MASK (1 << (QUADSPI_BFGENCR_PAR_EN_SHIFT)) +#define QUADSPI_BFGENCR_SEQID_SHIFT 12 +#define QUADSPI_BFGENCR_SEQID_MASK (0xF << QUADSPI_BFGENCR_SEQID_SHIFT) + +#define QUADSPI_BUF0IND 0x30 +#define QUADSPI_BUF1IND 0x34 +#define QUADSPI_BUF2IND 0x38 +#define QUADSPI_SFAR 0x100 + +#define QUADSPI_SMPR 0x108 +#define QUADSPI_SMPR_DDRSMP_SHIFT 16 +#define QUADSPI_SMPR_DDRSMP_MASK (7 << QUADSPI_SMPR_DDRSMP_SHIFT) +#define QUADSPI_SMPR_FSDLY_SHIFT 6 +#define QUADSPI_SMPR_FSDLY_MASK (1 << QUADSPI_SMPR_FSDLY_SHIFT) +#define QUADSPI_SMPR_FSPHS_SHIFT 5 +#define QUADSPI_SMPR_FSPHS_MASK (1 << QUADSPI_SMPR_FSPHS_SHIFT) +#define QUADSPI_SMPR_HSENA_SHIFT 0 +#define QUADSPI_SMPR_HSENA_MASK (1 << QUADSPI_SMPR_HSENA_SHIFT) + +#define QUADSPI_RBSR 0x10c +#define QUADSPI_RBSR_RDBFL_SHIFT 8 +#define QUADSPI_RBSR_RDBFL_MASK (0x3F << QUADSPI_RBSR_RDBFL_SHIFT) + +#define QUADSPI_RBCT 0x110 +#define QUADSPI_RBCT_WMRK_MASK 0x1F +#define QUADSPI_RBCT_RXBRD_SHIFT 8 +#define QUADSPI_RBCT_RXBRD_USEIPS (0x1 << QUADSPI_RBCT_RXBRD_SHIFT) + +#define QUADSPI_TBSR 0x150 +#define QUADSPI_TBDR 0x154 +#define QUADSPI_SR 0x15c +#define QUADSPI_SR_IP_ACC_SHIFT 1 +#define QUADSPI_SR_IP_ACC_MASK (0x1 << QUADSPI_SR_IP_ACC_SHIFT) +#define QUADSPI_SR_AHB_ACC_SHIFT 2 +#define QUADSPI_SR_AHB_ACC_MASK (0x1 << QUADSPI_SR_AHB_ACC_SHIFT) + +#define QUADSPI_FR 0x160 +#define QUADSPI_FR_TFF_MASK 0x1 + +#define QUADSPI_SFA1AD 0x180 +#define QUADSPI_SFA2AD 0x184 +#define QUADSPI_SFB1AD 0x188 +#define QUADSPI_SFB2AD 0x18c +#define QUADSPI_RBDR 0x200 + +#define QUADSPI_LUTKEY 0x300 +#define QUADSPI_LUTKEY_VALUE 0x5AF05AF0 + +#define QUADSPI_LCKCR 0x304 +#define QUADSPI_LCKER_LOCK 0x1 +#define QUADSPI_LCKER_UNLOCK 0x2 + +#define QUADSPI_RSER 0x164 +#define QUADSPI_RSER_TFIE (0x1 << 0) + +#define QUADSPI_LUT_BASE 0x310 + +/* + * The definition of the LUT register shows below: + * + * --------------------------------------------------- + * | INSTR1 | PAD1 | OPRND1 | INSTR0 | PAD0 | OPRND0 | + * --------------------------------------------------- + */ +#define OPRND0_SHIFT 0 +#define PAD0_SHIFT 8 +#define INSTR0_SHIFT 10 +#define OPRND1_SHIFT 16 + +/* Instruction set for the LUT register. */ +#define LUT_STOP 0 +#define LUT_CMD 1 +#define LUT_ADDR 2 +#define LUT_DUMMY 3 +#define LUT_MODE 4 +#define LUT_MODE2 5 +#define LUT_MODE4 6 +#define LUT_READ 7 +#define LUT_WRITE 8 +#define LUT_JMP_ON_CS 9 +#define LUT_ADDR_DDR 10 +#define LUT_MODE_DDR 11 +#define LUT_MODE2_DDR 12 +#define LUT_MODE4_DDR 13 +#define LUT_READ_DDR 14 +#define LUT_WRITE_DDR 15 +#define LUT_DATA_LEARN 16 + +/* + * The PAD definitions for LUT register. + * + * The pad stands for the lines number of IO[0:3]. + * For example, the Quad read need four IO lines, so you should + * set LUT_PAD4 which means we use four IO lines. + */ +#define LUT_PAD1 0 +#define LUT_PAD2 1 +#define LUT_PAD4 2 + +/* Oprands for the LUT register. */ +#define ADDR24BIT 0x18 +#define ADDR32BIT 0x20 + +/* Macros for constructing the LUT register. */ +#define LUT0(ins, pad, opr) \ + (((opr) << OPRND0_SHIFT) | ((LUT_##pad) << PAD0_SHIFT) | \ + ((LUT_##ins) << INSTR0_SHIFT)) + +#define LUT1(ins, pad, opr) (LUT0(ins, pad, opr) << OPRND1_SHIFT) + +/* other macros for LUT register. */ +#define QUADSPI_LUT(x) (QUADSPI_LUT_BASE + (x) * 4) +#define QUADSPI_LUT_NUM 64 + +/* SEQID -- we can have 16 seqids at most. */ +#define SEQID_QUAD_READ 0 +#define SEQID_WREN 1 +#define SEQID_WRDI 2 +#define SEQID_RDSR 3 +#define SEQID_SE 4 +#define SEQID_CHIP_ERASE 5 +#define SEQID_PP 6 +#define SEQID_RDID 7 +#define SEQID_WRSR 8 +#define SEQID_RDCR 9 +#define SEQID_EN4B 10 +#define SEQID_BRWR 11 + +enum fsl_qspi_devtype { + FSL_QUADSPI_VYBRID, + FSL_QUADSPI_IMX6SX, +}; + +struct fsl_qspi_devtype_data { + enum fsl_qspi_devtype devtype; + int rxfifo; + int txfifo; +}; + +static struct fsl_qspi_devtype_data vybrid_data = { + .devtype = FSL_QUADSPI_VYBRID, + .rxfifo = 128, + .txfifo = 64 +}; + +static struct fsl_qspi_devtype_data imx6sx_data = { + .devtype = FSL_QUADSPI_IMX6SX, + .rxfifo = 128, + .txfifo = 512 +}; + +#define FSL_QSPI_MAX_CHIP 4 +struct fsl_qspi { + struct mtd_info mtd[FSL_QSPI_MAX_CHIP]; + struct spi_nor nor[FSL_QSPI_MAX_CHIP]; + void __iomem *iobase; + void __iomem *ahb_base; /* Used when read from AHB bus */ + u32 memmap_phy; + struct clk *clk, *clk_en; + struct device *dev; + struct completion c; + struct fsl_qspi_devtype_data *devtype_data; + u32 nor_size; + u32 nor_num; + u32 clk_rate; + unsigned int chip_base_addr; /* We may support two chips. */ +}; + +static inline int is_vybrid_qspi(struct fsl_qspi *q) +{ + return q->devtype_data->devtype == FSL_QUADSPI_VYBRID; +} + +static inline int is_imx6sx_qspi(struct fsl_qspi *q) +{ + return q->devtype_data->devtype == FSL_QUADSPI_IMX6SX; +} + +/* + * An IC bug makes us to re-arrange the 32-bit data. + * The following chips, such as IMX6SLX, have fixed this bug. + */ +static inline u32 fsl_qspi_endian_xchg(struct fsl_qspi *q, u32 a) +{ + return is_vybrid_qspi(q) ? __swab32(a) : a; +} + +static inline void fsl_qspi_unlock_lut(struct fsl_qspi *q) +{ + writel(QUADSPI_LUTKEY_VALUE, q->iobase + QUADSPI_LUTKEY); + writel(QUADSPI_LCKER_UNLOCK, q->iobase + QUADSPI_LCKCR); +} + +static inline void fsl_qspi_lock_lut(struct fsl_qspi *q) +{ + writel(QUADSPI_LUTKEY_VALUE, q->iobase + QUADSPI_LUTKEY); + writel(QUADSPI_LCKER_LOCK, q->iobase + QUADSPI_LCKCR); +} + +static irqreturn_t fsl_qspi_irq_handler(int irq, void *dev_id) +{ + struct fsl_qspi *q = dev_id; + u32 reg; + + /* clear interrupt */ + reg = readl(q->iobase + QUADSPI_FR); + writel(reg, q->iobase + QUADSPI_FR); + + if (reg & QUADSPI_FR_TFF_MASK) + complete(&q->c); + + dev_dbg(q->dev, "QUADSPI_FR : 0x%.8x:0x%.8x\n", q->chip_base_addr, reg); + return IRQ_HANDLED; +} + +static void fsl_qspi_init_lut(struct fsl_qspi *q) +{ + void __iomem *base = q->iobase; + int rxfifo = q->devtype_data->rxfifo; + u32 lut_base; + u8 cmd, addrlen, dummy; + int i; + + fsl_qspi_unlock_lut(q); + + /* Clear all the LUT table */ + for (i = 0; i < QUADSPI_LUT_NUM; i++) + writel(0, base + QUADSPI_LUT_BASE + i * 4); + + /* Quad Read */ + lut_base = SEQID_QUAD_READ * 4; + + if (q->nor_size <= SZ_16M) { + cmd = SPINOR_OP_READ_1_1_4; + addrlen = ADDR24BIT; + dummy = 8; + } else { + /* use the 4-byte address */ + cmd = SPINOR_OP_READ_1_1_4; + addrlen = ADDR32BIT; + dummy = 8; + } + + writel(LUT0(CMD, PAD1, cmd) | LUT1(ADDR, PAD1, addrlen), + base + QUADSPI_LUT(lut_base)); + writel(LUT0(DUMMY, PAD1, dummy) | LUT1(READ, PAD4, rxfifo), + base + QUADSPI_LUT(lut_base + 1)); + + /* Write enable */ + lut_base = SEQID_WREN * 4; + writel(LUT0(CMD, PAD1, SPINOR_OP_WREN), base + QUADSPI_LUT(lut_base)); + + /* Page Program */ + lut_base = SEQID_PP * 4; + + if (q->nor_size <= SZ_16M) { + cmd = SPINOR_OP_PP; + addrlen = ADDR24BIT; + } else { + /* use the 4-byte address */ + cmd = SPINOR_OP_PP; + addrlen = ADDR32BIT; + } + + writel(LUT0(CMD, PAD1, cmd) | LUT1(ADDR, PAD1, addrlen), + base + QUADSPI_LUT(lut_base)); + writel(LUT0(WRITE, PAD1, 0), base + QUADSPI_LUT(lut_base + 1)); + + /* Read Status */ + lut_base = SEQID_RDSR * 4; + writel(LUT0(CMD, PAD1, SPINOR_OP_RDSR) | LUT1(READ, PAD1, 0x1), + base + QUADSPI_LUT(lut_base)); + + /* Erase a sector */ + lut_base = SEQID_SE * 4; + + if (q->nor_size <= SZ_16M) { + cmd = SPINOR_OP_SE; + addrlen = ADDR24BIT; + } else { + /* use the 4-byte address */ + cmd = SPINOR_OP_SE; + addrlen = ADDR32BIT; + } + + writel(LUT0(CMD, PAD1, cmd) | LUT1(ADDR, PAD1, addrlen), + base + QUADSPI_LUT(lut_base)); + + /* Erase the whole chip */ + lut_base = SEQID_CHIP_ERASE * 4; + writel(LUT0(CMD, PAD1, SPINOR_OP_CHIP_ERASE), + base + QUADSPI_LUT(lut_base)); + + /* READ ID */ + lut_base = SEQID_RDID * 4; + writel(LUT0(CMD, PAD1, SPINOR_OP_RDID) | LUT1(READ, PAD1, 0x8), + base + QUADSPI_LUT(lut_base)); + + /* Write Register */ + lut_base = SEQID_WRSR * 4; + writel(LUT0(CMD, PAD1, SPINOR_OP_WRSR) | LUT1(WRITE, PAD1, 0x2), + base + QUADSPI_LUT(lut_base)); + + /* Read Configuration Register */ + lut_base = SEQID_RDCR * 4; + writel(LUT0(CMD, PAD1, SPINOR_OP_RDCR) | LUT1(READ, PAD1, 0x1), + base + QUADSPI_LUT(lut_base)); + + /* Write disable */ + lut_base = SEQID_WRDI * 4; + writel(LUT0(CMD, PAD1, SPINOR_OP_WRDI), base + QUADSPI_LUT(lut_base)); + + /* Enter 4 Byte Mode (Micron) */ + lut_base = SEQID_EN4B * 4; + writel(LUT0(CMD, PAD1, SPINOR_OP_EN4B), base + QUADSPI_LUT(lut_base)); + + /* Enter 4 Byte Mode (Spansion) */ + lut_base = SEQID_BRWR * 4; + writel(LUT0(CMD, PAD1, SPINOR_OP_BRWR), base + QUADSPI_LUT(lut_base)); + + fsl_qspi_lock_lut(q); +} + +/* Get the SEQID for the command */ +static int fsl_qspi_get_seqid(struct fsl_qspi *q, u8 cmd) +{ + switch (cmd) { + case SPINOR_OP_READ_1_1_4: + return SEQID_QUAD_READ; + case SPINOR_OP_WREN: + return SEQID_WREN; + case SPINOR_OP_WRDI: + return SEQID_WRDI; + case SPINOR_OP_RDSR: + return SEQID_RDSR; + case SPINOR_OP_SE: + return SEQID_SE; + case SPINOR_OP_CHIP_ERASE: + return SEQID_CHIP_ERASE; + case SPINOR_OP_PP: + return SEQID_PP; + case SPINOR_OP_RDID: + return SEQID_RDID; + case SPINOR_OP_WRSR: + return SEQID_WRSR; + case SPINOR_OP_RDCR: + return SEQID_RDCR; + case SPINOR_OP_EN4B: + return SEQID_EN4B; + case SPINOR_OP_BRWR: + return SEQID_BRWR; + default: + dev_err(q->dev, "Unsupported cmd 0x%.2x\n", cmd); + break; + } + return -EINVAL; +} + +static int +fsl_qspi_runcmd(struct fsl_qspi *q, u8 cmd, unsigned int addr, int len) +{ + void __iomem *base = q->iobase; + int seqid; + u32 reg, reg2; + int err; + + init_completion(&q->c); + dev_dbg(q->dev, "to 0x%.8x:0x%.8x, len:%d, cmd:%.2x\n", + q->chip_base_addr, addr, len, cmd); + + /* save the reg */ + reg = readl(base + QUADSPI_MCR); + + writel(q->memmap_phy + q->chip_base_addr + addr, base + QUADSPI_SFAR); + writel(QUADSPI_RBCT_WMRK_MASK | QUADSPI_RBCT_RXBRD_USEIPS, + base + QUADSPI_RBCT); + writel(reg | QUADSPI_MCR_CLR_RXF_MASK, base + QUADSPI_MCR); + + do { + reg2 = readl(base + QUADSPI_SR); + if (reg2 & (QUADSPI_SR_IP_ACC_MASK | QUADSPI_SR_AHB_ACC_MASK)) { + udelay(1); + dev_dbg(q->dev, "The controller is busy, 0x%x\n", reg2); + continue; + } + break; + } while (1); + + /* trigger the LUT now */ + seqid = fsl_qspi_get_seqid(q, cmd); + writel((seqid << QUADSPI_IPCR_SEQID_SHIFT) | len, base + QUADSPI_IPCR); + + /* Wait for the interrupt. */ + err = wait_for_completion_timeout(&q->c, msecs_to_jiffies(1000)); + if (!err) { + dev_err(q->dev, + "cmd 0x%.2x timeout, addr@%.8x, FR:0x%.8x, SR:0x%.8x\n", + cmd, addr, readl(base + QUADSPI_FR), + readl(base + QUADSPI_SR)); + err = -ETIMEDOUT; + } else { + err = 0; + } + + /* restore the MCR */ + writel(reg, base + QUADSPI_MCR); + + return err; +} + +/* Read out the data from the QUADSPI_RBDR buffer registers. */ +static void fsl_qspi_read_data(struct fsl_qspi *q, int len, u8 *rxbuf) +{ + u32 tmp; + int i = 0; + + while (len > 0) { + tmp = readl(q->iobase + QUADSPI_RBDR + i * 4); + tmp = fsl_qspi_endian_xchg(q, tmp); + dev_dbg(q->dev, "chip addr:0x%.8x, rcv:0x%.8x\n", + q->chip_base_addr, tmp); + + if (len >= 4) { + *((u32 *)rxbuf) = tmp; + rxbuf += 4; + } else { + memcpy(rxbuf, &tmp, len); + break; + } + + len -= 4; + i++; + } +} + +/* + * If we have changed the content of the flash by writing or erasing, + * we need to invalidate the AHB buffer. If we do not do so, we may read out + * the wrong data. The spec tells us reset the AHB domain and Serial Flash + * domain at the same time. + */ +static inline void fsl_qspi_invalid(struct fsl_qspi *q) +{ + u32 reg; + + reg = readl(q->iobase + QUADSPI_MCR); + reg |= QUADSPI_MCR_SWRSTHD_MASK | QUADSPI_MCR_SWRSTSD_MASK; + writel(reg, q->iobase + QUADSPI_MCR); + + /* + * The minimum delay : 1 AHB + 2 SFCK clocks. + * Delay 1 us is enough. + */ + udelay(1); + + reg &= ~(QUADSPI_MCR_SWRSTHD_MASK | QUADSPI_MCR_SWRSTSD_MASK); + writel(reg, q->iobase + QUADSPI_MCR); +} + +static int fsl_qspi_nor_write(struct fsl_qspi *q, struct spi_nor *nor, + u8 opcode, unsigned int to, u32 *txbuf, + unsigned count, size_t *retlen) +{ + int ret, i, j; + u32 tmp; + + dev_dbg(q->dev, "to 0x%.8x:0x%.8x, len : %d\n", + q->chip_base_addr, to, count); + + /* clear the TX FIFO. */ + tmp = readl(q->iobase + QUADSPI_MCR); + writel(tmp | QUADSPI_MCR_CLR_RXF_MASK, q->iobase + QUADSPI_MCR); + + /* fill the TX data to the FIFO */ + for (j = 0, i = ((count + 3) / 4); j < i; j++) { + tmp = fsl_qspi_endian_xchg(q, *txbuf); + writel(tmp, q->iobase + QUADSPI_TBDR); + txbuf++; + } + + /* Trigger it */ + ret = fsl_qspi_runcmd(q, opcode, to, count); + + if (ret == 0 && retlen) + *retlen += count; + + return ret; +} + +static void fsl_qspi_set_map_addr(struct fsl_qspi *q) +{ + int nor_size = q->nor_size; + void __iomem *base = q->iobase; + + writel(nor_size + q->memmap_phy, base + QUADSPI_SFA1AD); + writel(nor_size * 2 + q->memmap_phy, base + QUADSPI_SFA2AD); + writel(nor_size * 3 + q->memmap_phy, base + QUADSPI_SFB1AD); + writel(nor_size * 4 + q->memmap_phy, base + QUADSPI_SFB2AD); +} + +/* + * There are two different ways to read out the data from the flash: + * the "IP Command Read" and the "AHB Command Read". + * + * The IC guy suggests we use the "AHB Command Read" which is faster + * then the "IP Command Read". (What's more is that there is a bug in + * the "IP Command Read" in the Vybrid.) + * + * After we set up the registers for the "AHB Command Read", we can use + * the memcpy to read the data directly. A "missed" access to the buffer + * causes the controller to clear the buffer, and use the sequence pointed + * by the QUADSPI_BFGENCR[SEQID] to initiate a read from the flash. + */ +static void fsl_qspi_init_abh_read(struct fsl_qspi *q) +{ + void __iomem *base = q->iobase; + int seqid; + + /* AHB configuration for access buffer 0/1/2 .*/ + writel(QUADSPI_BUFXCR_INVALID_MSTRID, base + QUADSPI_BUF0CR); + writel(QUADSPI_BUFXCR_INVALID_MSTRID, base + QUADSPI_BUF1CR); + writel(QUADSPI_BUFXCR_INVALID_MSTRID, base + QUADSPI_BUF2CR); + writel(QUADSPI_BUF3CR_ALLMST, base + QUADSPI_BUF3CR); + + /* We only use the buffer3 */ + writel(0, base + QUADSPI_BUF0IND); + writel(0, base + QUADSPI_BUF1IND); + writel(0, base + QUADSPI_BUF2IND); + + /* Set the default lut sequence for AHB Read. */ + seqid = fsl_qspi_get_seqid(q, q->nor[0].read_opcode); + writel(seqid << QUADSPI_BFGENCR_SEQID_SHIFT, + q->iobase + QUADSPI_BFGENCR); +} + +/* We use this function to do some basic init for spi_nor_scan(). */ +static int fsl_qspi_nor_setup(struct fsl_qspi *q) +{ + void __iomem *base = q->iobase; + u32 reg; + int ret; + + /* the default frequency, we will change it in the future.*/ + ret = clk_set_rate(q->clk, 66000000); + if (ret) + return ret; + + /* Init the LUT table. */ + fsl_qspi_init_lut(q); + + /* Disable the module */ + writel(QUADSPI_MCR_MDIS_MASK | QUADSPI_MCR_RESERVED_MASK, + base + QUADSPI_MCR); + + reg = readl(base + QUADSPI_SMPR); + writel(reg & ~(QUADSPI_SMPR_FSDLY_MASK + | QUADSPI_SMPR_FSPHS_MASK + | QUADSPI_SMPR_HSENA_MASK + | QUADSPI_SMPR_DDRSMP_MASK), base + QUADSPI_SMPR); + + /* Enable the module */ + writel(QUADSPI_MCR_RESERVED_MASK | QUADSPI_MCR_END_CFG_MASK, + base + QUADSPI_MCR); + + /* enable the interrupt */ + writel(QUADSPI_RSER_TFIE, q->iobase + QUADSPI_RSER); + + return 0; +} + +static int fsl_qspi_nor_setup_last(struct fsl_qspi *q) +{ + unsigned long rate = q->clk_rate; + int ret; + + if (is_imx6sx_qspi(q)) + rate *= 4; + + ret = clk_set_rate(q->clk, rate); + if (ret) + return ret; + + /* Init the LUT table again. */ + fsl_qspi_init_lut(q); + + /* Init for AHB read */ + fsl_qspi_init_abh_read(q); + + return 0; +} + +static struct of_device_id fsl_qspi_dt_ids[] = { + { .compatible = "fsl,vf610-qspi", .data = (void *)&vybrid_data, }, + { .compatible = "fsl,imx6sx-qspi", .data = (void *)&imx6sx_data, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fsl_qspi_dt_ids); + +static void fsl_qspi_set_base_addr(struct fsl_qspi *q, struct spi_nor *nor) +{ + q->chip_base_addr = q->nor_size * (nor - q->nor); +} + +static int fsl_qspi_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len) +{ + int ret; + struct fsl_qspi *q = nor->priv; + + ret = fsl_qspi_runcmd(q, opcode, 0, len); + if (ret) + return ret; + + fsl_qspi_read_data(q, len, buf); + return 0; +} + +static int fsl_qspi_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len, + int write_enable) +{ + struct fsl_qspi *q = nor->priv; + int ret; + + if (!buf) { + ret = fsl_qspi_runcmd(q, opcode, 0, 1); + if (ret) + return ret; + + if (opcode == SPINOR_OP_CHIP_ERASE) + fsl_qspi_invalid(q); + + } else if (len > 0) { + ret = fsl_qspi_nor_write(q, nor, opcode, 0, + (u32 *)buf, len, NULL); + } else { + dev_err(q->dev, "invalid cmd %d\n", opcode); + ret = -EINVAL; + } + + return ret; +} + +static void fsl_qspi_write(struct spi_nor *nor, loff_t to, + size_t len, size_t *retlen, const u_char *buf) +{ + struct fsl_qspi *q = nor->priv; + + fsl_qspi_nor_write(q, nor, nor->program_opcode, to, + (u32 *)buf, len, retlen); + + /* invalid the data in the AHB buffer. */ + fsl_qspi_invalid(q); +} + +static int fsl_qspi_read(struct spi_nor *nor, loff_t from, + size_t len, size_t *retlen, u_char *buf) +{ + struct fsl_qspi *q = nor->priv; + u8 cmd = nor->read_opcode; + int ret; + + dev_dbg(q->dev, "cmd [%x],read from (0x%p, 0x%.8x, 0x%.8x),len:%d\n", + cmd, q->ahb_base, q->chip_base_addr, (unsigned int)from, len); + + /* Wait until the previous command is finished. */ + ret = nor->wait_till_ready(nor); + if (ret) + return ret; + + /* Read out the data directly from the AHB buffer.*/ + memcpy(buf, q->ahb_base + q->chip_base_addr + from, len); + + *retlen += len; + return 0; +} + +static int fsl_qspi_erase(struct spi_nor *nor, loff_t offs) +{ + struct fsl_qspi *q = nor->priv; + int ret; + + dev_dbg(nor->dev, "%dKiB at 0x%08x:0x%08x\n", + nor->mtd->erasesize / 1024, q->chip_base_addr, (u32)offs); + + /* Wait until finished previous write command. */ + ret = nor->wait_till_ready(nor); + if (ret) + return ret; + + /* Send write enable, then erase commands. */ + ret = nor->write_reg(nor, SPINOR_OP_WREN, NULL, 0, 0); + if (ret) + return ret; + + ret = fsl_qspi_runcmd(q, nor->erase_opcode, offs, 0); + if (ret) + return ret; + + fsl_qspi_invalid(q); + return 0; +} + +static int fsl_qspi_prep(struct spi_nor *nor, enum spi_nor_ops ops) +{ + struct fsl_qspi *q = nor->priv; + int ret; + + ret = clk_enable(q->clk_en); + if (ret) + return ret; + + ret = clk_enable(q->clk); + if (ret) { + clk_disable(q->clk_en); + return ret; + } + + fsl_qspi_set_base_addr(q, nor); + return 0; +} + +static void fsl_qspi_unprep(struct spi_nor *nor, enum spi_nor_ops ops) +{ + struct fsl_qspi *q = nor->priv; + + clk_disable(q->clk); + clk_disable(q->clk_en); +} + +static int fsl_qspi_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct mtd_part_parser_data ppdata; + struct device *dev = &pdev->dev; + struct fsl_qspi *q; + struct resource *res; + struct spi_nor *nor; + struct mtd_info *mtd; + int ret, i = 0; + bool has_second_chip = false; + const struct of_device_id *of_id = + of_match_device(fsl_qspi_dt_ids, &pdev->dev); + + q = devm_kzalloc(dev, sizeof(*q), GFP_KERNEL); + if (!q) + return -ENOMEM; + + q->nor_num = of_get_child_count(dev->of_node); + if (!q->nor_num || q->nor_num > FSL_QSPI_MAX_CHIP) + return -ENODEV; + + /* find the resources */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "QuadSPI"); + q->iobase = devm_ioremap_resource(dev, res); + if (IS_ERR(q->iobase)) { + ret = PTR_ERR(q->iobase); + goto map_failed; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "QuadSPI-memory"); + q->ahb_base = devm_ioremap_resource(dev, res); + if (IS_ERR(q->ahb_base)) { + ret = PTR_ERR(q->ahb_base); + goto map_failed; + } + q->memmap_phy = res->start; + + /* find the clocks */ + q->clk_en = devm_clk_get(dev, "qspi_en"); + if (IS_ERR(q->clk_en)) { + ret = PTR_ERR(q->clk_en); + goto map_failed; + } + + q->clk = devm_clk_get(dev, "qspi"); + if (IS_ERR(q->clk)) { + ret = PTR_ERR(q->clk); + goto map_failed; + } + + ret = clk_prepare_enable(q->clk_en); + if (ret) { + dev_err(dev, "can not enable the qspi_en clock\n"); + goto map_failed; + } + + ret = clk_prepare_enable(q->clk); + if (ret) { + clk_disable_unprepare(q->clk_en); + dev_err(dev, "can not enable the qspi clock\n"); + goto map_failed; + } + + /* find the irq */ + ret = platform_get_irq(pdev, 0); + if (ret < 0) { + dev_err(dev, "failed to get the irq\n"); + goto irq_failed; + } + + ret = devm_request_irq(dev, ret, + fsl_qspi_irq_handler, 0, pdev->name, q); + if (ret) { + dev_err(dev, "failed to request irq.\n"); + goto irq_failed; + } + + q->dev = dev; + q->devtype_data = (struct fsl_qspi_devtype_data *)of_id->data; + platform_set_drvdata(pdev, q); + + ret = fsl_qspi_nor_setup(q); + if (ret) + goto irq_failed; + + if (of_get_property(np, "fsl,qspi-has-second-chip", NULL)) + has_second_chip = true; + + /* iterate the subnodes. */ + for_each_available_child_of_node(dev->of_node, np) { + const struct spi_device_id *id; + char modalias[40]; + + /* skip the holes */ + if (!has_second_chip) + i *= 2; + + nor = &q->nor[i]; + mtd = &q->mtd[i]; + + nor->mtd = mtd; + nor->dev = dev; + nor->priv = q; + mtd->priv = nor; + + /* fill the hooks */ + nor->read_reg = fsl_qspi_read_reg; + nor->write_reg = fsl_qspi_write_reg; + nor->read = fsl_qspi_read; + nor->write = fsl_qspi_write; + nor->erase = fsl_qspi_erase; + + nor->prepare = fsl_qspi_prep; + nor->unprepare = fsl_qspi_unprep; + + if (of_modalias_node(np, modalias, sizeof(modalias)) < 0) + goto map_failed; + + id = spi_nor_match_id(modalias); + if (!id) + goto map_failed; + + ret = of_property_read_u32(np, "spi-max-frequency", + &q->clk_rate); + if (ret < 0) + goto map_failed; + + /* set the chip address for READID */ + fsl_qspi_set_base_addr(q, nor); + + ret = spi_nor_scan(nor, id, SPI_NOR_QUAD); + if (ret) + goto map_failed; + + ppdata.of_node = np; + ret = mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0); + if (ret) + goto map_failed; + + /* Set the correct NOR size now. */ + if (q->nor_size == 0) { + q->nor_size = mtd->size; + + /* Map the SPI NOR to accessiable address */ + fsl_qspi_set_map_addr(q); + } + + /* + * The TX FIFO is 64 bytes in the Vybrid, but the Page Program + * may writes 265 bytes per time. The write is working in the + * unit of the TX FIFO, not in the unit of the SPI NOR's page + * size. + * + * So shrink the spi_nor->page_size if it is larger then the + * TX FIFO. + */ + if (nor->page_size > q->devtype_data->txfifo) + nor->page_size = q->devtype_data->txfifo; + + i++; + } + + /* finish the rest init. */ + ret = fsl_qspi_nor_setup_last(q); + if (ret) + goto last_init_failed; + + clk_disable(q->clk); + clk_disable(q->clk_en); + dev_info(dev, "QuadSPI SPI NOR flash driver\n"); + return 0; + +last_init_failed: + for (i = 0; i < q->nor_num; i++) + mtd_device_unregister(&q->mtd[i]); + +irq_failed: + clk_disable_unprepare(q->clk); + clk_disable_unprepare(q->clk_en); +map_failed: + dev_err(dev, "Freescale QuadSPI probe failed\n"); + return ret; +} + +static int fsl_qspi_remove(struct platform_device *pdev) +{ + struct fsl_qspi *q = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < q->nor_num; i++) + mtd_device_unregister(&q->mtd[i]); + + /* disable the hardware */ + writel(QUADSPI_MCR_MDIS_MASK, q->iobase + QUADSPI_MCR); + writel(0x0, q->iobase + QUADSPI_RSER); + + clk_unprepare(q->clk); + clk_unprepare(q->clk_en); + return 0; +} + +static struct platform_driver fsl_qspi_driver = { + .driver = { + .name = "fsl-quadspi", + .bus = &platform_bus_type, + .owner = THIS_MODULE, + .of_match_table = fsl_qspi_dt_ids, + }, + .probe = fsl_qspi_probe, + .remove = fsl_qspi_remove, +}; +module_platform_driver(fsl_qspi_driver); + +MODULE_DESCRIPTION("Freescale QuadSPI Controller Driver"); +MODULE_AUTHOR("Freescale Semiconductor Inc."); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c new file mode 100644 index 00000000000..c713c865671 --- /dev/null +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -0,0 +1,1107 @@ +/* + * Based on m25p80.c, by Mike Lavender (mike@steroidmicros.com), with + * influence from lart.c (Abraham Van Der Merwe) and mtd_dataflash.c + * + * Copyright (C) 2005, Intec Automation Inc. + * Copyright (C) 2014, Freescale Semiconductor, Inc. + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/mutex.h> +#include <linux/math64.h> + +#include <linux/mtd/cfi.h> +#include <linux/mtd/mtd.h> +#include <linux/of_platform.h> +#include <linux/spi/flash.h> +#include <linux/mtd/spi-nor.h> + +/* Define max times to check status register before we give up. */ +#define MAX_READY_WAIT_JIFFIES (40 * HZ) /* M25P16 specs 40s max chip erase */ + +#define JEDEC_MFR(_jedec_id) ((_jedec_id) >> 16) + +/* + * Read the status register, returning its value in the location + * Return the status register value. + * Returns negative if error occurred. + */ +static int read_sr(struct spi_nor *nor) +{ + int ret; + u8 val; + + ret = nor->read_reg(nor, SPINOR_OP_RDSR, &val, 1); + if (ret < 0) { + pr_err("error %d reading SR\n", (int) ret); + return ret; + } + + return val; +} + +/* + * Read configuration register, returning its value in the + * location. Return the configuration register value. + * Returns negative if error occured. + */ +static int read_cr(struct spi_nor *nor) +{ + int ret; + u8 val; + + ret = nor->read_reg(nor, SPINOR_OP_RDCR, &val, 1); + if (ret < 0) { + dev_err(nor->dev, "error %d reading CR\n", ret); + return ret; + } + + return val; +} + +/* + * Dummy Cycle calculation for different type of read. + * It can be used to support more commands with + * different dummy cycle requirements. + */ +static inline int spi_nor_read_dummy_cycles(struct spi_nor *nor) +{ + switch (nor->flash_read) { + case SPI_NOR_FAST: + case SPI_NOR_DUAL: + case SPI_NOR_QUAD: + return 1; + case SPI_NOR_NORMAL: + return 0; + } + return 0; +} + +/* + * Write status register 1 byte + * Returns negative if error occurred. + */ +static inline int write_sr(struct spi_nor *nor, u8 val) +{ + nor->cmd_buf[0] = val; + return nor->write_reg(nor, SPINOR_OP_WRSR, nor->cmd_buf, 1, 0); +} + +/* + * Set write enable latch with Write Enable command. + * Returns negative if error occurred. + */ +static inline int write_enable(struct spi_nor *nor) +{ + return nor->write_reg(nor, SPINOR_OP_WREN, NULL, 0, 0); +} + +/* + * Send write disble instruction to the chip. + */ +static inline int write_disable(struct spi_nor *nor) +{ + return nor->write_reg(nor, SPINOR_OP_WRDI, NULL, 0, 0); +} + +static inline struct spi_nor *mtd_to_spi_nor(struct mtd_info *mtd) +{ + return mtd->priv; +} + +/* Enable/disable 4-byte addressing mode. */ +static inline int set_4byte(struct spi_nor *nor, u32 jedec_id, int enable) +{ + int status; + bool need_wren = false; + u8 cmd; + + switch (JEDEC_MFR(jedec_id)) { + case CFI_MFR_ST: /* Micron, actually */ + /* Some Micron need WREN command; all will accept it */ + need_wren = true; + case CFI_MFR_MACRONIX: + case 0xEF /* winbond */: + if (need_wren) + write_enable(nor); + + cmd = enable ? SPINOR_OP_EN4B : SPINOR_OP_EX4B; + status = nor->write_reg(nor, cmd, NULL, 0, 0); + if (need_wren) + write_disable(nor); + + return status; + default: + /* Spansion style */ + nor->cmd_buf[0] = enable << 7; + return nor->write_reg(nor, SPINOR_OP_BRWR, nor->cmd_buf, 1, 0); + } +} + +static int spi_nor_wait_till_ready(struct spi_nor *nor) +{ + unsigned long deadline; + int sr; + + deadline = jiffies + MAX_READY_WAIT_JIFFIES; + + do { + cond_resched(); + + sr = read_sr(nor); + if (sr < 0) + break; + else if (!(sr & SR_WIP)) + return 0; + } while (!time_after_eq(jiffies, deadline)); + + return -ETIMEDOUT; +} + +/* + * Service routine to read status register until ready, or timeout occurs. + * Returns non-zero if error. + */ +static int wait_till_ready(struct spi_nor *nor) +{ + return nor->wait_till_ready(nor); +} + +/* + * Erase the whole flash memory + * + * Returns 0 if successful, non-zero otherwise. + */ +static int erase_chip(struct spi_nor *nor) +{ + int ret; + + dev_dbg(nor->dev, " %lldKiB\n", (long long)(nor->mtd->size >> 10)); + + /* Wait until finished previous write command. */ + ret = wait_till_ready(nor); + if (ret) + return ret; + + /* Send write enable, then erase commands. */ + write_enable(nor); + + return nor->write_reg(nor, SPINOR_OP_CHIP_ERASE, NULL, 0, 0); +} + +static int spi_nor_lock_and_prep(struct spi_nor *nor, enum spi_nor_ops ops) +{ + int ret = 0; + + mutex_lock(&nor->lock); + + if (nor->prepare) { + ret = nor->prepare(nor, ops); + if (ret) { + dev_err(nor->dev, "failed in the preparation.\n"); + mutex_unlock(&nor->lock); + return ret; + } + } + return ret; +} + +static void spi_nor_unlock_and_unprep(struct spi_nor *nor, enum spi_nor_ops ops) +{ + if (nor->unprepare) + nor->unprepare(nor, ops); + mutex_unlock(&nor->lock); +} + +/* + * Erase an address range on the nor chip. The address range may extend + * one or more erase sectors. Return an error is there is a problem erasing. + */ +static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + struct spi_nor *nor = mtd_to_spi_nor(mtd); + u32 addr, len; + uint32_t rem; + int ret; + + dev_dbg(nor->dev, "at 0x%llx, len %lld\n", (long long)instr->addr, + (long long)instr->len); + + div_u64_rem(instr->len, mtd->erasesize, &rem); + if (rem) + return -EINVAL; + + addr = instr->addr; + len = instr->len; + + ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_ERASE); + if (ret) + return ret; + + /* whole-chip erase? */ + if (len == mtd->size) { + if (erase_chip(nor)) { + ret = -EIO; + goto erase_err; + } + + /* REVISIT in some cases we could speed up erasing large regions + * by using SPINOR_OP_SE instead of SPINOR_OP_BE_4K. We may have set up + * to use "small sector erase", but that's not always optimal. + */ + + /* "sector"-at-a-time erase */ + } else { + while (len) { + if (nor->erase(nor, addr)) { + ret = -EIO; + goto erase_err; + } + + addr += mtd->erasesize; + len -= mtd->erasesize; + } + } + + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_ERASE); + + instr->state = MTD_ERASE_DONE; + mtd_erase_callback(instr); + + return ret; + +erase_err: + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_ERASE); + instr->state = MTD_ERASE_FAILED; + return ret; +} + +static int spi_nor_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + struct spi_nor *nor = mtd_to_spi_nor(mtd); + uint32_t offset = ofs; + uint8_t status_old, status_new; + int ret = 0; + + ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_LOCK); + if (ret) + return ret; + + /* Wait until finished previous command */ + ret = wait_till_ready(nor); + if (ret) + goto err; + + status_old = read_sr(nor); + + if (offset < mtd->size - (mtd->size / 2)) + status_new = status_old | SR_BP2 | SR_BP1 | SR_BP0; + else if (offset < mtd->size - (mtd->size / 4)) + status_new = (status_old & ~SR_BP0) | SR_BP2 | SR_BP1; + else if (offset < mtd->size - (mtd->size / 8)) + status_new = (status_old & ~SR_BP1) | SR_BP2 | SR_BP0; + else if (offset < mtd->size - (mtd->size / 16)) + status_new = (status_old & ~(SR_BP0 | SR_BP1)) | SR_BP2; + else if (offset < mtd->size - (mtd->size / 32)) + status_new = (status_old & ~SR_BP2) | SR_BP1 | SR_BP0; + else if (offset < mtd->size - (mtd->size / 64)) + status_new = (status_old & ~(SR_BP2 | SR_BP0)) | SR_BP1; + else + status_new = (status_old & ~(SR_BP2 | SR_BP1)) | SR_BP0; + + /* Only modify protection if it will not unlock other areas */ + if ((status_new & (SR_BP2 | SR_BP1 | SR_BP0)) > + (status_old & (SR_BP2 | SR_BP1 | SR_BP0))) { + write_enable(nor); + ret = write_sr(nor, status_new); + if (ret) + goto err; + } + +err: + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_LOCK); + return ret; +} + +static int spi_nor_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + struct spi_nor *nor = mtd_to_spi_nor(mtd); + uint32_t offset = ofs; + uint8_t status_old, status_new; + int ret = 0; + + ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_UNLOCK); + if (ret) + return ret; + + /* Wait until finished previous command */ + ret = wait_till_ready(nor); + if (ret) + goto err; + + status_old = read_sr(nor); + + if (offset+len > mtd->size - (mtd->size / 64)) + status_new = status_old & ~(SR_BP2 | SR_BP1 | SR_BP0); + else if (offset+len > mtd->size - (mtd->size / 32)) + status_new = (status_old & ~(SR_BP2 | SR_BP1)) | SR_BP0; + else if (offset+len > mtd->size - (mtd->size / 16)) + status_new = (status_old & ~(SR_BP2 | SR_BP0)) | SR_BP1; + else if (offset+len > mtd->size - (mtd->size / 8)) + status_new = (status_old & ~SR_BP2) | SR_BP1 | SR_BP0; + else if (offset+len > mtd->size - (mtd->size / 4)) + status_new = (status_old & ~(SR_BP0 | SR_BP1)) | SR_BP2; + else if (offset+len > mtd->size - (mtd->size / 2)) + status_new = (status_old & ~SR_BP1) | SR_BP2 | SR_BP0; + else + status_new = (status_old & ~SR_BP0) | SR_BP2 | SR_BP1; + + /* Only modify protection if it will not lock other areas */ + if ((status_new & (SR_BP2 | SR_BP1 | SR_BP0)) < + (status_old & (SR_BP2 | SR_BP1 | SR_BP0))) { + write_enable(nor); + ret = write_sr(nor, status_new); + if (ret) + goto err; + } + +err: + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_UNLOCK); + return ret; +} + +struct flash_info { + /* JEDEC id zero means "no ID" (most older chips); otherwise it has + * a high byte of zero plus three data bytes: the manufacturer id, + * then a two byte device id. + */ + u32 jedec_id; + u16 ext_id; + + /* The size listed here is what works with SPINOR_OP_SE, which isn't + * necessarily called a "sector" by the vendor. + */ + unsigned sector_size; + u16 n_sectors; + + u16 page_size; + u16 addr_width; + + u16 flags; +#define SECT_4K 0x01 /* SPINOR_OP_BE_4K works uniformly */ +#define SPI_NOR_NO_ERASE 0x02 /* No erase command needed */ +#define SST_WRITE 0x04 /* use SST byte programming */ +#define SPI_NOR_NO_FR 0x08 /* Can't do fastread */ +#define SECT_4K_PMC 0x10 /* SPINOR_OP_BE_4K_PMC works uniformly */ +#define SPI_NOR_DUAL_READ 0x20 /* Flash supports Dual Read */ +#define SPI_NOR_QUAD_READ 0x40 /* Flash supports Quad Read */ +}; + +#define INFO(_jedec_id, _ext_id, _sector_size, _n_sectors, _flags) \ + ((kernel_ulong_t)&(struct flash_info) { \ + .jedec_id = (_jedec_id), \ + .ext_id = (_ext_id), \ + .sector_size = (_sector_size), \ + .n_sectors = (_n_sectors), \ + .page_size = 256, \ + .flags = (_flags), \ + }) + +#define CAT25_INFO(_sector_size, _n_sectors, _page_size, _addr_width, _flags) \ + ((kernel_ulong_t)&(struct flash_info) { \ + .sector_size = (_sector_size), \ + .n_sectors = (_n_sectors), \ + .page_size = (_page_size), \ + .addr_width = (_addr_width), \ + .flags = (_flags), \ + }) + +/* NOTE: double check command sets and memory organization when you add + * more nor chips. This current list focusses on newer chips, which + * have been converging on command sets which including JEDEC ID. + */ +const struct spi_device_id spi_nor_ids[] = { + /* Atmel -- some are (confusingly) marketed as "DataFlash" */ + { "at25fs010", INFO(0x1f6601, 0, 32 * 1024, 4, SECT_4K) }, + { "at25fs040", INFO(0x1f6604, 0, 64 * 1024, 8, SECT_4K) }, + + { "at25df041a", INFO(0x1f4401, 0, 64 * 1024, 8, SECT_4K) }, + { "at25df321a", INFO(0x1f4701, 0, 64 * 1024, 64, SECT_4K) }, + { "at25df641", INFO(0x1f4800, 0, 64 * 1024, 128, SECT_4K) }, + + { "at26f004", INFO(0x1f0400, 0, 64 * 1024, 8, SECT_4K) }, + { "at26df081a", INFO(0x1f4501, 0, 64 * 1024, 16, SECT_4K) }, + { "at26df161a", INFO(0x1f4601, 0, 64 * 1024, 32, SECT_4K) }, + { "at26df321", INFO(0x1f4700, 0, 64 * 1024, 64, SECT_4K) }, + + { "at45db081d", INFO(0x1f2500, 0, 64 * 1024, 16, SECT_4K) }, + + /* EON -- en25xxx */ + { "en25f32", INFO(0x1c3116, 0, 64 * 1024, 64, SECT_4K) }, + { "en25p32", INFO(0x1c2016, 0, 64 * 1024, 64, 0) }, + { "en25q32b", INFO(0x1c3016, 0, 64 * 1024, 64, 0) }, + { "en25p64", INFO(0x1c2017, 0, 64 * 1024, 128, 0) }, + { "en25q64", INFO(0x1c3017, 0, 64 * 1024, 128, SECT_4K) }, + { "en25qh256", INFO(0x1c7019, 0, 64 * 1024, 512, 0) }, + + /* ESMT */ + { "f25l32pa", INFO(0x8c2016, 0, 64 * 1024, 64, SECT_4K) }, + + /* Everspin */ + { "mr25h256", CAT25_INFO( 32 * 1024, 1, 256, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) }, + { "mr25h10", CAT25_INFO(128 * 1024, 1, 256, 3, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) }, + + /* GigaDevice */ + { "gd25q32", INFO(0xc84016, 0, 64 * 1024, 64, SECT_4K) }, + { "gd25q64", INFO(0xc84017, 0, 64 * 1024, 128, SECT_4K) }, + + /* Intel/Numonyx -- xxxs33b */ + { "160s33b", INFO(0x898911, 0, 64 * 1024, 32, 0) }, + { "320s33b", INFO(0x898912, 0, 64 * 1024, 64, 0) }, + { "640s33b", INFO(0x898913, 0, 64 * 1024, 128, 0) }, + + /* Macronix */ + { "mx25l2005a", INFO(0xc22012, 0, 64 * 1024, 4, SECT_4K) }, + { "mx25l4005a", INFO(0xc22013, 0, 64 * 1024, 8, SECT_4K) }, + { "mx25l8005", INFO(0xc22014, 0, 64 * 1024, 16, 0) }, + { "mx25l1606e", INFO(0xc22015, 0, 64 * 1024, 32, SECT_4K) }, + { "mx25l3205d", INFO(0xc22016, 0, 64 * 1024, 64, 0) }, + { "mx25l3255e", INFO(0xc29e16, 0, 64 * 1024, 64, SECT_4K) }, + { "mx25l6405d", INFO(0xc22017, 0, 64 * 1024, 128, 0) }, + { "mx25l12805d", INFO(0xc22018, 0, 64 * 1024, 256, 0) }, + { "mx25l12855e", INFO(0xc22618, 0, 64 * 1024, 256, 0) }, + { "mx25l25635e", INFO(0xc22019, 0, 64 * 1024, 512, 0) }, + { "mx25l25655e", INFO(0xc22619, 0, 64 * 1024, 512, 0) }, + { "mx66l51235l", INFO(0xc2201a, 0, 64 * 1024, 1024, SPI_NOR_QUAD_READ) }, + { "mx66l1g55g", INFO(0xc2261b, 0, 64 * 1024, 2048, SPI_NOR_QUAD_READ) }, + + /* Micron */ + { "n25q064", INFO(0x20ba17, 0, 64 * 1024, 128, 0) }, + { "n25q128a11", INFO(0x20bb18, 0, 64 * 1024, 256, 0) }, + { "n25q128a13", INFO(0x20ba18, 0, 64 * 1024, 256, 0) }, + { "n25q256a", INFO(0x20ba19, 0, 64 * 1024, 512, SECT_4K) }, + { "n25q512a", INFO(0x20bb20, 0, 64 * 1024, 1024, SECT_4K) }, + + /* PMC */ + { "pm25lv512", INFO(0, 0, 32 * 1024, 2, SECT_4K_PMC) }, + { "pm25lv010", INFO(0, 0, 32 * 1024, 4, SECT_4K_PMC) }, + { "pm25lq032", INFO(0x7f9d46, 0, 64 * 1024, 64, SECT_4K) }, + + /* Spansion -- single (large) sector size only, at least + * for the chips listed here (without boot sectors). + */ + { "s25sl032p", INFO(0x010215, 0x4d00, 64 * 1024, 64, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "s25sl064p", INFO(0x010216, 0x4d00, 64 * 1024, 128, 0) }, + { "s25fl256s0", INFO(0x010219, 0x4d00, 256 * 1024, 128, 0) }, + { "s25fl256s1", INFO(0x010219, 0x4d01, 64 * 1024, 512, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "s25fl512s", INFO(0x010220, 0x4d00, 256 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "s70fl01gs", INFO(0x010221, 0x4d00, 256 * 1024, 256, 0) }, + { "s25sl12800", INFO(0x012018, 0x0300, 256 * 1024, 64, 0) }, + { "s25sl12801", INFO(0x012018, 0x0301, 64 * 1024, 256, 0) }, + { "s25fl129p0", INFO(0x012018, 0x4d00, 256 * 1024, 64, 0) }, + { "s25fl129p1", INFO(0x012018, 0x4d01, 64 * 1024, 256, 0) }, + { "s25sl004a", INFO(0x010212, 0, 64 * 1024, 8, 0) }, + { "s25sl008a", INFO(0x010213, 0, 64 * 1024, 16, 0) }, + { "s25sl016a", INFO(0x010214, 0, 64 * 1024, 32, 0) }, + { "s25sl032a", INFO(0x010215, 0, 64 * 1024, 64, 0) }, + { "s25sl064a", INFO(0x010216, 0, 64 * 1024, 128, 0) }, + { "s25fl008k", INFO(0xef4014, 0, 64 * 1024, 16, SECT_4K) }, + { "s25fl016k", INFO(0xef4015, 0, 64 * 1024, 32, SECT_4K) }, + { "s25fl064k", INFO(0xef4017, 0, 64 * 1024, 128, SECT_4K) }, + + /* SST -- large erase sizes are "overlays", "sectors" are 4K */ + { "sst25vf040b", INFO(0xbf258d, 0, 64 * 1024, 8, SECT_4K | SST_WRITE) }, + { "sst25vf080b", INFO(0xbf258e, 0, 64 * 1024, 16, SECT_4K | SST_WRITE) }, + { "sst25vf016b", INFO(0xbf2541, 0, 64 * 1024, 32, SECT_4K | SST_WRITE) }, + { "sst25vf032b", INFO(0xbf254a, 0, 64 * 1024, 64, SECT_4K | SST_WRITE) }, + { "sst25vf064c", INFO(0xbf254b, 0, 64 * 1024, 128, SECT_4K) }, + { "sst25wf512", INFO(0xbf2501, 0, 64 * 1024, 1, SECT_4K | SST_WRITE) }, + { "sst25wf010", INFO(0xbf2502, 0, 64 * 1024, 2, SECT_4K | SST_WRITE) }, + { "sst25wf020", INFO(0xbf2503, 0, 64 * 1024, 4, SECT_4K | SST_WRITE) }, + { "sst25wf040", INFO(0xbf2504, 0, 64 * 1024, 8, SECT_4K | SST_WRITE) }, + + /* ST Microelectronics -- newer production may have feature updates */ + { "m25p05", INFO(0x202010, 0, 32 * 1024, 2, 0) }, + { "m25p10", INFO(0x202011, 0, 32 * 1024, 4, 0) }, + { "m25p20", INFO(0x202012, 0, 64 * 1024, 4, 0) }, + { "m25p40", INFO(0x202013, 0, 64 * 1024, 8, 0) }, + { "m25p80", INFO(0x202014, 0, 64 * 1024, 16, 0) }, + { "m25p16", INFO(0x202015, 0, 64 * 1024, 32, 0) }, + { "m25p32", INFO(0x202016, 0, 64 * 1024, 64, 0) }, + { "m25p64", INFO(0x202017, 0, 64 * 1024, 128, 0) }, + { "m25p128", INFO(0x202018, 0, 256 * 1024, 64, 0) }, + { "n25q032", INFO(0x20ba16, 0, 64 * 1024, 64, 0) }, + + { "m25p05-nonjedec", INFO(0, 0, 32 * 1024, 2, 0) }, + { "m25p10-nonjedec", INFO(0, 0, 32 * 1024, 4, 0) }, + { "m25p20-nonjedec", INFO(0, 0, 64 * 1024, 4, 0) }, + { "m25p40-nonjedec", INFO(0, 0, 64 * 1024, 8, 0) }, + { "m25p80-nonjedec", INFO(0, 0, 64 * 1024, 16, 0) }, + { "m25p16-nonjedec", INFO(0, 0, 64 * 1024, 32, 0) }, + { "m25p32-nonjedec", INFO(0, 0, 64 * 1024, 64, 0) }, + { "m25p64-nonjedec", INFO(0, 0, 64 * 1024, 128, 0) }, + { "m25p128-nonjedec", INFO(0, 0, 256 * 1024, 64, 0) }, + + { "m45pe10", INFO(0x204011, 0, 64 * 1024, 2, 0) }, + { "m45pe80", INFO(0x204014, 0, 64 * 1024, 16, 0) }, + { "m45pe16", INFO(0x204015, 0, 64 * 1024, 32, 0) }, + + { "m25pe20", INFO(0x208012, 0, 64 * 1024, 4, 0) }, + { "m25pe80", INFO(0x208014, 0, 64 * 1024, 16, 0) }, + { "m25pe16", INFO(0x208015, 0, 64 * 1024, 32, SECT_4K) }, + + { "m25px16", INFO(0x207115, 0, 64 * 1024, 32, SECT_4K) }, + { "m25px32", INFO(0x207116, 0, 64 * 1024, 64, SECT_4K) }, + { "m25px32-s0", INFO(0x207316, 0, 64 * 1024, 64, SECT_4K) }, + { "m25px32-s1", INFO(0x206316, 0, 64 * 1024, 64, SECT_4K) }, + { "m25px64", INFO(0x207117, 0, 64 * 1024, 128, 0) }, + + /* Winbond -- w25x "blocks" are 64K, "sectors" are 4KiB */ + { "w25x10", INFO(0xef3011, 0, 64 * 1024, 2, SECT_4K) }, + { "w25x20", INFO(0xef3012, 0, 64 * 1024, 4, SECT_4K) }, + { "w25x40", INFO(0xef3013, 0, 64 * 1024, 8, SECT_4K) }, + { "w25x80", INFO(0xef3014, 0, 64 * 1024, 16, SECT_4K) }, + { "w25x16", INFO(0xef3015, 0, 64 * 1024, 32, SECT_4K) }, + { "w25x32", INFO(0xef3016, 0, 64 * 1024, 64, SECT_4K) }, + { "w25q32", INFO(0xef4016, 0, 64 * 1024, 64, SECT_4K) }, + { "w25q32dw", INFO(0xef6016, 0, 64 * 1024, 64, SECT_4K) }, + { "w25x64", INFO(0xef3017, 0, 64 * 1024, 128, SECT_4K) }, + { "w25q64", INFO(0xef4017, 0, 64 * 1024, 128, SECT_4K) }, + { "w25q128", INFO(0xef4018, 0, 64 * 1024, 256, SECT_4K) }, + { "w25q80", INFO(0xef5014, 0, 64 * 1024, 16, SECT_4K) }, + { "w25q80bl", INFO(0xef4014, 0, 64 * 1024, 16, SECT_4K) }, + { "w25q128", INFO(0xef4018, 0, 64 * 1024, 256, SECT_4K) }, + { "w25q256", INFO(0xef4019, 0, 64 * 1024, 512, SECT_4K) }, + + /* Catalyst / On Semiconductor -- non-JEDEC */ + { "cat25c11", CAT25_INFO( 16, 8, 16, 1, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) }, + { "cat25c03", CAT25_INFO( 32, 8, 16, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) }, + { "cat25c09", CAT25_INFO( 128, 8, 32, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) }, + { "cat25c17", CAT25_INFO( 256, 8, 32, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) }, + { "cat25128", CAT25_INFO(2048, 8, 64, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) }, + { }, +}; +EXPORT_SYMBOL_GPL(spi_nor_ids); + +static const struct spi_device_id *spi_nor_read_id(struct spi_nor *nor) +{ + int tmp; + u8 id[5]; + u32 jedec; + u16 ext_jedec; + struct flash_info *info; + + tmp = nor->read_reg(nor, SPINOR_OP_RDID, id, 5); + if (tmp < 0) { + dev_dbg(nor->dev, " error %d reading JEDEC ID\n", tmp); + return ERR_PTR(tmp); + } + jedec = id[0]; + jedec = jedec << 8; + jedec |= id[1]; + jedec = jedec << 8; + jedec |= id[2]; + + ext_jedec = id[3] << 8 | id[4]; + + for (tmp = 0; tmp < ARRAY_SIZE(spi_nor_ids) - 1; tmp++) { + info = (void *)spi_nor_ids[tmp].driver_data; + if (info->jedec_id == jedec) { + if (info->ext_id == 0 || info->ext_id == ext_jedec) + return &spi_nor_ids[tmp]; + } + } + dev_err(nor->dev, "unrecognized JEDEC id %06x\n", jedec); + return ERR_PTR(-ENODEV); +} + +static const struct spi_device_id *jedec_probe(struct spi_nor *nor) +{ + return nor->read_id(nor); +} + +static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct spi_nor *nor = mtd_to_spi_nor(mtd); + int ret; + + dev_dbg(nor->dev, "from 0x%08x, len %zd\n", (u32)from, len); + + ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_READ); + if (ret) + return ret; + + ret = nor->read(nor, from, len, retlen, buf); + + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_READ); + return ret; +} + +static int sst_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct spi_nor *nor = mtd_to_spi_nor(mtd); + size_t actual; + int ret; + + dev_dbg(nor->dev, "to 0x%08x, len %zd\n", (u32)to, len); + + ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_WRITE); + if (ret) + return ret; + + /* Wait until finished previous write command. */ + ret = wait_till_ready(nor); + if (ret) + goto time_out; + + write_enable(nor); + + nor->sst_write_second = false; + + actual = to % 2; + /* Start write from odd address. */ + if (actual) { + nor->program_opcode = SPINOR_OP_BP; + + /* write one byte. */ + nor->write(nor, to, 1, retlen, buf); + ret = wait_till_ready(nor); + if (ret) + goto time_out; + } + to += actual; + + /* Write out most of the data here. */ + for (; actual < len - 1; actual += 2) { + nor->program_opcode = SPINOR_OP_AAI_WP; + + /* write two bytes. */ + nor->write(nor, to, 2, retlen, buf + actual); + ret = wait_till_ready(nor); + if (ret) + goto time_out; + to += 2; + nor->sst_write_second = true; + } + nor->sst_write_second = false; + + write_disable(nor); + ret = wait_till_ready(nor); + if (ret) + goto time_out; + + /* Write out trailing byte if it exists. */ + if (actual != len) { + write_enable(nor); + + nor->program_opcode = SPINOR_OP_BP; + nor->write(nor, to, 1, retlen, buf + actual); + + ret = wait_till_ready(nor); + if (ret) + goto time_out; + write_disable(nor); + } +time_out: + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_WRITE); + return ret; +} + +/* + * Write an address range to the nor chip. Data must be written in + * FLASH_PAGESIZE chunks. The address range may be any size provided + * it is within the physical boundaries. + */ +static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct spi_nor *nor = mtd_to_spi_nor(mtd); + u32 page_offset, page_size, i; + int ret; + + dev_dbg(nor->dev, "to 0x%08x, len %zd\n", (u32)to, len); + + ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_WRITE); + if (ret) + return ret; + + /* Wait until finished previous write command. */ + ret = wait_till_ready(nor); + if (ret) + goto write_err; + + write_enable(nor); + + page_offset = to & (nor->page_size - 1); + + /* do all the bytes fit onto one page? */ + if (page_offset + len <= nor->page_size) { + nor->write(nor, to, len, retlen, buf); + } else { + /* the size of data remaining on the first page */ + page_size = nor->page_size - page_offset; + nor->write(nor, to, page_size, retlen, buf); + + /* write everything in nor->page_size chunks */ + for (i = page_size; i < len; i += page_size) { + page_size = len - i; + if (page_size > nor->page_size) + page_size = nor->page_size; + + wait_till_ready(nor); + write_enable(nor); + + nor->write(nor, to + i, page_size, retlen, buf + i); + } + } + +write_err: + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_WRITE); + return 0; +} + +static int macronix_quad_enable(struct spi_nor *nor) +{ + int ret, val; + + val = read_sr(nor); + write_enable(nor); + + nor->cmd_buf[0] = val | SR_QUAD_EN_MX; + nor->write_reg(nor, SPINOR_OP_WRSR, nor->cmd_buf, 1, 0); + + if (wait_till_ready(nor)) + return 1; + + ret = read_sr(nor); + if (!(ret > 0 && (ret & SR_QUAD_EN_MX))) { + dev_err(nor->dev, "Macronix Quad bit not set\n"); + return -EINVAL; + } + + return 0; +} + +/* + * Write status Register and configuration register with 2 bytes + * The first byte will be written to the status register, while the + * second byte will be written to the configuration register. + * Return negative if error occured. + */ +static int write_sr_cr(struct spi_nor *nor, u16 val) +{ + nor->cmd_buf[0] = val & 0xff; + nor->cmd_buf[1] = (val >> 8); + + return nor->write_reg(nor, SPINOR_OP_WRSR, nor->cmd_buf, 2, 0); +} + +static int spansion_quad_enable(struct spi_nor *nor) +{ + int ret; + int quad_en = CR_QUAD_EN_SPAN << 8; + + write_enable(nor); + + ret = write_sr_cr(nor, quad_en); + if (ret < 0) { + dev_err(nor->dev, + "error while writing configuration register\n"); + return -EINVAL; + } + + /* read back and check it */ + ret = read_cr(nor); + if (!(ret > 0 && (ret & CR_QUAD_EN_SPAN))) { + dev_err(nor->dev, "Spansion Quad bit not set\n"); + return -EINVAL; + } + + return 0; +} + +static int set_quad_mode(struct spi_nor *nor, u32 jedec_id) +{ + int status; + + switch (JEDEC_MFR(jedec_id)) { + case CFI_MFR_MACRONIX: + status = macronix_quad_enable(nor); + if (status) { + dev_err(nor->dev, "Macronix quad-read not enabled\n"); + return -EINVAL; + } + return status; + default: + status = spansion_quad_enable(nor); + if (status) { + dev_err(nor->dev, "Spansion quad-read not enabled\n"); + return -EINVAL; + } + return status; + } +} + +static int spi_nor_check(struct spi_nor *nor) +{ + if (!nor->dev || !nor->read || !nor->write || + !nor->read_reg || !nor->write_reg || !nor->erase) { + pr_err("spi-nor: please fill all the necessary fields!\n"); + return -EINVAL; + } + + if (!nor->read_id) + nor->read_id = spi_nor_read_id; + if (!nor->wait_till_ready) + nor->wait_till_ready = spi_nor_wait_till_ready; + + return 0; +} + +int spi_nor_scan(struct spi_nor *nor, const struct spi_device_id *id, + enum read_mode mode) +{ + struct flash_info *info; + struct flash_platform_data *data; + struct device *dev = nor->dev; + struct mtd_info *mtd = nor->mtd; + struct device_node *np = dev->of_node; + int ret; + int i; + + ret = spi_nor_check(nor); + if (ret) + return ret; + + /* Platform data helps sort out which chip type we have, as + * well as how this board partitions it. If we don't have + * a chip ID, try the JEDEC id commands; they'll work for most + * newer chips, even if we don't recognize the particular chip. + */ + data = dev_get_platdata(dev); + if (data && data->type) { + const struct spi_device_id *plat_id; + + for (i = 0; i < ARRAY_SIZE(spi_nor_ids) - 1; i++) { + plat_id = &spi_nor_ids[i]; + if (strcmp(data->type, plat_id->name)) + continue; + break; + } + + if (i < ARRAY_SIZE(spi_nor_ids) - 1) + id = plat_id; + else + dev_warn(dev, "unrecognized id %s\n", data->type); + } + + info = (void *)id->driver_data; + + if (info->jedec_id) { + const struct spi_device_id *jid; + + jid = jedec_probe(nor); + if (IS_ERR(jid)) { + return PTR_ERR(jid); + } else if (jid != id) { + /* + * JEDEC knows better, so overwrite platform ID. We + * can't trust partitions any longer, but we'll let + * mtd apply them anyway, since some partitions may be + * marked read-only, and we don't want to lose that + * information, even if it's not 100% accurate. + */ + dev_warn(dev, "found %s, expected %s\n", + jid->name, id->name); + id = jid; + info = (void *)jid->driver_data; + } + } + + mutex_init(&nor->lock); + + /* + * Atmel, SST and Intel/Numonyx serial nor tend to power + * up with the software protection bits set + */ + + if (JEDEC_MFR(info->jedec_id) == CFI_MFR_ATMEL || + JEDEC_MFR(info->jedec_id) == CFI_MFR_INTEL || + JEDEC_MFR(info->jedec_id) == CFI_MFR_SST) { + write_enable(nor); + write_sr(nor, 0); + } + + if (data && data->name) + mtd->name = data->name; + else + mtd->name = dev_name(dev); + + mtd->type = MTD_NORFLASH; + mtd->writesize = 1; + mtd->flags = MTD_CAP_NORFLASH; + mtd->size = info->sector_size * info->n_sectors; + mtd->_erase = spi_nor_erase; + mtd->_read = spi_nor_read; + + /* nor protection support for STmicro chips */ + if (JEDEC_MFR(info->jedec_id) == CFI_MFR_ST) { + mtd->_lock = spi_nor_lock; + mtd->_unlock = spi_nor_unlock; + } + + /* sst nor chips use AAI word program */ + if (info->flags & SST_WRITE) + mtd->_write = sst_write; + else + mtd->_write = spi_nor_write; + + /* prefer "small sector" erase if possible */ + if (info->flags & SECT_4K) { + nor->erase_opcode = SPINOR_OP_BE_4K; + mtd->erasesize = 4096; + } else if (info->flags & SECT_4K_PMC) { + nor->erase_opcode = SPINOR_OP_BE_4K_PMC; + mtd->erasesize = 4096; + } else { + nor->erase_opcode = SPINOR_OP_SE; + mtd->erasesize = info->sector_size; + } + + if (info->flags & SPI_NOR_NO_ERASE) + mtd->flags |= MTD_NO_ERASE; + + mtd->dev.parent = dev; + nor->page_size = info->page_size; + mtd->writebufsize = nor->page_size; + + if (np) { + /* If we were instantiated by DT, use it */ + if (of_property_read_bool(np, "m25p,fast-read")) + nor->flash_read = SPI_NOR_FAST; + else + nor->flash_read = SPI_NOR_NORMAL; + } else { + /* If we weren't instantiated by DT, default to fast-read */ + nor->flash_read = SPI_NOR_FAST; + } + + /* Some devices cannot do fast-read, no matter what DT tells us */ + if (info->flags & SPI_NOR_NO_FR) + nor->flash_read = SPI_NOR_NORMAL; + + /* Quad/Dual-read mode takes precedence over fast/normal */ + if (mode == SPI_NOR_QUAD && info->flags & SPI_NOR_QUAD_READ) { + ret = set_quad_mode(nor, info->jedec_id); + if (ret) { + dev_err(dev, "quad mode not supported\n"); + return ret; + } + nor->flash_read = SPI_NOR_QUAD; + } else if (mode == SPI_NOR_DUAL && info->flags & SPI_NOR_DUAL_READ) { + nor->flash_read = SPI_NOR_DUAL; + } + + /* Default commands */ + switch (nor->flash_read) { + case SPI_NOR_QUAD: + nor->read_opcode = SPINOR_OP_READ_1_1_4; + break; + case SPI_NOR_DUAL: + nor->read_opcode = SPINOR_OP_READ_1_1_2; + break; + case SPI_NOR_FAST: + nor->read_opcode = SPINOR_OP_READ_FAST; + break; + case SPI_NOR_NORMAL: + nor->read_opcode = SPINOR_OP_READ; + break; + default: + dev_err(dev, "No Read opcode defined\n"); + return -EINVAL; + } + + nor->program_opcode = SPINOR_OP_PP; + + if (info->addr_width) + nor->addr_width = info->addr_width; + else if (mtd->size > 0x1000000) { + /* enable 4-byte addressing if the device exceeds 16MiB */ + nor->addr_width = 4; + if (JEDEC_MFR(info->jedec_id) == CFI_MFR_AMD) { + /* Dedicated 4-byte command set */ + switch (nor->flash_read) { + case SPI_NOR_QUAD: + nor->read_opcode = SPINOR_OP_READ4_1_1_4; + break; + case SPI_NOR_DUAL: + nor->read_opcode = SPINOR_OP_READ4_1_1_2; + break; + case SPI_NOR_FAST: + nor->read_opcode = SPINOR_OP_READ4_FAST; + break; + case SPI_NOR_NORMAL: + nor->read_opcode = SPINOR_OP_READ4; + break; + } + nor->program_opcode = SPINOR_OP_PP_4B; + /* No small sector erase for 4-byte command set */ + nor->erase_opcode = SPINOR_OP_SE_4B; + mtd->erasesize = info->sector_size; + } else + set_4byte(nor, info->jedec_id, 1); + } else { + nor->addr_width = 3; + } + + nor->read_dummy = spi_nor_read_dummy_cycles(nor); + + dev_info(dev, "%s (%lld Kbytes)\n", id->name, + (long long)mtd->size >> 10); + + dev_dbg(dev, + "mtd .name = %s, .size = 0x%llx (%lldMiB), " + ".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n", + mtd->name, (long long)mtd->size, (long long)(mtd->size >> 20), + mtd->erasesize, mtd->erasesize / 1024, mtd->numeraseregions); + + if (mtd->numeraseregions) + for (i = 0; i < mtd->numeraseregions; i++) + dev_dbg(dev, + "mtd.eraseregions[%d] = { .offset = 0x%llx, " + ".erasesize = 0x%.8x (%uKiB), " + ".numblocks = %d }\n", + i, (long long)mtd->eraseregions[i].offset, + mtd->eraseregions[i].erasesize, + mtd->eraseregions[i].erasesize / 1024, + mtd->eraseregions[i].numblocks); + return 0; +} +EXPORT_SYMBOL_GPL(spi_nor_scan); + +const struct spi_device_id *spi_nor_match_id(char *name) +{ + const struct spi_device_id *id = spi_nor_ids; + + while (id->name[0]) { + if (!strcmp(name, id->name)) + return id; + id++; + } + return NULL; +} +EXPORT_SYMBOL_GPL(spi_nor_match_id); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Huang Shijie <shijie8@gmail.com>"); +MODULE_AUTHOR("Mike Lavender"); +MODULE_DESCRIPTION("framework for SPI NOR"); diff --git a/drivers/mtd/ssfdc.c b/drivers/mtd/ssfdc.c index 79d3bb659bf..daf82ba7aba 100644 --- a/drivers/mtd/ssfdc.c +++ b/drivers/mtd/ssfdc.c @@ -1,6 +1,6 @@ /* * Linux driver for SSFDC Flash Translation Layer (Read only) - * (c) 2005 Eptar srl + * © 2005 Eptar srl * Author: Claudio Lanconelli <lanconelli.claudio@eptar.com> * * Based on NTFL and MTDBLOCK_RO drivers @@ -122,9 +122,9 @@ static int get_valid_cis_sector(struct mtd_info *mtd) * is not SSFDC formatted */ for (k = 0, offset = 0; k < 4; k++, offset += mtd->erasesize) { - if (!mtd->block_isbad(mtd, offset)) { - ret = mtd->read(mtd, offset, SECTOR_SIZE, &retlen, - sect_buf); + if (mtd_block_isbad(mtd, offset)) { + ret = mtd_read(mtd, offset, SECTOR_SIZE, &retlen, + sect_buf); /* CIS pattern match on the sector buffer */ if (ret < 0 || retlen != SECTOR_SIZE) { @@ -135,8 +135,7 @@ static int get_valid_cis_sector(struct mtd_info *mtd) /* Found */ cis_sector = (int)(offset >> SECTOR_SHIFT); } else { - DEBUG(MTD_DEBUG_LEVEL1, - "SSFDC_RO: CIS/IDI sector not found" + pr_debug("SSFDC_RO: CIS/IDI sector not found" " on %s (mtd%d)\n", mtd->name, mtd->index); } @@ -157,7 +156,7 @@ static int read_physical_sector(struct mtd_info *mtd, uint8_t *sect_buf, size_t retlen; loff_t offset = (loff_t)sect_no << SECTOR_SHIFT; - ret = mtd->read(mtd, offset, SECTOR_SIZE, &retlen, sect_buf); + ret = mtd_read(mtd, offset, SECTOR_SIZE, &retlen, sect_buf); if (ret < 0 || retlen != SECTOR_SIZE) return -1; @@ -170,15 +169,14 @@ static int read_raw_oob(struct mtd_info *mtd, loff_t offs, uint8_t *buf) struct mtd_oob_ops ops; int ret; - ops.mode = MTD_OOB_RAW; + ops.mode = MTD_OPS_RAW; ops.ooboffs = 0; - ops.ooblen = mtd->oobsize; - ops.len = OOB_SIZE; + ops.ooblen = OOB_SIZE; ops.oobbuf = buf; ops.datbuf = NULL; - ret = mtd->read_oob(mtd, offs, &ops); - if (ret < 0 || ops.retlen != OOB_SIZE) + ret = mtd_read_oob(mtd, offs, &ops); + if (ret < 0 || ops.oobretlen != OOB_SIZE) return -1; return 0; @@ -222,8 +220,7 @@ static int get_logical_address(uint8_t *oob_buf) block_address >>= 1; if (get_parity(block_address, 10) != parity) { - DEBUG(MTD_DEBUG_LEVEL0, - "SSFDC_RO: logical address field%d" + pr_debug("SSFDC_RO: logical address field%d" "parity error(0x%04X)\n", j+1, block_address); } else { @@ -236,7 +233,7 @@ static int get_logical_address(uint8_t *oob_buf) if (!ok) block_address = -2; - DEBUG(MTD_DEBUG_LEVEL3, "SSFDC_RO: get_logical_address() %d\n", + pr_debug("SSFDC_RO: get_logical_address() %d\n", block_address); return block_address; @@ -250,7 +247,7 @@ static int build_logical_block_map(struct ssfdcr_record *ssfdc) int ret, block_address, phys_block; struct mtd_info *mtd = ssfdc->mbd.mtd; - DEBUG(MTD_DEBUG_LEVEL1, "SSFDC_RO: build_block_map() nblks=%d (%luK)\n", + pr_debug("SSFDC_RO: build_block_map() nblks=%d (%luK)\n", ssfdc->map_len, (unsigned long)ssfdc->map_len * ssfdc->erase_size / 1024); @@ -258,13 +255,12 @@ static int build_logical_block_map(struct ssfdcr_record *ssfdc) for (phys_block = ssfdc->cis_block + 1; phys_block < ssfdc->map_len; phys_block++) { offset = (unsigned long)phys_block * ssfdc->erase_size; - if (mtd->block_isbad(mtd, offset)) + if (mtd_block_isbad(mtd, offset)) continue; /* skip bad blocks */ ret = read_raw_oob(mtd, offset, oob_buf); if (ret < 0) { - DEBUG(MTD_DEBUG_LEVEL0, - "SSFDC_RO: mtd read_oob() failed at %lu\n", + pr_debug("SSFDC_RO: mtd read_oob() failed at %lu\n", offset); return -1; } @@ -280,8 +276,7 @@ static int build_logical_block_map(struct ssfdcr_record *ssfdc) ssfdc->logic_block_map[block_address] = (unsigned short)phys_block; - DEBUG(MTD_DEBUG_LEVEL2, - "SSFDC_RO: build_block_map() phys_block=%d," + pr_debug("SSFDC_RO: build_block_map() phys_block=%d," "logic_block_addr=%d, zone=%d\n", phys_block, block_address, zone_index); } @@ -295,7 +290,8 @@ static void ssfdcr_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) int cis_sector; /* Check for small page NAND flash */ - if (mtd->type != MTD_NANDFLASH || mtd->oobsize != OOB_SIZE) + if (!mtd_type_is_nand(mtd) || mtd->oobsize != OOB_SIZE || + mtd->size > UINT_MAX) return; /* Check for SSDFC format by reading CIS/IDI sector */ @@ -304,36 +300,30 @@ static void ssfdcr_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) return; ssfdc = kzalloc(sizeof(struct ssfdcr_record), GFP_KERNEL); - if (!ssfdc) { - printk(KERN_WARNING - "SSFDC_RO: out of memory for data structures\n"); + if (!ssfdc) return; - } ssfdc->mbd.mtd = mtd; ssfdc->mbd.devnum = -1; - ssfdc->mbd.blksize = SECTOR_SIZE; ssfdc->mbd.tr = tr; ssfdc->mbd.readonly = 1; ssfdc->cis_block = cis_sector / (mtd->erasesize >> SECTOR_SHIFT); ssfdc->erase_size = mtd->erasesize; - ssfdc->map_len = mtd->size / mtd->erasesize; + ssfdc->map_len = (u32)mtd->size / mtd->erasesize; - DEBUG(MTD_DEBUG_LEVEL1, - "SSFDC_RO: cis_block=%d,erase_size=%d,map_len=%d,n_zones=%d\n", + pr_debug("SSFDC_RO: cis_block=%d,erase_size=%d,map_len=%d,n_zones=%d\n", ssfdc->cis_block, ssfdc->erase_size, ssfdc->map_len, - (ssfdc->map_len + MAX_PHYS_BLK_PER_ZONE - 1) / - MAX_PHYS_BLK_PER_ZONE); + DIV_ROUND_UP(ssfdc->map_len, MAX_PHYS_BLK_PER_ZONE)); /* Set geometry */ ssfdc->heads = 16; ssfdc->sectors = 32; get_chs(mtd->size, NULL, &ssfdc->heads, &ssfdc->sectors); - ssfdc->cylinders = (unsigned short)((mtd->size >> SECTOR_SHIFT) / + ssfdc->cylinders = (unsigned short)(((u32)mtd->size >> SECTOR_SHIFT) / ((long)ssfdc->sectors * (long)ssfdc->heads)); - DEBUG(MTD_DEBUG_LEVEL1, "SSFDC_RO: using C:%d H:%d S:%d == %ld sects\n", + pr_debug("SSFDC_RO: using C:%d H:%d S:%d == %ld sects\n", ssfdc->cylinders, ssfdc->heads , ssfdc->sectors, (long)ssfdc->cylinders * (long)ssfdc->heads * (long)ssfdc->sectors); @@ -344,11 +334,8 @@ static void ssfdcr_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) /* Allocate logical block map */ ssfdc->logic_block_map = kmalloc(sizeof(ssfdc->logic_block_map[0]) * ssfdc->map_len, GFP_KERNEL); - if (!ssfdc->logic_block_map) { - printk(KERN_WARNING - "SSFDC_RO: out of memory for data structures\n"); + if (!ssfdc->logic_block_map) goto out_err; - } memset(ssfdc->logic_block_map, 0xff, sizeof(ssfdc->logic_block_map[0]) * ssfdc->map_len); @@ -373,11 +360,10 @@ static void ssfdcr_remove_dev(struct mtd_blktrans_dev *dev) { struct ssfdcr_record *ssfdc = (struct ssfdcr_record *)dev; - DEBUG(MTD_DEBUG_LEVEL1, "SSFDC_RO: remove_dev (i=%d)\n", dev->devnum); + pr_debug("SSFDC_RO: remove_dev (i=%d)\n", dev->devnum); del_mtd_blktrans_dev(dev); kfree(ssfdc->logic_block_map); - kfree(ssfdc); } static int ssfdcr_readsect(struct mtd_blktrans_dev *dev, @@ -390,8 +376,7 @@ static int ssfdcr_readsect(struct mtd_blktrans_dev *dev, offset = (int)(logic_sect_no % sectors_per_block); block_address = (int)(logic_sect_no / sectors_per_block); - DEBUG(MTD_DEBUG_LEVEL3, - "SSFDC_RO: ssfdcr_readsect(%lu) sec_per_blk=%d, ofst=%d," + pr_debug("SSFDC_RO: ssfdcr_readsect(%lu) sec_per_blk=%d, ofst=%d," " block_addr=%d\n", logic_sect_no, sectors_per_block, offset, block_address); @@ -400,8 +385,7 @@ static int ssfdcr_readsect(struct mtd_blktrans_dev *dev, block_address = ssfdc->logic_block_map[block_address]; - DEBUG(MTD_DEBUG_LEVEL3, - "SSFDC_RO: ssfdcr_readsect() phys_block_addr=%d\n", + pr_debug("SSFDC_RO: ssfdcr_readsect() phys_block_addr=%d\n", block_address); if (block_address < 0xffff) { @@ -410,8 +394,7 @@ static int ssfdcr_readsect(struct mtd_blktrans_dev *dev, sect_no = (unsigned long)block_address * sectors_per_block + offset; - DEBUG(MTD_DEBUG_LEVEL3, - "SSFDC_RO: ssfdcr_readsect() phys_sect_no=%lu\n", + pr_debug("SSFDC_RO: ssfdcr_readsect() phys_sect_no=%lu\n", sect_no); if (read_physical_sector(ssfdc->mbd.mtd, buf, sect_no) < 0) @@ -427,7 +410,7 @@ static int ssfdcr_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo) { struct ssfdcr_record *ssfdc = (struct ssfdcr_record *)dev; - DEBUG(MTD_DEBUG_LEVEL1, "SSFDC_RO: ssfdcr_getgeo() C=%d, H=%d, S=%d\n", + pr_debug("SSFDC_RO: ssfdcr_getgeo() C=%d, H=%d, S=%d\n", ssfdc->cylinders, ssfdc->heads, ssfdc->sectors); geo->heads = ssfdc->heads; @@ -447,6 +430,7 @@ static struct mtd_blktrans_ops ssfdcr_tr = { .name = "ssfdc", .major = SSFDCR_MAJOR, .part_bits = SSFDCR_PARTN_BITS, + .blksize = SECTOR_SIZE, .getgeo = ssfdcr_getgeo, .readsect = ssfdcr_readsect, .add_mtd = ssfdcr_add_mtd, diff --git a/drivers/mtd/tests/Makefile b/drivers/mtd/tests/Makefile new file mode 100644 index 00000000000..937a829bb70 --- /dev/null +++ b/drivers/mtd/tests/Makefile @@ -0,0 +1,18 @@ +obj-$(CONFIG_MTD_TESTS) += mtd_oobtest.o +obj-$(CONFIG_MTD_TESTS) += mtd_pagetest.o +obj-$(CONFIG_MTD_TESTS) += mtd_readtest.o +obj-$(CONFIG_MTD_TESTS) += mtd_speedtest.o +obj-$(CONFIG_MTD_TESTS) += mtd_stresstest.o +obj-$(CONFIG_MTD_TESTS) += mtd_subpagetest.o +obj-$(CONFIG_MTD_TESTS) += mtd_torturetest.o +obj-$(CONFIG_MTD_TESTS) += mtd_nandecctest.o +obj-$(CONFIG_MTD_TESTS) += mtd_nandbiterrs.o + +mtd_oobtest-objs := oobtest.o mtd_test.o +mtd_pagetest-objs := pagetest.o mtd_test.o +mtd_readtest-objs := readtest.o mtd_test.o +mtd_speedtest-objs := speedtest.o mtd_test.o +mtd_stresstest-objs := stresstest.o mtd_test.o +mtd_subpagetest-objs := subpagetest.o mtd_test.o +mtd_torturetest-objs := torturetest.o mtd_test.o +mtd_nandbiterrs-objs := nandbiterrs.o mtd_test.o diff --git a/drivers/mtd/tests/mtd_nandecctest.c b/drivers/mtd/tests/mtd_nandecctest.c new file mode 100644 index 00000000000..e579f9027c4 --- /dev/null +++ b/drivers/mtd/tests/mtd_nandecctest.c @@ -0,0 +1,316 @@ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/list.h> +#include <linux/random.h> +#include <linux/string.h> +#include <linux/bitops.h> +#include <linux/slab.h> +#include <linux/mtd/nand_ecc.h> + +/* + * Test the implementation for software ECC + * + * No actual MTD device is needed, So we don't need to warry about losing + * important data by human error. + * + * This covers possible patterns of corruption which can be reliably corrected + * or detected. + */ + +#if IS_ENABLED(CONFIG_MTD_NAND) + +struct nand_ecc_test { + const char *name; + void (*prepare)(void *, void *, void *, void *, const size_t); + int (*verify)(void *, void *, void *, const size_t); +}; + +/* + * The reason for this __change_bit_le() instead of __change_bit() is to inject + * bit error properly within the region which is not a multiple of + * sizeof(unsigned long) on big-endian systems + */ +#ifdef __LITTLE_ENDIAN +#define __change_bit_le(nr, addr) __change_bit(nr, addr) +#elif defined(__BIG_ENDIAN) +#define __change_bit_le(nr, addr) \ + __change_bit((nr) ^ ((BITS_PER_LONG - 1) & ~0x7), addr) +#else +#error "Unknown byte order" +#endif + +static void single_bit_error_data(void *error_data, void *correct_data, + size_t size) +{ + unsigned int offset = prandom_u32() % (size * BITS_PER_BYTE); + + memcpy(error_data, correct_data, size); + __change_bit_le(offset, error_data); +} + +static void double_bit_error_data(void *error_data, void *correct_data, + size_t size) +{ + unsigned int offset[2]; + + offset[0] = prandom_u32() % (size * BITS_PER_BYTE); + do { + offset[1] = prandom_u32() % (size * BITS_PER_BYTE); + } while (offset[0] == offset[1]); + + memcpy(error_data, correct_data, size); + + __change_bit_le(offset[0], error_data); + __change_bit_le(offset[1], error_data); +} + +static unsigned int random_ecc_bit(size_t size) +{ + unsigned int offset = prandom_u32() % (3 * BITS_PER_BYTE); + + if (size == 256) { + /* + * Don't inject a bit error into the insignificant bits (16th + * and 17th bit) in ECC code for 256 byte data block + */ + while (offset == 16 || offset == 17) + offset = prandom_u32() % (3 * BITS_PER_BYTE); + } + + return offset; +} + +static void single_bit_error_ecc(void *error_ecc, void *correct_ecc, + size_t size) +{ + unsigned int offset = random_ecc_bit(size); + + memcpy(error_ecc, correct_ecc, 3); + __change_bit_le(offset, error_ecc); +} + +static void double_bit_error_ecc(void *error_ecc, void *correct_ecc, + size_t size) +{ + unsigned int offset[2]; + + offset[0] = random_ecc_bit(size); + do { + offset[1] = random_ecc_bit(size); + } while (offset[0] == offset[1]); + + memcpy(error_ecc, correct_ecc, 3); + __change_bit_le(offset[0], error_ecc); + __change_bit_le(offset[1], error_ecc); +} + +static void no_bit_error(void *error_data, void *error_ecc, + void *correct_data, void *correct_ecc, const size_t size) +{ + memcpy(error_data, correct_data, size); + memcpy(error_ecc, correct_ecc, 3); +} + +static int no_bit_error_verify(void *error_data, void *error_ecc, + void *correct_data, const size_t size) +{ + unsigned char calc_ecc[3]; + int ret; + + __nand_calculate_ecc(error_data, size, calc_ecc); + ret = __nand_correct_data(error_data, error_ecc, calc_ecc, size); + if (ret == 0 && !memcmp(correct_data, error_data, size)) + return 0; + + return -EINVAL; +} + +static void single_bit_error_in_data(void *error_data, void *error_ecc, + void *correct_data, void *correct_ecc, const size_t size) +{ + single_bit_error_data(error_data, correct_data, size); + memcpy(error_ecc, correct_ecc, 3); +} + +static void single_bit_error_in_ecc(void *error_data, void *error_ecc, + void *correct_data, void *correct_ecc, const size_t size) +{ + memcpy(error_data, correct_data, size); + single_bit_error_ecc(error_ecc, correct_ecc, size); +} + +static int single_bit_error_correct(void *error_data, void *error_ecc, + void *correct_data, const size_t size) +{ + unsigned char calc_ecc[3]; + int ret; + + __nand_calculate_ecc(error_data, size, calc_ecc); + ret = __nand_correct_data(error_data, error_ecc, calc_ecc, size); + if (ret == 1 && !memcmp(correct_data, error_data, size)) + return 0; + + return -EINVAL; +} + +static void double_bit_error_in_data(void *error_data, void *error_ecc, + void *correct_data, void *correct_ecc, const size_t size) +{ + double_bit_error_data(error_data, correct_data, size); + memcpy(error_ecc, correct_ecc, 3); +} + +static void single_bit_error_in_data_and_ecc(void *error_data, void *error_ecc, + void *correct_data, void *correct_ecc, const size_t size) +{ + single_bit_error_data(error_data, correct_data, size); + single_bit_error_ecc(error_ecc, correct_ecc, size); +} + +static void double_bit_error_in_ecc(void *error_data, void *error_ecc, + void *correct_data, void *correct_ecc, const size_t size) +{ + memcpy(error_data, correct_data, size); + double_bit_error_ecc(error_ecc, correct_ecc, size); +} + +static int double_bit_error_detect(void *error_data, void *error_ecc, + void *correct_data, const size_t size) +{ + unsigned char calc_ecc[3]; + int ret; + + __nand_calculate_ecc(error_data, size, calc_ecc); + ret = __nand_correct_data(error_data, error_ecc, calc_ecc, size); + + return (ret == -1) ? 0 : -EINVAL; +} + +static const struct nand_ecc_test nand_ecc_test[] = { + { + .name = "no-bit-error", + .prepare = no_bit_error, + .verify = no_bit_error_verify, + }, + { + .name = "single-bit-error-in-data-correct", + .prepare = single_bit_error_in_data, + .verify = single_bit_error_correct, + }, + { + .name = "single-bit-error-in-ecc-correct", + .prepare = single_bit_error_in_ecc, + .verify = single_bit_error_correct, + }, + { + .name = "double-bit-error-in-data-detect", + .prepare = double_bit_error_in_data, + .verify = double_bit_error_detect, + }, + { + .name = "single-bit-error-in-data-and-ecc-detect", + .prepare = single_bit_error_in_data_and_ecc, + .verify = double_bit_error_detect, + }, + { + .name = "double-bit-error-in-ecc-detect", + .prepare = double_bit_error_in_ecc, + .verify = double_bit_error_detect, + }, +}; + +static void dump_data_ecc(void *error_data, void *error_ecc, void *correct_data, + void *correct_ecc, const size_t size) +{ + pr_info("hexdump of error data:\n"); + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 4, + error_data, size, false); + print_hex_dump(KERN_INFO, "hexdump of error ecc: ", + DUMP_PREFIX_NONE, 16, 1, error_ecc, 3, false); + + pr_info("hexdump of correct data:\n"); + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 4, + correct_data, size, false); + print_hex_dump(KERN_INFO, "hexdump of correct ecc: ", + DUMP_PREFIX_NONE, 16, 1, correct_ecc, 3, false); +} + +static int nand_ecc_test_run(const size_t size) +{ + int i; + int err = 0; + void *error_data; + void *error_ecc; + void *correct_data; + void *correct_ecc; + + error_data = kmalloc(size, GFP_KERNEL); + error_ecc = kmalloc(3, GFP_KERNEL); + correct_data = kmalloc(size, GFP_KERNEL); + correct_ecc = kmalloc(3, GFP_KERNEL); + + if (!error_data || !error_ecc || !correct_data || !correct_ecc) { + err = -ENOMEM; + goto error; + } + + prandom_bytes(correct_data, size); + __nand_calculate_ecc(correct_data, size, correct_ecc); + + for (i = 0; i < ARRAY_SIZE(nand_ecc_test); i++) { + nand_ecc_test[i].prepare(error_data, error_ecc, + correct_data, correct_ecc, size); + err = nand_ecc_test[i].verify(error_data, error_ecc, + correct_data, size); + + if (err) { + pr_err("not ok - %s-%zd\n", + nand_ecc_test[i].name, size); + dump_data_ecc(error_data, error_ecc, + correct_data, correct_ecc, size); + break; + } + pr_info("ok - %s-%zd\n", + nand_ecc_test[i].name, size); + } +error: + kfree(error_data); + kfree(error_ecc); + kfree(correct_data); + kfree(correct_ecc); + + return err; +} + +#else + +static int nand_ecc_test_run(const size_t size) +{ + return 0; +} + +#endif + +static int __init ecc_test_init(void) +{ + int err; + + err = nand_ecc_test_run(256); + if (err) + return err; + + return nand_ecc_test_run(512); +} + +static void __exit ecc_test_exit(void) +{ +} + +module_init(ecc_test_init); +module_exit(ecc_test_exit); + +MODULE_DESCRIPTION("NAND ECC function test module"); +MODULE_AUTHOR("Akinobu Mita"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/tests/mtd_test.c b/drivers/mtd/tests/mtd_test.c new file mode 100644 index 00000000000..111ee46a742 --- /dev/null +++ b/drivers/mtd/tests/mtd_test.c @@ -0,0 +1,113 @@ +#define pr_fmt(fmt) "mtd_test: " fmt + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/printk.h> + +#include "mtd_test.h" + +int mtdtest_erase_eraseblock(struct mtd_info *mtd, unsigned int ebnum) +{ + int err; + struct erase_info ei; + loff_t addr = ebnum * mtd->erasesize; + + memset(&ei, 0, sizeof(struct erase_info)); + ei.mtd = mtd; + ei.addr = addr; + ei.len = mtd->erasesize; + + err = mtd_erase(mtd, &ei); + if (err) { + pr_info("error %d while erasing EB %d\n", err, ebnum); + return err; + } + + if (ei.state == MTD_ERASE_FAILED) { + pr_info("some erase error occurred at EB %d\n", ebnum); + return -EIO; + } + return 0; +} + +static int is_block_bad(struct mtd_info *mtd, unsigned int ebnum) +{ + int ret; + loff_t addr = ebnum * mtd->erasesize; + + ret = mtd_block_isbad(mtd, addr); + if (ret) + pr_info("block %d is bad\n", ebnum); + + return ret; +} + +int mtdtest_scan_for_bad_eraseblocks(struct mtd_info *mtd, unsigned char *bbt, + unsigned int eb, int ebcnt) +{ + int i, bad = 0; + + if (!mtd_can_have_bb(mtd)) + return 0; + + pr_info("scanning for bad eraseblocks\n"); + for (i = 0; i < ebcnt; ++i) { + bbt[i] = is_block_bad(mtd, eb + i) ? 1 : 0; + if (bbt[i]) + bad += 1; + cond_resched(); + } + pr_info("scanned %d eraseblocks, %d are bad\n", i, bad); + + return 0; +} + +int mtdtest_erase_good_eraseblocks(struct mtd_info *mtd, unsigned char *bbt, + unsigned int eb, int ebcnt) +{ + int err; + unsigned int i; + + for (i = 0; i < ebcnt; ++i) { + if (bbt[i]) + continue; + err = mtdtest_erase_eraseblock(mtd, eb + i); + if (err) + return err; + cond_resched(); + } + + return 0; +} + +int mtdtest_read(struct mtd_info *mtd, loff_t addr, size_t size, void *buf) +{ + size_t read; + int err; + + err = mtd_read(mtd, addr, size, &read, buf); + /* Ignore corrected ECC errors */ + if (mtd_is_bitflip(err)) + err = 0; + if (!err && read != size) + err = -EIO; + if (err) + pr_err("error: read failed at %#llx\n", addr); + + return err; +} + +int mtdtest_write(struct mtd_info *mtd, loff_t addr, size_t size, + const void *buf) +{ + size_t written; + int err; + + err = mtd_write(mtd, addr, size, &written, buf); + if (!err && written != size) + err = -EIO; + if (err) + pr_err("error: write failed at %#llx\n", addr); + + return err; +} diff --git a/drivers/mtd/tests/mtd_test.h b/drivers/mtd/tests/mtd_test.h new file mode 100644 index 00000000000..f437c776c54 --- /dev/null +++ b/drivers/mtd/tests/mtd_test.h @@ -0,0 +1,11 @@ +#include <linux/mtd/mtd.h> + +int mtdtest_erase_eraseblock(struct mtd_info *mtd, unsigned int ebnum); +int mtdtest_scan_for_bad_eraseblocks(struct mtd_info *mtd, unsigned char *bbt, + unsigned int eb, int ebcnt); +int mtdtest_erase_good_eraseblocks(struct mtd_info *mtd, unsigned char *bbt, + unsigned int eb, int ebcnt); + +int mtdtest_read(struct mtd_info *mtd, loff_t addr, size_t size, void *buf); +int mtdtest_write(struct mtd_info *mtd, loff_t addr, size_t size, + const void *buf); diff --git a/drivers/mtd/tests/nandbiterrs.c b/drivers/mtd/tests/nandbiterrs.c new file mode 100644 index 00000000000..6f976159611 --- /dev/null +++ b/drivers/mtd/tests/nandbiterrs.c @@ -0,0 +1,428 @@ +/* + * Copyright © 2012 NetCommWireless + * Iwo Mergler <Iwo.Mergler@netcommwireless.com.au> + * + * Test for multi-bit error recovery on a NAND page This mostly tests the + * ECC controller / driver. + * + * There are two test modes: + * + * 0 - artificially inserting bit errors until the ECC fails + * This is the default method and fairly quick. It should + * be independent of the quality of the FLASH. + * + * 1 - re-writing the same pattern repeatedly until the ECC fails. + * This method relies on the physics of NAND FLASH to eventually + * generate '0' bits if '1' has been written sufficient times. + * Depending on the NAND, the first bit errors will appear after + * 1000 or more writes and then will usually snowball, reaching the + * limits of the ECC quickly. + * + * The test stops after 10000 cycles, should your FLASH be + * exceptionally good and not generate bit errors before that. Try + * a different page in that case. + * + * Please note that neither of these tests will significantly 'use up' any + * FLASH endurance. Only a maximum of two erase operations will be performed. + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; see the file COPYING. If not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/mtd/mtd.h> +#include <linux/err.h> +#include <linux/mtd/nand.h> +#include <linux/slab.h> +#include "mtd_test.h" + +static int dev; +module_param(dev, int, S_IRUGO); +MODULE_PARM_DESC(dev, "MTD device number to use"); + +static unsigned page_offset; +module_param(page_offset, uint, S_IRUGO); +MODULE_PARM_DESC(page_offset, "Page number relative to dev start"); + +static unsigned seed; +module_param(seed, uint, S_IRUGO); +MODULE_PARM_DESC(seed, "Random seed"); + +static int mode; +module_param(mode, int, S_IRUGO); +MODULE_PARM_DESC(mode, "0=incremental errors, 1=overwrite test"); + +static unsigned max_overwrite = 10000; + +static loff_t offset; /* Offset of the page we're using. */ +static unsigned eraseblock; /* Eraseblock number for our page. */ + +/* We assume that the ECC can correct up to a certain number + * of biterrors per subpage. */ +static unsigned subsize; /* Size of subpages */ +static unsigned subcount; /* Number of subpages per page */ + +static struct mtd_info *mtd; /* MTD device */ + +static uint8_t *wbuffer; /* One page write / compare buffer */ +static uint8_t *rbuffer; /* One page read buffer */ + +/* 'random' bytes from known offsets */ +static uint8_t hash(unsigned offset) +{ + unsigned v = offset; + unsigned char c; + v ^= 0x7f7edfd3; + v = v ^ (v >> 3); + v = v ^ (v >> 5); + v = v ^ (v >> 13); + c = v & 0xFF; + /* Reverse bits of result. */ + c = (c & 0x0F) << 4 | (c & 0xF0) >> 4; + c = (c & 0x33) << 2 | (c & 0xCC) >> 2; + c = (c & 0x55) << 1 | (c & 0xAA) >> 1; + return c; +} + +/* Writes wbuffer to page */ +static int write_page(int log) +{ + if (log) + pr_info("write_page\n"); + + return mtdtest_write(mtd, offset, mtd->writesize, wbuffer); +} + +/* Re-writes the data area while leaving the OOB alone. */ +static int rewrite_page(int log) +{ + int err = 0; + struct mtd_oob_ops ops; + + if (log) + pr_info("rewrite page\n"); + + ops.mode = MTD_OPS_RAW; /* No ECC */ + ops.len = mtd->writesize; + ops.retlen = 0; + ops.ooblen = 0; + ops.oobretlen = 0; + ops.ooboffs = 0; + ops.datbuf = wbuffer; + ops.oobbuf = NULL; + + err = mtd_write_oob(mtd, offset, &ops); + if (err || ops.retlen != mtd->writesize) { + pr_err("error: write_oob failed (%d)\n", err); + if (!err) + err = -EIO; + } + + return err; +} + +/* Reads page into rbuffer. Returns number of corrected bit errors (>=0) + * or error (<0) */ +static int read_page(int log) +{ + int err = 0; + size_t read; + struct mtd_ecc_stats oldstats; + + if (log) + pr_info("read_page\n"); + + /* Saving last mtd stats */ + memcpy(&oldstats, &mtd->ecc_stats, sizeof(oldstats)); + + err = mtd_read(mtd, offset, mtd->writesize, &read, rbuffer); + if (err == -EUCLEAN) + err = mtd->ecc_stats.corrected - oldstats.corrected; + + if (err < 0 || read != mtd->writesize) { + pr_err("error: read failed at %#llx\n", (long long)offset); + if (err >= 0) + err = -EIO; + } + + return err; +} + +/* Verifies rbuffer against random sequence */ +static int verify_page(int log) +{ + unsigned i, errs = 0; + + if (log) + pr_info("verify_page\n"); + + for (i = 0; i < mtd->writesize; i++) { + if (rbuffer[i] != hash(i+seed)) { + pr_err("Error: page offset %u, expected %02x, got %02x\n", + i, hash(i+seed), rbuffer[i]); + errs++; + } + } + + if (errs) + return -EIO; + else + return 0; +} + +#define CBIT(v, n) ((v) & (1 << (n))) +#define BCLR(v, n) ((v) = (v) & ~(1 << (n))) + +/* Finds the first '1' bit in wbuffer starting at offset 'byte' + * and sets it to '0'. */ +static int insert_biterror(unsigned byte) +{ + int bit; + + while (byte < mtd->writesize) { + for (bit = 7; bit >= 0; bit--) { + if (CBIT(wbuffer[byte], bit)) { + BCLR(wbuffer[byte], bit); + pr_info("Inserted biterror @ %u/%u\n", byte, bit); + return 0; + } + } + byte++; + } + pr_err("biterror: Failed to find a '1' bit\n"); + return -EIO; +} + +/* Writes 'random' data to page and then introduces deliberate bit + * errors into the page, while verifying each step. */ +static int incremental_errors_test(void) +{ + int err = 0; + unsigned i; + unsigned errs_per_subpage = 0; + + pr_info("incremental biterrors test\n"); + + for (i = 0; i < mtd->writesize; i++) + wbuffer[i] = hash(i+seed); + + err = write_page(1); + if (err) + goto exit; + + while (1) { + + err = rewrite_page(1); + if (err) + goto exit; + + err = read_page(1); + if (err > 0) + pr_info("Read reported %d corrected bit errors\n", err); + if (err < 0) { + pr_err("After %d biterrors per subpage, read reported error %d\n", + errs_per_subpage, err); + err = 0; + goto exit; + } + + err = verify_page(1); + if (err) { + pr_err("ECC failure, read data is incorrect despite read success\n"); + goto exit; + } + + pr_info("Successfully corrected %d bit errors per subpage\n", + errs_per_subpage); + + for (i = 0; i < subcount; i++) { + err = insert_biterror(i * subsize); + if (err < 0) + goto exit; + } + errs_per_subpage++; + } + +exit: + return err; +} + + +/* Writes 'random' data to page and then re-writes that same data repeatedly. + This eventually develops bit errors (bits written as '1' will slowly become + '0'), which are corrected as far as the ECC is capable of. */ +static int overwrite_test(void) +{ + int err = 0; + unsigned i; + unsigned max_corrected = 0; + unsigned opno = 0; + /* We don't expect more than this many correctable bit errors per + * page. */ + #define MAXBITS 512 + static unsigned bitstats[MAXBITS]; /* bit error histogram. */ + + memset(bitstats, 0, sizeof(bitstats)); + + pr_info("overwrite biterrors test\n"); + + for (i = 0; i < mtd->writesize; i++) + wbuffer[i] = hash(i+seed); + + err = write_page(1); + if (err) + goto exit; + + while (opno < max_overwrite) { + + err = rewrite_page(0); + if (err) + break; + + err = read_page(0); + if (err >= 0) { + if (err >= MAXBITS) { + pr_info("Implausible number of bit errors corrected\n"); + err = -EIO; + break; + } + bitstats[err]++; + if (err > max_corrected) { + max_corrected = err; + pr_info("Read reported %d corrected bit errors\n", + err); + } + } else { /* err < 0 */ + pr_info("Read reported error %d\n", err); + err = 0; + break; + } + + err = verify_page(0); + if (err) { + bitstats[max_corrected] = opno; + pr_info("ECC failure, read data is incorrect despite read success\n"); + break; + } + + opno++; + } + + /* At this point bitstats[0] contains the number of ops with no bit + * errors, bitstats[1] the number of ops with 1 bit error, etc. */ + pr_info("Bit error histogram (%d operations total):\n", opno); + for (i = 0; i < max_corrected; i++) + pr_info("Page reads with %3d corrected bit errors: %d\n", + i, bitstats[i]); + +exit: + return err; +} + +static int __init mtd_nandbiterrs_init(void) +{ + int err = 0; + + printk("\n"); + printk(KERN_INFO "==================================================\n"); + pr_info("MTD device: %d\n", dev); + + mtd = get_mtd_device(NULL, dev); + if (IS_ERR(mtd)) { + err = PTR_ERR(mtd); + pr_err("error: cannot get MTD device\n"); + goto exit_mtddev; + } + + if (!mtd_type_is_nand(mtd)) { + pr_info("this test requires NAND flash\n"); + err = -ENODEV; + goto exit_nand; + } + + pr_info("MTD device size %llu, eraseblock=%u, page=%u, oob=%u\n", + (unsigned long long)mtd->size, mtd->erasesize, + mtd->writesize, mtd->oobsize); + + subsize = mtd->writesize >> mtd->subpage_sft; + subcount = mtd->writesize / subsize; + + pr_info("Device uses %d subpages of %d bytes\n", subcount, subsize); + + offset = page_offset * mtd->writesize; + eraseblock = mtd_div_by_eb(offset, mtd); + + pr_info("Using page=%u, offset=%llu, eraseblock=%u\n", + page_offset, offset, eraseblock); + + wbuffer = kmalloc(mtd->writesize, GFP_KERNEL); + if (!wbuffer) { + err = -ENOMEM; + goto exit_wbuffer; + } + + rbuffer = kmalloc(mtd->writesize, GFP_KERNEL); + if (!rbuffer) { + err = -ENOMEM; + goto exit_rbuffer; + } + + err = mtdtest_erase_eraseblock(mtd, eraseblock); + if (err) + goto exit_error; + + if (mode == 0) + err = incremental_errors_test(); + else + err = overwrite_test(); + + if (err) + goto exit_error; + + /* We leave the block un-erased in case of test failure. */ + err = mtdtest_erase_eraseblock(mtd, eraseblock); + if (err) + goto exit_error; + + err = -EIO; + pr_info("finished successfully.\n"); + printk(KERN_INFO "==================================================\n"); + +exit_error: + kfree(rbuffer); +exit_rbuffer: + kfree(wbuffer); +exit_wbuffer: + /* Nothing */ +exit_nand: + put_mtd_device(mtd); +exit_mtddev: + return err; +} + +static void __exit mtd_nandbiterrs_exit(void) +{ + return; +} + +module_init(mtd_nandbiterrs_init); +module_exit(mtd_nandbiterrs_exit); + +MODULE_DESCRIPTION("NAND bit error recovery test"); +MODULE_AUTHOR("Iwo Mergler"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/tests/oobtest.c b/drivers/mtd/tests/oobtest.c new file mode 100644 index 00000000000..f19ab1acde1 --- /dev/null +++ b/drivers/mtd/tests/oobtest.c @@ -0,0 +1,649 @@ +/* + * Copyright (C) 2006-2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; see the file COPYING. If not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Test OOB read and write on MTD device. + * + * Author: Adrian Hunter <ext-adrian.hunter@nokia.com> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <asm/div64.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/err.h> +#include <linux/mtd/mtd.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/random.h> + +#include "mtd_test.h" + +static int dev = -EINVAL; +module_param(dev, int, S_IRUGO); +MODULE_PARM_DESC(dev, "MTD device number to use"); + +static struct mtd_info *mtd; +static unsigned char *readbuf; +static unsigned char *writebuf; +static unsigned char *bbt; + +static int ebcnt; +static int pgcnt; +static int errcnt; +static int use_offset; +static int use_len; +static int use_len_max; +static int vary_offset; +static struct rnd_state rnd_state; + +static void do_vary_offset(void) +{ + use_len -= 1; + if (use_len < 1) { + use_offset += 1; + if (use_offset >= use_len_max) + use_offset = 0; + use_len = use_len_max - use_offset; + } +} + +static int write_eraseblock(int ebnum) +{ + int i; + struct mtd_oob_ops ops; + int err = 0; + loff_t addr = ebnum * mtd->erasesize; + + prandom_bytes_state(&rnd_state, writebuf, use_len_max * pgcnt); + for (i = 0; i < pgcnt; ++i, addr += mtd->writesize) { + ops.mode = MTD_OPS_AUTO_OOB; + ops.len = 0; + ops.retlen = 0; + ops.ooblen = use_len; + ops.oobretlen = 0; + ops.ooboffs = use_offset; + ops.datbuf = NULL; + ops.oobbuf = writebuf + (use_len_max * i) + use_offset; + err = mtd_write_oob(mtd, addr, &ops); + if (err || ops.oobretlen != use_len) { + pr_err("error: writeoob failed at %#llx\n", + (long long)addr); + pr_err("error: use_len %d, use_offset %d\n", + use_len, use_offset); + errcnt += 1; + return err ? err : -1; + } + if (vary_offset) + do_vary_offset(); + } + + return err; +} + +static int write_whole_device(void) +{ + int err; + unsigned int i; + + pr_info("writing OOBs of whole device\n"); + for (i = 0; i < ebcnt; ++i) { + if (bbt[i]) + continue; + err = write_eraseblock(i); + if (err) + return err; + if (i % 256 == 0) + pr_info("written up to eraseblock %u\n", i); + cond_resched(); + } + pr_info("written %u eraseblocks\n", i); + return 0; +} + +static int verify_eraseblock(int ebnum) +{ + int i; + struct mtd_oob_ops ops; + int err = 0; + loff_t addr = ebnum * mtd->erasesize; + + prandom_bytes_state(&rnd_state, writebuf, use_len_max * pgcnt); + for (i = 0; i < pgcnt; ++i, addr += mtd->writesize) { + ops.mode = MTD_OPS_AUTO_OOB; + ops.len = 0; + ops.retlen = 0; + ops.ooblen = use_len; + ops.oobretlen = 0; + ops.ooboffs = use_offset; + ops.datbuf = NULL; + ops.oobbuf = readbuf; + err = mtd_read_oob(mtd, addr, &ops); + if (err || ops.oobretlen != use_len) { + pr_err("error: readoob failed at %#llx\n", + (long long)addr); + errcnt += 1; + return err ? err : -1; + } + if (memcmp(readbuf, writebuf + (use_len_max * i) + use_offset, + use_len)) { + pr_err("error: verify failed at %#llx\n", + (long long)addr); + errcnt += 1; + if (errcnt > 1000) { + pr_err("error: too many errors\n"); + return -1; + } + } + if (use_offset != 0 || use_len < mtd->ecclayout->oobavail) { + int k; + + ops.mode = MTD_OPS_AUTO_OOB; + ops.len = 0; + ops.retlen = 0; + ops.ooblen = mtd->ecclayout->oobavail; + ops.oobretlen = 0; + ops.ooboffs = 0; + ops.datbuf = NULL; + ops.oobbuf = readbuf; + err = mtd_read_oob(mtd, addr, &ops); + if (err || ops.oobretlen != mtd->ecclayout->oobavail) { + pr_err("error: readoob failed at %#llx\n", + (long long)addr); + errcnt += 1; + return err ? err : -1; + } + if (memcmp(readbuf + use_offset, + writebuf + (use_len_max * i) + use_offset, + use_len)) { + pr_err("error: verify failed at %#llx\n", + (long long)addr); + errcnt += 1; + if (errcnt > 1000) { + pr_err("error: too many errors\n"); + return -1; + } + } + for (k = 0; k < use_offset; ++k) + if (readbuf[k] != 0xff) { + pr_err("error: verify 0xff " + "failed at %#llx\n", + (long long)addr); + errcnt += 1; + if (errcnt > 1000) { + pr_err("error: too " + "many errors\n"); + return -1; + } + } + for (k = use_offset + use_len; + k < mtd->ecclayout->oobavail; ++k) + if (readbuf[k] != 0xff) { + pr_err("error: verify 0xff " + "failed at %#llx\n", + (long long)addr); + errcnt += 1; + if (errcnt > 1000) { + pr_err("error: too " + "many errors\n"); + return -1; + } + } + } + if (vary_offset) + do_vary_offset(); + } + return err; +} + +static int verify_eraseblock_in_one_go(int ebnum) +{ + struct mtd_oob_ops ops; + int err = 0; + loff_t addr = ebnum * mtd->erasesize; + size_t len = mtd->ecclayout->oobavail * pgcnt; + + prandom_bytes_state(&rnd_state, writebuf, len); + ops.mode = MTD_OPS_AUTO_OOB; + ops.len = 0; + ops.retlen = 0; + ops.ooblen = len; + ops.oobretlen = 0; + ops.ooboffs = 0; + ops.datbuf = NULL; + ops.oobbuf = readbuf; + err = mtd_read_oob(mtd, addr, &ops); + if (err || ops.oobretlen != len) { + pr_err("error: readoob failed at %#llx\n", + (long long)addr); + errcnt += 1; + return err ? err : -1; + } + if (memcmp(readbuf, writebuf, len)) { + pr_err("error: verify failed at %#llx\n", + (long long)addr); + errcnt += 1; + if (errcnt > 1000) { + pr_err("error: too many errors\n"); + return -1; + } + } + + return err; +} + +static int verify_all_eraseblocks(void) +{ + int err; + unsigned int i; + + pr_info("verifying all eraseblocks\n"); + for (i = 0; i < ebcnt; ++i) { + if (bbt[i]) + continue; + err = verify_eraseblock(i); + if (err) + return err; + if (i % 256 == 0) + pr_info("verified up to eraseblock %u\n", i); + cond_resched(); + } + pr_info("verified %u eraseblocks\n", i); + return 0; +} + +static int __init mtd_oobtest_init(void) +{ + int err = 0; + unsigned int i; + uint64_t tmp; + struct mtd_oob_ops ops; + loff_t addr = 0, addr0; + + printk(KERN_INFO "\n"); + printk(KERN_INFO "=================================================\n"); + + if (dev < 0) { + pr_info("Please specify a valid mtd-device via module parameter\n"); + pr_crit("CAREFUL: This test wipes all data on the specified MTD device!\n"); + return -EINVAL; + } + + pr_info("MTD device: %d\n", dev); + + mtd = get_mtd_device(NULL, dev); + if (IS_ERR(mtd)) { + err = PTR_ERR(mtd); + pr_err("error: cannot get MTD device\n"); + return err; + } + + if (!mtd_type_is_nand(mtd)) { + pr_info("this test requires NAND flash\n"); + goto out; + } + + tmp = mtd->size; + do_div(tmp, mtd->erasesize); + ebcnt = tmp; + pgcnt = mtd->erasesize / mtd->writesize; + + pr_info("MTD device size %llu, eraseblock size %u, " + "page size %u, count of eraseblocks %u, pages per " + "eraseblock %u, OOB size %u\n", + (unsigned long long)mtd->size, mtd->erasesize, + mtd->writesize, ebcnt, pgcnt, mtd->oobsize); + + err = -ENOMEM; + readbuf = kmalloc(mtd->erasesize, GFP_KERNEL); + if (!readbuf) + goto out; + writebuf = kmalloc(mtd->erasesize, GFP_KERNEL); + if (!writebuf) + goto out; + bbt = kzalloc(ebcnt, GFP_KERNEL); + if (!bbt) + goto out; + + err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, 0, ebcnt); + if (err) + goto out; + + use_offset = 0; + use_len = mtd->ecclayout->oobavail; + use_len_max = mtd->ecclayout->oobavail; + vary_offset = 0; + + /* First test: write all OOB, read it back and verify */ + pr_info("test 1 of 5\n"); + + err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt); + if (err) + goto out; + + prandom_seed_state(&rnd_state, 1); + err = write_whole_device(); + if (err) + goto out; + + prandom_seed_state(&rnd_state, 1); + err = verify_all_eraseblocks(); + if (err) + goto out; + + /* + * Second test: write all OOB, a block at a time, read it back and + * verify. + */ + pr_info("test 2 of 5\n"); + + err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt); + if (err) + goto out; + + prandom_seed_state(&rnd_state, 3); + err = write_whole_device(); + if (err) + goto out; + + /* Check all eraseblocks */ + prandom_seed_state(&rnd_state, 3); + pr_info("verifying all eraseblocks\n"); + for (i = 0; i < ebcnt; ++i) { + if (bbt[i]) + continue; + err = verify_eraseblock_in_one_go(i); + if (err) + goto out; + if (i % 256 == 0) + pr_info("verified up to eraseblock %u\n", i); + cond_resched(); + } + pr_info("verified %u eraseblocks\n", i); + + /* + * Third test: write OOB at varying offsets and lengths, read it back + * and verify. + */ + pr_info("test 3 of 5\n"); + + err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt); + if (err) + goto out; + + /* Write all eraseblocks */ + use_offset = 0; + use_len = mtd->ecclayout->oobavail; + use_len_max = mtd->ecclayout->oobavail; + vary_offset = 1; + prandom_seed_state(&rnd_state, 5); + + err = write_whole_device(); + if (err) + goto out; + + /* Check all eraseblocks */ + use_offset = 0; + use_len = mtd->ecclayout->oobavail; + use_len_max = mtd->ecclayout->oobavail; + vary_offset = 1; + prandom_seed_state(&rnd_state, 5); + err = verify_all_eraseblocks(); + if (err) + goto out; + + use_offset = 0; + use_len = mtd->ecclayout->oobavail; + use_len_max = mtd->ecclayout->oobavail; + vary_offset = 0; + + /* Fourth test: try to write off end of device */ + pr_info("test 4 of 5\n"); + + err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt); + if (err) + goto out; + + addr0 = 0; + for (i = 0; i < ebcnt && bbt[i]; ++i) + addr0 += mtd->erasesize; + + /* Attempt to write off end of OOB */ + ops.mode = MTD_OPS_AUTO_OOB; + ops.len = 0; + ops.retlen = 0; + ops.ooblen = 1; + ops.oobretlen = 0; + ops.ooboffs = mtd->ecclayout->oobavail; + ops.datbuf = NULL; + ops.oobbuf = writebuf; + pr_info("attempting to start write past end of OOB\n"); + pr_info("an error is expected...\n"); + err = mtd_write_oob(mtd, addr0, &ops); + if (err) { + pr_info("error occurred as expected\n"); + err = 0; + } else { + pr_err("error: can write past end of OOB\n"); + errcnt += 1; + } + + /* Attempt to read off end of OOB */ + ops.mode = MTD_OPS_AUTO_OOB; + ops.len = 0; + ops.retlen = 0; + ops.ooblen = 1; + ops.oobretlen = 0; + ops.ooboffs = mtd->ecclayout->oobavail; + ops.datbuf = NULL; + ops.oobbuf = readbuf; + pr_info("attempting to start read past end of OOB\n"); + pr_info("an error is expected...\n"); + err = mtd_read_oob(mtd, addr0, &ops); + if (err) { + pr_info("error occurred as expected\n"); + err = 0; + } else { + pr_err("error: can read past end of OOB\n"); + errcnt += 1; + } + + if (bbt[ebcnt - 1]) + pr_info("skipping end of device tests because last " + "block is bad\n"); + else { + /* Attempt to write off end of device */ + ops.mode = MTD_OPS_AUTO_OOB; + ops.len = 0; + ops.retlen = 0; + ops.ooblen = mtd->ecclayout->oobavail + 1; + ops.oobretlen = 0; + ops.ooboffs = 0; + ops.datbuf = NULL; + ops.oobbuf = writebuf; + pr_info("attempting to write past end of device\n"); + pr_info("an error is expected...\n"); + err = mtd_write_oob(mtd, mtd->size - mtd->writesize, &ops); + if (err) { + pr_info("error occurred as expected\n"); + err = 0; + } else { + pr_err("error: wrote past end of device\n"); + errcnt += 1; + } + + /* Attempt to read off end of device */ + ops.mode = MTD_OPS_AUTO_OOB; + ops.len = 0; + ops.retlen = 0; + ops.ooblen = mtd->ecclayout->oobavail + 1; + ops.oobretlen = 0; + ops.ooboffs = 0; + ops.datbuf = NULL; + ops.oobbuf = readbuf; + pr_info("attempting to read past end of device\n"); + pr_info("an error is expected...\n"); + err = mtd_read_oob(mtd, mtd->size - mtd->writesize, &ops); + if (err) { + pr_info("error occurred as expected\n"); + err = 0; + } else { + pr_err("error: read past end of device\n"); + errcnt += 1; + } + + err = mtdtest_erase_eraseblock(mtd, ebcnt - 1); + if (err) + goto out; + + /* Attempt to write off end of device */ + ops.mode = MTD_OPS_AUTO_OOB; + ops.len = 0; + ops.retlen = 0; + ops.ooblen = mtd->ecclayout->oobavail; + ops.oobretlen = 0; + ops.ooboffs = 1; + ops.datbuf = NULL; + ops.oobbuf = writebuf; + pr_info("attempting to write past end of device\n"); + pr_info("an error is expected...\n"); + err = mtd_write_oob(mtd, mtd->size - mtd->writesize, &ops); + if (err) { + pr_info("error occurred as expected\n"); + err = 0; + } else { + pr_err("error: wrote past end of device\n"); + errcnt += 1; + } + + /* Attempt to read off end of device */ + ops.mode = MTD_OPS_AUTO_OOB; + ops.len = 0; + ops.retlen = 0; + ops.ooblen = mtd->ecclayout->oobavail; + ops.oobretlen = 0; + ops.ooboffs = 1; + ops.datbuf = NULL; + ops.oobbuf = readbuf; + pr_info("attempting to read past end of device\n"); + pr_info("an error is expected...\n"); + err = mtd_read_oob(mtd, mtd->size - mtd->writesize, &ops); + if (err) { + pr_info("error occurred as expected\n"); + err = 0; + } else { + pr_err("error: read past end of device\n"); + errcnt += 1; + } + } + + /* Fifth test: write / read across block boundaries */ + pr_info("test 5 of 5\n"); + + /* Erase all eraseblocks */ + err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt); + if (err) + goto out; + + /* Write all eraseblocks */ + prandom_seed_state(&rnd_state, 11); + pr_info("writing OOBs of whole device\n"); + for (i = 0; i < ebcnt - 1; ++i) { + int cnt = 2; + int pg; + size_t sz = mtd->ecclayout->oobavail; + if (bbt[i] || bbt[i + 1]) + continue; + addr = (i + 1) * mtd->erasesize - mtd->writesize; + prandom_bytes_state(&rnd_state, writebuf, sz * cnt); + for (pg = 0; pg < cnt; ++pg) { + ops.mode = MTD_OPS_AUTO_OOB; + ops.len = 0; + ops.retlen = 0; + ops.ooblen = sz; + ops.oobretlen = 0; + ops.ooboffs = 0; + ops.datbuf = NULL; + ops.oobbuf = writebuf + pg * sz; + err = mtd_write_oob(mtd, addr, &ops); + if (err) + goto out; + if (i % 256 == 0) + pr_info("written up to eraseblock %u\n", i); + cond_resched(); + addr += mtd->writesize; + } + } + pr_info("written %u eraseblocks\n", i); + + /* Check all eraseblocks */ + prandom_seed_state(&rnd_state, 11); + pr_info("verifying all eraseblocks\n"); + for (i = 0; i < ebcnt - 1; ++i) { + if (bbt[i] || bbt[i + 1]) + continue; + prandom_bytes_state(&rnd_state, writebuf, + mtd->ecclayout->oobavail * 2); + addr = (i + 1) * mtd->erasesize - mtd->writesize; + ops.mode = MTD_OPS_AUTO_OOB; + ops.len = 0; + ops.retlen = 0; + ops.ooblen = mtd->ecclayout->oobavail * 2; + ops.oobretlen = 0; + ops.ooboffs = 0; + ops.datbuf = NULL; + ops.oobbuf = readbuf; + err = mtd_read_oob(mtd, addr, &ops); + if (err) + goto out; + if (memcmp(readbuf, writebuf, mtd->ecclayout->oobavail * 2)) { + pr_err("error: verify failed at %#llx\n", + (long long)addr); + errcnt += 1; + if (errcnt > 1000) { + pr_err("error: too many errors\n"); + goto out; + } + } + if (i % 256 == 0) + pr_info("verified up to eraseblock %u\n", i); + cond_resched(); + } + pr_info("verified %u eraseblocks\n", i); + + pr_info("finished with %d errors\n", errcnt); +out: + kfree(bbt); + kfree(writebuf); + kfree(readbuf); + put_mtd_device(mtd); + if (err) + pr_info("error %d occurred\n", err); + printk(KERN_INFO "=================================================\n"); + return err; +} +module_init(mtd_oobtest_init); + +static void __exit mtd_oobtest_exit(void) +{ + return; +} +module_exit(mtd_oobtest_exit); + +MODULE_DESCRIPTION("Out-of-band test module"); +MODULE_AUTHOR("Adrian Hunter"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/tests/pagetest.c b/drivers/mtd/tests/pagetest.c new file mode 100644 index 00000000000..ed2d3f656fd --- /dev/null +++ b/drivers/mtd/tests/pagetest.c @@ -0,0 +1,464 @@ +/* + * Copyright (C) 2006-2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; see the file COPYING. If not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Test page read and write on MTD device. + * + * Author: Adrian Hunter <ext-adrian.hunter@nokia.com> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <asm/div64.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/err.h> +#include <linux/mtd/mtd.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/random.h> + +#include "mtd_test.h" + +static int dev = -EINVAL; +module_param(dev, int, S_IRUGO); +MODULE_PARM_DESC(dev, "MTD device number to use"); + +static struct mtd_info *mtd; +static unsigned char *twopages; +static unsigned char *writebuf; +static unsigned char *boundary; +static unsigned char *bbt; + +static int pgsize; +static int bufsize; +static int ebcnt; +static int pgcnt; +static int errcnt; +static struct rnd_state rnd_state; + +static int write_eraseblock(int ebnum) +{ + loff_t addr = ebnum * mtd->erasesize; + + prandom_bytes_state(&rnd_state, writebuf, mtd->erasesize); + cond_resched(); + return mtdtest_write(mtd, addr, mtd->erasesize, writebuf); +} + +static int verify_eraseblock(int ebnum) +{ + uint32_t j; + int err = 0, i; + loff_t addr0, addrn; + loff_t addr = ebnum * mtd->erasesize; + + addr0 = 0; + for (i = 0; i < ebcnt && bbt[i]; ++i) + addr0 += mtd->erasesize; + + addrn = mtd->size; + for (i = 0; i < ebcnt && bbt[ebcnt - i - 1]; ++i) + addrn -= mtd->erasesize; + + prandom_bytes_state(&rnd_state, writebuf, mtd->erasesize); + for (j = 0; j < pgcnt - 1; ++j, addr += pgsize) { + /* Do a read to set the internal dataRAMs to different data */ + err = mtdtest_read(mtd, addr0, bufsize, twopages); + if (err) + return err; + err = mtdtest_read(mtd, addrn - bufsize, bufsize, twopages); + if (err) + return err; + memset(twopages, 0, bufsize); + err = mtdtest_read(mtd, addr, bufsize, twopages); + if (err) + break; + if (memcmp(twopages, writebuf + (j * pgsize), bufsize)) { + pr_err("error: verify failed at %#llx\n", + (long long)addr); + errcnt += 1; + } + } + /* Check boundary between eraseblocks */ + if (addr <= addrn - pgsize - pgsize && !bbt[ebnum + 1]) { + struct rnd_state old_state = rnd_state; + + /* Do a read to set the internal dataRAMs to different data */ + err = mtdtest_read(mtd, addr0, bufsize, twopages); + if (err) + return err; + err = mtdtest_read(mtd, addrn - bufsize, bufsize, twopages); + if (err) + return err; + memset(twopages, 0, bufsize); + err = mtdtest_read(mtd, addr, bufsize, twopages); + if (err) + return err; + memcpy(boundary, writebuf + mtd->erasesize - pgsize, pgsize); + prandom_bytes_state(&rnd_state, boundary + pgsize, pgsize); + if (memcmp(twopages, boundary, bufsize)) { + pr_err("error: verify failed at %#llx\n", + (long long)addr); + errcnt += 1; + } + rnd_state = old_state; + } + return err; +} + +static int crosstest(void) +{ + int err = 0, i; + loff_t addr, addr0, addrn; + unsigned char *pp1, *pp2, *pp3, *pp4; + + pr_info("crosstest\n"); + pp1 = kmalloc(pgsize * 4, GFP_KERNEL); + if (!pp1) + return -ENOMEM; + pp2 = pp1 + pgsize; + pp3 = pp2 + pgsize; + pp4 = pp3 + pgsize; + memset(pp1, 0, pgsize * 4); + + addr0 = 0; + for (i = 0; i < ebcnt && bbt[i]; ++i) + addr0 += mtd->erasesize; + + addrn = mtd->size; + for (i = 0; i < ebcnt && bbt[ebcnt - i - 1]; ++i) + addrn -= mtd->erasesize; + + /* Read 2nd-to-last page to pp1 */ + addr = addrn - pgsize - pgsize; + err = mtdtest_read(mtd, addr, pgsize, pp1); + if (err) { + kfree(pp1); + return err; + } + + /* Read 3rd-to-last page to pp1 */ + addr = addrn - pgsize - pgsize - pgsize; + err = mtdtest_read(mtd, addr, pgsize, pp1); + if (err) { + kfree(pp1); + return err; + } + + /* Read first page to pp2 */ + addr = addr0; + pr_info("reading page at %#llx\n", (long long)addr); + err = mtdtest_read(mtd, addr, pgsize, pp2); + if (err) { + kfree(pp1); + return err; + } + + /* Read last page to pp3 */ + addr = addrn - pgsize; + pr_info("reading page at %#llx\n", (long long)addr); + err = mtdtest_read(mtd, addr, pgsize, pp3); + if (err) { + kfree(pp1); + return err; + } + + /* Read first page again to pp4 */ + addr = addr0; + pr_info("reading page at %#llx\n", (long long)addr); + err = mtdtest_read(mtd, addr, pgsize, pp4); + if (err) { + kfree(pp1); + return err; + } + + /* pp2 and pp4 should be the same */ + pr_info("verifying pages read at %#llx match\n", + (long long)addr0); + if (memcmp(pp2, pp4, pgsize)) { + pr_err("verify failed!\n"); + errcnt += 1; + } else if (!err) + pr_info("crosstest ok\n"); + kfree(pp1); + return err; +} + +static int erasecrosstest(void) +{ + int err = 0, i, ebnum, ebnum2; + loff_t addr0; + char *readbuf = twopages; + + pr_info("erasecrosstest\n"); + + ebnum = 0; + addr0 = 0; + for (i = 0; i < ebcnt && bbt[i]; ++i) { + addr0 += mtd->erasesize; + ebnum += 1; + } + + ebnum2 = ebcnt - 1; + while (ebnum2 && bbt[ebnum2]) + ebnum2 -= 1; + + pr_info("erasing block %d\n", ebnum); + err = mtdtest_erase_eraseblock(mtd, ebnum); + if (err) + return err; + + pr_info("writing 1st page of block %d\n", ebnum); + prandom_bytes_state(&rnd_state, writebuf, pgsize); + strcpy(writebuf, "There is no data like this!"); + err = mtdtest_write(mtd, addr0, pgsize, writebuf); + if (err) + return err; + + pr_info("reading 1st page of block %d\n", ebnum); + memset(readbuf, 0, pgsize); + err = mtdtest_read(mtd, addr0, pgsize, readbuf); + if (err) + return err; + + pr_info("verifying 1st page of block %d\n", ebnum); + if (memcmp(writebuf, readbuf, pgsize)) { + pr_err("verify failed!\n"); + errcnt += 1; + return -1; + } + + pr_info("erasing block %d\n", ebnum); + err = mtdtest_erase_eraseblock(mtd, ebnum); + if (err) + return err; + + pr_info("writing 1st page of block %d\n", ebnum); + prandom_bytes_state(&rnd_state, writebuf, pgsize); + strcpy(writebuf, "There is no data like this!"); + err = mtdtest_write(mtd, addr0, pgsize, writebuf); + if (err) + return err; + + pr_info("erasing block %d\n", ebnum2); + err = mtdtest_erase_eraseblock(mtd, ebnum2); + if (err) + return err; + + pr_info("reading 1st page of block %d\n", ebnum); + memset(readbuf, 0, pgsize); + err = mtdtest_read(mtd, addr0, pgsize, readbuf); + if (err) + return err; + + pr_info("verifying 1st page of block %d\n", ebnum); + if (memcmp(writebuf, readbuf, pgsize)) { + pr_err("verify failed!\n"); + errcnt += 1; + return -1; + } + + if (!err) + pr_info("erasecrosstest ok\n"); + return err; +} + +static int erasetest(void) +{ + int err = 0, i, ebnum, ok = 1; + loff_t addr0; + + pr_info("erasetest\n"); + + ebnum = 0; + addr0 = 0; + for (i = 0; i < ebcnt && bbt[i]; ++i) { + addr0 += mtd->erasesize; + ebnum += 1; + } + + pr_info("erasing block %d\n", ebnum); + err = mtdtest_erase_eraseblock(mtd, ebnum); + if (err) + return err; + + pr_info("writing 1st page of block %d\n", ebnum); + prandom_bytes_state(&rnd_state, writebuf, pgsize); + err = mtdtest_write(mtd, addr0, pgsize, writebuf); + if (err) + return err; + + pr_info("erasing block %d\n", ebnum); + err = mtdtest_erase_eraseblock(mtd, ebnum); + if (err) + return err; + + pr_info("reading 1st page of block %d\n", ebnum); + err = mtdtest_read(mtd, addr0, pgsize, twopages); + if (err) + return err; + + pr_info("verifying 1st page of block %d is all 0xff\n", + ebnum); + for (i = 0; i < pgsize; ++i) + if (twopages[i] != 0xff) { + pr_err("verifying all 0xff failed at %d\n", + i); + errcnt += 1; + ok = 0; + break; + } + + if (ok && !err) + pr_info("erasetest ok\n"); + + return err; +} + +static int __init mtd_pagetest_init(void) +{ + int err = 0; + uint64_t tmp; + uint32_t i; + + printk(KERN_INFO "\n"); + printk(KERN_INFO "=================================================\n"); + + if (dev < 0) { + pr_info("Please specify a valid mtd-device via module parameter\n"); + pr_crit("CAREFUL: This test wipes all data on the specified MTD device!\n"); + return -EINVAL; + } + + pr_info("MTD device: %d\n", dev); + + mtd = get_mtd_device(NULL, dev); + if (IS_ERR(mtd)) { + err = PTR_ERR(mtd); + pr_err("error: cannot get MTD device\n"); + return err; + } + + if (!mtd_type_is_nand(mtd)) { + pr_info("this test requires NAND flash\n"); + goto out; + } + + tmp = mtd->size; + do_div(tmp, mtd->erasesize); + ebcnt = tmp; + pgcnt = mtd->erasesize / mtd->writesize; + pgsize = mtd->writesize; + + pr_info("MTD device size %llu, eraseblock size %u, " + "page size %u, count of eraseblocks %u, pages per " + "eraseblock %u, OOB size %u\n", + (unsigned long long)mtd->size, mtd->erasesize, + pgsize, ebcnt, pgcnt, mtd->oobsize); + + err = -ENOMEM; + bufsize = pgsize * 2; + writebuf = kmalloc(mtd->erasesize, GFP_KERNEL); + if (!writebuf) + goto out; + twopages = kmalloc(bufsize, GFP_KERNEL); + if (!twopages) + goto out; + boundary = kmalloc(bufsize, GFP_KERNEL); + if (!boundary) + goto out; + + bbt = kzalloc(ebcnt, GFP_KERNEL); + if (!bbt) + goto out; + err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, 0, ebcnt); + if (err) + goto out; + + /* Erase all eraseblocks */ + pr_info("erasing whole device\n"); + err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt); + if (err) + goto out; + pr_info("erased %u eraseblocks\n", ebcnt); + + /* Write all eraseblocks */ + prandom_seed_state(&rnd_state, 1); + pr_info("writing whole device\n"); + for (i = 0; i < ebcnt; ++i) { + if (bbt[i]) + continue; + err = write_eraseblock(i); + if (err) + goto out; + if (i % 256 == 0) + pr_info("written up to eraseblock %u\n", i); + cond_resched(); + } + pr_info("written %u eraseblocks\n", i); + + /* Check all eraseblocks */ + prandom_seed_state(&rnd_state, 1); + pr_info("verifying all eraseblocks\n"); + for (i = 0; i < ebcnt; ++i) { + if (bbt[i]) + continue; + err = verify_eraseblock(i); + if (err) + goto out; + if (i % 256 == 0) + pr_info("verified up to eraseblock %u\n", i); + cond_resched(); + } + pr_info("verified %u eraseblocks\n", i); + + err = crosstest(); + if (err) + goto out; + + err = erasecrosstest(); + if (err) + goto out; + + err = erasetest(); + if (err) + goto out; + + pr_info("finished with %d errors\n", errcnt); +out: + + kfree(bbt); + kfree(boundary); + kfree(twopages); + kfree(writebuf); + put_mtd_device(mtd); + if (err) + pr_info("error %d occurred\n", err); + printk(KERN_INFO "=================================================\n"); + return err; +} +module_init(mtd_pagetest_init); + +static void __exit mtd_pagetest_exit(void) +{ + return; +} +module_exit(mtd_pagetest_exit); + +MODULE_DESCRIPTION("NAND page test"); +MODULE_AUTHOR("Adrian Hunter"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/tests/readtest.c b/drivers/mtd/tests/readtest.c new file mode 100644 index 00000000000..626e66d0f7e --- /dev/null +++ b/drivers/mtd/tests/readtest.c @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2006-2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; see the file COPYING. If not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Check MTD device read. + * + * Author: Adrian Hunter <ext-adrian.hunter@nokia.com> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/err.h> +#include <linux/mtd/mtd.h> +#include <linux/slab.h> +#include <linux/sched.h> + +#include "mtd_test.h" + +static int dev = -EINVAL; +module_param(dev, int, S_IRUGO); +MODULE_PARM_DESC(dev, "MTD device number to use"); + +static struct mtd_info *mtd; +static unsigned char *iobuf; +static unsigned char *iobuf1; +static unsigned char *bbt; + +static int pgsize; +static int ebcnt; +static int pgcnt; + +static int read_eraseblock_by_page(int ebnum) +{ + int i, ret, err = 0; + loff_t addr = ebnum * mtd->erasesize; + void *buf = iobuf; + void *oobbuf = iobuf1; + + for (i = 0; i < pgcnt; i++) { + memset(buf, 0 , pgsize); + ret = mtdtest_read(mtd, addr, pgsize, buf); + if (ret) { + if (!err) + err = ret; + } + if (mtd->oobsize) { + struct mtd_oob_ops ops; + + ops.mode = MTD_OPS_PLACE_OOB; + ops.len = 0; + ops.retlen = 0; + ops.ooblen = mtd->oobsize; + ops.oobretlen = 0; + ops.ooboffs = 0; + ops.datbuf = NULL; + ops.oobbuf = oobbuf; + ret = mtd_read_oob(mtd, addr, &ops); + if ((ret && !mtd_is_bitflip(ret)) || + ops.oobretlen != mtd->oobsize) { + pr_err("error: read oob failed at " + "%#llx\n", (long long)addr); + if (!err) + err = ret; + if (!err) + err = -EINVAL; + } + oobbuf += mtd->oobsize; + } + addr += pgsize; + buf += pgsize; + } + + return err; +} + +static void dump_eraseblock(int ebnum) +{ + int i, j, n; + char line[128]; + int pg, oob; + + pr_info("dumping eraseblock %d\n", ebnum); + n = mtd->erasesize; + for (i = 0; i < n;) { + char *p = line; + + p += sprintf(p, "%05x: ", i); + for (j = 0; j < 32 && i < n; j++, i++) + p += sprintf(p, "%02x", (unsigned int)iobuf[i]); + printk(KERN_CRIT "%s\n", line); + cond_resched(); + } + if (!mtd->oobsize) + return; + pr_info("dumping oob from eraseblock %d\n", ebnum); + n = mtd->oobsize; + for (pg = 0, i = 0; pg < pgcnt; pg++) + for (oob = 0; oob < n;) { + char *p = line; + + p += sprintf(p, "%05x: ", i); + for (j = 0; j < 32 && oob < n; j++, oob++, i++) + p += sprintf(p, "%02x", + (unsigned int)iobuf1[i]); + printk(KERN_CRIT "%s\n", line); + cond_resched(); + } +} + +static int __init mtd_readtest_init(void) +{ + uint64_t tmp; + int err, i; + + printk(KERN_INFO "\n"); + printk(KERN_INFO "=================================================\n"); + + if (dev < 0) { + pr_info("Please specify a valid mtd-device via module parameter\n"); + return -EINVAL; + } + + pr_info("MTD device: %d\n", dev); + + mtd = get_mtd_device(NULL, dev); + if (IS_ERR(mtd)) { + err = PTR_ERR(mtd); + pr_err("error: Cannot get MTD device\n"); + return err; + } + + if (mtd->writesize == 1) { + pr_info("not NAND flash, assume page size is 512 " + "bytes.\n"); + pgsize = 512; + } else + pgsize = mtd->writesize; + + tmp = mtd->size; + do_div(tmp, mtd->erasesize); + ebcnt = tmp; + pgcnt = mtd->erasesize / pgsize; + + pr_info("MTD device size %llu, eraseblock size %u, " + "page size %u, count of eraseblocks %u, pages per " + "eraseblock %u, OOB size %u\n", + (unsigned long long)mtd->size, mtd->erasesize, + pgsize, ebcnt, pgcnt, mtd->oobsize); + + err = -ENOMEM; + iobuf = kmalloc(mtd->erasesize, GFP_KERNEL); + if (!iobuf) + goto out; + iobuf1 = kmalloc(mtd->erasesize, GFP_KERNEL); + if (!iobuf1) + goto out; + + bbt = kzalloc(ebcnt, GFP_KERNEL); + if (!bbt) + goto out; + err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, 0, ebcnt); + if (err) + goto out; + + /* Read all eraseblocks 1 page at a time */ + pr_info("testing page read\n"); + for (i = 0; i < ebcnt; ++i) { + int ret; + + if (bbt[i]) + continue; + ret = read_eraseblock_by_page(i); + if (ret) { + dump_eraseblock(i); + if (!err) + err = ret; + } + cond_resched(); + } + + if (err) + pr_info("finished with errors\n"); + else + pr_info("finished\n"); + +out: + + kfree(iobuf); + kfree(iobuf1); + kfree(bbt); + put_mtd_device(mtd); + if (err) + pr_info("error %d occurred\n", err); + printk(KERN_INFO "=================================================\n"); + return err; +} +module_init(mtd_readtest_init); + +static void __exit mtd_readtest_exit(void) +{ + return; +} +module_exit(mtd_readtest_exit); + +MODULE_DESCRIPTION("Read test module"); +MODULE_AUTHOR("Adrian Hunter"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/tests/speedtest.c b/drivers/mtd/tests/speedtest.c new file mode 100644 index 00000000000..87ff6a29f84 --- /dev/null +++ b/drivers/mtd/tests/speedtest.c @@ -0,0 +1,416 @@ +/* + * Copyright (C) 2007 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; see the file COPYING. If not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Test read and write speed of a MTD device. + * + * Author: Adrian Hunter <adrian.hunter@nokia.com> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/err.h> +#include <linux/mtd/mtd.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/random.h> + +#include "mtd_test.h" + +static int dev = -EINVAL; +module_param(dev, int, S_IRUGO); +MODULE_PARM_DESC(dev, "MTD device number to use"); + +static int count; +module_param(count, int, S_IRUGO); +MODULE_PARM_DESC(count, "Maximum number of eraseblocks to use " + "(0 means use all)"); + +static struct mtd_info *mtd; +static unsigned char *iobuf; +static unsigned char *bbt; + +static int pgsize; +static int ebcnt; +static int pgcnt; +static int goodebcnt; +static struct timeval start, finish; + +static int multiblock_erase(int ebnum, int blocks) +{ + int err; + struct erase_info ei; + loff_t addr = ebnum * mtd->erasesize; + + memset(&ei, 0, sizeof(struct erase_info)); + ei.mtd = mtd; + ei.addr = addr; + ei.len = mtd->erasesize * blocks; + + err = mtd_erase(mtd, &ei); + if (err) { + pr_err("error %d while erasing EB %d, blocks %d\n", + err, ebnum, blocks); + return err; + } + + if (ei.state == MTD_ERASE_FAILED) { + pr_err("some erase error occurred at EB %d," + "blocks %d\n", ebnum, blocks); + return -EIO; + } + + return 0; +} + +static int write_eraseblock(int ebnum) +{ + loff_t addr = ebnum * mtd->erasesize; + + return mtdtest_write(mtd, addr, mtd->erasesize, iobuf); +} + +static int write_eraseblock_by_page(int ebnum) +{ + int i, err = 0; + loff_t addr = ebnum * mtd->erasesize; + void *buf = iobuf; + + for (i = 0; i < pgcnt; i++) { + err = mtdtest_write(mtd, addr, pgsize, buf); + if (err) + break; + addr += pgsize; + buf += pgsize; + } + + return err; +} + +static int write_eraseblock_by_2pages(int ebnum) +{ + size_t sz = pgsize * 2; + int i, n = pgcnt / 2, err = 0; + loff_t addr = ebnum * mtd->erasesize; + void *buf = iobuf; + + for (i = 0; i < n; i++) { + err = mtdtest_write(mtd, addr, sz, buf); + if (err) + return err; + addr += sz; + buf += sz; + } + if (pgcnt % 2) + err = mtdtest_write(mtd, addr, pgsize, buf); + + return err; +} + +static int read_eraseblock(int ebnum) +{ + loff_t addr = ebnum * mtd->erasesize; + + return mtdtest_read(mtd, addr, mtd->erasesize, iobuf); +} + +static int read_eraseblock_by_page(int ebnum) +{ + int i, err = 0; + loff_t addr = ebnum * mtd->erasesize; + void *buf = iobuf; + + for (i = 0; i < pgcnt; i++) { + err = mtdtest_read(mtd, addr, pgsize, buf); + if (err) + break; + addr += pgsize; + buf += pgsize; + } + + return err; +} + +static int read_eraseblock_by_2pages(int ebnum) +{ + size_t sz = pgsize * 2; + int i, n = pgcnt / 2, err = 0; + loff_t addr = ebnum * mtd->erasesize; + void *buf = iobuf; + + for (i = 0; i < n; i++) { + err = mtdtest_read(mtd, addr, sz, buf); + if (err) + return err; + addr += sz; + buf += sz; + } + if (pgcnt % 2) + err = mtdtest_read(mtd, addr, pgsize, buf); + + return err; +} + +static inline void start_timing(void) +{ + do_gettimeofday(&start); +} + +static inline void stop_timing(void) +{ + do_gettimeofday(&finish); +} + +static long calc_speed(void) +{ + uint64_t k; + long ms; + + ms = (finish.tv_sec - start.tv_sec) * 1000 + + (finish.tv_usec - start.tv_usec) / 1000; + if (ms == 0) + return 0; + k = goodebcnt * (mtd->erasesize / 1024) * 1000; + do_div(k, ms); + return k; +} + +static int __init mtd_speedtest_init(void) +{ + int err, i, blocks, j, k; + long speed; + uint64_t tmp; + + printk(KERN_INFO "\n"); + printk(KERN_INFO "=================================================\n"); + + if (dev < 0) { + pr_info("Please specify a valid mtd-device via module parameter\n"); + pr_crit("CAREFUL: This test wipes all data on the specified MTD device!\n"); + return -EINVAL; + } + + if (count) + pr_info("MTD device: %d count: %d\n", dev, count); + else + pr_info("MTD device: %d\n", dev); + + mtd = get_mtd_device(NULL, dev); + if (IS_ERR(mtd)) { + err = PTR_ERR(mtd); + pr_err("error: cannot get MTD device\n"); + return err; + } + + if (mtd->writesize == 1) { + pr_info("not NAND flash, assume page size is 512 " + "bytes.\n"); + pgsize = 512; + } else + pgsize = mtd->writesize; + + tmp = mtd->size; + do_div(tmp, mtd->erasesize); + ebcnt = tmp; + pgcnt = mtd->erasesize / pgsize; + + pr_info("MTD device size %llu, eraseblock size %u, " + "page size %u, count of eraseblocks %u, pages per " + "eraseblock %u, OOB size %u\n", + (unsigned long long)mtd->size, mtd->erasesize, + pgsize, ebcnt, pgcnt, mtd->oobsize); + + if (count > 0 && count < ebcnt) + ebcnt = count; + + err = -ENOMEM; + iobuf = kmalloc(mtd->erasesize, GFP_KERNEL); + if (!iobuf) + goto out; + + prandom_bytes(iobuf, mtd->erasesize); + + bbt = kzalloc(ebcnt, GFP_KERNEL); + if (!bbt) + goto out; + err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, 0, ebcnt); + if (err) + goto out; + for (i = 0; i < ebcnt; i++) { + if (!bbt[i]) + goodebcnt++; + } + + err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt); + if (err) + goto out; + + /* Write all eraseblocks, 1 eraseblock at a time */ + pr_info("testing eraseblock write speed\n"); + start_timing(); + for (i = 0; i < ebcnt; ++i) { + if (bbt[i]) + continue; + err = write_eraseblock(i); + if (err) + goto out; + cond_resched(); + } + stop_timing(); + speed = calc_speed(); + pr_info("eraseblock write speed is %ld KiB/s\n", speed); + + /* Read all eraseblocks, 1 eraseblock at a time */ + pr_info("testing eraseblock read speed\n"); + start_timing(); + for (i = 0; i < ebcnt; ++i) { + if (bbt[i]) + continue; + err = read_eraseblock(i); + if (err) + goto out; + cond_resched(); + } + stop_timing(); + speed = calc_speed(); + pr_info("eraseblock read speed is %ld KiB/s\n", speed); + + err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt); + if (err) + goto out; + + /* Write all eraseblocks, 1 page at a time */ + pr_info("testing page write speed\n"); + start_timing(); + for (i = 0; i < ebcnt; ++i) { + if (bbt[i]) + continue; + err = write_eraseblock_by_page(i); + if (err) + goto out; + cond_resched(); + } + stop_timing(); + speed = calc_speed(); + pr_info("page write speed is %ld KiB/s\n", speed); + + /* Read all eraseblocks, 1 page at a time */ + pr_info("testing page read speed\n"); + start_timing(); + for (i = 0; i < ebcnt; ++i) { + if (bbt[i]) + continue; + err = read_eraseblock_by_page(i); + if (err) + goto out; + cond_resched(); + } + stop_timing(); + speed = calc_speed(); + pr_info("page read speed is %ld KiB/s\n", speed); + + err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt); + if (err) + goto out; + + /* Write all eraseblocks, 2 pages at a time */ + pr_info("testing 2 page write speed\n"); + start_timing(); + for (i = 0; i < ebcnt; ++i) { + if (bbt[i]) + continue; + err = write_eraseblock_by_2pages(i); + if (err) + goto out; + cond_resched(); + } + stop_timing(); + speed = calc_speed(); + pr_info("2 page write speed is %ld KiB/s\n", speed); + + /* Read all eraseblocks, 2 pages at a time */ + pr_info("testing 2 page read speed\n"); + start_timing(); + for (i = 0; i < ebcnt; ++i) { + if (bbt[i]) + continue; + err = read_eraseblock_by_2pages(i); + if (err) + goto out; + cond_resched(); + } + stop_timing(); + speed = calc_speed(); + pr_info("2 page read speed is %ld KiB/s\n", speed); + + /* Erase all eraseblocks */ + pr_info("Testing erase speed\n"); + start_timing(); + err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt); + if (err) + goto out; + stop_timing(); + speed = calc_speed(); + pr_info("erase speed is %ld KiB/s\n", speed); + + /* Multi-block erase all eraseblocks */ + for (k = 1; k < 7; k++) { + blocks = 1 << k; + pr_info("Testing %dx multi-block erase speed\n", + blocks); + start_timing(); + for (i = 0; i < ebcnt; ) { + for (j = 0; j < blocks && (i + j) < ebcnt; j++) + if (bbt[i + j]) + break; + if (j < 1) { + i++; + continue; + } + err = multiblock_erase(i, j); + if (err) + goto out; + cond_resched(); + i += j; + } + stop_timing(); + speed = calc_speed(); + pr_info("%dx multi-block erase speed is %ld KiB/s\n", + blocks, speed); + } + pr_info("finished\n"); +out: + kfree(iobuf); + kfree(bbt); + put_mtd_device(mtd); + if (err) + pr_info("error %d occurred\n", err); + printk(KERN_INFO "=================================================\n"); + return err; +} +module_init(mtd_speedtest_init); + +static void __exit mtd_speedtest_exit(void) +{ + return; +} +module_exit(mtd_speedtest_exit); + +MODULE_DESCRIPTION("Speed test module"); +MODULE_AUTHOR("Adrian Hunter"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/tests/stresstest.c b/drivers/mtd/tests/stresstest.c new file mode 100644 index 00000000000..c9d42cc2df1 --- /dev/null +++ b/drivers/mtd/tests/stresstest.c @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2006-2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; see the file COPYING. If not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Test random reads, writes and erases on MTD device. + * + * Author: Adrian Hunter <ext-adrian.hunter@nokia.com> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/err.h> +#include <linux/mtd/mtd.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/vmalloc.h> +#include <linux/random.h> + +#include "mtd_test.h" + +static int dev = -EINVAL; +module_param(dev, int, S_IRUGO); +MODULE_PARM_DESC(dev, "MTD device number to use"); + +static int count = 10000; +module_param(count, int, S_IRUGO); +MODULE_PARM_DESC(count, "Number of operations to do (default is 10000)"); + +static struct mtd_info *mtd; +static unsigned char *writebuf; +static unsigned char *readbuf; +static unsigned char *bbt; +static int *offsets; + +static int pgsize; +static int bufsize; +static int ebcnt; +static int pgcnt; + +static int rand_eb(void) +{ + unsigned int eb; + +again: + eb = prandom_u32(); + /* Read or write up 2 eraseblocks at a time - hence 'ebcnt - 1' */ + eb %= (ebcnt - 1); + if (bbt[eb]) + goto again; + return eb; +} + +static int rand_offs(void) +{ + unsigned int offs; + + offs = prandom_u32(); + offs %= bufsize; + return offs; +} + +static int rand_len(int offs) +{ + unsigned int len; + + len = prandom_u32(); + len %= (bufsize - offs); + return len; +} + +static int do_read(void) +{ + int eb = rand_eb(); + int offs = rand_offs(); + int len = rand_len(offs); + loff_t addr; + + if (bbt[eb + 1]) { + if (offs >= mtd->erasesize) + offs -= mtd->erasesize; + if (offs + len > mtd->erasesize) + len = mtd->erasesize - offs; + } + addr = eb * mtd->erasesize + offs; + return mtdtest_read(mtd, addr, len, readbuf); +} + +static int do_write(void) +{ + int eb = rand_eb(), offs, err, len; + loff_t addr; + + offs = offsets[eb]; + if (offs >= mtd->erasesize) { + err = mtdtest_erase_eraseblock(mtd, eb); + if (err) + return err; + offs = offsets[eb] = 0; + } + len = rand_len(offs); + len = ((len + pgsize - 1) / pgsize) * pgsize; + if (offs + len > mtd->erasesize) { + if (bbt[eb + 1]) + len = mtd->erasesize - offs; + else { + err = mtdtest_erase_eraseblock(mtd, eb + 1); + if (err) + return err; + offsets[eb + 1] = 0; + } + } + addr = eb * mtd->erasesize + offs; + err = mtdtest_write(mtd, addr, len, writebuf); + if (unlikely(err)) + return err; + offs += len; + while (offs > mtd->erasesize) { + offsets[eb++] = mtd->erasesize; + offs -= mtd->erasesize; + } + offsets[eb] = offs; + return 0; +} + +static int do_operation(void) +{ + if (prandom_u32() & 1) + return do_read(); + else + return do_write(); +} + +static int __init mtd_stresstest_init(void) +{ + int err; + int i, op; + uint64_t tmp; + + printk(KERN_INFO "\n"); + printk(KERN_INFO "=================================================\n"); + + if (dev < 0) { + pr_info("Please specify a valid mtd-device via module parameter\n"); + pr_crit("CAREFUL: This test wipes all data on the specified MTD device!\n"); + return -EINVAL; + } + + pr_info("MTD device: %d\n", dev); + + mtd = get_mtd_device(NULL, dev); + if (IS_ERR(mtd)) { + err = PTR_ERR(mtd); + pr_err("error: cannot get MTD device\n"); + return err; + } + + if (mtd->writesize == 1) { + pr_info("not NAND flash, assume page size is 512 " + "bytes.\n"); + pgsize = 512; + } else + pgsize = mtd->writesize; + + tmp = mtd->size; + do_div(tmp, mtd->erasesize); + ebcnt = tmp; + pgcnt = mtd->erasesize / pgsize; + + pr_info("MTD device size %llu, eraseblock size %u, " + "page size %u, count of eraseblocks %u, pages per " + "eraseblock %u, OOB size %u\n", + (unsigned long long)mtd->size, mtd->erasesize, + pgsize, ebcnt, pgcnt, mtd->oobsize); + + if (ebcnt < 2) { + pr_err("error: need at least 2 eraseblocks\n"); + err = -ENOSPC; + goto out_put_mtd; + } + + /* Read or write up 2 eraseblocks at a time */ + bufsize = mtd->erasesize * 2; + + err = -ENOMEM; + readbuf = vmalloc(bufsize); + writebuf = vmalloc(bufsize); + offsets = kmalloc(ebcnt * sizeof(int), GFP_KERNEL); + if (!readbuf || !writebuf || !offsets) + goto out; + for (i = 0; i < ebcnt; i++) + offsets[i] = mtd->erasesize; + prandom_bytes(writebuf, bufsize); + + bbt = kzalloc(ebcnt, GFP_KERNEL); + if (!bbt) + goto out; + err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, 0, ebcnt); + if (err) + goto out; + + /* Do operations */ + pr_info("doing operations\n"); + for (op = 0; op < count; op++) { + if ((op & 1023) == 0) + pr_info("%d operations done\n", op); + err = do_operation(); + if (err) + goto out; + cond_resched(); + } + pr_info("finished, %d operations done\n", op); + +out: + kfree(offsets); + kfree(bbt); + vfree(writebuf); + vfree(readbuf); +out_put_mtd: + put_mtd_device(mtd); + if (err) + pr_info("error %d occurred\n", err); + printk(KERN_INFO "=================================================\n"); + return err; +} +module_init(mtd_stresstest_init); + +static void __exit mtd_stresstest_exit(void) +{ + return; +} +module_exit(mtd_stresstest_exit); + +MODULE_DESCRIPTION("Stress test module"); +MODULE_AUTHOR("Adrian Hunter"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/tests/subpagetest.c b/drivers/mtd/tests/subpagetest.c new file mode 100644 index 00000000000..a876371ad41 --- /dev/null +++ b/drivers/mtd/tests/subpagetest.c @@ -0,0 +1,435 @@ +/* + * Copyright (C) 2006-2007 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; see the file COPYING. If not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Test sub-page read and write on MTD device. + * Author: Adrian Hunter <ext-adrian.hunter@nokia.com> + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/err.h> +#include <linux/mtd/mtd.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/random.h> + +#include "mtd_test.h" + +static int dev = -EINVAL; +module_param(dev, int, S_IRUGO); +MODULE_PARM_DESC(dev, "MTD device number to use"); + +static struct mtd_info *mtd; +static unsigned char *writebuf; +static unsigned char *readbuf; +static unsigned char *bbt; + +static int subpgsize; +static int bufsize; +static int ebcnt; +static int pgcnt; +static int errcnt; +static struct rnd_state rnd_state; + +static inline void clear_data(unsigned char *buf, size_t len) +{ + memset(buf, 0, len); +} + +static int write_eraseblock(int ebnum) +{ + size_t written; + int err = 0; + loff_t addr = ebnum * mtd->erasesize; + + prandom_bytes_state(&rnd_state, writebuf, subpgsize); + err = mtd_write(mtd, addr, subpgsize, &written, writebuf); + if (unlikely(err || written != subpgsize)) { + pr_err("error: write failed at %#llx\n", + (long long)addr); + if (written != subpgsize) { + pr_err(" write size: %#x\n", subpgsize); + pr_err(" written: %#zx\n", written); + } + return err ? err : -1; + } + + addr += subpgsize; + + prandom_bytes_state(&rnd_state, writebuf, subpgsize); + err = mtd_write(mtd, addr, subpgsize, &written, writebuf); + if (unlikely(err || written != subpgsize)) { + pr_err("error: write failed at %#llx\n", + (long long)addr); + if (written != subpgsize) { + pr_err(" write size: %#x\n", subpgsize); + pr_err(" written: %#zx\n", written); + } + return err ? err : -1; + } + + return err; +} + +static int write_eraseblock2(int ebnum) +{ + size_t written; + int err = 0, k; + loff_t addr = ebnum * mtd->erasesize; + + for (k = 1; k < 33; ++k) { + if (addr + (subpgsize * k) > (ebnum + 1) * mtd->erasesize) + break; + prandom_bytes_state(&rnd_state, writebuf, subpgsize * k); + err = mtd_write(mtd, addr, subpgsize * k, &written, writebuf); + if (unlikely(err || written != subpgsize * k)) { + pr_err("error: write failed at %#llx\n", + (long long)addr); + if (written != subpgsize) { + pr_err(" write size: %#x\n", + subpgsize * k); + pr_err(" written: %#08zx\n", + written); + } + return err ? err : -1; + } + addr += subpgsize * k; + } + + return err; +} + +static void print_subpage(unsigned char *p) +{ + int i, j; + + for (i = 0; i < subpgsize; ) { + for (j = 0; i < subpgsize && j < 32; ++i, ++j) + printk("%02x", *p++); + printk("\n"); + } +} + +static int verify_eraseblock(int ebnum) +{ + size_t read; + int err = 0; + loff_t addr = ebnum * mtd->erasesize; + + prandom_bytes_state(&rnd_state, writebuf, subpgsize); + clear_data(readbuf, subpgsize); + err = mtd_read(mtd, addr, subpgsize, &read, readbuf); + if (unlikely(err || read != subpgsize)) { + if (mtd_is_bitflip(err) && read == subpgsize) { + pr_info("ECC correction at %#llx\n", + (long long)addr); + err = 0; + } else { + pr_err("error: read failed at %#llx\n", + (long long)addr); + return err ? err : -1; + } + } + if (unlikely(memcmp(readbuf, writebuf, subpgsize))) { + pr_err("error: verify failed at %#llx\n", + (long long)addr); + pr_info("------------- written----------------\n"); + print_subpage(writebuf); + pr_info("------------- read ------------------\n"); + print_subpage(readbuf); + pr_info("-------------------------------------\n"); + errcnt += 1; + } + + addr += subpgsize; + + prandom_bytes_state(&rnd_state, writebuf, subpgsize); + clear_data(readbuf, subpgsize); + err = mtd_read(mtd, addr, subpgsize, &read, readbuf); + if (unlikely(err || read != subpgsize)) { + if (mtd_is_bitflip(err) && read == subpgsize) { + pr_info("ECC correction at %#llx\n", + (long long)addr); + err = 0; + } else { + pr_err("error: read failed at %#llx\n", + (long long)addr); + return err ? err : -1; + } + } + if (unlikely(memcmp(readbuf, writebuf, subpgsize))) { + pr_info("error: verify failed at %#llx\n", + (long long)addr); + pr_info("------------- written----------------\n"); + print_subpage(writebuf); + pr_info("------------- read ------------------\n"); + print_subpage(readbuf); + pr_info("-------------------------------------\n"); + errcnt += 1; + } + + return err; +} + +static int verify_eraseblock2(int ebnum) +{ + size_t read; + int err = 0, k; + loff_t addr = ebnum * mtd->erasesize; + + for (k = 1; k < 33; ++k) { + if (addr + (subpgsize * k) > (ebnum + 1) * mtd->erasesize) + break; + prandom_bytes_state(&rnd_state, writebuf, subpgsize * k); + clear_data(readbuf, subpgsize * k); + err = mtd_read(mtd, addr, subpgsize * k, &read, readbuf); + if (unlikely(err || read != subpgsize * k)) { + if (mtd_is_bitflip(err) && read == subpgsize * k) { + pr_info("ECC correction at %#llx\n", + (long long)addr); + err = 0; + } else { + pr_err("error: read failed at " + "%#llx\n", (long long)addr); + return err ? err : -1; + } + } + if (unlikely(memcmp(readbuf, writebuf, subpgsize * k))) { + pr_err("error: verify failed at %#llx\n", + (long long)addr); + errcnt += 1; + } + addr += subpgsize * k; + } + + return err; +} + +static int verify_eraseblock_ff(int ebnum) +{ + uint32_t j; + size_t read; + int err = 0; + loff_t addr = ebnum * mtd->erasesize; + + memset(writebuf, 0xff, subpgsize); + for (j = 0; j < mtd->erasesize / subpgsize; ++j) { + clear_data(readbuf, subpgsize); + err = mtd_read(mtd, addr, subpgsize, &read, readbuf); + if (unlikely(err || read != subpgsize)) { + if (mtd_is_bitflip(err) && read == subpgsize) { + pr_info("ECC correction at %#llx\n", + (long long)addr); + err = 0; + } else { + pr_err("error: read failed at " + "%#llx\n", (long long)addr); + return err ? err : -1; + } + } + if (unlikely(memcmp(readbuf, writebuf, subpgsize))) { + pr_err("error: verify 0xff failed at " + "%#llx\n", (long long)addr); + errcnt += 1; + } + addr += subpgsize; + } + + return err; +} + +static int verify_all_eraseblocks_ff(void) +{ + int err; + unsigned int i; + + pr_info("verifying all eraseblocks for 0xff\n"); + for (i = 0; i < ebcnt; ++i) { + if (bbt[i]) + continue; + err = verify_eraseblock_ff(i); + if (err) + return err; + if (i % 256 == 0) + pr_info("verified up to eraseblock %u\n", i); + cond_resched(); + } + pr_info("verified %u eraseblocks\n", i); + return 0; +} + +static int __init mtd_subpagetest_init(void) +{ + int err = 0; + uint32_t i; + uint64_t tmp; + + printk(KERN_INFO "\n"); + printk(KERN_INFO "=================================================\n"); + + if (dev < 0) { + pr_info("Please specify a valid mtd-device via module parameter\n"); + pr_crit("CAREFUL: This test wipes all data on the specified MTD device!\n"); + return -EINVAL; + } + + pr_info("MTD device: %d\n", dev); + + mtd = get_mtd_device(NULL, dev); + if (IS_ERR(mtd)) { + err = PTR_ERR(mtd); + pr_err("error: cannot get MTD device\n"); + return err; + } + + if (!mtd_type_is_nand(mtd)) { + pr_info("this test requires NAND flash\n"); + goto out; + } + + subpgsize = mtd->writesize >> mtd->subpage_sft; + tmp = mtd->size; + do_div(tmp, mtd->erasesize); + ebcnt = tmp; + pgcnt = mtd->erasesize / mtd->writesize; + + pr_info("MTD device size %llu, eraseblock size %u, " + "page size %u, subpage size %u, count of eraseblocks %u, " + "pages per eraseblock %u, OOB size %u\n", + (unsigned long long)mtd->size, mtd->erasesize, + mtd->writesize, subpgsize, ebcnt, pgcnt, mtd->oobsize); + + err = -ENOMEM; + bufsize = subpgsize * 32; + writebuf = kmalloc(bufsize, GFP_KERNEL); + if (!writebuf) + goto out; + readbuf = kmalloc(bufsize, GFP_KERNEL); + if (!readbuf) + goto out; + bbt = kzalloc(ebcnt, GFP_KERNEL); + if (!bbt) + goto out; + + err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, 0, ebcnt); + if (err) + goto out; + + err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt); + if (err) + goto out; + + pr_info("writing whole device\n"); + prandom_seed_state(&rnd_state, 1); + for (i = 0; i < ebcnt; ++i) { + if (bbt[i]) + continue; + err = write_eraseblock(i); + if (unlikely(err)) + goto out; + if (i % 256 == 0) + pr_info("written up to eraseblock %u\n", i); + cond_resched(); + } + pr_info("written %u eraseblocks\n", i); + + prandom_seed_state(&rnd_state, 1); + pr_info("verifying all eraseblocks\n"); + for (i = 0; i < ebcnt; ++i) { + if (bbt[i]) + continue; + err = verify_eraseblock(i); + if (unlikely(err)) + goto out; + if (i % 256 == 0) + pr_info("verified up to eraseblock %u\n", i); + cond_resched(); + } + pr_info("verified %u eraseblocks\n", i); + + err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt); + if (err) + goto out; + + err = verify_all_eraseblocks_ff(); + if (err) + goto out; + + /* Write all eraseblocks */ + prandom_seed_state(&rnd_state, 3); + pr_info("writing whole device\n"); + for (i = 0; i < ebcnt; ++i) { + if (bbt[i]) + continue; + err = write_eraseblock2(i); + if (unlikely(err)) + goto out; + if (i % 256 == 0) + pr_info("written up to eraseblock %u\n", i); + cond_resched(); + } + pr_info("written %u eraseblocks\n", i); + + /* Check all eraseblocks */ + prandom_seed_state(&rnd_state, 3); + pr_info("verifying all eraseblocks\n"); + for (i = 0; i < ebcnt; ++i) { + if (bbt[i]) + continue; + err = verify_eraseblock2(i); + if (unlikely(err)) + goto out; + if (i % 256 == 0) + pr_info("verified up to eraseblock %u\n", i); + cond_resched(); + } + pr_info("verified %u eraseblocks\n", i); + + err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt); + if (err) + goto out; + + err = verify_all_eraseblocks_ff(); + if (err) + goto out; + + pr_info("finished with %d errors\n", errcnt); + +out: + kfree(bbt); + kfree(readbuf); + kfree(writebuf); + put_mtd_device(mtd); + if (err) + pr_info("error %d occurred\n", err); + printk(KERN_INFO "=================================================\n"); + return err; +} +module_init(mtd_subpagetest_init); + +static void __exit mtd_subpagetest_exit(void) +{ + return; +} +module_exit(mtd_subpagetest_exit); + +MODULE_DESCRIPTION("Subpage test module"); +MODULE_AUTHOR("Adrian Hunter"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/tests/torturetest.c b/drivers/mtd/tests/torturetest.c new file mode 100644 index 00000000000..eeab96973cf --- /dev/null +++ b/drivers/mtd/tests/torturetest.c @@ -0,0 +1,483 @@ +/* + * Copyright (C) 2006-2008 Artem Bityutskiy + * Copyright (C) 2006-2008 Jarkko Lavinen + * Copyright (C) 2006-2008 Adrian Hunter + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; see the file COPYING. If not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Authors: Artem Bityutskiy, Jarkko Lavinen, Adria Hunter + * + * WARNING: this test program may kill your flash and your device. Do not + * use it unless you know what you do. Authors are not responsible for any + * damage caused by this program. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/err.h> +#include <linux/mtd/mtd.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include "mtd_test.h" + +#define RETRIES 3 + +static int eb = 8; +module_param(eb, int, S_IRUGO); +MODULE_PARM_DESC(eb, "eraseblock number within the selected MTD device"); + +static int ebcnt = 32; +module_param(ebcnt, int, S_IRUGO); +MODULE_PARM_DESC(ebcnt, "number of consecutive eraseblocks to torture"); + +static int pgcnt; +module_param(pgcnt, int, S_IRUGO); +MODULE_PARM_DESC(pgcnt, "number of pages per eraseblock to torture (0 => all)"); + +static int dev = -EINVAL; +module_param(dev, int, S_IRUGO); +MODULE_PARM_DESC(dev, "MTD device number to use"); + +static int gran = 512; +module_param(gran, int, S_IRUGO); +MODULE_PARM_DESC(gran, "how often the status information should be printed"); + +static int check = 1; +module_param(check, int, S_IRUGO); +MODULE_PARM_DESC(check, "if the written data should be checked"); + +static unsigned int cycles_count; +module_param(cycles_count, uint, S_IRUGO); +MODULE_PARM_DESC(cycles_count, "how many erase cycles to do " + "(infinite by default)"); + +static struct mtd_info *mtd; + +/* This buffer contains 0x555555...0xAAAAAA... pattern */ +static unsigned char *patt_5A5; +/* This buffer contains 0xAAAAAA...0x555555... pattern */ +static unsigned char *patt_A5A; +/* This buffer contains all 0xFF bytes */ +static unsigned char *patt_FF; +/* This a temporary buffer is use when checking data */ +static unsigned char *check_buf; +/* How many erase cycles were done */ +static unsigned int erase_cycles; + +static int pgsize; +static struct timeval start, finish; + +static void report_corrupt(unsigned char *read, unsigned char *written); + +static inline void start_timing(void) +{ + do_gettimeofday(&start); +} + +static inline void stop_timing(void) +{ + do_gettimeofday(&finish); +} + +/* + * Check that the contents of eraseblock number @enbum is equivalent to the + * @buf buffer. + */ +static inline int check_eraseblock(int ebnum, unsigned char *buf) +{ + int err, retries = 0; + size_t read; + loff_t addr = ebnum * mtd->erasesize; + size_t len = mtd->erasesize; + + if (pgcnt) { + addr = (ebnum + 1) * mtd->erasesize - pgcnt * pgsize; + len = pgcnt * pgsize; + } + +retry: + err = mtd_read(mtd, addr, len, &read, check_buf); + if (mtd_is_bitflip(err)) + pr_err("single bit flip occurred at EB %d " + "MTD reported that it was fixed.\n", ebnum); + else if (err) { + pr_err("error %d while reading EB %d, " + "read %zd\n", err, ebnum, read); + return err; + } + + if (read != len) { + pr_err("failed to read %zd bytes from EB %d, " + "read only %zd, but no error reported\n", + len, ebnum, read); + return -EIO; + } + + if (memcmp(buf, check_buf, len)) { + pr_err("read wrong data from EB %d\n", ebnum); + report_corrupt(check_buf, buf); + + if (retries++ < RETRIES) { + /* Try read again */ + yield(); + pr_info("re-try reading data from EB %d\n", + ebnum); + goto retry; + } else { + pr_info("retried %d times, still errors, " + "give-up\n", RETRIES); + return -EINVAL; + } + } + + if (retries != 0) + pr_info("only attempt number %d was OK (!!!)\n", + retries); + + return 0; +} + +static inline int write_pattern(int ebnum, void *buf) +{ + int err; + size_t written; + loff_t addr = ebnum * mtd->erasesize; + size_t len = mtd->erasesize; + + if (pgcnt) { + addr = (ebnum + 1) * mtd->erasesize - pgcnt * pgsize; + len = pgcnt * pgsize; + } + err = mtd_write(mtd, addr, len, &written, buf); + if (err) { + pr_err("error %d while writing EB %d, written %zd" + " bytes\n", err, ebnum, written); + return err; + } + if (written != len) { + pr_info("written only %zd bytes of %zd, but no error" + " reported\n", written, len); + return -EIO; + } + + return 0; +} + +static int __init tort_init(void) +{ + int err = 0, i, infinite = !cycles_count; + unsigned char *bad_ebs; + + printk(KERN_INFO "\n"); + printk(KERN_INFO "=================================================\n"); + pr_info("Warning: this program is trying to wear out your " + "flash, stop it if this is not wanted.\n"); + + if (dev < 0) { + pr_info("Please specify a valid mtd-device via module parameter\n"); + pr_crit("CAREFUL: This test wipes all data on the specified MTD device!\n"); + return -EINVAL; + } + + pr_info("MTD device: %d\n", dev); + pr_info("torture %d eraseblocks (%d-%d) of mtd%d\n", + ebcnt, eb, eb + ebcnt - 1, dev); + if (pgcnt) + pr_info("torturing just %d pages per eraseblock\n", + pgcnt); + pr_info("write verify %s\n", check ? "enabled" : "disabled"); + + mtd = get_mtd_device(NULL, dev); + if (IS_ERR(mtd)) { + err = PTR_ERR(mtd); + pr_err("error: cannot get MTD device\n"); + return err; + } + + if (mtd->writesize == 1) { + pr_info("not NAND flash, assume page size is 512 " + "bytes.\n"); + pgsize = 512; + } else + pgsize = mtd->writesize; + + if (pgcnt && (pgcnt > mtd->erasesize / pgsize || pgcnt < 0)) { + pr_err("error: invalid pgcnt value %d\n", pgcnt); + goto out_mtd; + } + + err = -ENOMEM; + patt_5A5 = kmalloc(mtd->erasesize, GFP_KERNEL); + if (!patt_5A5) + goto out_mtd; + + patt_A5A = kmalloc(mtd->erasesize, GFP_KERNEL); + if (!patt_A5A) + goto out_patt_5A5; + + patt_FF = kmalloc(mtd->erasesize, GFP_KERNEL); + if (!patt_FF) + goto out_patt_A5A; + + check_buf = kmalloc(mtd->erasesize, GFP_KERNEL); + if (!check_buf) + goto out_patt_FF; + + bad_ebs = kzalloc(ebcnt, GFP_KERNEL); + if (!bad_ebs) + goto out_check_buf; + + err = 0; + + /* Initialize patterns */ + memset(patt_FF, 0xFF, mtd->erasesize); + for (i = 0; i < mtd->erasesize / pgsize; i++) { + if (!(i & 1)) { + memset(patt_5A5 + i * pgsize, 0x55, pgsize); + memset(patt_A5A + i * pgsize, 0xAA, pgsize); + } else { + memset(patt_5A5 + i * pgsize, 0xAA, pgsize); + memset(patt_A5A + i * pgsize, 0x55, pgsize); + } + } + + err = mtdtest_scan_for_bad_eraseblocks(mtd, bad_ebs, eb, ebcnt); + if (err) + goto out; + + start_timing(); + while (1) { + int i; + void *patt; + + mtdtest_erase_good_eraseblocks(mtd, bad_ebs, eb, ebcnt); + + /* Check if the eraseblocks contain only 0xFF bytes */ + if (check) { + for (i = eb; i < eb + ebcnt; i++) { + if (bad_ebs[i - eb]) + continue; + err = check_eraseblock(i, patt_FF); + if (err) { + pr_info("verify failed" + " for 0xFF... pattern\n"); + goto out; + } + cond_resched(); + } + } + + /* Write the pattern */ + for (i = eb; i < eb + ebcnt; i++) { + if (bad_ebs[i - eb]) + continue; + if ((eb + erase_cycles) & 1) + patt = patt_5A5; + else + patt = patt_A5A; + err = write_pattern(i, patt); + if (err) + goto out; + cond_resched(); + } + + /* Verify what we wrote */ + if (check) { + for (i = eb; i < eb + ebcnt; i++) { + if (bad_ebs[i - eb]) + continue; + if ((eb + erase_cycles) & 1) + patt = patt_5A5; + else + patt = patt_A5A; + err = check_eraseblock(i, patt); + if (err) { + pr_info("verify failed for %s" + " pattern\n", + ((eb + erase_cycles) & 1) ? + "0x55AA55..." : "0xAA55AA..."); + goto out; + } + cond_resched(); + } + } + + erase_cycles += 1; + + if (erase_cycles % gran == 0) { + long ms; + + stop_timing(); + ms = (finish.tv_sec - start.tv_sec) * 1000 + + (finish.tv_usec - start.tv_usec) / 1000; + pr_info("%08u erase cycles done, took %lu " + "milliseconds (%lu seconds)\n", + erase_cycles, ms, ms / 1000); + start_timing(); + } + + if (!infinite && --cycles_count == 0) + break; + } +out: + + pr_info("finished after %u erase cycles\n", + erase_cycles); + kfree(bad_ebs); +out_check_buf: + kfree(check_buf); +out_patt_FF: + kfree(patt_FF); +out_patt_A5A: + kfree(patt_A5A); +out_patt_5A5: + kfree(patt_5A5); +out_mtd: + put_mtd_device(mtd); + if (err) + pr_info("error %d occurred during torturing\n", err); + printk(KERN_INFO "=================================================\n"); + return err; +} +module_init(tort_init); + +static void __exit tort_exit(void) +{ + return; +} +module_exit(tort_exit); + +static int countdiffs(unsigned char *buf, unsigned char *check_buf, + unsigned offset, unsigned len, unsigned *bytesp, + unsigned *bitsp); +static void print_bufs(unsigned char *read, unsigned char *written, int start, + int len); + +/* + * Report the detailed information about how the read EB differs from what was + * written. + */ +static void report_corrupt(unsigned char *read, unsigned char *written) +{ + int i; + int bytes, bits, pages, first; + int offset, len; + size_t check_len = mtd->erasesize; + + if (pgcnt) + check_len = pgcnt * pgsize; + + bytes = bits = pages = 0; + for (i = 0; i < check_len; i += pgsize) + if (countdiffs(written, read, i, pgsize, &bytes, + &bits) >= 0) + pages++; + + pr_info("verify fails on %d pages, %d bytes/%d bits\n", + pages, bytes, bits); + pr_info("The following is a list of all differences between" + " what was read from flash and what was expected\n"); + + for (i = 0; i < check_len; i += pgsize) { + cond_resched(); + bytes = bits = 0; + first = countdiffs(written, read, i, pgsize, &bytes, + &bits); + if (first < 0) + continue; + + printk("-------------------------------------------------------" + "----------------------------------\n"); + + pr_info("Page %zd has %d bytes/%d bits failing verify," + " starting at offset 0x%x\n", + (mtd->erasesize - check_len + i) / pgsize, + bytes, bits, first); + + offset = first & ~0x7; + len = ((first + bytes) | 0x7) + 1 - offset; + + print_bufs(read, written, offset, len); + } +} + +static void print_bufs(unsigned char *read, unsigned char *written, int start, + int len) +{ + int i = 0, j1, j2; + char *diff; + + printk("Offset Read Written\n"); + while (i < len) { + printk("0x%08x: ", start + i); + diff = " "; + for (j1 = 0; j1 < 8 && i + j1 < len; j1++) { + printk(" %02x", read[start + i + j1]); + if (read[start + i + j1] != written[start + i + j1]) + diff = "***"; + } + + while (j1 < 8) { + printk(" "); + j1 += 1; + } + + printk(" %s ", diff); + + for (j2 = 0; j2 < 8 && i + j2 < len; j2++) + printk(" %02x", written[start + i + j2]); + printk("\n"); + i += 8; + } +} + +/* + * Count the number of differing bytes and bits and return the first differing + * offset. + */ +static int countdiffs(unsigned char *buf, unsigned char *check_buf, + unsigned offset, unsigned len, unsigned *bytesp, + unsigned *bitsp) +{ + unsigned i, bit; + int first = -1; + + for (i = offset; i < offset + len; i++) + if (buf[i] != check_buf[i]) { + first = i; + break; + } + + while (i < offset + len) { + if (buf[i] != check_buf[i]) { + (*bytesp)++; + bit = 1; + while (bit < 256) { + if ((buf[i] & bit) != (check_buf[i] & bit)) + (*bitsp)++; + bit <<= 1; + } + } + i++; + } + + return first; +} + +MODULE_DESCRIPTION("Eraseblock torturing module"); +MODULE_AUTHOR("Artem Bityutskiy, Jarkko Lavinen, Adrian Hunter"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/ubi/Kconfig b/drivers/mtd/ubi/Kconfig new file mode 100644 index 00000000000..f0855ce08ed --- /dev/null +++ b/drivers/mtd/ubi/Kconfig @@ -0,0 +1,106 @@ +menuconfig MTD_UBI + tristate "Enable UBI - Unsorted block images" + select CRC32 + help + UBI is a software layer above MTD layer which admits of LVM-like + logical volumes on top of MTD devices, hides some complexities of + flash chips like wear and bad blocks and provides some other useful + capabilities. Please, consult the MTD web site for more details + (www.linux-mtd.infradead.org). + +if MTD_UBI + +config MTD_UBI_WL_THRESHOLD + int "UBI wear-leveling threshold" + default 4096 + range 2 65536 + help + This parameter defines the maximum difference between the highest + erase counter value and the lowest erase counter value of eraseblocks + of UBI devices. When this threshold is exceeded, UBI starts performing + wear leveling by means of moving data from eraseblock with low erase + counter to eraseblocks with high erase counter. + + The default value should be OK for SLC NAND flashes, NOR flashes and + other flashes which have eraseblock life-cycle 100000 or more. + However, in case of MLC NAND flashes which typically have eraseblock + life-cycle less than 10000, the threshold should be lessened (e.g., + to 128 or 256, although it does not have to be power of 2). + +config MTD_UBI_BEB_LIMIT + int "Maximum expected bad eraseblock count per 1024 eraseblocks" + default 20 + range 0 768 + help + This option specifies the maximum bad physical eraseblocks UBI + expects on the MTD device (per 1024 eraseblocks). If the underlying + flash does not admit of bad eraseblocks (e.g. NOR flash), this value + is ignored. + + NAND datasheets often specify the minimum and maximum NVM (Number of + Valid Blocks) for the flashes' endurance lifetime. The maximum + expected bad eraseblocks per 1024 eraseblocks then can be calculated + as "1024 * (1 - MinNVB / MaxNVB)", which gives 20 for most NANDs + (MaxNVB is basically the total count of eraseblocks on the chip). + + To put it differently, if this value is 20, UBI will try to reserve + about 1.9% of physical eraseblocks for bad blocks handling. And that + will be 1.9% of eraseblocks on the entire NAND chip, not just the MTD + partition UBI attaches. This means that if you have, say, a NAND + flash chip admits maximum 40 bad eraseblocks, and it is split on two + MTD partitions of the same size, UBI will reserve 40 eraseblocks when + attaching a partition. + + This option can be overridden by the "mtd=" UBI module parameter or + by the "attach" ioctl. + + Leave the default value if unsure. + +config MTD_UBI_FASTMAP + bool "UBI Fastmap (Experimental feature)" + default n + help + Important: this feature is experimental so far and the on-flash + format for fastmap may change in the next kernel versions + + Fastmap is a mechanism which allows attaching an UBI device + in nearly constant time. Instead of scanning the whole MTD device it + only has to locate a checkpoint (called fastmap) on the device. + The on-flash fastmap contains all information needed to attach + the device. Using fastmap makes only sense on large devices where + attaching by scanning takes long. UBI will not automatically install + a fastmap on old images, but you can set the UBI module parameter + fm_autoconvert to 1 if you want so. Please note that fastmap-enabled + images are still usable with UBI implementations without + fastmap support. On typical flash devices the whole fastmap fits + into one PEB. UBI will reserve PEBs to hold two fastmaps. + + If in doubt, say "N". + +config MTD_UBI_GLUEBI + tristate "MTD devices emulation driver (gluebi)" + help + This option enables gluebi - an additional driver which emulates MTD + devices on top of UBI volumes: for each UBI volumes an MTD device is + created, and all I/O to this MTD device is redirected to the UBI + volume. This is handy to make MTD-oriented software (like JFFS2) + work on top of UBI. Do not enable this unless you use legacy + software. + +config MTD_UBI_BLOCK + bool "Read-only block devices on top of UBI volumes" + default n + depends on BLOCK + help + This option enables read-only UBI block devices support. UBI block + devices will be layered on top of UBI volumes, which means that the + UBI driver will transparently handle things like bad eraseblocks and + bit-flips. You can put any block-oriented file system on top of UBI + volumes in read-only mode (e.g., ext4), but it is probably most + practical for read-only file systems, like squashfs. + + When selected, this feature will be built in the UBI driver. + + If in doubt, say "N". + +endif # MTD_UBI diff --git a/drivers/mtd/ubi/Makefile b/drivers/mtd/ubi/Makefile new file mode 100644 index 00000000000..4e3c3d70d8c --- /dev/null +++ b/drivers/mtd/ubi/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_MTD_UBI) += ubi.o + +ubi-y += vtbl.o vmt.o upd.o build.o cdev.o kapi.o eba.o io.o wl.o attach.o +ubi-y += misc.o debug.o +ubi-$(CONFIG_MTD_UBI_FASTMAP) += fastmap.o +ubi-$(CONFIG_MTD_UBI_BLOCK) += block.o + +obj-$(CONFIG_MTD_UBI_GLUEBI) += gluebi.o diff --git a/drivers/mtd/ubi/attach.c b/drivers/mtd/ubi/attach.c new file mode 100644 index 00000000000..6f27d9a1be3 --- /dev/null +++ b/drivers/mtd/ubi/attach.c @@ -0,0 +1,1757 @@ +/* + * Copyright (c) International Business Machines Corp., 2006 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Artem Bityutskiy (Битюцкий Артём) + */ + +/* + * UBI attaching sub-system. + * + * This sub-system is responsible for attaching MTD devices and it also + * implements flash media scanning. + * + * The attaching information is represented by a &struct ubi_attach_info' + * object. Information about volumes is represented by &struct ubi_ainf_volume + * objects which are kept in volume RB-tree with root at the @volumes field. + * The RB-tree is indexed by the volume ID. + * + * Logical eraseblocks are represented by &struct ubi_ainf_peb objects. These + * objects are kept in per-volume RB-trees with the root at the corresponding + * &struct ubi_ainf_volume object. To put it differently, we keep an RB-tree of + * per-volume objects and each of these objects is the root of RB-tree of + * per-LEB objects. + * + * Corrupted physical eraseblocks are put to the @corr list, free physical + * eraseblocks are put to the @free list and the physical eraseblock to be + * erased are put to the @erase list. + * + * About corruptions + * ~~~~~~~~~~~~~~~~~ + * + * UBI protects EC and VID headers with CRC-32 checksums, so it can detect + * whether the headers are corrupted or not. Sometimes UBI also protects the + * data with CRC-32, e.g., when it executes the atomic LEB change operation, or + * when it moves the contents of a PEB for wear-leveling purposes. + * + * UBI tries to distinguish between 2 types of corruptions. + * + * 1. Corruptions caused by power cuts. These are expected corruptions and UBI + * tries to handle them gracefully, without printing too many warnings and + * error messages. The idea is that we do not lose important data in these + * cases - we may lose only the data which were being written to the media just + * before the power cut happened, and the upper layers (e.g., UBIFS) are + * supposed to handle such data losses (e.g., by using the FS journal). + * + * When UBI detects a corruption (CRC-32 mismatch) in a PEB, and it looks like + * the reason is a power cut, UBI puts this PEB to the @erase list, and all + * PEBs in the @erase list are scheduled for erasure later. + * + * 2. Unexpected corruptions which are not caused by power cuts. During + * attaching, such PEBs are put to the @corr list and UBI preserves them. + * Obviously, this lessens the amount of available PEBs, and if at some point + * UBI runs out of free PEBs, it switches to R/O mode. UBI also loudly informs + * about such PEBs every time the MTD device is attached. + * + * However, it is difficult to reliably distinguish between these types of + * corruptions and UBI's strategy is as follows (in case of attaching by + * scanning). UBI assumes corruption type 2 if the VID header is corrupted and + * the data area does not contain all 0xFFs, and there were no bit-flips or + * integrity errors (e.g., ECC errors in case of NAND) while reading the data + * area. Otherwise UBI assumes corruption type 1. So the decision criteria + * are as follows. + * o If the data area contains only 0xFFs, there are no data, and it is safe + * to just erase this PEB - this is corruption type 1. + * o If the data area has bit-flips or data integrity errors (ECC errors on + * NAND), it is probably a PEB which was being erased when power cut + * happened, so this is corruption type 1. However, this is just a guess, + * which might be wrong. + * o Otherwise this is corruption type 2. + */ + +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/crc32.h> +#include <linux/math64.h> +#include <linux/random.h> +#include "ubi.h" + +static int self_check_ai(struct ubi_device *ubi, struct ubi_attach_info *ai); + +/* Temporary variables used during scanning */ +static struct ubi_ec_hdr *ech; +static struct ubi_vid_hdr *vidh; + +/** + * add_to_list - add physical eraseblock to a list. + * @ai: attaching information + * @pnum: physical eraseblock number to add + * @vol_id: the last used volume id for the PEB + * @lnum: the last used LEB number for the PEB + * @ec: erase counter of the physical eraseblock + * @to_head: if not zero, add to the head of the list + * @list: the list to add to + * + * This function allocates a 'struct ubi_ainf_peb' object for physical + * eraseblock @pnum and adds it to the "free", "erase", or "alien" lists. + * It stores the @lnum and @vol_id alongside, which can both be + * %UBI_UNKNOWN if they are not available, not readable, or not assigned. + * If @to_head is not zero, PEB will be added to the head of the list, which + * basically means it will be processed first later. E.g., we add corrupted + * PEBs (corrupted due to power cuts) to the head of the erase list to make + * sure we erase them first and get rid of corruptions ASAP. This function + * returns zero in case of success and a negative error code in case of + * failure. + */ +static int add_to_list(struct ubi_attach_info *ai, int pnum, int vol_id, + int lnum, int ec, int to_head, struct list_head *list) +{ + struct ubi_ainf_peb *aeb; + + if (list == &ai->free) { + dbg_bld("add to free: PEB %d, EC %d", pnum, ec); + } else if (list == &ai->erase) { + dbg_bld("add to erase: PEB %d, EC %d", pnum, ec); + } else if (list == &ai->alien) { + dbg_bld("add to alien: PEB %d, EC %d", pnum, ec); + ai->alien_peb_count += 1; + } else + BUG(); + + aeb = kmem_cache_alloc(ai->aeb_slab_cache, GFP_KERNEL); + if (!aeb) + return -ENOMEM; + + aeb->pnum = pnum; + aeb->vol_id = vol_id; + aeb->lnum = lnum; + aeb->ec = ec; + if (to_head) + list_add(&aeb->u.list, list); + else + list_add_tail(&aeb->u.list, list); + return 0; +} + +/** + * add_corrupted - add a corrupted physical eraseblock. + * @ai: attaching information + * @pnum: physical eraseblock number to add + * @ec: erase counter of the physical eraseblock + * + * This function allocates a 'struct ubi_ainf_peb' object for a corrupted + * physical eraseblock @pnum and adds it to the 'corr' list. The corruption + * was presumably not caused by a power cut. Returns zero in case of success + * and a negative error code in case of failure. + */ +static int add_corrupted(struct ubi_attach_info *ai, int pnum, int ec) +{ + struct ubi_ainf_peb *aeb; + + dbg_bld("add to corrupted: PEB %d, EC %d", pnum, ec); + + aeb = kmem_cache_alloc(ai->aeb_slab_cache, GFP_KERNEL); + if (!aeb) + return -ENOMEM; + + ai->corr_peb_count += 1; + aeb->pnum = pnum; + aeb->ec = ec; + list_add(&aeb->u.list, &ai->corr); + return 0; +} + +/** + * validate_vid_hdr - check volume identifier header. + * @vid_hdr: the volume identifier header to check + * @av: information about the volume this logical eraseblock belongs to + * @pnum: physical eraseblock number the VID header came from + * + * This function checks that data stored in @vid_hdr is consistent. Returns + * non-zero if an inconsistency was found and zero if not. + * + * Note, UBI does sanity check of everything it reads from the flash media. + * Most of the checks are done in the I/O sub-system. Here we check that the + * information in the VID header is consistent to the information in other VID + * headers of the same volume. + */ +static int validate_vid_hdr(const struct ubi_vid_hdr *vid_hdr, + const struct ubi_ainf_volume *av, int pnum) +{ + int vol_type = vid_hdr->vol_type; + int vol_id = be32_to_cpu(vid_hdr->vol_id); + int used_ebs = be32_to_cpu(vid_hdr->used_ebs); + int data_pad = be32_to_cpu(vid_hdr->data_pad); + + if (av->leb_count != 0) { + int av_vol_type; + + /* + * This is not the first logical eraseblock belonging to this + * volume. Ensure that the data in its VID header is consistent + * to the data in previous logical eraseblock headers. + */ + + if (vol_id != av->vol_id) { + ubi_err("inconsistent vol_id"); + goto bad; + } + + if (av->vol_type == UBI_STATIC_VOLUME) + av_vol_type = UBI_VID_STATIC; + else + av_vol_type = UBI_VID_DYNAMIC; + + if (vol_type != av_vol_type) { + ubi_err("inconsistent vol_type"); + goto bad; + } + + if (used_ebs != av->used_ebs) { + ubi_err("inconsistent used_ebs"); + goto bad; + } + + if (data_pad != av->data_pad) { + ubi_err("inconsistent data_pad"); + goto bad; + } + } + + return 0; + +bad: + ubi_err("inconsistent VID header at PEB %d", pnum); + ubi_dump_vid_hdr(vid_hdr); + ubi_dump_av(av); + return -EINVAL; +} + +/** + * add_volume - add volume to the attaching information. + * @ai: attaching information + * @vol_id: ID of the volume to add + * @pnum: physical eraseblock number + * @vid_hdr: volume identifier header + * + * If the volume corresponding to the @vid_hdr logical eraseblock is already + * present in the attaching information, this function does nothing. Otherwise + * it adds corresponding volume to the attaching information. Returns a pointer + * to the allocated "av" object in case of success and a negative error code in + * case of failure. + */ +static struct ubi_ainf_volume *add_volume(struct ubi_attach_info *ai, + int vol_id, int pnum, + const struct ubi_vid_hdr *vid_hdr) +{ + struct ubi_ainf_volume *av; + struct rb_node **p = &ai->volumes.rb_node, *parent = NULL; + + ubi_assert(vol_id == be32_to_cpu(vid_hdr->vol_id)); + + /* Walk the volume RB-tree to look if this volume is already present */ + while (*p) { + parent = *p; + av = rb_entry(parent, struct ubi_ainf_volume, rb); + + if (vol_id == av->vol_id) + return av; + + if (vol_id > av->vol_id) + p = &(*p)->rb_left; + else + p = &(*p)->rb_right; + } + + /* The volume is absent - add it */ + av = kmalloc(sizeof(struct ubi_ainf_volume), GFP_KERNEL); + if (!av) + return ERR_PTR(-ENOMEM); + + av->highest_lnum = av->leb_count = 0; + av->vol_id = vol_id; + av->root = RB_ROOT; + av->used_ebs = be32_to_cpu(vid_hdr->used_ebs); + av->data_pad = be32_to_cpu(vid_hdr->data_pad); + av->compat = vid_hdr->compat; + av->vol_type = vid_hdr->vol_type == UBI_VID_DYNAMIC ? UBI_DYNAMIC_VOLUME + : UBI_STATIC_VOLUME; + if (vol_id > ai->highest_vol_id) + ai->highest_vol_id = vol_id; + + rb_link_node(&av->rb, parent, p); + rb_insert_color(&av->rb, &ai->volumes); + ai->vols_found += 1; + dbg_bld("added volume %d", vol_id); + return av; +} + +/** + * ubi_compare_lebs - find out which logical eraseblock is newer. + * @ubi: UBI device description object + * @aeb: first logical eraseblock to compare + * @pnum: physical eraseblock number of the second logical eraseblock to + * compare + * @vid_hdr: volume identifier header of the second logical eraseblock + * + * This function compares 2 copies of a LEB and informs which one is newer. In + * case of success this function returns a positive value, in case of failure, a + * negative error code is returned. The success return codes use the following + * bits: + * o bit 0 is cleared: the first PEB (described by @aeb) is newer than the + * second PEB (described by @pnum and @vid_hdr); + * o bit 0 is set: the second PEB is newer; + * o bit 1 is cleared: no bit-flips were detected in the newer LEB; + * o bit 1 is set: bit-flips were detected in the newer LEB; + * o bit 2 is cleared: the older LEB is not corrupted; + * o bit 2 is set: the older LEB is corrupted. + */ +int ubi_compare_lebs(struct ubi_device *ubi, const struct ubi_ainf_peb *aeb, + int pnum, const struct ubi_vid_hdr *vid_hdr) +{ + int len, err, second_is_newer, bitflips = 0, corrupted = 0; + uint32_t data_crc, crc; + struct ubi_vid_hdr *vh = NULL; + unsigned long long sqnum2 = be64_to_cpu(vid_hdr->sqnum); + + if (sqnum2 == aeb->sqnum) { + /* + * This must be a really ancient UBI image which has been + * created before sequence numbers support has been added. At + * that times we used 32-bit LEB versions stored in logical + * eraseblocks. That was before UBI got into mainline. We do not + * support these images anymore. Well, those images still work, + * but only if no unclean reboots happened. + */ + ubi_err("unsupported on-flash UBI format"); + return -EINVAL; + } + + /* Obviously the LEB with lower sequence counter is older */ + second_is_newer = (sqnum2 > aeb->sqnum); + + /* + * Now we know which copy is newer. If the copy flag of the PEB with + * newer version is not set, then we just return, otherwise we have to + * check data CRC. For the second PEB we already have the VID header, + * for the first one - we'll need to re-read it from flash. + * + * Note: this may be optimized so that we wouldn't read twice. + */ + + if (second_is_newer) { + if (!vid_hdr->copy_flag) { + /* It is not a copy, so it is newer */ + dbg_bld("second PEB %d is newer, copy_flag is unset", + pnum); + return 1; + } + } else { + if (!aeb->copy_flag) { + /* It is not a copy, so it is newer */ + dbg_bld("first PEB %d is newer, copy_flag is unset", + pnum); + return bitflips << 1; + } + + vh = ubi_zalloc_vid_hdr(ubi, GFP_KERNEL); + if (!vh) + return -ENOMEM; + + pnum = aeb->pnum; + err = ubi_io_read_vid_hdr(ubi, pnum, vh, 0); + if (err) { + if (err == UBI_IO_BITFLIPS) + bitflips = 1; + else { + ubi_err("VID of PEB %d header is bad, but it was OK earlier, err %d", + pnum, err); + if (err > 0) + err = -EIO; + + goto out_free_vidh; + } + } + + vid_hdr = vh; + } + + /* Read the data of the copy and check the CRC */ + + len = be32_to_cpu(vid_hdr->data_size); + + mutex_lock(&ubi->buf_mutex); + err = ubi_io_read_data(ubi, ubi->peb_buf, pnum, 0, len); + if (err && err != UBI_IO_BITFLIPS && !mtd_is_eccerr(err)) + goto out_unlock; + + data_crc = be32_to_cpu(vid_hdr->data_crc); + crc = crc32(UBI_CRC32_INIT, ubi->peb_buf, len); + if (crc != data_crc) { + dbg_bld("PEB %d CRC error: calculated %#08x, must be %#08x", + pnum, crc, data_crc); + corrupted = 1; + bitflips = 0; + second_is_newer = !second_is_newer; + } else { + dbg_bld("PEB %d CRC is OK", pnum); + bitflips = !!err; + } + mutex_unlock(&ubi->buf_mutex); + + ubi_free_vid_hdr(ubi, vh); + + if (second_is_newer) + dbg_bld("second PEB %d is newer, copy_flag is set", pnum); + else + dbg_bld("first PEB %d is newer, copy_flag is set", pnum); + + return second_is_newer | (bitflips << 1) | (corrupted << 2); + +out_unlock: + mutex_unlock(&ubi->buf_mutex); +out_free_vidh: + ubi_free_vid_hdr(ubi, vh); + return err; +} + +/** + * ubi_add_to_av - add used physical eraseblock to the attaching information. + * @ubi: UBI device description object + * @ai: attaching information + * @pnum: the physical eraseblock number + * @ec: erase counter + * @vid_hdr: the volume identifier header + * @bitflips: if bit-flips were detected when this physical eraseblock was read + * + * This function adds information about a used physical eraseblock to the + * 'used' tree of the corresponding volume. The function is rather complex + * because it has to handle cases when this is not the first physical + * eraseblock belonging to the same logical eraseblock, and the newer one has + * to be picked, while the older one has to be dropped. This function returns + * zero in case of success and a negative error code in case of failure. + */ +int ubi_add_to_av(struct ubi_device *ubi, struct ubi_attach_info *ai, int pnum, + int ec, const struct ubi_vid_hdr *vid_hdr, int bitflips) +{ + int err, vol_id, lnum; + unsigned long long sqnum; + struct ubi_ainf_volume *av; + struct ubi_ainf_peb *aeb; + struct rb_node **p, *parent = NULL; + + vol_id = be32_to_cpu(vid_hdr->vol_id); + lnum = be32_to_cpu(vid_hdr->lnum); + sqnum = be64_to_cpu(vid_hdr->sqnum); + + dbg_bld("PEB %d, LEB %d:%d, EC %d, sqnum %llu, bitflips %d", + pnum, vol_id, lnum, ec, sqnum, bitflips); + + av = add_volume(ai, vol_id, pnum, vid_hdr); + if (IS_ERR(av)) + return PTR_ERR(av); + + if (ai->max_sqnum < sqnum) + ai->max_sqnum = sqnum; + + /* + * Walk the RB-tree of logical eraseblocks of volume @vol_id to look + * if this is the first instance of this logical eraseblock or not. + */ + p = &av->root.rb_node; + while (*p) { + int cmp_res; + + parent = *p; + aeb = rb_entry(parent, struct ubi_ainf_peb, u.rb); + if (lnum != aeb->lnum) { + if (lnum < aeb->lnum) + p = &(*p)->rb_left; + else + p = &(*p)->rb_right; + continue; + } + + /* + * There is already a physical eraseblock describing the same + * logical eraseblock present. + */ + + dbg_bld("this LEB already exists: PEB %d, sqnum %llu, EC %d", + aeb->pnum, aeb->sqnum, aeb->ec); + + /* + * Make sure that the logical eraseblocks have different + * sequence numbers. Otherwise the image is bad. + * + * However, if the sequence number is zero, we assume it must + * be an ancient UBI image from the era when UBI did not have + * sequence numbers. We still can attach these images, unless + * there is a need to distinguish between old and new + * eraseblocks, in which case we'll refuse the image in + * 'ubi_compare_lebs()'. In other words, we attach old clean + * images, but refuse attaching old images with duplicated + * logical eraseblocks because there was an unclean reboot. + */ + if (aeb->sqnum == sqnum && sqnum != 0) { + ubi_err("two LEBs with same sequence number %llu", + sqnum); + ubi_dump_aeb(aeb, 0); + ubi_dump_vid_hdr(vid_hdr); + return -EINVAL; + } + + /* + * Now we have to drop the older one and preserve the newer + * one. + */ + cmp_res = ubi_compare_lebs(ubi, aeb, pnum, vid_hdr); + if (cmp_res < 0) + return cmp_res; + + if (cmp_res & 1) { + /* + * This logical eraseblock is newer than the one + * found earlier. + */ + err = validate_vid_hdr(vid_hdr, av, pnum); + if (err) + return err; + + err = add_to_list(ai, aeb->pnum, aeb->vol_id, + aeb->lnum, aeb->ec, cmp_res & 4, + &ai->erase); + if (err) + return err; + + aeb->ec = ec; + aeb->pnum = pnum; + aeb->vol_id = vol_id; + aeb->lnum = lnum; + aeb->scrub = ((cmp_res & 2) || bitflips); + aeb->copy_flag = vid_hdr->copy_flag; + aeb->sqnum = sqnum; + + if (av->highest_lnum == lnum) + av->last_data_size = + be32_to_cpu(vid_hdr->data_size); + + return 0; + } else { + /* + * This logical eraseblock is older than the one found + * previously. + */ + return add_to_list(ai, pnum, vol_id, lnum, ec, + cmp_res & 4, &ai->erase); + } + } + + /* + * We've met this logical eraseblock for the first time, add it to the + * attaching information. + */ + + err = validate_vid_hdr(vid_hdr, av, pnum); + if (err) + return err; + + aeb = kmem_cache_alloc(ai->aeb_slab_cache, GFP_KERNEL); + if (!aeb) + return -ENOMEM; + + aeb->ec = ec; + aeb->pnum = pnum; + aeb->vol_id = vol_id; + aeb->lnum = lnum; + aeb->scrub = bitflips; + aeb->copy_flag = vid_hdr->copy_flag; + aeb->sqnum = sqnum; + + if (av->highest_lnum <= lnum) { + av->highest_lnum = lnum; + av->last_data_size = be32_to_cpu(vid_hdr->data_size); + } + + av->leb_count += 1; + rb_link_node(&aeb->u.rb, parent, p); + rb_insert_color(&aeb->u.rb, &av->root); + return 0; +} + +/** + * ubi_find_av - find volume in the attaching information. + * @ai: attaching information + * @vol_id: the requested volume ID + * + * This function returns a pointer to the volume description or %NULL if there + * are no data about this volume in the attaching information. + */ +struct ubi_ainf_volume *ubi_find_av(const struct ubi_attach_info *ai, + int vol_id) +{ + struct ubi_ainf_volume *av; + struct rb_node *p = ai->volumes.rb_node; + + while (p) { + av = rb_entry(p, struct ubi_ainf_volume, rb); + + if (vol_id == av->vol_id) + return av; + + if (vol_id > av->vol_id) + p = p->rb_left; + else + p = p->rb_right; + } + + return NULL; +} + +/** + * ubi_remove_av - delete attaching information about a volume. + * @ai: attaching information + * @av: the volume attaching information to delete + */ +void ubi_remove_av(struct ubi_attach_info *ai, struct ubi_ainf_volume *av) +{ + struct rb_node *rb; + struct ubi_ainf_peb *aeb; + + dbg_bld("remove attaching information about volume %d", av->vol_id); + + while ((rb = rb_first(&av->root))) { + aeb = rb_entry(rb, struct ubi_ainf_peb, u.rb); + rb_erase(&aeb->u.rb, &av->root); + list_add_tail(&aeb->u.list, &ai->erase); + } + + rb_erase(&av->rb, &ai->volumes); + kfree(av); + ai->vols_found -= 1; +} + +/** + * early_erase_peb - erase a physical eraseblock. + * @ubi: UBI device description object + * @ai: attaching information + * @pnum: physical eraseblock number to erase; + * @ec: erase counter value to write (%UBI_UNKNOWN if it is unknown) + * + * This function erases physical eraseblock 'pnum', and writes the erase + * counter header to it. This function should only be used on UBI device + * initialization stages, when the EBA sub-system had not been yet initialized. + * This function returns zero in case of success and a negative error code in + * case of failure. + */ +static int early_erase_peb(struct ubi_device *ubi, + const struct ubi_attach_info *ai, int pnum, int ec) +{ + int err; + struct ubi_ec_hdr *ec_hdr; + + if ((long long)ec >= UBI_MAX_ERASECOUNTER) { + /* + * Erase counter overflow. Upgrade UBI and use 64-bit + * erase counters internally. + */ + ubi_err("erase counter overflow at PEB %d, EC %d", pnum, ec); + return -EINVAL; + } + + ec_hdr = kzalloc(ubi->ec_hdr_alsize, GFP_KERNEL); + if (!ec_hdr) + return -ENOMEM; + + ec_hdr->ec = cpu_to_be64(ec); + + err = ubi_io_sync_erase(ubi, pnum, 0); + if (err < 0) + goto out_free; + + err = ubi_io_write_ec_hdr(ubi, pnum, ec_hdr); + +out_free: + kfree(ec_hdr); + return err; +} + +/** + * ubi_early_get_peb - get a free physical eraseblock. + * @ubi: UBI device description object + * @ai: attaching information + * + * This function returns a free physical eraseblock. It is supposed to be + * called on the UBI initialization stages when the wear-leveling sub-system is + * not initialized yet. This function picks a physical eraseblocks from one of + * the lists, writes the EC header if it is needed, and removes it from the + * list. + * + * This function returns a pointer to the "aeb" of the found free PEB in case + * of success and an error code in case of failure. + */ +struct ubi_ainf_peb *ubi_early_get_peb(struct ubi_device *ubi, + struct ubi_attach_info *ai) +{ + int err = 0; + struct ubi_ainf_peb *aeb, *tmp_aeb; + + if (!list_empty(&ai->free)) { + aeb = list_entry(ai->free.next, struct ubi_ainf_peb, u.list); + list_del(&aeb->u.list); + dbg_bld("return free PEB %d, EC %d", aeb->pnum, aeb->ec); + return aeb; + } + + /* + * We try to erase the first physical eraseblock from the erase list + * and pick it if we succeed, or try to erase the next one if not. And + * so forth. We don't want to take care about bad eraseblocks here - + * they'll be handled later. + */ + list_for_each_entry_safe(aeb, tmp_aeb, &ai->erase, u.list) { + if (aeb->ec == UBI_UNKNOWN) + aeb->ec = ai->mean_ec; + + err = early_erase_peb(ubi, ai, aeb->pnum, aeb->ec+1); + if (err) + continue; + + aeb->ec += 1; + list_del(&aeb->u.list); + dbg_bld("return PEB %d, EC %d", aeb->pnum, aeb->ec); + return aeb; + } + + ubi_err("no free eraseblocks"); + return ERR_PTR(-ENOSPC); +} + +/** + * check_corruption - check the data area of PEB. + * @ubi: UBI device description object + * @vid_hdr: the (corrupted) VID header of this PEB + * @pnum: the physical eraseblock number to check + * + * This is a helper function which is used to distinguish between VID header + * corruptions caused by power cuts and other reasons. If the PEB contains only + * 0xFF bytes in the data area, the VID header is most probably corrupted + * because of a power cut (%0 is returned in this case). Otherwise, it was + * probably corrupted for some other reasons (%1 is returned in this case). A + * negative error code is returned if a read error occurred. + * + * If the corruption reason was a power cut, UBI can safely erase this PEB. + * Otherwise, it should preserve it to avoid possibly destroying important + * information. + */ +static int check_corruption(struct ubi_device *ubi, struct ubi_vid_hdr *vid_hdr, + int pnum) +{ + int err; + + mutex_lock(&ubi->buf_mutex); + memset(ubi->peb_buf, 0x00, ubi->leb_size); + + err = ubi_io_read(ubi, ubi->peb_buf, pnum, ubi->leb_start, + ubi->leb_size); + if (err == UBI_IO_BITFLIPS || mtd_is_eccerr(err)) { + /* + * Bit-flips or integrity errors while reading the data area. + * It is difficult to say for sure what type of corruption is + * this, but presumably a power cut happened while this PEB was + * erased, so it became unstable and corrupted, and should be + * erased. + */ + err = 0; + goto out_unlock; + } + + if (err) + goto out_unlock; + + if (ubi_check_pattern(ubi->peb_buf, 0xFF, ubi->leb_size)) + goto out_unlock; + + ubi_err("PEB %d contains corrupted VID header, and the data does not contain all 0xFF", + pnum); + ubi_err("this may be a non-UBI PEB or a severe VID header corruption which requires manual inspection"); + ubi_dump_vid_hdr(vid_hdr); + pr_err("hexdump of PEB %d offset %d, length %d", + pnum, ubi->leb_start, ubi->leb_size); + ubi_dbg_print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 32, 1, + ubi->peb_buf, ubi->leb_size, 1); + err = 1; + +out_unlock: + mutex_unlock(&ubi->buf_mutex); + return err; +} + +/** + * scan_peb - scan and process UBI headers of a PEB. + * @ubi: UBI device description object + * @ai: attaching information + * @pnum: the physical eraseblock number + * @vid: The volume ID of the found volume will be stored in this pointer + * @sqnum: The sqnum of the found volume will be stored in this pointer + * + * This function reads UBI headers of PEB @pnum, checks them, and adds + * information about this PEB to the corresponding list or RB-tree in the + * "attaching info" structure. Returns zero if the physical eraseblock was + * successfully handled and a negative error code in case of failure. + */ +static int scan_peb(struct ubi_device *ubi, struct ubi_attach_info *ai, + int pnum, int *vid, unsigned long long *sqnum) +{ + long long uninitialized_var(ec); + int err, bitflips = 0, vol_id = -1, ec_err = 0; + + dbg_bld("scan PEB %d", pnum); + + /* Skip bad physical eraseblocks */ + err = ubi_io_is_bad(ubi, pnum); + if (err < 0) + return err; + else if (err) { + ai->bad_peb_count += 1; + return 0; + } + + err = ubi_io_read_ec_hdr(ubi, pnum, ech, 0); + if (err < 0) + return err; + switch (err) { + case 0: + break; + case UBI_IO_BITFLIPS: + bitflips = 1; + break; + case UBI_IO_FF: + ai->empty_peb_count += 1; + return add_to_list(ai, pnum, UBI_UNKNOWN, UBI_UNKNOWN, + UBI_UNKNOWN, 0, &ai->erase); + case UBI_IO_FF_BITFLIPS: + ai->empty_peb_count += 1; + return add_to_list(ai, pnum, UBI_UNKNOWN, UBI_UNKNOWN, + UBI_UNKNOWN, 1, &ai->erase); + case UBI_IO_BAD_HDR_EBADMSG: + case UBI_IO_BAD_HDR: + /* + * We have to also look at the VID header, possibly it is not + * corrupted. Set %bitflips flag in order to make this PEB be + * moved and EC be re-created. + */ + ec_err = err; + ec = UBI_UNKNOWN; + bitflips = 1; + break; + default: + ubi_err("'ubi_io_read_ec_hdr()' returned unknown code %d", err); + return -EINVAL; + } + + if (!ec_err) { + int image_seq; + + /* Make sure UBI version is OK */ + if (ech->version != UBI_VERSION) { + ubi_err("this UBI version is %d, image version is %d", + UBI_VERSION, (int)ech->version); + return -EINVAL; + } + + ec = be64_to_cpu(ech->ec); + if (ec > UBI_MAX_ERASECOUNTER) { + /* + * Erase counter overflow. The EC headers have 64 bits + * reserved, but we anyway make use of only 31 bit + * values, as this seems to be enough for any existing + * flash. Upgrade UBI and use 64-bit erase counters + * internally. + */ + ubi_err("erase counter overflow, max is %d", + UBI_MAX_ERASECOUNTER); + ubi_dump_ec_hdr(ech); + return -EINVAL; + } + + /* + * Make sure that all PEBs have the same image sequence number. + * This allows us to detect situations when users flash UBI + * images incorrectly, so that the flash has the new UBI image + * and leftovers from the old one. This feature was added + * relatively recently, and the sequence number was always + * zero, because old UBI implementations always set it to zero. + * For this reasons, we do not panic if some PEBs have zero + * sequence number, while other PEBs have non-zero sequence + * number. + */ + image_seq = be32_to_cpu(ech->image_seq); + if (!ubi->image_seq) + ubi->image_seq = image_seq; + if (image_seq && ubi->image_seq != image_seq) { + ubi_err("bad image sequence number %d in PEB %d, expected %d", + image_seq, pnum, ubi->image_seq); + ubi_dump_ec_hdr(ech); + return -EINVAL; + } + } + + /* OK, we've done with the EC header, let's look at the VID header */ + + err = ubi_io_read_vid_hdr(ubi, pnum, vidh, 0); + if (err < 0) + return err; + switch (err) { + case 0: + break; + case UBI_IO_BITFLIPS: + bitflips = 1; + break; + case UBI_IO_BAD_HDR_EBADMSG: + if (ec_err == UBI_IO_BAD_HDR_EBADMSG) + /* + * Both EC and VID headers are corrupted and were read + * with data integrity error, probably this is a bad + * PEB, bit it is not marked as bad yet. This may also + * be a result of power cut during erasure. + */ + ai->maybe_bad_peb_count += 1; + case UBI_IO_BAD_HDR: + if (ec_err) + /* + * Both headers are corrupted. There is a possibility + * that this a valid UBI PEB which has corresponding + * LEB, but the headers are corrupted. However, it is + * impossible to distinguish it from a PEB which just + * contains garbage because of a power cut during erase + * operation. So we just schedule this PEB for erasure. + * + * Besides, in case of NOR flash, we deliberately + * corrupt both headers because NOR flash erasure is + * slow and can start from the end. + */ + err = 0; + else + /* + * The EC was OK, but the VID header is corrupted. We + * have to check what is in the data area. + */ + err = check_corruption(ubi, vidh, pnum); + + if (err < 0) + return err; + else if (!err) + /* This corruption is caused by a power cut */ + err = add_to_list(ai, pnum, UBI_UNKNOWN, + UBI_UNKNOWN, ec, 1, &ai->erase); + else + /* This is an unexpected corruption */ + err = add_corrupted(ai, pnum, ec); + if (err) + return err; + goto adjust_mean_ec; + case UBI_IO_FF_BITFLIPS: + err = add_to_list(ai, pnum, UBI_UNKNOWN, UBI_UNKNOWN, + ec, 1, &ai->erase); + if (err) + return err; + goto adjust_mean_ec; + case UBI_IO_FF: + if (ec_err || bitflips) + err = add_to_list(ai, pnum, UBI_UNKNOWN, + UBI_UNKNOWN, ec, 1, &ai->erase); + else + err = add_to_list(ai, pnum, UBI_UNKNOWN, + UBI_UNKNOWN, ec, 0, &ai->free); + if (err) + return err; + goto adjust_mean_ec; + default: + ubi_err("'ubi_io_read_vid_hdr()' returned unknown code %d", + err); + return -EINVAL; + } + + vol_id = be32_to_cpu(vidh->vol_id); + if (vid) + *vid = vol_id; + if (sqnum) + *sqnum = be64_to_cpu(vidh->sqnum); + if (vol_id > UBI_MAX_VOLUMES && vol_id != UBI_LAYOUT_VOLUME_ID) { + int lnum = be32_to_cpu(vidh->lnum); + + /* Unsupported internal volume */ + switch (vidh->compat) { + case UBI_COMPAT_DELETE: + if (vol_id != UBI_FM_SB_VOLUME_ID + && vol_id != UBI_FM_DATA_VOLUME_ID) { + ubi_msg("\"delete\" compatible internal volume %d:%d found, will remove it", + vol_id, lnum); + } + err = add_to_list(ai, pnum, vol_id, lnum, + ec, 1, &ai->erase); + if (err) + return err; + return 0; + + case UBI_COMPAT_RO: + ubi_msg("read-only compatible internal volume %d:%d found, switch to read-only mode", + vol_id, lnum); + ubi->ro_mode = 1; + break; + + case UBI_COMPAT_PRESERVE: + ubi_msg("\"preserve\" compatible internal volume %d:%d found", + vol_id, lnum); + err = add_to_list(ai, pnum, vol_id, lnum, + ec, 0, &ai->alien); + if (err) + return err; + return 0; + + case UBI_COMPAT_REJECT: + ubi_err("incompatible internal volume %d:%d found", + vol_id, lnum); + return -EINVAL; + } + } + + if (ec_err) + ubi_warn("valid VID header but corrupted EC header at PEB %d", + pnum); + err = ubi_add_to_av(ubi, ai, pnum, ec, vidh, bitflips); + if (err) + return err; + +adjust_mean_ec: + if (!ec_err) { + ai->ec_sum += ec; + ai->ec_count += 1; + if (ec > ai->max_ec) + ai->max_ec = ec; + if (ec < ai->min_ec) + ai->min_ec = ec; + } + + return 0; +} + +/** + * late_analysis - analyze the overall situation with PEB. + * @ubi: UBI device description object + * @ai: attaching information + * + * This is a helper function which takes a look what PEBs we have after we + * gather information about all of them ("ai" is compete). It decides whether + * the flash is empty and should be formatted of whether there are too many + * corrupted PEBs and we should not attach this MTD device. Returns zero if we + * should proceed with attaching the MTD device, and %-EINVAL if we should not. + */ +static int late_analysis(struct ubi_device *ubi, struct ubi_attach_info *ai) +{ + struct ubi_ainf_peb *aeb; + int max_corr, peb_count; + + peb_count = ubi->peb_count - ai->bad_peb_count - ai->alien_peb_count; + max_corr = peb_count / 20 ?: 8; + + /* + * Few corrupted PEBs is not a problem and may be just a result of + * unclean reboots. However, many of them may indicate some problems + * with the flash HW or driver. + */ + if (ai->corr_peb_count) { + ubi_err("%d PEBs are corrupted and preserved", + ai->corr_peb_count); + pr_err("Corrupted PEBs are:"); + list_for_each_entry(aeb, &ai->corr, u.list) + pr_cont(" %d", aeb->pnum); + pr_cont("\n"); + + /* + * If too many PEBs are corrupted, we refuse attaching, + * otherwise, only print a warning. + */ + if (ai->corr_peb_count >= max_corr) { + ubi_err("too many corrupted PEBs, refusing"); + return -EINVAL; + } + } + + if (ai->empty_peb_count + ai->maybe_bad_peb_count == peb_count) { + /* + * All PEBs are empty, or almost all - a couple PEBs look like + * they may be bad PEBs which were not marked as bad yet. + * + * This piece of code basically tries to distinguish between + * the following situations: + * + * 1. Flash is empty, but there are few bad PEBs, which are not + * marked as bad so far, and which were read with error. We + * want to go ahead and format this flash. While formatting, + * the faulty PEBs will probably be marked as bad. + * + * 2. Flash contains non-UBI data and we do not want to format + * it and destroy possibly important information. + */ + if (ai->maybe_bad_peb_count <= 2) { + ai->is_empty = 1; + ubi_msg("empty MTD device detected"); + get_random_bytes(&ubi->image_seq, + sizeof(ubi->image_seq)); + } else { + ubi_err("MTD device is not UBI-formatted and possibly contains non-UBI data - refusing it"); + return -EINVAL; + } + + } + + return 0; +} + +/** + * destroy_av - free volume attaching information. + * @av: volume attaching information + * @ai: attaching information + * + * This function destroys the volume attaching information. + */ +static void destroy_av(struct ubi_attach_info *ai, struct ubi_ainf_volume *av) +{ + struct ubi_ainf_peb *aeb; + struct rb_node *this = av->root.rb_node; + + while (this) { + if (this->rb_left) + this = this->rb_left; + else if (this->rb_right) + this = this->rb_right; + else { + aeb = rb_entry(this, struct ubi_ainf_peb, u.rb); + this = rb_parent(this); + if (this) { + if (this->rb_left == &aeb->u.rb) + this->rb_left = NULL; + else + this->rb_right = NULL; + } + + kmem_cache_free(ai->aeb_slab_cache, aeb); + } + } + kfree(av); +} + +/** + * destroy_ai - destroy attaching information. + * @ai: attaching information + */ +static void destroy_ai(struct ubi_attach_info *ai) +{ + struct ubi_ainf_peb *aeb, *aeb_tmp; + struct ubi_ainf_volume *av; + struct rb_node *rb; + + list_for_each_entry_safe(aeb, aeb_tmp, &ai->alien, u.list) { + list_del(&aeb->u.list); + kmem_cache_free(ai->aeb_slab_cache, aeb); + } + list_for_each_entry_safe(aeb, aeb_tmp, &ai->erase, u.list) { + list_del(&aeb->u.list); + kmem_cache_free(ai->aeb_slab_cache, aeb); + } + list_for_each_entry_safe(aeb, aeb_tmp, &ai->corr, u.list) { + list_del(&aeb->u.list); + kmem_cache_free(ai->aeb_slab_cache, aeb); + } + list_for_each_entry_safe(aeb, aeb_tmp, &ai->free, u.list) { + list_del(&aeb->u.list); + kmem_cache_free(ai->aeb_slab_cache, aeb); + } + + /* Destroy the volume RB-tree */ + rb = ai->volumes.rb_node; + while (rb) { + if (rb->rb_left) + rb = rb->rb_left; + else if (rb->rb_right) + rb = rb->rb_right; + else { + av = rb_entry(rb, struct ubi_ainf_volume, rb); + + rb = rb_parent(rb); + if (rb) { + if (rb->rb_left == &av->rb) + rb->rb_left = NULL; + else + rb->rb_right = NULL; + } + + destroy_av(ai, av); + } + } + + if (ai->aeb_slab_cache) + kmem_cache_destroy(ai->aeb_slab_cache); + + kfree(ai); +} + +/** + * scan_all - scan entire MTD device. + * @ubi: UBI device description object + * @ai: attach info object + * @start: start scanning at this PEB + * + * This function does full scanning of an MTD device and returns complete + * information about it in form of a "struct ubi_attach_info" object. In case + * of failure, an error code is returned. + */ +static int scan_all(struct ubi_device *ubi, struct ubi_attach_info *ai, + int start) +{ + int err, pnum; + struct rb_node *rb1, *rb2; + struct ubi_ainf_volume *av; + struct ubi_ainf_peb *aeb; + + err = -ENOMEM; + + ech = kzalloc(ubi->ec_hdr_alsize, GFP_KERNEL); + if (!ech) + return err; + + vidh = ubi_zalloc_vid_hdr(ubi, GFP_KERNEL); + if (!vidh) + goto out_ech; + + for (pnum = start; pnum < ubi->peb_count; pnum++) { + cond_resched(); + + dbg_gen("process PEB %d", pnum); + err = scan_peb(ubi, ai, pnum, NULL, NULL); + if (err < 0) + goto out_vidh; + } + + ubi_msg("scanning is finished"); + + /* Calculate mean erase counter */ + if (ai->ec_count) + ai->mean_ec = div_u64(ai->ec_sum, ai->ec_count); + + err = late_analysis(ubi, ai); + if (err) + goto out_vidh; + + /* + * In case of unknown erase counter we use the mean erase counter + * value. + */ + ubi_rb_for_each_entry(rb1, av, &ai->volumes, rb) { + ubi_rb_for_each_entry(rb2, aeb, &av->root, u.rb) + if (aeb->ec == UBI_UNKNOWN) + aeb->ec = ai->mean_ec; + } + + list_for_each_entry(aeb, &ai->free, u.list) { + if (aeb->ec == UBI_UNKNOWN) + aeb->ec = ai->mean_ec; + } + + list_for_each_entry(aeb, &ai->corr, u.list) + if (aeb->ec == UBI_UNKNOWN) + aeb->ec = ai->mean_ec; + + list_for_each_entry(aeb, &ai->erase, u.list) + if (aeb->ec == UBI_UNKNOWN) + aeb->ec = ai->mean_ec; + + err = self_check_ai(ubi, ai); + if (err) + goto out_vidh; + + ubi_free_vid_hdr(ubi, vidh); + kfree(ech); + + return 0; + +out_vidh: + ubi_free_vid_hdr(ubi, vidh); +out_ech: + kfree(ech); + return err; +} + +#ifdef CONFIG_MTD_UBI_FASTMAP + +/** + * scan_fastmap - try to find a fastmap and attach from it. + * @ubi: UBI device description object + * @ai: attach info object + * + * Returns 0 on success, negative return values indicate an internal + * error. + * UBI_NO_FASTMAP denotes that no fastmap was found. + * UBI_BAD_FASTMAP denotes that the found fastmap was invalid. + */ +static int scan_fast(struct ubi_device *ubi, struct ubi_attach_info *ai) +{ + int err, pnum, fm_anchor = -1; + unsigned long long max_sqnum = 0; + + err = -ENOMEM; + + ech = kzalloc(ubi->ec_hdr_alsize, GFP_KERNEL); + if (!ech) + goto out; + + vidh = ubi_zalloc_vid_hdr(ubi, GFP_KERNEL); + if (!vidh) + goto out_ech; + + for (pnum = 0; pnum < UBI_FM_MAX_START; pnum++) { + int vol_id = -1; + unsigned long long sqnum = -1; + cond_resched(); + + dbg_gen("process PEB %d", pnum); + err = scan_peb(ubi, ai, pnum, &vol_id, &sqnum); + if (err < 0) + goto out_vidh; + + if (vol_id == UBI_FM_SB_VOLUME_ID && sqnum > max_sqnum) { + max_sqnum = sqnum; + fm_anchor = pnum; + } + } + + ubi_free_vid_hdr(ubi, vidh); + kfree(ech); + + if (fm_anchor < 0) + return UBI_NO_FASTMAP; + + return ubi_scan_fastmap(ubi, ai, fm_anchor); + +out_vidh: + ubi_free_vid_hdr(ubi, vidh); +out_ech: + kfree(ech); +out: + return err; +} + +#endif + +static struct ubi_attach_info *alloc_ai(const char *slab_name) +{ + struct ubi_attach_info *ai; + + ai = kzalloc(sizeof(struct ubi_attach_info), GFP_KERNEL); + if (!ai) + return ai; + + INIT_LIST_HEAD(&ai->corr); + INIT_LIST_HEAD(&ai->free); + INIT_LIST_HEAD(&ai->erase); + INIT_LIST_HEAD(&ai->alien); + ai->volumes = RB_ROOT; + ai->aeb_slab_cache = kmem_cache_create(slab_name, + sizeof(struct ubi_ainf_peb), + 0, 0, NULL); + if (!ai->aeb_slab_cache) { + kfree(ai); + ai = NULL; + } + + return ai; +} + +/** + * ubi_attach - attach an MTD device. + * @ubi: UBI device descriptor + * @force_scan: if set to non-zero attach by scanning + * + * This function returns zero in case of success and a negative error code in + * case of failure. + */ +int ubi_attach(struct ubi_device *ubi, int force_scan) +{ + int err; + struct ubi_attach_info *ai; + + ai = alloc_ai("ubi_aeb_slab_cache"); + if (!ai) + return -ENOMEM; + +#ifdef CONFIG_MTD_UBI_FASTMAP + /* On small flash devices we disable fastmap in any case. */ + if ((int)mtd_div_by_eb(ubi->mtd->size, ubi->mtd) <= UBI_FM_MAX_START) { + ubi->fm_disabled = 1; + force_scan = 1; + } + + if (force_scan) + err = scan_all(ubi, ai, 0); + else { + err = scan_fast(ubi, ai); + if (err > 0) { + if (err != UBI_NO_FASTMAP) { + destroy_ai(ai); + ai = alloc_ai("ubi_aeb_slab_cache2"); + if (!ai) + return -ENOMEM; + + err = scan_all(ubi, ai, 0); + } else { + err = scan_all(ubi, ai, UBI_FM_MAX_START); + } + } + } +#else + err = scan_all(ubi, ai, 0); +#endif + if (err) + goto out_ai; + + ubi->bad_peb_count = ai->bad_peb_count; + ubi->good_peb_count = ubi->peb_count - ubi->bad_peb_count; + ubi->corr_peb_count = ai->corr_peb_count; + ubi->max_ec = ai->max_ec; + ubi->mean_ec = ai->mean_ec; + dbg_gen("max. sequence number: %llu", ai->max_sqnum); + + err = ubi_read_volume_table(ubi, ai); + if (err) + goto out_ai; + + err = ubi_wl_init(ubi, ai); + if (err) + goto out_vtbl; + + err = ubi_eba_init(ubi, ai); + if (err) + goto out_wl; + +#ifdef CONFIG_MTD_UBI_FASTMAP + if (ubi->fm && ubi_dbg_chk_gen(ubi)) { + struct ubi_attach_info *scan_ai; + + scan_ai = alloc_ai("ubi_ckh_aeb_slab_cache"); + if (!scan_ai) { + err = -ENOMEM; + goto out_wl; + } + + err = scan_all(ubi, scan_ai, 0); + if (err) { + destroy_ai(scan_ai); + goto out_wl; + } + + err = self_check_eba(ubi, ai, scan_ai); + destroy_ai(scan_ai); + + if (err) + goto out_wl; + } +#endif + + destroy_ai(ai); + return 0; + +out_wl: + ubi_wl_close(ubi); +out_vtbl: + ubi_free_internal_volumes(ubi); + vfree(ubi->vtbl); +out_ai: + destroy_ai(ai); + return err; +} + +/** + * self_check_ai - check the attaching information. + * @ubi: UBI device description object + * @ai: attaching information + * + * This function returns zero if the attaching information is all right, and a + * negative error code if not or if an error occurred. + */ +static int self_check_ai(struct ubi_device *ubi, struct ubi_attach_info *ai) +{ + int pnum, err, vols_found = 0; + struct rb_node *rb1, *rb2; + struct ubi_ainf_volume *av; + struct ubi_ainf_peb *aeb, *last_aeb; + uint8_t *buf; + + if (!ubi_dbg_chk_gen(ubi)) + return 0; + + /* + * At first, check that attaching information is OK. + */ + ubi_rb_for_each_entry(rb1, av, &ai->volumes, rb) { + int leb_count = 0; + + cond_resched(); + + vols_found += 1; + + if (ai->is_empty) { + ubi_err("bad is_empty flag"); + goto bad_av; + } + + if (av->vol_id < 0 || av->highest_lnum < 0 || + av->leb_count < 0 || av->vol_type < 0 || av->used_ebs < 0 || + av->data_pad < 0 || av->last_data_size < 0) { + ubi_err("negative values"); + goto bad_av; + } + + if (av->vol_id >= UBI_MAX_VOLUMES && + av->vol_id < UBI_INTERNAL_VOL_START) { + ubi_err("bad vol_id"); + goto bad_av; + } + + if (av->vol_id > ai->highest_vol_id) { + ubi_err("highest_vol_id is %d, but vol_id %d is there", + ai->highest_vol_id, av->vol_id); + goto out; + } + + if (av->vol_type != UBI_DYNAMIC_VOLUME && + av->vol_type != UBI_STATIC_VOLUME) { + ubi_err("bad vol_type"); + goto bad_av; + } + + if (av->data_pad > ubi->leb_size / 2) { + ubi_err("bad data_pad"); + goto bad_av; + } + + last_aeb = NULL; + ubi_rb_for_each_entry(rb2, aeb, &av->root, u.rb) { + cond_resched(); + + last_aeb = aeb; + leb_count += 1; + + if (aeb->pnum < 0 || aeb->ec < 0) { + ubi_err("negative values"); + goto bad_aeb; + } + + if (aeb->ec < ai->min_ec) { + ubi_err("bad ai->min_ec (%d), %d found", + ai->min_ec, aeb->ec); + goto bad_aeb; + } + + if (aeb->ec > ai->max_ec) { + ubi_err("bad ai->max_ec (%d), %d found", + ai->max_ec, aeb->ec); + goto bad_aeb; + } + + if (aeb->pnum >= ubi->peb_count) { + ubi_err("too high PEB number %d, total PEBs %d", + aeb->pnum, ubi->peb_count); + goto bad_aeb; + } + + if (av->vol_type == UBI_STATIC_VOLUME) { + if (aeb->lnum >= av->used_ebs) { + ubi_err("bad lnum or used_ebs"); + goto bad_aeb; + } + } else { + if (av->used_ebs != 0) { + ubi_err("non-zero used_ebs"); + goto bad_aeb; + } + } + + if (aeb->lnum > av->highest_lnum) { + ubi_err("incorrect highest_lnum or lnum"); + goto bad_aeb; + } + } + + if (av->leb_count != leb_count) { + ubi_err("bad leb_count, %d objects in the tree", + leb_count); + goto bad_av; + } + + if (!last_aeb) + continue; + + aeb = last_aeb; + + if (aeb->lnum != av->highest_lnum) { + ubi_err("bad highest_lnum"); + goto bad_aeb; + } + } + + if (vols_found != ai->vols_found) { + ubi_err("bad ai->vols_found %d, should be %d", + ai->vols_found, vols_found); + goto out; + } + + /* Check that attaching information is correct */ + ubi_rb_for_each_entry(rb1, av, &ai->volumes, rb) { + last_aeb = NULL; + ubi_rb_for_each_entry(rb2, aeb, &av->root, u.rb) { + int vol_type; + + cond_resched(); + + last_aeb = aeb; + + err = ubi_io_read_vid_hdr(ubi, aeb->pnum, vidh, 1); + if (err && err != UBI_IO_BITFLIPS) { + ubi_err("VID header is not OK (%d)", err); + if (err > 0) + err = -EIO; + return err; + } + + vol_type = vidh->vol_type == UBI_VID_DYNAMIC ? + UBI_DYNAMIC_VOLUME : UBI_STATIC_VOLUME; + if (av->vol_type != vol_type) { + ubi_err("bad vol_type"); + goto bad_vid_hdr; + } + + if (aeb->sqnum != be64_to_cpu(vidh->sqnum)) { + ubi_err("bad sqnum %llu", aeb->sqnum); + goto bad_vid_hdr; + } + + if (av->vol_id != be32_to_cpu(vidh->vol_id)) { + ubi_err("bad vol_id %d", av->vol_id); + goto bad_vid_hdr; + } + + if (av->compat != vidh->compat) { + ubi_err("bad compat %d", vidh->compat); + goto bad_vid_hdr; + } + + if (aeb->lnum != be32_to_cpu(vidh->lnum)) { + ubi_err("bad lnum %d", aeb->lnum); + goto bad_vid_hdr; + } + + if (av->used_ebs != be32_to_cpu(vidh->used_ebs)) { + ubi_err("bad used_ebs %d", av->used_ebs); + goto bad_vid_hdr; + } + + if (av->data_pad != be32_to_cpu(vidh->data_pad)) { + ubi_err("bad data_pad %d", av->data_pad); + goto bad_vid_hdr; + } + } + + if (!last_aeb) + continue; + + if (av->highest_lnum != be32_to_cpu(vidh->lnum)) { + ubi_err("bad highest_lnum %d", av->highest_lnum); + goto bad_vid_hdr; + } + + if (av->last_data_size != be32_to_cpu(vidh->data_size)) { + ubi_err("bad last_data_size %d", av->last_data_size); + goto bad_vid_hdr; + } + } + + /* + * Make sure that all the physical eraseblocks are in one of the lists + * or trees. + */ + buf = kzalloc(ubi->peb_count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (pnum = 0; pnum < ubi->peb_count; pnum++) { + err = ubi_io_is_bad(ubi, pnum); + if (err < 0) { + kfree(buf); + return err; + } else if (err) + buf[pnum] = 1; + } + + ubi_rb_for_each_entry(rb1, av, &ai->volumes, rb) + ubi_rb_for_each_entry(rb2, aeb, &av->root, u.rb) + buf[aeb->pnum] = 1; + + list_for_each_entry(aeb, &ai->free, u.list) + buf[aeb->pnum] = 1; + + list_for_each_entry(aeb, &ai->corr, u.list) + buf[aeb->pnum] = 1; + + list_for_each_entry(aeb, &ai->erase, u.list) + buf[aeb->pnum] = 1; + + list_for_each_entry(aeb, &ai->alien, u.list) + buf[aeb->pnum] = 1; + + err = 0; + for (pnum = 0; pnum < ubi->peb_count; pnum++) + if (!buf[pnum]) { + ubi_err("PEB %d is not referred", pnum); + err = 1; + } + + kfree(buf); + if (err) + goto out; + return 0; + +bad_aeb: + ubi_err("bad attaching information about LEB %d", aeb->lnum); + ubi_dump_aeb(aeb, 0); + ubi_dump_av(av); + goto out; + +bad_av: + ubi_err("bad attaching information about volume %d", av->vol_id); + ubi_dump_av(av); + goto out; + +bad_vid_hdr: + ubi_err("bad attaching information about volume %d", av->vol_id); + ubi_dump_av(av); + ubi_dump_vid_hdr(vidh); + +out: + dump_stack(); + return -EINVAL; +} diff --git a/drivers/mtd/ubi/block.c b/drivers/mtd/ubi/block.c new file mode 100644 index 00000000000..8457df7ec5a --- /dev/null +++ b/drivers/mtd/ubi/block.c @@ -0,0 +1,649 @@ +/* + * Copyright (c) 2014 Ezequiel Garcia + * Copyright (c) 2011 Free Electrons + * + * Driver parameter handling strongly based on drivers/mtd/ubi/build.c + * Copyright (c) International Business Machines Corp., 2006 + * Copyright (c) Nokia Corporation, 2007 + * Authors: Artem Bityutskiy, Frank Haverkamp + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * 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. + */ + +/* + * Read-only block devices on top of UBI volumes + * + * A simple implementation to allow a block device to be layered on top of a + * UBI volume. The implementation is provided by creating a static 1-to-1 + * mapping between the block device and the UBI volume. + * + * The addressed byte is obtained from the addressed block sector, which is + * mapped linearly into the corresponding LEB: + * + * LEB number = addressed byte / LEB size + * + * This feature is compiled in the UBI core, and adds a 'block' parameter + * to allow early creation of block devices on top of UBI volumes. Runtime + * block creation/removal for UBI volumes is provided through two UBI ioctls: + * UBI_IOCVOLCRBLK and UBI_IOCVOLRMBLK. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/mtd/ubi.h> +#include <linux/workqueue.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> +#include <asm/div64.h> + +#include "ubi-media.h" +#include "ubi.h" + +/* Maximum number of supported devices */ +#define UBIBLOCK_MAX_DEVICES 32 + +/* Maximum length of the 'block=' parameter */ +#define UBIBLOCK_PARAM_LEN 63 + +/* Maximum number of comma-separated items in the 'block=' parameter */ +#define UBIBLOCK_PARAM_COUNT 2 + +struct ubiblock_param { + int ubi_num; + int vol_id; + char name[UBIBLOCK_PARAM_LEN+1]; +}; + +/* Numbers of elements set in the @ubiblock_param array */ +static int ubiblock_devs __initdata; + +/* MTD devices specification parameters */ +static struct ubiblock_param ubiblock_param[UBIBLOCK_MAX_DEVICES] __initdata; + +struct ubiblock { + struct ubi_volume_desc *desc; + int ubi_num; + int vol_id; + int refcnt; + int leb_size; + + struct gendisk *gd; + struct request_queue *rq; + + struct workqueue_struct *wq; + struct work_struct work; + + struct mutex dev_mutex; + spinlock_t queue_lock; + struct list_head list; +}; + +/* Linked list of all ubiblock instances */ +static LIST_HEAD(ubiblock_devices); +static DEFINE_MUTEX(devices_mutex); +static int ubiblock_major; + +static int __init ubiblock_set_param(const char *val, + const struct kernel_param *kp) +{ + int i, ret; + size_t len; + struct ubiblock_param *param; + char buf[UBIBLOCK_PARAM_LEN]; + char *pbuf = &buf[0]; + char *tokens[UBIBLOCK_PARAM_COUNT]; + + if (!val) + return -EINVAL; + + len = strnlen(val, UBIBLOCK_PARAM_LEN); + if (len == 0) { + ubi_warn("block: empty 'block=' parameter - ignored\n"); + return 0; + } + + if (len == UBIBLOCK_PARAM_LEN) { + ubi_err("block: parameter \"%s\" is too long, max. is %d\n", + val, UBIBLOCK_PARAM_LEN); + return -EINVAL; + } + + strcpy(buf, val); + + /* Get rid of the final newline */ + if (buf[len - 1] == '\n') + buf[len - 1] = '\0'; + + for (i = 0; i < UBIBLOCK_PARAM_COUNT; i++) + tokens[i] = strsep(&pbuf, ","); + + param = &ubiblock_param[ubiblock_devs]; + if (tokens[1]) { + /* Two parameters: can be 'ubi, vol_id' or 'ubi, vol_name' */ + ret = kstrtoint(tokens[0], 10, ¶m->ubi_num); + if (ret < 0) + return -EINVAL; + + /* Second param can be a number or a name */ + ret = kstrtoint(tokens[1], 10, ¶m->vol_id); + if (ret < 0) { + param->vol_id = -1; + strcpy(param->name, tokens[1]); + } + + } else { + /* One parameter: must be device path */ + strcpy(param->name, tokens[0]); + param->ubi_num = -1; + param->vol_id = -1; + } + + ubiblock_devs++; + + return 0; +} + +static struct kernel_param_ops ubiblock_param_ops = { + .set = ubiblock_set_param, +}; +module_param_cb(block, &ubiblock_param_ops, NULL, 0); +MODULE_PARM_DESC(block, "Attach block devices to UBI volumes. Parameter format: block=<path|dev,num|dev,name>.\n" + "Multiple \"block\" parameters may be specified.\n" + "UBI volumes may be specified by their number, name, or path to the device node.\n" + "Examples\n" + "Using the UBI volume path:\n" + "ubi.block=/dev/ubi0_0\n" + "Using the UBI device, and the volume name:\n" + "ubi.block=0,rootfs\n" + "Using both UBI device number and UBI volume number:\n" + "ubi.block=0,0\n"); + +static struct ubiblock *find_dev_nolock(int ubi_num, int vol_id) +{ + struct ubiblock *dev; + + list_for_each_entry(dev, &ubiblock_devices, list) + if (dev->ubi_num == ubi_num && dev->vol_id == vol_id) + return dev; + return NULL; +} + +static int ubiblock_read_to_buf(struct ubiblock *dev, char *buffer, + int leb, int offset, int len) +{ + int ret; + + ret = ubi_read(dev->desc, leb, buffer, offset, len); + if (ret) { + ubi_err("%s ubi_read error %d", + dev->gd->disk_name, ret); + return ret; + } + return 0; +} + +static int ubiblock_read(struct ubiblock *dev, char *buffer, + sector_t sec, int len) +{ + int ret, leb, offset; + int bytes_left = len; + int to_read = len; + u64 pos = sec << 9; + + /* Get LEB:offset address to read from */ + offset = do_div(pos, dev->leb_size); + leb = pos; + + while (bytes_left) { + /* + * We can only read one LEB at a time. Therefore if the read + * length is larger than one LEB size, we split the operation. + */ + if (offset + to_read > dev->leb_size) + to_read = dev->leb_size - offset; + + ret = ubiblock_read_to_buf(dev, buffer, leb, offset, to_read); + if (ret) + return ret; + + buffer += to_read; + bytes_left -= to_read; + to_read = bytes_left; + leb += 1; + offset = 0; + } + return 0; +} + +static int do_ubiblock_request(struct ubiblock *dev, struct request *req) +{ + int len, ret; + sector_t sec; + + if (req->cmd_type != REQ_TYPE_FS) + return -EIO; + + if (blk_rq_pos(req) + blk_rq_cur_sectors(req) > + get_capacity(req->rq_disk)) + return -EIO; + + if (rq_data_dir(req) != READ) + return -ENOSYS; /* Write not implemented */ + + sec = blk_rq_pos(req); + len = blk_rq_cur_bytes(req); + + /* + * Let's prevent the device from being removed while we're doing I/O + * work. Notice that this means we serialize all the I/O operations, + * but it's probably of no impact given the NAND core serializes + * flash access anyway. + */ + mutex_lock(&dev->dev_mutex); + ret = ubiblock_read(dev, bio_data(req->bio), sec, len); + mutex_unlock(&dev->dev_mutex); + + return ret; +} + +static void ubiblock_do_work(struct work_struct *work) +{ + struct ubiblock *dev = + container_of(work, struct ubiblock, work); + struct request_queue *rq = dev->rq; + struct request *req; + int res; + + spin_lock_irq(rq->queue_lock); + + req = blk_fetch_request(rq); + while (req) { + + spin_unlock_irq(rq->queue_lock); + res = do_ubiblock_request(dev, req); + spin_lock_irq(rq->queue_lock); + + /* + * If we're done with this request, + * we need to fetch a new one + */ + if (!__blk_end_request_cur(req, res)) + req = blk_fetch_request(rq); + } + + spin_unlock_irq(rq->queue_lock); +} + +static void ubiblock_request(struct request_queue *rq) +{ + struct ubiblock *dev; + struct request *req; + + dev = rq->queuedata; + + if (!dev) + while ((req = blk_fetch_request(rq)) != NULL) + __blk_end_request_all(req, -ENODEV); + else + queue_work(dev->wq, &dev->work); +} + +static int ubiblock_open(struct block_device *bdev, fmode_t mode) +{ + struct ubiblock *dev = bdev->bd_disk->private_data; + int ret; + + mutex_lock(&dev->dev_mutex); + if (dev->refcnt > 0) { + /* + * The volume is already open, just increase the reference + * counter. + */ + goto out_done; + } + + /* + * We want users to be aware they should only mount us as read-only. + * It's just a paranoid check, as write requests will get rejected + * in any case. + */ + if (mode & FMODE_WRITE) { + ret = -EPERM; + goto out_unlock; + } + + dev->desc = ubi_open_volume(dev->ubi_num, dev->vol_id, UBI_READONLY); + if (IS_ERR(dev->desc)) { + ubi_err("%s failed to open ubi volume %d_%d", + dev->gd->disk_name, dev->ubi_num, dev->vol_id); + ret = PTR_ERR(dev->desc); + dev->desc = NULL; + goto out_unlock; + } + +out_done: + dev->refcnt++; + mutex_unlock(&dev->dev_mutex); + return 0; + +out_unlock: + mutex_unlock(&dev->dev_mutex); + return ret; +} + +static void ubiblock_release(struct gendisk *gd, fmode_t mode) +{ + struct ubiblock *dev = gd->private_data; + + mutex_lock(&dev->dev_mutex); + dev->refcnt--; + if (dev->refcnt == 0) { + ubi_close_volume(dev->desc); + dev->desc = NULL; + } + mutex_unlock(&dev->dev_mutex); +} + +static int ubiblock_getgeo(struct block_device *bdev, struct hd_geometry *geo) +{ + /* Some tools might require this information */ + geo->heads = 1; + geo->cylinders = 1; + geo->sectors = get_capacity(bdev->bd_disk); + geo->start = 0; + return 0; +} + +static const struct block_device_operations ubiblock_ops = { + .owner = THIS_MODULE, + .open = ubiblock_open, + .release = ubiblock_release, + .getgeo = ubiblock_getgeo, +}; + +int ubiblock_create(struct ubi_volume_info *vi) +{ + struct ubiblock *dev; + struct gendisk *gd; + int disk_capacity; + int ret; + + /* Check that the volume isn't already handled */ + mutex_lock(&devices_mutex); + if (find_dev_nolock(vi->ubi_num, vi->vol_id)) { + mutex_unlock(&devices_mutex); + return -EEXIST; + } + mutex_unlock(&devices_mutex); + + dev = kzalloc(sizeof(struct ubiblock), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + mutex_init(&dev->dev_mutex); + + dev->ubi_num = vi->ubi_num; + dev->vol_id = vi->vol_id; + dev->leb_size = vi->usable_leb_size; + + /* Initialize the gendisk of this ubiblock device */ + gd = alloc_disk(1); + if (!gd) { + ubi_err("block: alloc_disk failed"); + ret = -ENODEV; + goto out_free_dev; + } + + gd->fops = &ubiblock_ops; + gd->major = ubiblock_major; + gd->first_minor = dev->ubi_num * UBI_MAX_VOLUMES + dev->vol_id; + gd->private_data = dev; + sprintf(gd->disk_name, "ubiblock%d_%d", dev->ubi_num, dev->vol_id); + disk_capacity = (vi->size * vi->usable_leb_size) >> 9; + set_capacity(gd, disk_capacity); + dev->gd = gd; + + spin_lock_init(&dev->queue_lock); + dev->rq = blk_init_queue(ubiblock_request, &dev->queue_lock); + if (!dev->rq) { + ubi_err("block: blk_init_queue failed"); + ret = -ENODEV; + goto out_put_disk; + } + + dev->rq->queuedata = dev; + dev->gd->queue = dev->rq; + + /* + * Create one workqueue per volume (per registered block device). + * Rembember workqueues are cheap, they're not threads. + */ + dev->wq = alloc_workqueue("%s", 0, 0, gd->disk_name); + if (!dev->wq) { + ret = -ENOMEM; + goto out_free_queue; + } + INIT_WORK(&dev->work, ubiblock_do_work); + + mutex_lock(&devices_mutex); + list_add_tail(&dev->list, &ubiblock_devices); + mutex_unlock(&devices_mutex); + + /* Must be the last step: anyone can call file ops from now on */ + add_disk(dev->gd); + ubi_msg("%s created from ubi%d:%d(%s)", + dev->gd->disk_name, dev->ubi_num, dev->vol_id, vi->name); + return 0; + +out_free_queue: + blk_cleanup_queue(dev->rq); +out_put_disk: + put_disk(dev->gd); +out_free_dev: + kfree(dev); + + return ret; +} + +static void ubiblock_cleanup(struct ubiblock *dev) +{ + del_gendisk(dev->gd); + blk_cleanup_queue(dev->rq); + ubi_msg("%s released", dev->gd->disk_name); + put_disk(dev->gd); +} + +int ubiblock_remove(struct ubi_volume_info *vi) +{ + struct ubiblock *dev; + + mutex_lock(&devices_mutex); + dev = find_dev_nolock(vi->ubi_num, vi->vol_id); + if (!dev) { + mutex_unlock(&devices_mutex); + return -ENODEV; + } + + /* Found a device, let's lock it so we can check if it's busy */ + mutex_lock(&dev->dev_mutex); + if (dev->refcnt > 0) { + mutex_unlock(&dev->dev_mutex); + mutex_unlock(&devices_mutex); + return -EBUSY; + } + + /* Remove from device list */ + list_del(&dev->list); + mutex_unlock(&devices_mutex); + + /* Flush pending work and stop this workqueue */ + destroy_workqueue(dev->wq); + + ubiblock_cleanup(dev); + mutex_unlock(&dev->dev_mutex); + kfree(dev); + return 0; +} + +static void ubiblock_resize(struct ubi_volume_info *vi) +{ + struct ubiblock *dev; + int disk_capacity; + + /* + * Need to lock the device list until we stop using the device, + * otherwise the device struct might get released in + * 'ubiblock_remove()'. + */ + mutex_lock(&devices_mutex); + dev = find_dev_nolock(vi->ubi_num, vi->vol_id); + if (!dev) { + mutex_unlock(&devices_mutex); + return; + } + + mutex_lock(&dev->dev_mutex); + disk_capacity = (vi->size * vi->usable_leb_size) >> 9; + set_capacity(dev->gd, disk_capacity); + ubi_msg("%s resized to %d LEBs", dev->gd->disk_name, vi->size); + mutex_unlock(&dev->dev_mutex); + mutex_unlock(&devices_mutex); +} + +static int ubiblock_notify(struct notifier_block *nb, + unsigned long notification_type, void *ns_ptr) +{ + struct ubi_notification *nt = ns_ptr; + + switch (notification_type) { + case UBI_VOLUME_ADDED: + /* + * We want to enforce explicit block device creation for + * volumes, so when a volume is added we do nothing. + */ + break; + case UBI_VOLUME_REMOVED: + ubiblock_remove(&nt->vi); + break; + case UBI_VOLUME_RESIZED: + ubiblock_resize(&nt->vi); + break; + default: + break; + } + return NOTIFY_OK; +} + +static struct notifier_block ubiblock_notifier = { + .notifier_call = ubiblock_notify, +}; + +static struct ubi_volume_desc * __init +open_volume_desc(const char *name, int ubi_num, int vol_id) +{ + if (ubi_num == -1) + /* No ubi num, name must be a vol device path */ + return ubi_open_volume_path(name, UBI_READONLY); + else if (vol_id == -1) + /* No vol_id, must be vol_name */ + return ubi_open_volume_nm(ubi_num, name, UBI_READONLY); + else + return ubi_open_volume(ubi_num, vol_id, UBI_READONLY); +} + +static int __init ubiblock_create_from_param(void) +{ + int i, ret; + struct ubiblock_param *p; + struct ubi_volume_desc *desc; + struct ubi_volume_info vi; + + for (i = 0; i < ubiblock_devs; i++) { + p = &ubiblock_param[i]; + + desc = open_volume_desc(p->name, p->ubi_num, p->vol_id); + if (IS_ERR(desc)) { + ubi_err("block: can't open volume, err=%ld\n", + PTR_ERR(desc)); + ret = PTR_ERR(desc); + break; + } + + ubi_get_volume_info(desc, &vi); + ubi_close_volume(desc); + + ret = ubiblock_create(&vi); + if (ret) { + ubi_err("block: can't add '%s' volume, err=%d\n", + vi.name, ret); + break; + } + } + return ret; +} + +static void ubiblock_remove_all(void) +{ + struct ubiblock *next; + struct ubiblock *dev; + + list_for_each_entry_safe(dev, next, &ubiblock_devices, list) { + /* Flush pending work and stop workqueue */ + destroy_workqueue(dev->wq); + /* The module is being forcefully removed */ + WARN_ON(dev->desc); + /* Remove from device list */ + list_del(&dev->list); + ubiblock_cleanup(dev); + kfree(dev); + } +} + +int __init ubiblock_init(void) +{ + int ret; + + ubiblock_major = register_blkdev(0, "ubiblock"); + if (ubiblock_major < 0) + return ubiblock_major; + + /* Attach block devices from 'block=' module param */ + ret = ubiblock_create_from_param(); + if (ret) + goto err_remove; + + /* + * Block devices are only created upon user requests, so we ignore + * existing volumes. + */ + ret = ubi_register_volume_notifier(&ubiblock_notifier, 1); + if (ret) + goto err_unreg; + return 0; + +err_unreg: + unregister_blkdev(ubiblock_major, "ubiblock"); +err_remove: + ubiblock_remove_all(); + return ret; +} + +void __exit ubiblock_exit(void) +{ + ubi_unregister_volume_notifier(&ubiblock_notifier); + ubiblock_remove_all(); + unregister_blkdev(ubiblock_major, "ubiblock"); +} diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c new file mode 100644 index 00000000000..6e30a3c280d --- /dev/null +++ b/drivers/mtd/ubi/build.c @@ -0,0 +1,1500 @@ +/* + * Copyright (c) International Business Machines Corp., 2006 + * Copyright (c) Nokia Corporation, 2007 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Artem Bityutskiy (Битюцкий Артём), + * Frank Haverkamp + */ + +/* + * This file includes UBI initialization and building of UBI devices. + * + * When UBI is initialized, it attaches all the MTD devices specified as the + * module load parameters or the kernel boot parameters. If MTD devices were + * specified, UBI does not attach any MTD device, but it is possible to do + * later using the "UBI control device". + */ + +#include <linux/err.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/stringify.h> +#include <linux/namei.h> +#include <linux/stat.h> +#include <linux/miscdevice.h> +#include <linux/mtd/partitions.h> +#include <linux/log2.h> +#include <linux/kthread.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/major.h> +#include "ubi.h" + +/* Maximum length of the 'mtd=' parameter */ +#define MTD_PARAM_LEN_MAX 64 + +/* Maximum number of comma-separated items in the 'mtd=' parameter */ +#define MTD_PARAM_MAX_COUNT 4 + +/* Maximum value for the number of bad PEBs per 1024 PEBs */ +#define MAX_MTD_UBI_BEB_LIMIT 768 + +#ifdef CONFIG_MTD_UBI_MODULE +#define ubi_is_module() 1 +#else +#define ubi_is_module() 0 +#endif + +/** + * struct mtd_dev_param - MTD device parameter description data structure. + * @name: MTD character device node path, MTD device name, or MTD device number + * string + * @vid_hdr_offs: VID header offset + * @max_beb_per1024: maximum expected number of bad PEBs per 1024 PEBs + */ +struct mtd_dev_param { + char name[MTD_PARAM_LEN_MAX]; + int ubi_num; + int vid_hdr_offs; + int max_beb_per1024; +}; + +/* Numbers of elements set in the @mtd_dev_param array */ +static int __initdata mtd_devs; + +/* MTD devices specification parameters */ +static struct mtd_dev_param __initdata mtd_dev_param[UBI_MAX_DEVICES]; +#ifdef CONFIG_MTD_UBI_FASTMAP +/* UBI module parameter to enable fastmap automatically on non-fastmap images */ +static bool fm_autoconvert; +#endif +/* Root UBI "class" object (corresponds to '/<sysfs>/class/ubi/') */ +struct class *ubi_class; + +/* Slab cache for wear-leveling entries */ +struct kmem_cache *ubi_wl_entry_slab; + +/* UBI control character device */ +static struct miscdevice ubi_ctrl_cdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "ubi_ctrl", + .fops = &ubi_ctrl_cdev_operations, +}; + +/* All UBI devices in system */ +static struct ubi_device *ubi_devices[UBI_MAX_DEVICES]; + +/* Serializes UBI devices creations and removals */ +DEFINE_MUTEX(ubi_devices_mutex); + +/* Protects @ubi_devices and @ubi->ref_count */ +static DEFINE_SPINLOCK(ubi_devices_lock); + +/* "Show" method for files in '/<sysfs>/class/ubi/' */ +static ssize_t ubi_version_show(struct class *class, + struct class_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", UBI_VERSION); +} + +/* UBI version attribute ('/<sysfs>/class/ubi/version') */ +static struct class_attribute ubi_version = + __ATTR(version, S_IRUGO, ubi_version_show, NULL); + +static ssize_t dev_attribute_show(struct device *dev, + struct device_attribute *attr, char *buf); + +/* UBI device attributes (correspond to files in '/<sysfs>/class/ubi/ubiX') */ +static struct device_attribute dev_eraseblock_size = + __ATTR(eraseblock_size, S_IRUGO, dev_attribute_show, NULL); +static struct device_attribute dev_avail_eraseblocks = + __ATTR(avail_eraseblocks, S_IRUGO, dev_attribute_show, NULL); +static struct device_attribute dev_total_eraseblocks = + __ATTR(total_eraseblocks, S_IRUGO, dev_attribute_show, NULL); +static struct device_attribute dev_volumes_count = + __ATTR(volumes_count, S_IRUGO, dev_attribute_show, NULL); +static struct device_attribute dev_max_ec = + __ATTR(max_ec, S_IRUGO, dev_attribute_show, NULL); +static struct device_attribute dev_reserved_for_bad = + __ATTR(reserved_for_bad, S_IRUGO, dev_attribute_show, NULL); +static struct device_attribute dev_bad_peb_count = + __ATTR(bad_peb_count, S_IRUGO, dev_attribute_show, NULL); +static struct device_attribute dev_max_vol_count = + __ATTR(max_vol_count, S_IRUGO, dev_attribute_show, NULL); +static struct device_attribute dev_min_io_size = + __ATTR(min_io_size, S_IRUGO, dev_attribute_show, NULL); +static struct device_attribute dev_bgt_enabled = + __ATTR(bgt_enabled, S_IRUGO, dev_attribute_show, NULL); +static struct device_attribute dev_mtd_num = + __ATTR(mtd_num, S_IRUGO, dev_attribute_show, NULL); + +/** + * ubi_volume_notify - send a volume change notification. + * @ubi: UBI device description object + * @vol: volume description object of the changed volume + * @ntype: notification type to send (%UBI_VOLUME_ADDED, etc) + * + * This is a helper function which notifies all subscribers about a volume + * change event (creation, removal, re-sizing, re-naming, updating). Returns + * zero in case of success and a negative error code in case of failure. + */ +int ubi_volume_notify(struct ubi_device *ubi, struct ubi_volume *vol, int ntype) +{ + struct ubi_notification nt; + + ubi_do_get_device_info(ubi, &nt.di); + ubi_do_get_volume_info(ubi, vol, &nt.vi); + +#ifdef CONFIG_MTD_UBI_FASTMAP + switch (ntype) { + case UBI_VOLUME_ADDED: + case UBI_VOLUME_REMOVED: + case UBI_VOLUME_RESIZED: + case UBI_VOLUME_RENAMED: + if (ubi_update_fastmap(ubi)) { + ubi_err("Unable to update fastmap!"); + ubi_ro_mode(ubi); + } + } +#endif + return blocking_notifier_call_chain(&ubi_notifiers, ntype, &nt); +} + +/** + * ubi_notify_all - send a notification to all volumes. + * @ubi: UBI device description object + * @ntype: notification type to send (%UBI_VOLUME_ADDED, etc) + * @nb: the notifier to call + * + * This function walks all volumes of UBI device @ubi and sends the @ntype + * notification for each volume. If @nb is %NULL, then all registered notifiers + * are called, otherwise only the @nb notifier is called. Returns the number of + * sent notifications. + */ +int ubi_notify_all(struct ubi_device *ubi, int ntype, struct notifier_block *nb) +{ + struct ubi_notification nt; + int i, count = 0; + + ubi_do_get_device_info(ubi, &nt.di); + + mutex_lock(&ubi->device_mutex); + for (i = 0; i < ubi->vtbl_slots; i++) { + /* + * Since the @ubi->device is locked, and we are not going to + * change @ubi->volumes, we do not have to lock + * @ubi->volumes_lock. + */ + if (!ubi->volumes[i]) + continue; + + ubi_do_get_volume_info(ubi, ubi->volumes[i], &nt.vi); + if (nb) + nb->notifier_call(nb, ntype, &nt); + else + blocking_notifier_call_chain(&ubi_notifiers, ntype, + &nt); + count += 1; + } + mutex_unlock(&ubi->device_mutex); + + return count; +} + +/** + * ubi_enumerate_volumes - send "add" notification for all existing volumes. + * @nb: the notifier to call + * + * This function walks all UBI devices and volumes and sends the + * %UBI_VOLUME_ADDED notification for each volume. If @nb is %NULL, then all + * registered notifiers are called, otherwise only the @nb notifier is called. + * Returns the number of sent notifications. + */ +int ubi_enumerate_volumes(struct notifier_block *nb) +{ + int i, count = 0; + + /* + * Since the @ubi_devices_mutex is locked, and we are not going to + * change @ubi_devices, we do not have to lock @ubi_devices_lock. + */ + for (i = 0; i < UBI_MAX_DEVICES; i++) { + struct ubi_device *ubi = ubi_devices[i]; + + if (!ubi) + continue; + count += ubi_notify_all(ubi, UBI_VOLUME_ADDED, nb); + } + + return count; +} + +/** + * ubi_get_device - get UBI device. + * @ubi_num: UBI device number + * + * This function returns UBI device description object for UBI device number + * @ubi_num, or %NULL if the device does not exist. This function increases the + * device reference count to prevent removal of the device. In other words, the + * device cannot be removed if its reference count is not zero. + */ +struct ubi_device *ubi_get_device(int ubi_num) +{ + struct ubi_device *ubi; + + spin_lock(&ubi_devices_lock); + ubi = ubi_devices[ubi_num]; + if (ubi) { + ubi_assert(ubi->ref_count >= 0); + ubi->ref_count += 1; + get_device(&ubi->dev); + } + spin_unlock(&ubi_devices_lock); + + return ubi; +} + +/** + * ubi_put_device - drop an UBI device reference. + * @ubi: UBI device description object + */ +void ubi_put_device(struct ubi_device *ubi) +{ + spin_lock(&ubi_devices_lock); + ubi->ref_count -= 1; + put_device(&ubi->dev); + spin_unlock(&ubi_devices_lock); +} + +/** + * ubi_get_by_major - get UBI device by character device major number. + * @major: major number + * + * This function is similar to 'ubi_get_device()', but it searches the device + * by its major number. + */ +struct ubi_device *ubi_get_by_major(int major) +{ + int i; + struct ubi_device *ubi; + + spin_lock(&ubi_devices_lock); + for (i = 0; i < UBI_MAX_DEVICES; i++) { + ubi = ubi_devices[i]; + if (ubi && MAJOR(ubi->cdev.dev) == major) { + ubi_assert(ubi->ref_count >= 0); + ubi->ref_count += 1; + get_device(&ubi->dev); + spin_unlock(&ubi_devices_lock); + return ubi; + } + } + spin_unlock(&ubi_devices_lock); + + return NULL; +} + +/** + * ubi_major2num - get UBI device number by character device major number. + * @major: major number + * + * This function searches UBI device number object by its major number. If UBI + * device was not found, this function returns -ENODEV, otherwise the UBI device + * number is returned. + */ +int ubi_major2num(int major) +{ + int i, ubi_num = -ENODEV; + + spin_lock(&ubi_devices_lock); + for (i = 0; i < UBI_MAX_DEVICES; i++) { + struct ubi_device *ubi = ubi_devices[i]; + + if (ubi && MAJOR(ubi->cdev.dev) == major) { + ubi_num = ubi->ubi_num; + break; + } + } + spin_unlock(&ubi_devices_lock); + + return ubi_num; +} + +/* "Show" method for files in '/<sysfs>/class/ubi/ubiX/' */ +static ssize_t dev_attribute_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret; + struct ubi_device *ubi; + + /* + * The below code looks weird, but it actually makes sense. We get the + * UBI device reference from the contained 'struct ubi_device'. But it + * is unclear if the device was removed or not yet. Indeed, if the + * device was removed before we increased its reference count, + * 'ubi_get_device()' will return -ENODEV and we fail. + * + * Remember, 'struct ubi_device' is freed in the release function, so + * we still can use 'ubi->ubi_num'. + */ + ubi = container_of(dev, struct ubi_device, dev); + ubi = ubi_get_device(ubi->ubi_num); + if (!ubi) + return -ENODEV; + + if (attr == &dev_eraseblock_size) + ret = sprintf(buf, "%d\n", ubi->leb_size); + else if (attr == &dev_avail_eraseblocks) + ret = sprintf(buf, "%d\n", ubi->avail_pebs); + else if (attr == &dev_total_eraseblocks) + ret = sprintf(buf, "%d\n", ubi->good_peb_count); + else if (attr == &dev_volumes_count) + ret = sprintf(buf, "%d\n", ubi->vol_count - UBI_INT_VOL_COUNT); + else if (attr == &dev_max_ec) + ret = sprintf(buf, "%d\n", ubi->max_ec); + else if (attr == &dev_reserved_for_bad) + ret = sprintf(buf, "%d\n", ubi->beb_rsvd_pebs); + else if (attr == &dev_bad_peb_count) + ret = sprintf(buf, "%d\n", ubi->bad_peb_count); + else if (attr == &dev_max_vol_count) + ret = sprintf(buf, "%d\n", ubi->vtbl_slots); + else if (attr == &dev_min_io_size) + ret = sprintf(buf, "%d\n", ubi->min_io_size); + else if (attr == &dev_bgt_enabled) + ret = sprintf(buf, "%d\n", ubi->thread_enabled); + else if (attr == &dev_mtd_num) + ret = sprintf(buf, "%d\n", ubi->mtd->index); + else + ret = -EINVAL; + + ubi_put_device(ubi); + return ret; +} + +static void dev_release(struct device *dev) +{ + struct ubi_device *ubi = container_of(dev, struct ubi_device, dev); + + kfree(ubi); +} + +/** + * ubi_sysfs_init - initialize sysfs for an UBI device. + * @ubi: UBI device description object + * @ref: set to %1 on exit in case of failure if a reference to @ubi->dev was + * taken + * + * This function returns zero in case of success and a negative error code in + * case of failure. + */ +static int ubi_sysfs_init(struct ubi_device *ubi, int *ref) +{ + int err; + + ubi->dev.release = dev_release; + ubi->dev.devt = ubi->cdev.dev; + ubi->dev.class = ubi_class; + dev_set_name(&ubi->dev, UBI_NAME_STR"%d", ubi->ubi_num); + err = device_register(&ubi->dev); + if (err) + return err; + + *ref = 1; + err = device_create_file(&ubi->dev, &dev_eraseblock_size); + if (err) + return err; + err = device_create_file(&ubi->dev, &dev_avail_eraseblocks); + if (err) + return err; + err = device_create_file(&ubi->dev, &dev_total_eraseblocks); + if (err) + return err; + err = device_create_file(&ubi->dev, &dev_volumes_count); + if (err) + return err; + err = device_create_file(&ubi->dev, &dev_max_ec); + if (err) + return err; + err = device_create_file(&ubi->dev, &dev_reserved_for_bad); + if (err) + return err; + err = device_create_file(&ubi->dev, &dev_bad_peb_count); + if (err) + return err; + err = device_create_file(&ubi->dev, &dev_max_vol_count); + if (err) + return err; + err = device_create_file(&ubi->dev, &dev_min_io_size); + if (err) + return err; + err = device_create_file(&ubi->dev, &dev_bgt_enabled); + if (err) + return err; + err = device_create_file(&ubi->dev, &dev_mtd_num); + return err; +} + +/** + * ubi_sysfs_close - close sysfs for an UBI device. + * @ubi: UBI device description object + */ +static void ubi_sysfs_close(struct ubi_device *ubi) +{ + device_remove_file(&ubi->dev, &dev_mtd_num); + device_remove_file(&ubi->dev, &dev_bgt_enabled); + device_remove_file(&ubi->dev, &dev_min_io_size); + device_remove_file(&ubi->dev, &dev_max_vol_count); + device_remove_file(&ubi->dev, &dev_bad_peb_count); + device_remove_file(&ubi->dev, &dev_reserved_for_bad); + device_remove_file(&ubi->dev, &dev_max_ec); + device_remove_file(&ubi->dev, &dev_volumes_count); + device_remove_file(&ubi->dev, &dev_total_eraseblocks); + device_remove_file(&ubi->dev, &dev_avail_eraseblocks); + device_remove_file(&ubi->dev, &dev_eraseblock_size); + device_unregister(&ubi->dev); +} + +/** + * kill_volumes - destroy all user volumes. + * @ubi: UBI device description object + */ +static void kill_volumes(struct ubi_device *ubi) +{ + int i; + + for (i = 0; i < ubi->vtbl_slots; i++) + if (ubi->volumes[i]) + ubi_free_volume(ubi, ubi->volumes[i]); +} + +/** + * uif_init - initialize user interfaces for an UBI device. + * @ubi: UBI device description object + * @ref: set to %1 on exit in case of failure if a reference to @ubi->dev was + * taken, otherwise set to %0 + * + * This function initializes various user interfaces for an UBI device. If the + * initialization fails at an early stage, this function frees all the + * resources it allocated, returns an error, and @ref is set to %0. However, + * if the initialization fails after the UBI device was registered in the + * driver core subsystem, this function takes a reference to @ubi->dev, because + * otherwise the release function ('dev_release()') would free whole @ubi + * object. The @ref argument is set to %1 in this case. The caller has to put + * this reference. + * + * This function returns zero in case of success and a negative error code in + * case of failure. + */ +static int uif_init(struct ubi_device *ubi, int *ref) +{ + int i, err; + dev_t dev; + + *ref = 0; + sprintf(ubi->ubi_name, UBI_NAME_STR "%d", ubi->ubi_num); + + /* + * Major numbers for the UBI character devices are allocated + * dynamically. Major numbers of volume character devices are + * equivalent to ones of the corresponding UBI character device. Minor + * numbers of UBI character devices are 0, while minor numbers of + * volume character devices start from 1. Thus, we allocate one major + * number and ubi->vtbl_slots + 1 minor numbers. + */ + err = alloc_chrdev_region(&dev, 0, ubi->vtbl_slots + 1, ubi->ubi_name); + if (err) { + ubi_err("cannot register UBI character devices"); + return err; + } + + ubi_assert(MINOR(dev) == 0); + cdev_init(&ubi->cdev, &ubi_cdev_operations); + dbg_gen("%s major is %u", ubi->ubi_name, MAJOR(dev)); + ubi->cdev.owner = THIS_MODULE; + + err = cdev_add(&ubi->cdev, dev, 1); + if (err) { + ubi_err("cannot add character device"); + goto out_unreg; + } + + err = ubi_sysfs_init(ubi, ref); + if (err) + goto out_sysfs; + + for (i = 0; i < ubi->vtbl_slots; i++) + if (ubi->volumes[i]) { + err = ubi_add_volume(ubi, ubi->volumes[i]); + if (err) { + ubi_err("cannot add volume %d", i); + goto out_volumes; + } + } + + return 0; + +out_volumes: + kill_volumes(ubi); +out_sysfs: + if (*ref) + get_device(&ubi->dev); + ubi_sysfs_close(ubi); + cdev_del(&ubi->cdev); +out_unreg: + unregister_chrdev_region(ubi->cdev.dev, ubi->vtbl_slots + 1); + ubi_err("cannot initialize UBI %s, error %d", ubi->ubi_name, err); + return err; +} + +/** + * uif_close - close user interfaces for an UBI device. + * @ubi: UBI device description object + * + * Note, since this function un-registers UBI volume device objects (@vol->dev), + * the memory allocated voe the volumes is freed as well (in the release + * function). + */ +static void uif_close(struct ubi_device *ubi) +{ + kill_volumes(ubi); + ubi_sysfs_close(ubi); + cdev_del(&ubi->cdev); + unregister_chrdev_region(ubi->cdev.dev, ubi->vtbl_slots + 1); +} + +/** + * ubi_free_internal_volumes - free internal volumes. + * @ubi: UBI device description object + */ +void ubi_free_internal_volumes(struct ubi_device *ubi) +{ + int i; + + for (i = ubi->vtbl_slots; + i < ubi->vtbl_slots + UBI_INT_VOL_COUNT; i++) { + kfree(ubi->volumes[i]->eba_tbl); + kfree(ubi->volumes[i]); + } +} + +static int get_bad_peb_limit(const struct ubi_device *ubi, int max_beb_per1024) +{ + int limit, device_pebs; + uint64_t device_size; + + if (!max_beb_per1024) + return 0; + + /* + * Here we are using size of the entire flash chip and + * not just the MTD partition size because the maximum + * number of bad eraseblocks is a percentage of the + * whole device and bad eraseblocks are not fairly + * distributed over the flash chip. So the worst case + * is that all the bad eraseblocks of the chip are in + * the MTD partition we are attaching (ubi->mtd). + */ + device_size = mtd_get_device_size(ubi->mtd); + device_pebs = mtd_div_by_eb(device_size, ubi->mtd); + limit = mult_frac(device_pebs, max_beb_per1024, 1024); + + /* Round it up */ + if (mult_frac(limit, 1024, max_beb_per1024) < device_pebs) + limit += 1; + + return limit; +} + +/** + * io_init - initialize I/O sub-system for a given UBI device. + * @ubi: UBI device description object + * @max_beb_per1024: maximum expected number of bad PEB per 1024 PEBs + * + * If @ubi->vid_hdr_offset or @ubi->leb_start is zero, default offsets are + * assumed: + * o EC header is always at offset zero - this cannot be changed; + * o VID header starts just after the EC header at the closest address + * aligned to @io->hdrs_min_io_size; + * o data starts just after the VID header at the closest address aligned to + * @io->min_io_size + * + * This function returns zero in case of success and a negative error code in + * case of failure. + */ +static int io_init(struct ubi_device *ubi, int max_beb_per1024) +{ + dbg_gen("sizeof(struct ubi_ainf_peb) %zu", sizeof(struct ubi_ainf_peb)); + dbg_gen("sizeof(struct ubi_wl_entry) %zu", sizeof(struct ubi_wl_entry)); + + if (ubi->mtd->numeraseregions != 0) { + /* + * Some flashes have several erase regions. Different regions + * may have different eraseblock size and other + * characteristics. It looks like mostly multi-region flashes + * have one "main" region and one or more small regions to + * store boot loader code or boot parameters or whatever. I + * guess we should just pick the largest region. But this is + * not implemented. + */ + ubi_err("multiple regions, not implemented"); + return -EINVAL; + } + + if (ubi->vid_hdr_offset < 0) + return -EINVAL; + + /* + * Note, in this implementation we support MTD devices with 0x7FFFFFFF + * physical eraseblocks maximum. + */ + + ubi->peb_size = ubi->mtd->erasesize; + ubi->peb_count = mtd_div_by_eb(ubi->mtd->size, ubi->mtd); + ubi->flash_size = ubi->mtd->size; + + if (mtd_can_have_bb(ubi->mtd)) { + ubi->bad_allowed = 1; + ubi->bad_peb_limit = get_bad_peb_limit(ubi, max_beb_per1024); + } + + if (ubi->mtd->type == MTD_NORFLASH) { + ubi_assert(ubi->mtd->writesize == 1); + ubi->nor_flash = 1; + } + + ubi->min_io_size = ubi->mtd->writesize; + ubi->hdrs_min_io_size = ubi->mtd->writesize >> ubi->mtd->subpage_sft; + + /* + * Make sure minimal I/O unit is power of 2. Note, there is no + * fundamental reason for this assumption. It is just an optimization + * which allows us to avoid costly division operations. + */ + if (!is_power_of_2(ubi->min_io_size)) { + ubi_err("min. I/O unit (%d) is not power of 2", + ubi->min_io_size); + return -EINVAL; + } + + ubi_assert(ubi->hdrs_min_io_size > 0); + ubi_assert(ubi->hdrs_min_io_size <= ubi->min_io_size); + ubi_assert(ubi->min_io_size % ubi->hdrs_min_io_size == 0); + + ubi->max_write_size = ubi->mtd->writebufsize; + /* + * Maximum write size has to be greater or equivalent to min. I/O + * size, and be multiple of min. I/O size. + */ + if (ubi->max_write_size < ubi->min_io_size || + ubi->max_write_size % ubi->min_io_size || + !is_power_of_2(ubi->max_write_size)) { + ubi_err("bad write buffer size %d for %d min. I/O unit", + ubi->max_write_size, ubi->min_io_size); + return -EINVAL; + } + + /* Calculate default aligned sizes of EC and VID headers */ + ubi->ec_hdr_alsize = ALIGN(UBI_EC_HDR_SIZE, ubi->hdrs_min_io_size); + ubi->vid_hdr_alsize = ALIGN(UBI_VID_HDR_SIZE, ubi->hdrs_min_io_size); + + dbg_gen("min_io_size %d", ubi->min_io_size); + dbg_gen("max_write_size %d", ubi->max_write_size); + dbg_gen("hdrs_min_io_size %d", ubi->hdrs_min_io_size); + dbg_gen("ec_hdr_alsize %d", ubi->ec_hdr_alsize); + dbg_gen("vid_hdr_alsize %d", ubi->vid_hdr_alsize); + + if (ubi->vid_hdr_offset == 0) + /* Default offset */ + ubi->vid_hdr_offset = ubi->vid_hdr_aloffset = + ubi->ec_hdr_alsize; + else { + ubi->vid_hdr_aloffset = ubi->vid_hdr_offset & + ~(ubi->hdrs_min_io_size - 1); + ubi->vid_hdr_shift = ubi->vid_hdr_offset - + ubi->vid_hdr_aloffset; + } + + /* Similar for the data offset */ + ubi->leb_start = ubi->vid_hdr_offset + UBI_VID_HDR_SIZE; + ubi->leb_start = ALIGN(ubi->leb_start, ubi->min_io_size); + + dbg_gen("vid_hdr_offset %d", ubi->vid_hdr_offset); + dbg_gen("vid_hdr_aloffset %d", ubi->vid_hdr_aloffset); + dbg_gen("vid_hdr_shift %d", ubi->vid_hdr_shift); + dbg_gen("leb_start %d", ubi->leb_start); + + /* The shift must be aligned to 32-bit boundary */ + if (ubi->vid_hdr_shift % 4) { + ubi_err("unaligned VID header shift %d", + ubi->vid_hdr_shift); + return -EINVAL; + } + + /* Check sanity */ + if (ubi->vid_hdr_offset < UBI_EC_HDR_SIZE || + ubi->leb_start < ubi->vid_hdr_offset + UBI_VID_HDR_SIZE || + ubi->leb_start > ubi->peb_size - UBI_VID_HDR_SIZE || + ubi->leb_start & (ubi->min_io_size - 1)) { + ubi_err("bad VID header (%d) or data offsets (%d)", + ubi->vid_hdr_offset, ubi->leb_start); + return -EINVAL; + } + + /* + * Set maximum amount of physical erroneous eraseblocks to be 10%. + * Erroneous PEB are those which have read errors. + */ + ubi->max_erroneous = ubi->peb_count / 10; + if (ubi->max_erroneous < 16) + ubi->max_erroneous = 16; + dbg_gen("max_erroneous %d", ubi->max_erroneous); + + /* + * It may happen that EC and VID headers are situated in one minimal + * I/O unit. In this case we can only accept this UBI image in + * read-only mode. + */ + if (ubi->vid_hdr_offset + UBI_VID_HDR_SIZE <= ubi->hdrs_min_io_size) { + ubi_warn("EC and VID headers are in the same minimal I/O unit, switch to read-only mode"); + ubi->ro_mode = 1; + } + + ubi->leb_size = ubi->peb_size - ubi->leb_start; + + if (!(ubi->mtd->flags & MTD_WRITEABLE)) { + ubi_msg("MTD device %d is write-protected, attach in read-only mode", + ubi->mtd->index); + ubi->ro_mode = 1; + } + + /* + * Note, ideally, we have to initialize @ubi->bad_peb_count here. But + * unfortunately, MTD does not provide this information. We should loop + * over all physical eraseblocks and invoke mtd->block_is_bad() for + * each physical eraseblock. So, we leave @ubi->bad_peb_count + * uninitialized so far. + */ + + return 0; +} + +/** + * autoresize - re-size the volume which has the "auto-resize" flag set. + * @ubi: UBI device description object + * @vol_id: ID of the volume to re-size + * + * This function re-sizes the volume marked by the %UBI_VTBL_AUTORESIZE_FLG in + * the volume table to the largest possible size. See comments in ubi-header.h + * for more description of the flag. Returns zero in case of success and a + * negative error code in case of failure. + */ +static int autoresize(struct ubi_device *ubi, int vol_id) +{ + struct ubi_volume_desc desc; + struct ubi_volume *vol = ubi->volumes[vol_id]; + int err, old_reserved_pebs = vol->reserved_pebs; + + if (ubi->ro_mode) { + ubi_warn("skip auto-resize because of R/O mode"); + return 0; + } + + /* + * Clear the auto-resize flag in the volume in-memory copy of the + * volume table, and 'ubi_resize_volume()' will propagate this change + * to the flash. + */ + ubi->vtbl[vol_id].flags &= ~UBI_VTBL_AUTORESIZE_FLG; + + if (ubi->avail_pebs == 0) { + struct ubi_vtbl_record vtbl_rec; + + /* + * No available PEBs to re-size the volume, clear the flag on + * flash and exit. + */ + vtbl_rec = ubi->vtbl[vol_id]; + err = ubi_change_vtbl_record(ubi, vol_id, &vtbl_rec); + if (err) + ubi_err("cannot clean auto-resize flag for volume %d", + vol_id); + } else { + desc.vol = vol; + err = ubi_resize_volume(&desc, + old_reserved_pebs + ubi->avail_pebs); + if (err) + ubi_err("cannot auto-resize volume %d", vol_id); + } + + if (err) + return err; + + ubi_msg("volume %d (\"%s\") re-sized from %d to %d LEBs", vol_id, + vol->name, old_reserved_pebs, vol->reserved_pebs); + return 0; +} + +/** + * ubi_attach_mtd_dev - attach an MTD device. + * @mtd: MTD device description object + * @ubi_num: number to assign to the new UBI device + * @vid_hdr_offset: VID header offset + * @max_beb_per1024: maximum expected number of bad PEB per 1024 PEBs + * + * This function attaches MTD device @mtd_dev to UBI and assign @ubi_num number + * to the newly created UBI device, unless @ubi_num is %UBI_DEV_NUM_AUTO, in + * which case this function finds a vacant device number and assigns it + * automatically. Returns the new UBI device number in case of success and a + * negative error code in case of failure. + * + * Note, the invocations of this function has to be serialized by the + * @ubi_devices_mutex. + */ +int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, + int vid_hdr_offset, int max_beb_per1024) +{ + struct ubi_device *ubi; + int i, err, ref = 0; + + if (max_beb_per1024 < 0 || max_beb_per1024 > MAX_MTD_UBI_BEB_LIMIT) + return -EINVAL; + + if (!max_beb_per1024) + max_beb_per1024 = CONFIG_MTD_UBI_BEB_LIMIT; + + /* + * Check if we already have the same MTD device attached. + * + * Note, this function assumes that UBI devices creations and deletions + * are serialized, so it does not take the &ubi_devices_lock. + */ + for (i = 0; i < UBI_MAX_DEVICES; i++) { + ubi = ubi_devices[i]; + if (ubi && mtd->index == ubi->mtd->index) { + ubi_err("mtd%d is already attached to ubi%d", + mtd->index, i); + return -EEXIST; + } + } + + /* + * Make sure this MTD device is not emulated on top of an UBI volume + * already. Well, generally this recursion works fine, but there are + * different problems like the UBI module takes a reference to itself + * by attaching (and thus, opening) the emulated MTD device. This + * results in inability to unload the module. And in general it makes + * no sense to attach emulated MTD devices, so we prohibit this. + */ + if (mtd->type == MTD_UBIVOLUME) { + ubi_err("refuse attaching mtd%d - it is already emulated on top of UBI", + mtd->index); + return -EINVAL; + } + + if (ubi_num == UBI_DEV_NUM_AUTO) { + /* Search for an empty slot in the @ubi_devices array */ + for (ubi_num = 0; ubi_num < UBI_MAX_DEVICES; ubi_num++) + if (!ubi_devices[ubi_num]) + break; + if (ubi_num == UBI_MAX_DEVICES) { + ubi_err("only %d UBI devices may be created", + UBI_MAX_DEVICES); + return -ENFILE; + } + } else { + if (ubi_num >= UBI_MAX_DEVICES) + return -EINVAL; + + /* Make sure ubi_num is not busy */ + if (ubi_devices[ubi_num]) { + ubi_err("ubi%d already exists", ubi_num); + return -EEXIST; + } + } + + ubi = kzalloc(sizeof(struct ubi_device), GFP_KERNEL); + if (!ubi) + return -ENOMEM; + + ubi->mtd = mtd; + ubi->ubi_num = ubi_num; + ubi->vid_hdr_offset = vid_hdr_offset; + ubi->autoresize_vol_id = -1; + +#ifdef CONFIG_MTD_UBI_FASTMAP + ubi->fm_pool.used = ubi->fm_pool.size = 0; + ubi->fm_wl_pool.used = ubi->fm_wl_pool.size = 0; + + /* + * fm_pool.max_size is 5% of the total number of PEBs but it's also + * between UBI_FM_MAX_POOL_SIZE and UBI_FM_MIN_POOL_SIZE. + */ + ubi->fm_pool.max_size = min(((int)mtd_div_by_eb(ubi->mtd->size, + ubi->mtd) / 100) * 5, UBI_FM_MAX_POOL_SIZE); + if (ubi->fm_pool.max_size < UBI_FM_MIN_POOL_SIZE) + ubi->fm_pool.max_size = UBI_FM_MIN_POOL_SIZE; + + ubi->fm_wl_pool.max_size = UBI_FM_WL_POOL_SIZE; + ubi->fm_disabled = !fm_autoconvert; + + if (!ubi->fm_disabled && (int)mtd_div_by_eb(ubi->mtd->size, ubi->mtd) + <= UBI_FM_MAX_START) { + ubi_err("More than %i PEBs are needed for fastmap, sorry.", + UBI_FM_MAX_START); + ubi->fm_disabled = 1; + } + + ubi_msg("default fastmap pool size: %d", ubi->fm_pool.max_size); + ubi_msg("default fastmap WL pool size: %d", ubi->fm_wl_pool.max_size); +#else + ubi->fm_disabled = 1; +#endif + mutex_init(&ubi->buf_mutex); + mutex_init(&ubi->ckvol_mutex); + mutex_init(&ubi->device_mutex); + spin_lock_init(&ubi->volumes_lock); + mutex_init(&ubi->fm_mutex); + init_rwsem(&ubi->fm_sem); + + ubi_msg("attaching mtd%d to ubi%d", mtd->index, ubi_num); + + err = io_init(ubi, max_beb_per1024); + if (err) + goto out_free; + + err = -ENOMEM; + ubi->peb_buf = vmalloc(ubi->peb_size); + if (!ubi->peb_buf) + goto out_free; + +#ifdef CONFIG_MTD_UBI_FASTMAP + ubi->fm_size = ubi_calc_fm_size(ubi); + ubi->fm_buf = vzalloc(ubi->fm_size); + if (!ubi->fm_buf) + goto out_free; +#endif + err = ubi_attach(ubi, 0); + if (err) { + ubi_err("failed to attach mtd%d, error %d", mtd->index, err); + goto out_free; + } + + if (ubi->autoresize_vol_id != -1) { + err = autoresize(ubi, ubi->autoresize_vol_id); + if (err) + goto out_detach; + } + + err = uif_init(ubi, &ref); + if (err) + goto out_detach; + + err = ubi_debugfs_init_dev(ubi); + if (err) + goto out_uif; + + ubi->bgt_thread = kthread_create(ubi_thread, ubi, "%s", ubi->bgt_name); + if (IS_ERR(ubi->bgt_thread)) { + err = PTR_ERR(ubi->bgt_thread); + ubi_err("cannot spawn \"%s\", error %d", ubi->bgt_name, + err); + goto out_debugfs; + } + + ubi_msg("attached mtd%d (name \"%s\", size %llu MiB) to ubi%d", + mtd->index, mtd->name, ubi->flash_size >> 20, ubi_num); + ubi_msg("PEB size: %d bytes (%d KiB), LEB size: %d bytes", + ubi->peb_size, ubi->peb_size >> 10, ubi->leb_size); + ubi_msg("min./max. I/O unit sizes: %d/%d, sub-page size %d", + ubi->min_io_size, ubi->max_write_size, ubi->hdrs_min_io_size); + ubi_msg("VID header offset: %d (aligned %d), data offset: %d", + ubi->vid_hdr_offset, ubi->vid_hdr_aloffset, ubi->leb_start); + ubi_msg("good PEBs: %d, bad PEBs: %d, corrupted PEBs: %d", + ubi->good_peb_count, ubi->bad_peb_count, ubi->corr_peb_count); + ubi_msg("user volume: %d, internal volumes: %d, max. volumes count: %d", + ubi->vol_count - UBI_INT_VOL_COUNT, UBI_INT_VOL_COUNT, + ubi->vtbl_slots); + ubi_msg("max/mean erase counter: %d/%d, WL threshold: %d, image sequence number: %u", + ubi->max_ec, ubi->mean_ec, CONFIG_MTD_UBI_WL_THRESHOLD, + ubi->image_seq); + ubi_msg("available PEBs: %d, total reserved PEBs: %d, PEBs reserved for bad PEB handling: %d", + ubi->avail_pebs, ubi->rsvd_pebs, ubi->beb_rsvd_pebs); + + /* + * The below lock makes sure we do not race with 'ubi_thread()' which + * checks @ubi->thread_enabled. Otherwise we may fail to wake it up. + */ + spin_lock(&ubi->wl_lock); + ubi->thread_enabled = 1; + wake_up_process(ubi->bgt_thread); + spin_unlock(&ubi->wl_lock); + + ubi_devices[ubi_num] = ubi; + ubi_notify_all(ubi, UBI_VOLUME_ADDED, NULL); + return ubi_num; + +out_debugfs: + ubi_debugfs_exit_dev(ubi); +out_uif: + get_device(&ubi->dev); + ubi_assert(ref); + uif_close(ubi); +out_detach: + ubi_wl_close(ubi); + ubi_free_internal_volumes(ubi); + vfree(ubi->vtbl); +out_free: + vfree(ubi->peb_buf); + vfree(ubi->fm_buf); + if (ref) + put_device(&ubi->dev); + else + kfree(ubi); + return err; +} + +/** + * ubi_detach_mtd_dev - detach an MTD device. + * @ubi_num: UBI device number to detach from + * @anyway: detach MTD even if device reference count is not zero + * + * This function destroys an UBI device number @ubi_num and detaches the + * underlying MTD device. Returns zero in case of success and %-EBUSY if the + * UBI device is busy and cannot be destroyed, and %-EINVAL if it does not + * exist. + * + * Note, the invocations of this function has to be serialized by the + * @ubi_devices_mutex. + */ +int ubi_detach_mtd_dev(int ubi_num, int anyway) +{ + struct ubi_device *ubi; + + if (ubi_num < 0 || ubi_num >= UBI_MAX_DEVICES) + return -EINVAL; + + ubi = ubi_get_device(ubi_num); + if (!ubi) + return -EINVAL; + + spin_lock(&ubi_devices_lock); + put_device(&ubi->dev); + ubi->ref_count -= 1; + if (ubi->ref_count) { + if (!anyway) { + spin_unlock(&ubi_devices_lock); + return -EBUSY; + } + /* This may only happen if there is a bug */ + ubi_err("%s reference count %d, destroy anyway", + ubi->ubi_name, ubi->ref_count); + } + ubi_devices[ubi_num] = NULL; + spin_unlock(&ubi_devices_lock); + + ubi_assert(ubi_num == ubi->ubi_num); + ubi_notify_all(ubi, UBI_VOLUME_REMOVED, NULL); + ubi_msg("detaching mtd%d from ubi%d", ubi->mtd->index, ubi_num); +#ifdef CONFIG_MTD_UBI_FASTMAP + /* If we don't write a new fastmap at detach time we lose all + * EC updates that have been made since the last written fastmap. */ + ubi_update_fastmap(ubi); +#endif + /* + * Before freeing anything, we have to stop the background thread to + * prevent it from doing anything on this device while we are freeing. + */ + if (ubi->bgt_thread) + kthread_stop(ubi->bgt_thread); + + /* + * Get a reference to the device in order to prevent 'dev_release()' + * from freeing the @ubi object. + */ + get_device(&ubi->dev); + + ubi_debugfs_exit_dev(ubi); + uif_close(ubi); + + ubi_wl_close(ubi); + ubi_free_internal_volumes(ubi); + vfree(ubi->vtbl); + put_mtd_device(ubi->mtd); + vfree(ubi->peb_buf); + vfree(ubi->fm_buf); + ubi_msg("mtd%d is detached from ubi%d", ubi->mtd->index, ubi->ubi_num); + put_device(&ubi->dev); + return 0; +} + +/** + * open_mtd_by_chdev - open an MTD device by its character device node path. + * @mtd_dev: MTD character device node path + * + * This helper function opens an MTD device by its character node device path. + * Returns MTD device description object in case of success and a negative + * error code in case of failure. + */ +static struct mtd_info * __init open_mtd_by_chdev(const char *mtd_dev) +{ + int err, major, minor, mode; + struct path path; + + /* Probably this is an MTD character device node path */ + err = kern_path(mtd_dev, LOOKUP_FOLLOW, &path); + if (err) + return ERR_PTR(err); + + /* MTD device number is defined by the major / minor numbers */ + major = imajor(path.dentry->d_inode); + minor = iminor(path.dentry->d_inode); + mode = path.dentry->d_inode->i_mode; + path_put(&path); + if (major != MTD_CHAR_MAJOR || !S_ISCHR(mode)) + return ERR_PTR(-EINVAL); + + if (minor & 1) + /* + * Just do not think the "/dev/mtdrX" devices support is need, + * so do not support them to avoid doing extra work. + */ + return ERR_PTR(-EINVAL); + + return get_mtd_device(NULL, minor / 2); +} + +/** + * open_mtd_device - open MTD device by name, character device path, or number. + * @mtd_dev: name, character device node path, or MTD device device number + * + * This function tries to open and MTD device described by @mtd_dev string, + * which is first treated as ASCII MTD device number, and if it is not true, it + * is treated as MTD device name, and if that is also not true, it is treated + * as MTD character device node path. Returns MTD device description object in + * case of success and a negative error code in case of failure. + */ +static struct mtd_info * __init open_mtd_device(const char *mtd_dev) +{ + struct mtd_info *mtd; + int mtd_num; + char *endp; + + mtd_num = simple_strtoul(mtd_dev, &endp, 0); + if (*endp != '\0' || mtd_dev == endp) { + /* + * This does not look like an ASCII integer, probably this is + * MTD device name. + */ + mtd = get_mtd_device_nm(mtd_dev); + if (IS_ERR(mtd) && PTR_ERR(mtd) == -ENODEV) + /* Probably this is an MTD character device node path */ + mtd = open_mtd_by_chdev(mtd_dev); + } else + mtd = get_mtd_device(NULL, mtd_num); + + return mtd; +} + +static int __init ubi_init(void) +{ + int err, i, k; + + /* Ensure that EC and VID headers have correct size */ + BUILD_BUG_ON(sizeof(struct ubi_ec_hdr) != 64); + BUILD_BUG_ON(sizeof(struct ubi_vid_hdr) != 64); + + if (mtd_devs > UBI_MAX_DEVICES) { + ubi_err("too many MTD devices, maximum is %d", UBI_MAX_DEVICES); + return -EINVAL; + } + + /* Create base sysfs directory and sysfs files */ + ubi_class = class_create(THIS_MODULE, UBI_NAME_STR); + if (IS_ERR(ubi_class)) { + err = PTR_ERR(ubi_class); + ubi_err("cannot create UBI class"); + goto out; + } + + err = class_create_file(ubi_class, &ubi_version); + if (err) { + ubi_err("cannot create sysfs file"); + goto out_class; + } + + err = misc_register(&ubi_ctrl_cdev); + if (err) { + ubi_err("cannot register device"); + goto out_version; + } + + ubi_wl_entry_slab = kmem_cache_create("ubi_wl_entry_slab", + sizeof(struct ubi_wl_entry), + 0, 0, NULL); + if (!ubi_wl_entry_slab) { + err = -ENOMEM; + goto out_dev_unreg; + } + + err = ubi_debugfs_init(); + if (err) + goto out_slab; + + + /* Attach MTD devices */ + for (i = 0; i < mtd_devs; i++) { + struct mtd_dev_param *p = &mtd_dev_param[i]; + struct mtd_info *mtd; + + cond_resched(); + + mtd = open_mtd_device(p->name); + if (IS_ERR(mtd)) { + err = PTR_ERR(mtd); + ubi_err("cannot open mtd %s, error %d", p->name, err); + /* See comment below re-ubi_is_module(). */ + if (ubi_is_module()) + goto out_detach; + continue; + } + + mutex_lock(&ubi_devices_mutex); + err = ubi_attach_mtd_dev(mtd, p->ubi_num, + p->vid_hdr_offs, p->max_beb_per1024); + mutex_unlock(&ubi_devices_mutex); + if (err < 0) { + ubi_err("cannot attach mtd%d", mtd->index); + put_mtd_device(mtd); + + /* + * Originally UBI stopped initializing on any error. + * However, later on it was found out that this + * behavior is not very good when UBI is compiled into + * the kernel and the MTD devices to attach are passed + * through the command line. Indeed, UBI failure + * stopped whole boot sequence. + * + * To fix this, we changed the behavior for the + * non-module case, but preserved the old behavior for + * the module case, just for compatibility. This is a + * little inconsistent, though. + */ + if (ubi_is_module()) + goto out_detach; + } + } + + err = ubiblock_init(); + if (err) { + ubi_err("block: cannot initialize, error %d", err); + + /* See comment above re-ubi_is_module(). */ + if (ubi_is_module()) + goto out_detach; + } + + return 0; + +out_detach: + for (k = 0; k < i; k++) + if (ubi_devices[k]) { + mutex_lock(&ubi_devices_mutex); + ubi_detach_mtd_dev(ubi_devices[k]->ubi_num, 1); + mutex_unlock(&ubi_devices_mutex); + } + ubi_debugfs_exit(); +out_slab: + kmem_cache_destroy(ubi_wl_entry_slab); +out_dev_unreg: + misc_deregister(&ubi_ctrl_cdev); +out_version: + class_remove_file(ubi_class, &ubi_version); +out_class: + class_destroy(ubi_class); +out: + ubi_err("cannot initialize UBI, error %d", err); + return err; +} +late_initcall(ubi_init); + +static void __exit ubi_exit(void) +{ + int i; + + ubiblock_exit(); + + for (i = 0; i < UBI_MAX_DEVICES; i++) + if (ubi_devices[i]) { + mutex_lock(&ubi_devices_mutex); + ubi_detach_mtd_dev(ubi_devices[i]->ubi_num, 1); + mutex_unlock(&ubi_devices_mutex); + } + ubi_debugfs_exit(); + kmem_cache_destroy(ubi_wl_entry_slab); + misc_deregister(&ubi_ctrl_cdev); + class_remove_file(ubi_class, &ubi_version); + class_destroy(ubi_class); +} +module_exit(ubi_exit); + +/** + * bytes_str_to_int - convert a number of bytes string into an integer. + * @str: the string to convert + * + * This function returns positive resulting integer in case of success and a + * negative error code in case of failure. + */ +static int __init bytes_str_to_int(const char *str) +{ + char *endp; + unsigned long result; + + result = simple_strtoul(str, &endp, 0); + if (str == endp || result >= INT_MAX) { + ubi_err("incorrect bytes count: \"%s\"\n", str); + return -EINVAL; + } + + switch (*endp) { + case 'G': + result *= 1024; + case 'M': + result *= 1024; + case 'K': + result *= 1024; + if (endp[1] == 'i' && endp[2] == 'B') + endp += 2; + case '\0': + break; + default: + ubi_err("incorrect bytes count: \"%s\"\n", str); + return -EINVAL; + } + + return result; +} + +/** + * ubi_mtd_param_parse - parse the 'mtd=' UBI parameter. + * @val: the parameter value to parse + * @kp: not used + * + * This function returns zero in case of success and a negative error code in + * case of error. + */ +static int __init ubi_mtd_param_parse(const char *val, struct kernel_param *kp) +{ + int i, len; + struct mtd_dev_param *p; + char buf[MTD_PARAM_LEN_MAX]; + char *pbuf = &buf[0]; + char *tokens[MTD_PARAM_MAX_COUNT], *token; + + if (!val) + return -EINVAL; + + if (mtd_devs == UBI_MAX_DEVICES) { + ubi_err("too many parameters, max. is %d\n", + UBI_MAX_DEVICES); + return -EINVAL; + } + + len = strnlen(val, MTD_PARAM_LEN_MAX); + if (len == MTD_PARAM_LEN_MAX) { + ubi_err("parameter \"%s\" is too long, max. is %d\n", + val, MTD_PARAM_LEN_MAX); + return -EINVAL; + } + + if (len == 0) { + pr_warn("UBI warning: empty 'mtd=' parameter - ignored\n"); + return 0; + } + + strcpy(buf, val); + + /* Get rid of the final newline */ + if (buf[len - 1] == '\n') + buf[len - 1] = '\0'; + + for (i = 0; i < MTD_PARAM_MAX_COUNT; i++) + tokens[i] = strsep(&pbuf, ","); + + if (pbuf) { + ubi_err("too many arguments at \"%s\"\n", val); + return -EINVAL; + } + + p = &mtd_dev_param[mtd_devs]; + strcpy(&p->name[0], tokens[0]); + + token = tokens[1]; + if (token) { + p->vid_hdr_offs = bytes_str_to_int(token); + + if (p->vid_hdr_offs < 0) + return p->vid_hdr_offs; + } + + token = tokens[2]; + if (token) { + int err = kstrtoint(token, 10, &p->max_beb_per1024); + + if (err) { + ubi_err("bad value for max_beb_per1024 parameter: %s", + token); + return -EINVAL; + } + } + + token = tokens[3]; + if (token) { + int err = kstrtoint(token, 10, &p->ubi_num); + + if (err) { + ubi_err("bad value for ubi_num parameter: %s", token); + return -EINVAL; + } + } else + p->ubi_num = UBI_DEV_NUM_AUTO; + + mtd_devs += 1; + return 0; +} + +module_param_call(mtd, ubi_mtd_param_parse, NULL, NULL, 000); +MODULE_PARM_DESC(mtd, "MTD devices to attach. Parameter format: mtd=<name|num|path>[,<vid_hdr_offs>[,max_beb_per1024[,ubi_num]]].\n" + "Multiple \"mtd\" parameters may be specified.\n" + "MTD devices may be specified by their number, name, or path to the MTD character device node.\n" + "Optional \"vid_hdr_offs\" parameter specifies UBI VID header position to be used by UBI. (default value if 0)\n" + "Optional \"max_beb_per1024\" parameter specifies the maximum expected bad eraseblock per 1024 eraseblocks. (default value (" + __stringify(CONFIG_MTD_UBI_BEB_LIMIT) ") if 0)\n" + "Optional \"ubi_num\" parameter specifies UBI device number which have to be assigned to the newly created UBI device (assigned automatically by default)\n" + "\n" + "Example 1: mtd=/dev/mtd0 - attach MTD device /dev/mtd0.\n" + "Example 2: mtd=content,1984 mtd=4 - attach MTD device with name \"content\" using VID header offset 1984, and MTD device number 4 with default VID header offset.\n" + "Example 3: mtd=/dev/mtd1,0,25 - attach MTD device /dev/mtd1 using default VID header offset and reserve 25*nand_size_in_blocks/1024 erase blocks for bad block handling.\n" + "Example 4: mtd=/dev/mtd1,0,0,5 - attach MTD device /dev/mtd1 to UBI 5 and using default values for the other fields.\n" + "\t(e.g. if the NAND *chipset* has 4096 PEB, 100 will be reserved for this UBI device)."); +#ifdef CONFIG_MTD_UBI_FASTMAP +module_param(fm_autoconvert, bool, 0644); +MODULE_PARM_DESC(fm_autoconvert, "Set this parameter to enable fastmap automatically on images without a fastmap."); +#endif +MODULE_VERSION(__stringify(UBI_VERSION)); +MODULE_DESCRIPTION("UBI - Unsorted Block Images"); +MODULE_AUTHOR("Artem Bityutskiy"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/ubi/cdev.c b/drivers/mtd/ubi/cdev.c new file mode 100644 index 00000000000..7646220ca6e --- /dev/null +++ b/drivers/mtd/ubi/cdev.c @@ -0,0 +1,1105 @@ +/* + * Copyright (c) International Business Machines Corp., 2006 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Artem Bityutskiy (Битюцкий Артём) + */ + +/* + * This file includes implementation of UBI character device operations. + * + * There are two kinds of character devices in UBI: UBI character devices and + * UBI volume character devices. UBI character devices allow users to + * manipulate whole volumes: create, remove, and re-size them. Volume character + * devices provide volume I/O capabilities. + * + * Major and minor numbers are assigned dynamically to both UBI and volume + * character devices. + * + * Well, there is the third kind of character devices - the UBI control + * character device, which allows to manipulate by UBI devices - create and + * delete them. In other words, it is used for attaching and detaching MTD + * devices. + */ + +#include <linux/module.h> +#include <linux/stat.h> +#include <linux/slab.h> +#include <linux/ioctl.h> +#include <linux/capability.h> +#include <linux/uaccess.h> +#include <linux/compat.h> +#include <linux/math64.h> +#include <mtd/ubi-user.h> +#include "ubi.h" + +/** + * get_exclusive - get exclusive access to an UBI volume. + * @desc: volume descriptor + * + * This function changes UBI volume open mode to "exclusive". Returns previous + * mode value (positive integer) in case of success and a negative error code + * in case of failure. + */ +static int get_exclusive(struct ubi_volume_desc *desc) +{ + int users, err; + struct ubi_volume *vol = desc->vol; + + spin_lock(&vol->ubi->volumes_lock); + users = vol->readers + vol->writers + vol->exclusive; + ubi_assert(users > 0); + if (users > 1) { + ubi_err("%d users for volume %d", users, vol->vol_id); + err = -EBUSY; + } else { + vol->readers = vol->writers = 0; + vol->exclusive = 1; + err = desc->mode; + desc->mode = UBI_EXCLUSIVE; + } + spin_unlock(&vol->ubi->volumes_lock); + + return err; +} + +/** + * revoke_exclusive - revoke exclusive mode. + * @desc: volume descriptor + * @mode: new mode to switch to + */ +static void revoke_exclusive(struct ubi_volume_desc *desc, int mode) +{ + struct ubi_volume *vol = desc->vol; + + spin_lock(&vol->ubi->volumes_lock); + ubi_assert(vol->readers == 0 && vol->writers == 0); + ubi_assert(vol->exclusive == 1 && desc->mode == UBI_EXCLUSIVE); + vol->exclusive = 0; + if (mode == UBI_READONLY) + vol->readers = 1; + else if (mode == UBI_READWRITE) + vol->writers = 1; + else + vol->exclusive = 1; + spin_unlock(&vol->ubi->volumes_lock); + + desc->mode = mode; +} + +static int vol_cdev_open(struct inode *inode, struct file *file) +{ + struct ubi_volume_desc *desc; + int vol_id = iminor(inode) - 1, mode, ubi_num; + + ubi_num = ubi_major2num(imajor(inode)); + if (ubi_num < 0) + return ubi_num; + + if (file->f_mode & FMODE_WRITE) + mode = UBI_READWRITE; + else + mode = UBI_READONLY; + + dbg_gen("open device %d, volume %d, mode %d", + ubi_num, vol_id, mode); + + desc = ubi_open_volume(ubi_num, vol_id, mode); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + file->private_data = desc; + return 0; +} + +static int vol_cdev_release(struct inode *inode, struct file *file) +{ + struct ubi_volume_desc *desc = file->private_data; + struct ubi_volume *vol = desc->vol; + + dbg_gen("release device %d, volume %d, mode %d", + vol->ubi->ubi_num, vol->vol_id, desc->mode); + + if (vol->updating) { + ubi_warn("update of volume %d not finished, volume is damaged", + vol->vol_id); + ubi_assert(!vol->changing_leb); + vol->updating = 0; + vfree(vol->upd_buf); + } else if (vol->changing_leb) { + dbg_gen("only %lld of %lld bytes received for atomic LEB change for volume %d:%d, cancel", + vol->upd_received, vol->upd_bytes, vol->ubi->ubi_num, + vol->vol_id); + vol->changing_leb = 0; + vfree(vol->upd_buf); + } + + ubi_close_volume(desc); + return 0; +} + +static loff_t vol_cdev_llseek(struct file *file, loff_t offset, int origin) +{ + struct ubi_volume_desc *desc = file->private_data; + struct ubi_volume *vol = desc->vol; + + if (vol->updating) { + /* Update is in progress, seeking is prohibited */ + ubi_err("updating"); + return -EBUSY; + } + + return fixed_size_llseek(file, offset, origin, vol->used_bytes); +} + +static int vol_cdev_fsync(struct file *file, loff_t start, loff_t end, + int datasync) +{ + struct ubi_volume_desc *desc = file->private_data; + struct ubi_device *ubi = desc->vol->ubi; + struct inode *inode = file_inode(file); + int err; + mutex_lock(&inode->i_mutex); + err = ubi_sync(ubi->ubi_num); + mutex_unlock(&inode->i_mutex); + return err; +} + + +static ssize_t vol_cdev_read(struct file *file, __user char *buf, size_t count, + loff_t *offp) +{ + struct ubi_volume_desc *desc = file->private_data; + struct ubi_volume *vol = desc->vol; + struct ubi_device *ubi = vol->ubi; + int err, lnum, off, len, tbuf_size; + size_t count_save = count; + void *tbuf; + + dbg_gen("read %zd bytes from offset %lld of volume %d", + count, *offp, vol->vol_id); + + if (vol->updating) { + ubi_err("updating"); + return -EBUSY; + } + if (vol->upd_marker) { + ubi_err("damaged volume, update marker is set"); + return -EBADF; + } + if (*offp == vol->used_bytes || count == 0) + return 0; + + if (vol->corrupted) + dbg_gen("read from corrupted volume %d", vol->vol_id); + + if (*offp + count > vol->used_bytes) + count_save = count = vol->used_bytes - *offp; + + tbuf_size = vol->usable_leb_size; + if (count < tbuf_size) + tbuf_size = ALIGN(count, ubi->min_io_size); + tbuf = vmalloc(tbuf_size); + if (!tbuf) + return -ENOMEM; + + len = count > tbuf_size ? tbuf_size : count; + lnum = div_u64_rem(*offp, vol->usable_leb_size, &off); + + do { + cond_resched(); + + if (off + len >= vol->usable_leb_size) + len = vol->usable_leb_size - off; + + err = ubi_eba_read_leb(ubi, vol, lnum, tbuf, off, len, 0); + if (err) + break; + + off += len; + if (off == vol->usable_leb_size) { + lnum += 1; + off -= vol->usable_leb_size; + } + + count -= len; + *offp += len; + + err = copy_to_user(buf, tbuf, len); + if (err) { + err = -EFAULT; + break; + } + + buf += len; + len = count > tbuf_size ? tbuf_size : count; + } while (count); + + vfree(tbuf); + return err ? err : count_save - count; +} + +/* + * This function allows to directly write to dynamic UBI volumes, without + * issuing the volume update operation. + */ +static ssize_t vol_cdev_direct_write(struct file *file, const char __user *buf, + size_t count, loff_t *offp) +{ + struct ubi_volume_desc *desc = file->private_data; + struct ubi_volume *vol = desc->vol; + struct ubi_device *ubi = vol->ubi; + int lnum, off, len, tbuf_size, err = 0; + size_t count_save = count; + char *tbuf; + + if (!vol->direct_writes) + return -EPERM; + + dbg_gen("requested: write %zd bytes to offset %lld of volume %u", + count, *offp, vol->vol_id); + + if (vol->vol_type == UBI_STATIC_VOLUME) + return -EROFS; + + lnum = div_u64_rem(*offp, vol->usable_leb_size, &off); + if (off & (ubi->min_io_size - 1)) { + ubi_err("unaligned position"); + return -EINVAL; + } + + if (*offp + count > vol->used_bytes) + count_save = count = vol->used_bytes - *offp; + + /* We can write only in fractions of the minimum I/O unit */ + if (count & (ubi->min_io_size - 1)) { + ubi_err("unaligned write length"); + return -EINVAL; + } + + tbuf_size = vol->usable_leb_size; + if (count < tbuf_size) + tbuf_size = ALIGN(count, ubi->min_io_size); + tbuf = vmalloc(tbuf_size); + if (!tbuf) + return -ENOMEM; + + len = count > tbuf_size ? tbuf_size : count; + + while (count) { + cond_resched(); + + if (off + len >= vol->usable_leb_size) + len = vol->usable_leb_size - off; + + err = copy_from_user(tbuf, buf, len); + if (err) { + err = -EFAULT; + break; + } + + err = ubi_eba_write_leb(ubi, vol, lnum, tbuf, off, len); + if (err) + break; + + off += len; + if (off == vol->usable_leb_size) { + lnum += 1; + off -= vol->usable_leb_size; + } + + count -= len; + *offp += len; + buf += len; + len = count > tbuf_size ? tbuf_size : count; + } + + vfree(tbuf); + return err ? err : count_save - count; +} + +static ssize_t vol_cdev_write(struct file *file, const char __user *buf, + size_t count, loff_t *offp) +{ + int err = 0; + struct ubi_volume_desc *desc = file->private_data; + struct ubi_volume *vol = desc->vol; + struct ubi_device *ubi = vol->ubi; + + if (!vol->updating && !vol->changing_leb) + return vol_cdev_direct_write(file, buf, count, offp); + + if (vol->updating) + err = ubi_more_update_data(ubi, vol, buf, count); + else + err = ubi_more_leb_change_data(ubi, vol, buf, count); + + if (err < 0) { + ubi_err("cannot accept more %zd bytes of data, error %d", + count, err); + return err; + } + + if (err) { + /* + * The operation is finished, @err contains number of actually + * written bytes. + */ + count = err; + + if (vol->changing_leb) { + revoke_exclusive(desc, UBI_READWRITE); + return count; + } + + err = ubi_check_volume(ubi, vol->vol_id); + if (err < 0) + return err; + + if (err) { + ubi_warn("volume %d on UBI device %d is corrupted", + vol->vol_id, ubi->ubi_num); + vol->corrupted = 1; + } + vol->checked = 1; + ubi_volume_notify(ubi, vol, UBI_VOLUME_UPDATED); + revoke_exclusive(desc, UBI_READWRITE); + } + + return count; +} + +static long vol_cdev_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int err = 0; + struct ubi_volume_desc *desc = file->private_data; + struct ubi_volume *vol = desc->vol; + struct ubi_device *ubi = vol->ubi; + void __user *argp = (void __user *)arg; + + switch (cmd) { + /* Volume update command */ + case UBI_IOCVOLUP: + { + int64_t bytes, rsvd_bytes; + + if (!capable(CAP_SYS_RESOURCE)) { + err = -EPERM; + break; + } + + err = copy_from_user(&bytes, argp, sizeof(int64_t)); + if (err) { + err = -EFAULT; + break; + } + + if (desc->mode == UBI_READONLY) { + err = -EROFS; + break; + } + + rsvd_bytes = (long long)vol->reserved_pebs * + ubi->leb_size-vol->data_pad; + if (bytes < 0 || bytes > rsvd_bytes) { + err = -EINVAL; + break; + } + + err = get_exclusive(desc); + if (err < 0) + break; + + err = ubi_start_update(ubi, vol, bytes); + if (bytes == 0) + revoke_exclusive(desc, UBI_READWRITE); + break; + } + + /* Atomic logical eraseblock change command */ + case UBI_IOCEBCH: + { + struct ubi_leb_change_req req; + + err = copy_from_user(&req, argp, + sizeof(struct ubi_leb_change_req)); + if (err) { + err = -EFAULT; + break; + } + + if (desc->mode == UBI_READONLY || + vol->vol_type == UBI_STATIC_VOLUME) { + err = -EROFS; + break; + } + + /* Validate the request */ + err = -EINVAL; + if (req.lnum < 0 || req.lnum >= vol->reserved_pebs || + req.bytes < 0 || req.lnum >= vol->usable_leb_size) + break; + + err = get_exclusive(desc); + if (err < 0) + break; + + err = ubi_start_leb_change(ubi, vol, &req); + if (req.bytes == 0) + revoke_exclusive(desc, UBI_READWRITE); + break; + } + + /* Logical eraseblock erasure command */ + case UBI_IOCEBER: + { + int32_t lnum; + + err = get_user(lnum, (__user int32_t *)argp); + if (err) { + err = -EFAULT; + break; + } + + if (desc->mode == UBI_READONLY || + vol->vol_type == UBI_STATIC_VOLUME) { + err = -EROFS; + break; + } + + if (lnum < 0 || lnum >= vol->reserved_pebs) { + err = -EINVAL; + break; + } + + dbg_gen("erase LEB %d:%d", vol->vol_id, lnum); + err = ubi_eba_unmap_leb(ubi, vol, lnum); + if (err) + break; + + err = ubi_wl_flush(ubi, UBI_ALL, UBI_ALL); + break; + } + + /* Logical eraseblock map command */ + case UBI_IOCEBMAP: + { + struct ubi_map_req req; + + err = copy_from_user(&req, argp, sizeof(struct ubi_map_req)); + if (err) { + err = -EFAULT; + break; + } + err = ubi_leb_map(desc, req.lnum); + break; + } + + /* Logical eraseblock un-map command */ + case UBI_IOCEBUNMAP: + { + int32_t lnum; + + err = get_user(lnum, (__user int32_t *)argp); + if (err) { + err = -EFAULT; + break; + } + err = ubi_leb_unmap(desc, lnum); + break; + } + + /* Check if logical eraseblock is mapped command */ + case UBI_IOCEBISMAP: + { + int32_t lnum; + + err = get_user(lnum, (__user int32_t *)argp); + if (err) { + err = -EFAULT; + break; + } + err = ubi_is_mapped(desc, lnum); + break; + } + + /* Set volume property command */ + case UBI_IOCSETVOLPROP: + { + struct ubi_set_vol_prop_req req; + + err = copy_from_user(&req, argp, + sizeof(struct ubi_set_vol_prop_req)); + if (err) { + err = -EFAULT; + break; + } + switch (req.property) { + case UBI_VOL_PROP_DIRECT_WRITE: + mutex_lock(&ubi->device_mutex); + desc->vol->direct_writes = !!req.value; + mutex_unlock(&ubi->device_mutex); + break; + default: + err = -EINVAL; + break; + } + break; + } + + /* Create a R/O block device on top of the UBI volume */ + case UBI_IOCVOLCRBLK: + { + struct ubi_volume_info vi; + + ubi_get_volume_info(desc, &vi); + err = ubiblock_create(&vi); + break; + } + + /* Remove the R/O block device */ + case UBI_IOCVOLRMBLK: + { + struct ubi_volume_info vi; + + ubi_get_volume_info(desc, &vi); + err = ubiblock_remove(&vi); + break; + } + + default: + err = -ENOTTY; + break; + } + return err; +} + +/** + * verify_mkvol_req - verify volume creation request. + * @ubi: UBI device description object + * @req: the request to check + * + * This function zero if the request is correct, and %-EINVAL if not. + */ +static int verify_mkvol_req(const struct ubi_device *ubi, + const struct ubi_mkvol_req *req) +{ + int n, err = -EINVAL; + + if (req->bytes < 0 || req->alignment < 0 || req->vol_type < 0 || + req->name_len < 0) + goto bad; + + if ((req->vol_id < 0 || req->vol_id >= ubi->vtbl_slots) && + req->vol_id != UBI_VOL_NUM_AUTO) + goto bad; + + if (req->alignment == 0) + goto bad; + + if (req->bytes == 0) + goto bad; + + if (req->vol_type != UBI_DYNAMIC_VOLUME && + req->vol_type != UBI_STATIC_VOLUME) + goto bad; + + if (req->alignment > ubi->leb_size) + goto bad; + + n = req->alignment & (ubi->min_io_size - 1); + if (req->alignment != 1 && n) + goto bad; + + if (!req->name[0] || !req->name_len) + goto bad; + + if (req->name_len > UBI_VOL_NAME_MAX) { + err = -ENAMETOOLONG; + goto bad; + } + + n = strnlen(req->name, req->name_len + 1); + if (n != req->name_len) + goto bad; + + return 0; + +bad: + ubi_err("bad volume creation request"); + ubi_dump_mkvol_req(req); + return err; +} + +/** + * verify_rsvol_req - verify volume re-size request. + * @ubi: UBI device description object + * @req: the request to check + * + * This function returns zero if the request is correct, and %-EINVAL if not. + */ +static int verify_rsvol_req(const struct ubi_device *ubi, + const struct ubi_rsvol_req *req) +{ + if (req->bytes <= 0) + return -EINVAL; + + if (req->vol_id < 0 || req->vol_id >= ubi->vtbl_slots) + return -EINVAL; + + return 0; +} + +/** + * rename_volumes - rename UBI volumes. + * @ubi: UBI device description object + * @req: volumes re-name request + * + * This is a helper function for the volume re-name IOCTL which validates the + * the request, opens the volume and calls corresponding volumes management + * function. Returns zero in case of success and a negative error code in case + * of failure. + */ +static int rename_volumes(struct ubi_device *ubi, + struct ubi_rnvol_req *req) +{ + int i, n, err; + struct list_head rename_list; + struct ubi_rename_entry *re, *re1; + + if (req->count < 0 || req->count > UBI_MAX_RNVOL) + return -EINVAL; + + if (req->count == 0) + return 0; + + /* Validate volume IDs and names in the request */ + for (i = 0; i < req->count; i++) { + if (req->ents[i].vol_id < 0 || + req->ents[i].vol_id >= ubi->vtbl_slots) + return -EINVAL; + if (req->ents[i].name_len < 0) + return -EINVAL; + if (req->ents[i].name_len > UBI_VOL_NAME_MAX) + return -ENAMETOOLONG; + req->ents[i].name[req->ents[i].name_len] = '\0'; + n = strlen(req->ents[i].name); + if (n != req->ents[i].name_len) + err = -EINVAL; + } + + /* Make sure volume IDs and names are unique */ + for (i = 0; i < req->count - 1; i++) { + for (n = i + 1; n < req->count; n++) { + if (req->ents[i].vol_id == req->ents[n].vol_id) { + ubi_err("duplicated volume id %d", + req->ents[i].vol_id); + return -EINVAL; + } + if (!strcmp(req->ents[i].name, req->ents[n].name)) { + ubi_err("duplicated volume name \"%s\"", + req->ents[i].name); + return -EINVAL; + } + } + } + + /* Create the re-name list */ + INIT_LIST_HEAD(&rename_list); + for (i = 0; i < req->count; i++) { + int vol_id = req->ents[i].vol_id; + int name_len = req->ents[i].name_len; + const char *name = req->ents[i].name; + + re = kzalloc(sizeof(struct ubi_rename_entry), GFP_KERNEL); + if (!re) { + err = -ENOMEM; + goto out_free; + } + + re->desc = ubi_open_volume(ubi->ubi_num, vol_id, UBI_READWRITE); + if (IS_ERR(re->desc)) { + err = PTR_ERR(re->desc); + ubi_err("cannot open volume %d, error %d", vol_id, err); + kfree(re); + goto out_free; + } + + /* Skip this re-naming if the name does not really change */ + if (re->desc->vol->name_len == name_len && + !memcmp(re->desc->vol->name, name, name_len)) { + ubi_close_volume(re->desc); + kfree(re); + continue; + } + + re->new_name_len = name_len; + memcpy(re->new_name, name, name_len); + list_add_tail(&re->list, &rename_list); + dbg_gen("will rename volume %d from \"%s\" to \"%s\"", + vol_id, re->desc->vol->name, name); + } + + if (list_empty(&rename_list)) + return 0; + + /* Find out the volumes which have to be removed */ + list_for_each_entry(re, &rename_list, list) { + struct ubi_volume_desc *desc; + int no_remove_needed = 0; + + /* + * Volume @re->vol_id is going to be re-named to + * @re->new_name, while its current name is @name. If a volume + * with name @re->new_name currently exists, it has to be + * removed, unless it is also re-named in the request (@req). + */ + list_for_each_entry(re1, &rename_list, list) { + if (re->new_name_len == re1->desc->vol->name_len && + !memcmp(re->new_name, re1->desc->vol->name, + re1->desc->vol->name_len)) { + no_remove_needed = 1; + break; + } + } + + if (no_remove_needed) + continue; + + /* + * It seems we need to remove volume with name @re->new_name, + * if it exists. + */ + desc = ubi_open_volume_nm(ubi->ubi_num, re->new_name, + UBI_EXCLUSIVE); + if (IS_ERR(desc)) { + err = PTR_ERR(desc); + if (err == -ENODEV) + /* Re-naming into a non-existing volume name */ + continue; + + /* The volume exists but busy, or an error occurred */ + ubi_err("cannot open volume \"%s\", error %d", + re->new_name, err); + goto out_free; + } + + re1 = kzalloc(sizeof(struct ubi_rename_entry), GFP_KERNEL); + if (!re1) { + err = -ENOMEM; + ubi_close_volume(desc); + goto out_free; + } + + re1->remove = 1; + re1->desc = desc; + list_add(&re1->list, &rename_list); + dbg_gen("will remove volume %d, name \"%s\"", + re1->desc->vol->vol_id, re1->desc->vol->name); + } + + mutex_lock(&ubi->device_mutex); + err = ubi_rename_volumes(ubi, &rename_list); + mutex_unlock(&ubi->device_mutex); + +out_free: + list_for_each_entry_safe(re, re1, &rename_list, list) { + ubi_close_volume(re->desc); + list_del(&re->list); + kfree(re); + } + return err; +} + +static long ubi_cdev_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int err = 0; + struct ubi_device *ubi; + struct ubi_volume_desc *desc; + void __user *argp = (void __user *)arg; + + if (!capable(CAP_SYS_RESOURCE)) + return -EPERM; + + ubi = ubi_get_by_major(imajor(file->f_mapping->host)); + if (!ubi) + return -ENODEV; + + switch (cmd) { + /* Create volume command */ + case UBI_IOCMKVOL: + { + struct ubi_mkvol_req req; + + dbg_gen("create volume"); + err = copy_from_user(&req, argp, sizeof(struct ubi_mkvol_req)); + if (err) { + err = -EFAULT; + break; + } + + err = verify_mkvol_req(ubi, &req); + if (err) + break; + + mutex_lock(&ubi->device_mutex); + err = ubi_create_volume(ubi, &req); + mutex_unlock(&ubi->device_mutex); + if (err) + break; + + err = put_user(req.vol_id, (__user int32_t *)argp); + if (err) + err = -EFAULT; + + break; + } + + /* Remove volume command */ + case UBI_IOCRMVOL: + { + int vol_id; + + dbg_gen("remove volume"); + err = get_user(vol_id, (__user int32_t *)argp); + if (err) { + err = -EFAULT; + break; + } + + desc = ubi_open_volume(ubi->ubi_num, vol_id, UBI_EXCLUSIVE); + if (IS_ERR(desc)) { + err = PTR_ERR(desc); + break; + } + + mutex_lock(&ubi->device_mutex); + err = ubi_remove_volume(desc, 0); + mutex_unlock(&ubi->device_mutex); + + /* + * The volume is deleted (unless an error occurred), and the + * 'struct ubi_volume' object will be freed when + * 'ubi_close_volume()' will call 'put_device()'. + */ + ubi_close_volume(desc); + break; + } + + /* Re-size volume command */ + case UBI_IOCRSVOL: + { + int pebs; + struct ubi_rsvol_req req; + + dbg_gen("re-size volume"); + err = copy_from_user(&req, argp, sizeof(struct ubi_rsvol_req)); + if (err) { + err = -EFAULT; + break; + } + + err = verify_rsvol_req(ubi, &req); + if (err) + break; + + desc = ubi_open_volume(ubi->ubi_num, req.vol_id, UBI_EXCLUSIVE); + if (IS_ERR(desc)) { + err = PTR_ERR(desc); + break; + } + + pebs = div_u64(req.bytes + desc->vol->usable_leb_size - 1, + desc->vol->usable_leb_size); + + mutex_lock(&ubi->device_mutex); + err = ubi_resize_volume(desc, pebs); + mutex_unlock(&ubi->device_mutex); + ubi_close_volume(desc); + break; + } + + /* Re-name volumes command */ + case UBI_IOCRNVOL: + { + struct ubi_rnvol_req *req; + + dbg_gen("re-name volumes"); + req = kmalloc(sizeof(struct ubi_rnvol_req), GFP_KERNEL); + if (!req) { + err = -ENOMEM; + break; + }; + + err = copy_from_user(req, argp, sizeof(struct ubi_rnvol_req)); + if (err) { + err = -EFAULT; + kfree(req); + break; + } + + err = rename_volumes(ubi, req); + kfree(req); + break; + } + + default: + err = -ENOTTY; + break; + } + + ubi_put_device(ubi); + return err; +} + +static long ctrl_cdev_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int err = 0; + void __user *argp = (void __user *)arg; + + if (!capable(CAP_SYS_RESOURCE)) + return -EPERM; + + switch (cmd) { + /* Attach an MTD device command */ + case UBI_IOCATT: + { + struct ubi_attach_req req; + struct mtd_info *mtd; + + dbg_gen("attach MTD device"); + err = copy_from_user(&req, argp, sizeof(struct ubi_attach_req)); + if (err) { + err = -EFAULT; + break; + } + + if (req.mtd_num < 0 || + (req.ubi_num < 0 && req.ubi_num != UBI_DEV_NUM_AUTO)) { + err = -EINVAL; + break; + } + + mtd = get_mtd_device(NULL, req.mtd_num); + if (IS_ERR(mtd)) { + err = PTR_ERR(mtd); + break; + } + + /* + * Note, further request verification is done by + * 'ubi_attach_mtd_dev()'. + */ + mutex_lock(&ubi_devices_mutex); + err = ubi_attach_mtd_dev(mtd, req.ubi_num, req.vid_hdr_offset, + req.max_beb_per1024); + mutex_unlock(&ubi_devices_mutex); + if (err < 0) + put_mtd_device(mtd); + else + /* @err contains UBI device number */ + err = put_user(err, (__user int32_t *)argp); + + break; + } + + /* Detach an MTD device command */ + case UBI_IOCDET: + { + int ubi_num; + + dbg_gen("detach MTD device"); + err = get_user(ubi_num, (__user int32_t *)argp); + if (err) { + err = -EFAULT; + break; + } + + mutex_lock(&ubi_devices_mutex); + err = ubi_detach_mtd_dev(ubi_num, 0); + mutex_unlock(&ubi_devices_mutex); + break; + } + + default: + err = -ENOTTY; + break; + } + + return err; +} + +#ifdef CONFIG_COMPAT +static long vol_cdev_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + unsigned long translated_arg = (unsigned long)compat_ptr(arg); + + return vol_cdev_ioctl(file, cmd, translated_arg); +} + +static long ubi_cdev_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + unsigned long translated_arg = (unsigned long)compat_ptr(arg); + + return ubi_cdev_ioctl(file, cmd, translated_arg); +} + +static long ctrl_cdev_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + unsigned long translated_arg = (unsigned long)compat_ptr(arg); + + return ctrl_cdev_ioctl(file, cmd, translated_arg); +} +#else +#define vol_cdev_compat_ioctl NULL +#define ubi_cdev_compat_ioctl NULL +#define ctrl_cdev_compat_ioctl NULL +#endif + +/* UBI volume character device operations */ +const struct file_operations ubi_vol_cdev_operations = { + .owner = THIS_MODULE, + .open = vol_cdev_open, + .release = vol_cdev_release, + .llseek = vol_cdev_llseek, + .read = vol_cdev_read, + .write = vol_cdev_write, + .fsync = vol_cdev_fsync, + .unlocked_ioctl = vol_cdev_ioctl, + .compat_ioctl = vol_cdev_compat_ioctl, +}; + +/* UBI character device operations */ +const struct file_operations ubi_cdev_operations = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = ubi_cdev_ioctl, + .compat_ioctl = ubi_cdev_compat_ioctl, +}; + +/* UBI control character device operations */ +const struct file_operations ubi_ctrl_cdev_operations = { + .owner = THIS_MODULE, + .unlocked_ioctl = ctrl_cdev_ioctl, + .compat_ioctl = ctrl_cdev_compat_ioctl, + .llseek = no_llseek, +}; diff --git a/drivers/mtd/ubi/debug.c b/drivers/mtd/ubi/debug.c new file mode 100644 index 00000000000..63cb1d7236c --- /dev/null +++ b/drivers/mtd/ubi/debug.c @@ -0,0 +1,449 @@ +/* + * Copyright (c) International Business Machines Corp., 2006 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Artem Bityutskiy (Битюцкий Артём) + */ + +#include "ubi.h" +#include <linux/debugfs.h> +#include <linux/uaccess.h> +#include <linux/module.h> + + +/** + * ubi_dump_flash - dump a region of flash. + * @ubi: UBI device description object + * @pnum: the physical eraseblock number to dump + * @offset: the starting offset within the physical eraseblock to dump + * @len: the length of the region to dump + */ +void ubi_dump_flash(struct ubi_device *ubi, int pnum, int offset, int len) +{ + int err; + size_t read; + void *buf; + loff_t addr = (loff_t)pnum * ubi->peb_size + offset; + + buf = vmalloc(len); + if (!buf) + return; + err = mtd_read(ubi->mtd, addr, len, &read, buf); + if (err && err != -EUCLEAN) { + ubi_err("error %d while reading %d bytes from PEB %d:%d, read %zd bytes", + err, len, pnum, offset, read); + goto out; + } + + ubi_msg("dumping %d bytes of data from PEB %d, offset %d", + len, pnum, offset); + print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 32, 1, buf, len, 1); +out: + vfree(buf); + return; +} + +/** + * ubi_dump_ec_hdr - dump an erase counter header. + * @ec_hdr: the erase counter header to dump + */ +void ubi_dump_ec_hdr(const struct ubi_ec_hdr *ec_hdr) +{ + pr_err("Erase counter header dump:\n"); + pr_err("\tmagic %#08x\n", be32_to_cpu(ec_hdr->magic)); + pr_err("\tversion %d\n", (int)ec_hdr->version); + pr_err("\tec %llu\n", (long long)be64_to_cpu(ec_hdr->ec)); + pr_err("\tvid_hdr_offset %d\n", be32_to_cpu(ec_hdr->vid_hdr_offset)); + pr_err("\tdata_offset %d\n", be32_to_cpu(ec_hdr->data_offset)); + pr_err("\timage_seq %d\n", be32_to_cpu(ec_hdr->image_seq)); + pr_err("\thdr_crc %#08x\n", be32_to_cpu(ec_hdr->hdr_crc)); + pr_err("erase counter header hexdump:\n"); + print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 32, 1, + ec_hdr, UBI_EC_HDR_SIZE, 1); +} + +/** + * ubi_dump_vid_hdr - dump a volume identifier header. + * @vid_hdr: the volume identifier header to dump + */ +void ubi_dump_vid_hdr(const struct ubi_vid_hdr *vid_hdr) +{ + pr_err("Volume identifier header dump:\n"); + pr_err("\tmagic %08x\n", be32_to_cpu(vid_hdr->magic)); + pr_err("\tversion %d\n", (int)vid_hdr->version); + pr_err("\tvol_type %d\n", (int)vid_hdr->vol_type); + pr_err("\tcopy_flag %d\n", (int)vid_hdr->copy_flag); + pr_err("\tcompat %d\n", (int)vid_hdr->compat); + pr_err("\tvol_id %d\n", be32_to_cpu(vid_hdr->vol_id)); + pr_err("\tlnum %d\n", be32_to_cpu(vid_hdr->lnum)); + pr_err("\tdata_size %d\n", be32_to_cpu(vid_hdr->data_size)); + pr_err("\tused_ebs %d\n", be32_to_cpu(vid_hdr->used_ebs)); + pr_err("\tdata_pad %d\n", be32_to_cpu(vid_hdr->data_pad)); + pr_err("\tsqnum %llu\n", + (unsigned long long)be64_to_cpu(vid_hdr->sqnum)); + pr_err("\thdr_crc %08x\n", be32_to_cpu(vid_hdr->hdr_crc)); + pr_err("Volume identifier header hexdump:\n"); + print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 32, 1, + vid_hdr, UBI_VID_HDR_SIZE, 1); +} + +/** + * ubi_dump_vol_info - dump volume information. + * @vol: UBI volume description object + */ +void ubi_dump_vol_info(const struct ubi_volume *vol) +{ + pr_err("Volume information dump:\n"); + pr_err("\tvol_id %d\n", vol->vol_id); + pr_err("\treserved_pebs %d\n", vol->reserved_pebs); + pr_err("\talignment %d\n", vol->alignment); + pr_err("\tdata_pad %d\n", vol->data_pad); + pr_err("\tvol_type %d\n", vol->vol_type); + pr_err("\tname_len %d\n", vol->name_len); + pr_err("\tusable_leb_size %d\n", vol->usable_leb_size); + pr_err("\tused_ebs %d\n", vol->used_ebs); + pr_err("\tused_bytes %lld\n", vol->used_bytes); + pr_err("\tlast_eb_bytes %d\n", vol->last_eb_bytes); + pr_err("\tcorrupted %d\n", vol->corrupted); + pr_err("\tupd_marker %d\n", vol->upd_marker); + + if (vol->name_len <= UBI_VOL_NAME_MAX && + strnlen(vol->name, vol->name_len + 1) == vol->name_len) { + pr_err("\tname %s\n", vol->name); + } else { + pr_err("\t1st 5 characters of name: %c%c%c%c%c\n", + vol->name[0], vol->name[1], vol->name[2], + vol->name[3], vol->name[4]); + } +} + +/** + * ubi_dump_vtbl_record - dump a &struct ubi_vtbl_record object. + * @r: the object to dump + * @idx: volume table index + */ +void ubi_dump_vtbl_record(const struct ubi_vtbl_record *r, int idx) +{ + int name_len = be16_to_cpu(r->name_len); + + pr_err("Volume table record %d dump:\n", idx); + pr_err("\treserved_pebs %d\n", be32_to_cpu(r->reserved_pebs)); + pr_err("\talignment %d\n", be32_to_cpu(r->alignment)); + pr_err("\tdata_pad %d\n", be32_to_cpu(r->data_pad)); + pr_err("\tvol_type %d\n", (int)r->vol_type); + pr_err("\tupd_marker %d\n", (int)r->upd_marker); + pr_err("\tname_len %d\n", name_len); + + if (r->name[0] == '\0') { + pr_err("\tname NULL\n"); + return; + } + + if (name_len <= UBI_VOL_NAME_MAX && + strnlen(&r->name[0], name_len + 1) == name_len) { + pr_err("\tname %s\n", &r->name[0]); + } else { + pr_err("\t1st 5 characters of name: %c%c%c%c%c\n", + r->name[0], r->name[1], r->name[2], r->name[3], + r->name[4]); + } + pr_err("\tcrc %#08x\n", be32_to_cpu(r->crc)); +} + +/** + * ubi_dump_av - dump a &struct ubi_ainf_volume object. + * @av: the object to dump + */ +void ubi_dump_av(const struct ubi_ainf_volume *av) +{ + pr_err("Volume attaching information dump:\n"); + pr_err("\tvol_id %d\n", av->vol_id); + pr_err("\thighest_lnum %d\n", av->highest_lnum); + pr_err("\tleb_count %d\n", av->leb_count); + pr_err("\tcompat %d\n", av->compat); + pr_err("\tvol_type %d\n", av->vol_type); + pr_err("\tused_ebs %d\n", av->used_ebs); + pr_err("\tlast_data_size %d\n", av->last_data_size); + pr_err("\tdata_pad %d\n", av->data_pad); +} + +/** + * ubi_dump_aeb - dump a &struct ubi_ainf_peb object. + * @aeb: the object to dump + * @type: object type: 0 - not corrupted, 1 - corrupted + */ +void ubi_dump_aeb(const struct ubi_ainf_peb *aeb, int type) +{ + pr_err("eraseblock attaching information dump:\n"); + pr_err("\tec %d\n", aeb->ec); + pr_err("\tpnum %d\n", aeb->pnum); + if (type == 0) { + pr_err("\tlnum %d\n", aeb->lnum); + pr_err("\tscrub %d\n", aeb->scrub); + pr_err("\tsqnum %llu\n", aeb->sqnum); + } +} + +/** + * ubi_dump_mkvol_req - dump a &struct ubi_mkvol_req object. + * @req: the object to dump + */ +void ubi_dump_mkvol_req(const struct ubi_mkvol_req *req) +{ + char nm[17]; + + pr_err("Volume creation request dump:\n"); + pr_err("\tvol_id %d\n", req->vol_id); + pr_err("\talignment %d\n", req->alignment); + pr_err("\tbytes %lld\n", (long long)req->bytes); + pr_err("\tvol_type %d\n", req->vol_type); + pr_err("\tname_len %d\n", req->name_len); + + memcpy(nm, req->name, 16); + nm[16] = 0; + pr_err("\t1st 16 characters of name: %s\n", nm); +} + +/* + * Root directory for UBI stuff in debugfs. Contains sub-directories which + * contain the stuff specific to particular UBI devices. + */ +static struct dentry *dfs_rootdir; + +/** + * ubi_debugfs_init - create UBI debugfs directory. + * + * Create UBI debugfs directory. Returns zero in case of success and a negative + * error code in case of failure. + */ +int ubi_debugfs_init(void) +{ + if (!IS_ENABLED(CONFIG_DEBUG_FS)) + return 0; + + dfs_rootdir = debugfs_create_dir("ubi", NULL); + if (IS_ERR_OR_NULL(dfs_rootdir)) { + int err = dfs_rootdir ? -ENODEV : PTR_ERR(dfs_rootdir); + + ubi_err("cannot create \"ubi\" debugfs directory, error %d\n", + err); + return err; + } + + return 0; +} + +/** + * ubi_debugfs_exit - remove UBI debugfs directory. + */ +void ubi_debugfs_exit(void) +{ + if (IS_ENABLED(CONFIG_DEBUG_FS)) + debugfs_remove(dfs_rootdir); +} + +/* Read an UBI debugfs file */ +static ssize_t dfs_file_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + unsigned long ubi_num = (unsigned long)file->private_data; + struct dentry *dent = file->f_path.dentry; + struct ubi_device *ubi; + struct ubi_debug_info *d; + char buf[3]; + int val; + + ubi = ubi_get_device(ubi_num); + if (!ubi) + return -ENODEV; + d = &ubi->dbg; + + if (dent == d->dfs_chk_gen) + val = d->chk_gen; + else if (dent == d->dfs_chk_io) + val = d->chk_io; + else if (dent == d->dfs_disable_bgt) + val = d->disable_bgt; + else if (dent == d->dfs_emulate_bitflips) + val = d->emulate_bitflips; + else if (dent == d->dfs_emulate_io_failures) + val = d->emulate_io_failures; + else { + count = -EINVAL; + goto out; + } + + if (val) + buf[0] = '1'; + else + buf[0] = '0'; + buf[1] = '\n'; + buf[2] = 0x00; + + count = simple_read_from_buffer(user_buf, count, ppos, buf, 2); + +out: + ubi_put_device(ubi); + return count; +} + +/* Write an UBI debugfs file */ +static ssize_t dfs_file_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + unsigned long ubi_num = (unsigned long)file->private_data; + struct dentry *dent = file->f_path.dentry; + struct ubi_device *ubi; + struct ubi_debug_info *d; + size_t buf_size; + char buf[8]; + int val; + + ubi = ubi_get_device(ubi_num); + if (!ubi) + return -ENODEV; + d = &ubi->dbg; + + buf_size = min_t(size_t, count, (sizeof(buf) - 1)); + if (copy_from_user(buf, user_buf, buf_size)) { + count = -EFAULT; + goto out; + } + + if (buf[0] == '1') + val = 1; + else if (buf[0] == '0') + val = 0; + else { + count = -EINVAL; + goto out; + } + + if (dent == d->dfs_chk_gen) + d->chk_gen = val; + else if (dent == d->dfs_chk_io) + d->chk_io = val; + else if (dent == d->dfs_disable_bgt) + d->disable_bgt = val; + else if (dent == d->dfs_emulate_bitflips) + d->emulate_bitflips = val; + else if (dent == d->dfs_emulate_io_failures) + d->emulate_io_failures = val; + else + count = -EINVAL; + +out: + ubi_put_device(ubi); + return count; +} + +/* File operations for all UBI debugfs files */ +static const struct file_operations dfs_fops = { + .read = dfs_file_read, + .write = dfs_file_write, + .open = simple_open, + .llseek = no_llseek, + .owner = THIS_MODULE, +}; + +/** + * ubi_debugfs_init_dev - initialize debugfs for an UBI device. + * @ubi: UBI device description object + * + * This function creates all debugfs files for UBI device @ubi. Returns zero in + * case of success and a negative error code in case of failure. + */ +int ubi_debugfs_init_dev(struct ubi_device *ubi) +{ + int err, n; + unsigned long ubi_num = ubi->ubi_num; + const char *fname; + struct dentry *dent; + struct ubi_debug_info *d = &ubi->dbg; + + if (!IS_ENABLED(CONFIG_DEBUG_FS)) + return 0; + + n = snprintf(d->dfs_dir_name, UBI_DFS_DIR_LEN + 1, UBI_DFS_DIR_NAME, + ubi->ubi_num); + if (n == UBI_DFS_DIR_LEN) { + /* The array size is too small */ + fname = UBI_DFS_DIR_NAME; + dent = ERR_PTR(-EINVAL); + goto out; + } + + fname = d->dfs_dir_name; + dent = debugfs_create_dir(fname, dfs_rootdir); + if (IS_ERR_OR_NULL(dent)) + goto out; + d->dfs_dir = dent; + + fname = "chk_gen"; + dent = debugfs_create_file(fname, S_IWUSR, d->dfs_dir, (void *)ubi_num, + &dfs_fops); + if (IS_ERR_OR_NULL(dent)) + goto out_remove; + d->dfs_chk_gen = dent; + + fname = "chk_io"; + dent = debugfs_create_file(fname, S_IWUSR, d->dfs_dir, (void *)ubi_num, + &dfs_fops); + if (IS_ERR_OR_NULL(dent)) + goto out_remove; + d->dfs_chk_io = dent; + + fname = "tst_disable_bgt"; + dent = debugfs_create_file(fname, S_IWUSR, d->dfs_dir, (void *)ubi_num, + &dfs_fops); + if (IS_ERR_OR_NULL(dent)) + goto out_remove; + d->dfs_disable_bgt = dent; + + fname = "tst_emulate_bitflips"; + dent = debugfs_create_file(fname, S_IWUSR, d->dfs_dir, (void *)ubi_num, + &dfs_fops); + if (IS_ERR_OR_NULL(dent)) + goto out_remove; + d->dfs_emulate_bitflips = dent; + + fname = "tst_emulate_io_failures"; + dent = debugfs_create_file(fname, S_IWUSR, d->dfs_dir, (void *)ubi_num, + &dfs_fops); + if (IS_ERR_OR_NULL(dent)) + goto out_remove; + d->dfs_emulate_io_failures = dent; + + return 0; + +out_remove: + debugfs_remove_recursive(d->dfs_dir); +out: + err = dent ? PTR_ERR(dent) : -ENODEV; + ubi_err("cannot create \"%s\" debugfs file or directory, error %d\n", + fname, err); + return err; +} + +/** + * dbg_debug_exit_dev - free all debugfs files corresponding to device @ubi + * @ubi: UBI device description object + */ +void ubi_debugfs_exit_dev(struct ubi_device *ubi) +{ + if (IS_ENABLED(CONFIG_DEBUG_FS)) + debugfs_remove_recursive(ubi->dbg.dfs_dir); +} diff --git a/drivers/mtd/ubi/debug.h b/drivers/mtd/ubi/debug.h new file mode 100644 index 00000000000..cba89fcd158 --- /dev/null +++ b/drivers/mtd/ubi/debug.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) International Business Machines Corp., 2006 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Artem Bityutskiy (Битюцкий Артём) + */ + +#ifndef __UBI_DEBUG_H__ +#define __UBI_DEBUG_H__ + +void ubi_dump_flash(struct ubi_device *ubi, int pnum, int offset, int len); +void ubi_dump_ec_hdr(const struct ubi_ec_hdr *ec_hdr); +void ubi_dump_vid_hdr(const struct ubi_vid_hdr *vid_hdr); + +#include <linux/random.h> + +#define ubi_assert(expr) do { \ + if (unlikely(!(expr))) { \ + pr_crit("UBI assert failed in %s at %u (pid %d)\n", \ + __func__, __LINE__, current->pid); \ + dump_stack(); \ + } \ +} while (0) + +#define ubi_dbg_print_hex_dump(l, ps, pt, r, g, b, len, a) \ + print_hex_dump(l, ps, pt, r, g, b, len, a) + +#define ubi_dbg_msg(type, fmt, ...) \ + pr_debug("UBI DBG " type " (pid %d): " fmt "\n", current->pid, \ + ##__VA_ARGS__) + +/* General debugging messages */ +#define dbg_gen(fmt, ...) ubi_dbg_msg("gen", fmt, ##__VA_ARGS__) +/* Messages from the eraseblock association sub-system */ +#define dbg_eba(fmt, ...) ubi_dbg_msg("eba", fmt, ##__VA_ARGS__) +/* Messages from the wear-leveling sub-system */ +#define dbg_wl(fmt, ...) ubi_dbg_msg("wl", fmt, ##__VA_ARGS__) +/* Messages from the input/output sub-system */ +#define dbg_io(fmt, ...) ubi_dbg_msg("io", fmt, ##__VA_ARGS__) +/* Initialization and build messages */ +#define dbg_bld(fmt, ...) ubi_dbg_msg("bld", fmt, ##__VA_ARGS__) + +void ubi_dump_vol_info(const struct ubi_volume *vol); +void ubi_dump_vtbl_record(const struct ubi_vtbl_record *r, int idx); +void ubi_dump_av(const struct ubi_ainf_volume *av); +void ubi_dump_aeb(const struct ubi_ainf_peb *aeb, int type); +void ubi_dump_mkvol_req(const struct ubi_mkvol_req *req); +int ubi_self_check_all_ff(struct ubi_device *ubi, int pnum, int offset, + int len); +int ubi_debugfs_init(void); +void ubi_debugfs_exit(void); +int ubi_debugfs_init_dev(struct ubi_device *ubi); +void ubi_debugfs_exit_dev(struct ubi_device *ubi); + +/** + * ubi_dbg_is_bgt_disabled - if the background thread is disabled. + * @ubi: UBI device description object + * + * Returns non-zero if the UBI background thread is disabled for testing + * purposes. + */ +static inline int ubi_dbg_is_bgt_disabled(const struct ubi_device *ubi) +{ + return ubi->dbg.disable_bgt; +} + +/** + * ubi_dbg_is_bitflip - if it is time to emulate a bit-flip. + * @ubi: UBI device description object + * + * Returns non-zero if a bit-flip should be emulated, otherwise returns zero. + */ +static inline int ubi_dbg_is_bitflip(const struct ubi_device *ubi) +{ + if (ubi->dbg.emulate_bitflips) + return !(prandom_u32() % 200); + return 0; +} + +/** + * ubi_dbg_is_write_failure - if it is time to emulate a write failure. + * @ubi: UBI device description object + * + * Returns non-zero if a write failure should be emulated, otherwise returns + * zero. + */ +static inline int ubi_dbg_is_write_failure(const struct ubi_device *ubi) +{ + if (ubi->dbg.emulate_io_failures) + return !(prandom_u32() % 500); + return 0; +} + +/** + * ubi_dbg_is_erase_failure - if its time to emulate an erase failure. + * @ubi: UBI device description object + * + * Returns non-zero if an erase failure should be emulated, otherwise returns + * zero. + */ +static inline int ubi_dbg_is_erase_failure(const struct ubi_device *ubi) +{ + if (ubi->dbg.emulate_io_failures) + return !(prandom_u32() % 400); + return 0; +} + +static inline int ubi_dbg_chk_io(const struct ubi_device *ubi) +{ + return ubi->dbg.chk_io; +} + +static inline int ubi_dbg_chk_gen(const struct ubi_device *ubi) +{ + return ubi->dbg.chk_gen; +} +#endif /* !__UBI_DEBUG_H__ */ diff --git a/drivers/mtd/ubi/eba.c b/drivers/mtd/ubi/eba.c new file mode 100644 index 00000000000..0e11671dadc --- /dev/null +++ b/drivers/mtd/ubi/eba.c @@ -0,0 +1,1406 @@ +/* + * Copyright (c) International Business Machines Corp., 2006 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Artem Bityutskiy (Битюцкий Артём) + */ + +/* + * The UBI Eraseblock Association (EBA) sub-system. + * + * This sub-system is responsible for I/O to/from logical eraseblock. + * + * Although in this implementation the EBA table is fully kept and managed in + * RAM, which assumes poor scalability, it might be (partially) maintained on + * flash in future implementations. + * + * The EBA sub-system implements per-logical eraseblock locking. Before + * accessing a logical eraseblock it is locked for reading or writing. The + * per-logical eraseblock locking is implemented by means of the lock tree. The + * lock tree is an RB-tree which refers all the currently locked logical + * eraseblocks. The lock tree elements are &struct ubi_ltree_entry objects. + * They are indexed by (@vol_id, @lnum) pairs. + * + * EBA also maintains the global sequence counter which is incremented each + * time a logical eraseblock is mapped to a physical eraseblock and it is + * stored in the volume identifier header. This means that each VID header has + * a unique sequence number. The sequence number is only increased an we assume + * 64 bits is enough to never overflow. + */ + +#include <linux/slab.h> +#include <linux/crc32.h> +#include <linux/err.h> +#include "ubi.h" + +/* Number of physical eraseblocks reserved for atomic LEB change operation */ +#define EBA_RESERVED_PEBS 1 + +/** + * next_sqnum - get next sequence number. + * @ubi: UBI device description object + * + * This function returns next sequence number to use, which is just the current + * global sequence counter value. It also increases the global sequence + * counter. + */ +unsigned long long ubi_next_sqnum(struct ubi_device *ubi) +{ + unsigned long long sqnum; + + spin_lock(&ubi->ltree_lock); + sqnum = ubi->global_sqnum++; + spin_unlock(&ubi->ltree_lock); + + return sqnum; +} + +/** + * ubi_get_compat - get compatibility flags of a volume. + * @ubi: UBI device description object + * @vol_id: volume ID + * + * This function returns compatibility flags for an internal volume. User + * volumes have no compatibility flags, so %0 is returned. + */ +static int ubi_get_compat(const struct ubi_device *ubi, int vol_id) +{ + if (vol_id == UBI_LAYOUT_VOLUME_ID) + return UBI_LAYOUT_VOLUME_COMPAT; + return 0; +} + +/** + * ltree_lookup - look up the lock tree. + * @ubi: UBI device description object + * @vol_id: volume ID + * @lnum: logical eraseblock number + * + * This function returns a pointer to the corresponding &struct ubi_ltree_entry + * object if the logical eraseblock is locked and %NULL if it is not. + * @ubi->ltree_lock has to be locked. + */ +static struct ubi_ltree_entry *ltree_lookup(struct ubi_device *ubi, int vol_id, + int lnum) +{ + struct rb_node *p; + + p = ubi->ltree.rb_node; + while (p) { + struct ubi_ltree_entry *le; + + le = rb_entry(p, struct ubi_ltree_entry, rb); + + if (vol_id < le->vol_id) + p = p->rb_left; + else if (vol_id > le->vol_id) + p = p->rb_right; + else { + if (lnum < le->lnum) + p = p->rb_left; + else if (lnum > le->lnum) + p = p->rb_right; + else + return le; + } + } + + return NULL; +} + +/** + * ltree_add_entry - add new entry to the lock tree. + * @ubi: UBI device description object + * @vol_id: volume ID + * @lnum: logical eraseblock number + * + * This function adds new entry for logical eraseblock (@vol_id, @lnum) to the + * lock tree. If such entry is already there, its usage counter is increased. + * Returns pointer to the lock tree entry or %-ENOMEM if memory allocation + * failed. + */ +static struct ubi_ltree_entry *ltree_add_entry(struct ubi_device *ubi, + int vol_id, int lnum) +{ + struct ubi_ltree_entry *le, *le1, *le_free; + + le = kmalloc(sizeof(struct ubi_ltree_entry), GFP_NOFS); + if (!le) + return ERR_PTR(-ENOMEM); + + le->users = 0; + init_rwsem(&le->mutex); + le->vol_id = vol_id; + le->lnum = lnum; + + spin_lock(&ubi->ltree_lock); + le1 = ltree_lookup(ubi, vol_id, lnum); + + if (le1) { + /* + * This logical eraseblock is already locked. The newly + * allocated lock entry is not needed. + */ + le_free = le; + le = le1; + } else { + struct rb_node **p, *parent = NULL; + + /* + * No lock entry, add the newly allocated one to the + * @ubi->ltree RB-tree. + */ + le_free = NULL; + + p = &ubi->ltree.rb_node; + while (*p) { + parent = *p; + le1 = rb_entry(parent, struct ubi_ltree_entry, rb); + + if (vol_id < le1->vol_id) + p = &(*p)->rb_left; + else if (vol_id > le1->vol_id) + p = &(*p)->rb_right; + else { + ubi_assert(lnum != le1->lnum); + if (lnum < le1->lnum) + p = &(*p)->rb_left; + else + p = &(*p)->rb_right; + } + } + + rb_link_node(&le->rb, parent, p); + rb_insert_color(&le->rb, &ubi->ltree); + } + le->users += 1; + spin_unlock(&ubi->ltree_lock); + + kfree(le_free); + return le; +} + +/** + * leb_read_lock - lock logical eraseblock for reading. + * @ubi: UBI device description object + * @vol_id: volume ID + * @lnum: logical eraseblock number + * + * This function locks a logical eraseblock for reading. Returns zero in case + * of success and a negative error code in case of failure. + */ +static int leb_read_lock(struct ubi_device *ubi, int vol_id, int lnum) +{ + struct ubi_ltree_entry *le; + + le = ltree_add_entry(ubi, vol_id, lnum); + if (IS_ERR(le)) + return PTR_ERR(le); + down_read(&le->mutex); + return 0; +} + +/** + * leb_read_unlock - unlock logical eraseblock. + * @ubi: UBI device description object + * @vol_id: volume ID + * @lnum: logical eraseblock number + */ +static void leb_read_unlock(struct ubi_device *ubi, int vol_id, int lnum) +{ + struct ubi_ltree_entry *le; + + spin_lock(&ubi->ltree_lock); + le = ltree_lookup(ubi, vol_id, lnum); + le->users -= 1; + ubi_assert(le->users >= 0); + up_read(&le->mutex); + if (le->users == 0) { + rb_erase(&le->rb, &ubi->ltree); + kfree(le); + } + spin_unlock(&ubi->ltree_lock); +} + +/** + * leb_write_lock - lock logical eraseblock for writing. + * @ubi: UBI device description object + * @vol_id: volume ID + * @lnum: logical eraseblock number + * + * This function locks a logical eraseblock for writing. Returns zero in case + * of success and a negative error code in case of failure. + */ +static int leb_write_lock(struct ubi_device *ubi, int vol_id, int lnum) +{ + struct ubi_ltree_entry *le; + + le = ltree_add_entry(ubi, vol_id, lnum); + if (IS_ERR(le)) + return PTR_ERR(le); + down_write(&le->mutex); + return 0; +} + +/** + * leb_write_lock - lock logical eraseblock for writing. + * @ubi: UBI device description object + * @vol_id: volume ID + * @lnum: logical eraseblock number + * + * This function locks a logical eraseblock for writing if there is no + * contention and does nothing if there is contention. Returns %0 in case of + * success, %1 in case of contention, and and a negative error code in case of + * failure. + */ +static int leb_write_trylock(struct ubi_device *ubi, int vol_id, int lnum) +{ + struct ubi_ltree_entry *le; + + le = ltree_add_entry(ubi, vol_id, lnum); + if (IS_ERR(le)) + return PTR_ERR(le); + if (down_write_trylock(&le->mutex)) + return 0; + + /* Contention, cancel */ + spin_lock(&ubi->ltree_lock); + le->users -= 1; + ubi_assert(le->users >= 0); + if (le->users == 0) { + rb_erase(&le->rb, &ubi->ltree); + kfree(le); + } + spin_unlock(&ubi->ltree_lock); + + return 1; +} + +/** + * leb_write_unlock - unlock logical eraseblock. + * @ubi: UBI device description object + * @vol_id: volume ID + * @lnum: logical eraseblock number + */ +static void leb_write_unlock(struct ubi_device *ubi, int vol_id, int lnum) +{ + struct ubi_ltree_entry *le; + + spin_lock(&ubi->ltree_lock); + le = ltree_lookup(ubi, vol_id, lnum); + le->users -= 1; + ubi_assert(le->users >= 0); + up_write(&le->mutex); + if (le->users == 0) { + rb_erase(&le->rb, &ubi->ltree); + kfree(le); + } + spin_unlock(&ubi->ltree_lock); +} + +/** + * ubi_eba_unmap_leb - un-map logical eraseblock. + * @ubi: UBI device description object + * @vol: volume description object + * @lnum: logical eraseblock number + * + * This function un-maps logical eraseblock @lnum and schedules corresponding + * physical eraseblock for erasure. Returns zero in case of success and a + * negative error code in case of failure. + */ +int ubi_eba_unmap_leb(struct ubi_device *ubi, struct ubi_volume *vol, + int lnum) +{ + int err, pnum, vol_id = vol->vol_id; + + if (ubi->ro_mode) + return -EROFS; + + err = leb_write_lock(ubi, vol_id, lnum); + if (err) + return err; + + pnum = vol->eba_tbl[lnum]; + if (pnum < 0) + /* This logical eraseblock is already unmapped */ + goto out_unlock; + + dbg_eba("erase LEB %d:%d, PEB %d", vol_id, lnum, pnum); + + down_read(&ubi->fm_sem); + vol->eba_tbl[lnum] = UBI_LEB_UNMAPPED; + up_read(&ubi->fm_sem); + err = ubi_wl_put_peb(ubi, vol_id, lnum, pnum, 0); + +out_unlock: + leb_write_unlock(ubi, vol_id, lnum); + return err; +} + +/** + * ubi_eba_read_leb - read data. + * @ubi: UBI device description object + * @vol: volume description object + * @lnum: logical eraseblock number + * @buf: buffer to store the read data + * @offset: offset from where to read + * @len: how many bytes to read + * @check: data CRC check flag + * + * If the logical eraseblock @lnum is unmapped, @buf is filled with 0xFF + * bytes. The @check flag only makes sense for static volumes and forces + * eraseblock data CRC checking. + * + * In case of success this function returns zero. In case of a static volume, + * if data CRC mismatches - %-EBADMSG is returned. %-EBADMSG may also be + * returned for any volume type if an ECC error was detected by the MTD device + * driver. Other negative error cored may be returned in case of other errors. + */ +int ubi_eba_read_leb(struct ubi_device *ubi, struct ubi_volume *vol, int lnum, + void *buf, int offset, int len, int check) +{ + int err, pnum, scrub = 0, vol_id = vol->vol_id; + struct ubi_vid_hdr *vid_hdr; + uint32_t uninitialized_var(crc); + + err = leb_read_lock(ubi, vol_id, lnum); + if (err) + return err; + + pnum = vol->eba_tbl[lnum]; + if (pnum < 0) { + /* + * The logical eraseblock is not mapped, fill the whole buffer + * with 0xFF bytes. The exception is static volumes for which + * it is an error to read unmapped logical eraseblocks. + */ + dbg_eba("read %d bytes from offset %d of LEB %d:%d (unmapped)", + len, offset, vol_id, lnum); + leb_read_unlock(ubi, vol_id, lnum); + ubi_assert(vol->vol_type != UBI_STATIC_VOLUME); + memset(buf, 0xFF, len); + return 0; + } + + dbg_eba("read %d bytes from offset %d of LEB %d:%d, PEB %d", + len, offset, vol_id, lnum, pnum); + + if (vol->vol_type == UBI_DYNAMIC_VOLUME) + check = 0; + +retry: + if (check) { + vid_hdr = ubi_zalloc_vid_hdr(ubi, GFP_NOFS); + if (!vid_hdr) { + err = -ENOMEM; + goto out_unlock; + } + + err = ubi_io_read_vid_hdr(ubi, pnum, vid_hdr, 1); + if (err && err != UBI_IO_BITFLIPS) { + if (err > 0) { + /* + * The header is either absent or corrupted. + * The former case means there is a bug - + * switch to read-only mode just in case. + * The latter case means a real corruption - we + * may try to recover data. FIXME: but this is + * not implemented. + */ + if (err == UBI_IO_BAD_HDR_EBADMSG || + err == UBI_IO_BAD_HDR) { + ubi_warn("corrupted VID header at PEB %d, LEB %d:%d", + pnum, vol_id, lnum); + err = -EBADMSG; + } else + ubi_ro_mode(ubi); + } + goto out_free; + } else if (err == UBI_IO_BITFLIPS) + scrub = 1; + + ubi_assert(lnum < be32_to_cpu(vid_hdr->used_ebs)); + ubi_assert(len == be32_to_cpu(vid_hdr->data_size)); + + crc = be32_to_cpu(vid_hdr->data_crc); + ubi_free_vid_hdr(ubi, vid_hdr); + } + + err = ubi_io_read_data(ubi, buf, pnum, offset, len); + if (err) { + if (err == UBI_IO_BITFLIPS) { + scrub = 1; + err = 0; + } else if (mtd_is_eccerr(err)) { + if (vol->vol_type == UBI_DYNAMIC_VOLUME) + goto out_unlock; + scrub = 1; + if (!check) { + ubi_msg("force data checking"); + check = 1; + goto retry; + } + } else + goto out_unlock; + } + + if (check) { + uint32_t crc1 = crc32(UBI_CRC32_INIT, buf, len); + if (crc1 != crc) { + ubi_warn("CRC error: calculated %#08x, must be %#08x", + crc1, crc); + err = -EBADMSG; + goto out_unlock; + } + } + + if (scrub) + err = ubi_wl_scrub_peb(ubi, pnum); + + leb_read_unlock(ubi, vol_id, lnum); + return err; + +out_free: + ubi_free_vid_hdr(ubi, vid_hdr); +out_unlock: + leb_read_unlock(ubi, vol_id, lnum); + return err; +} + +/** + * recover_peb - recover from write failure. + * @ubi: UBI device description object + * @pnum: the physical eraseblock to recover + * @vol_id: volume ID + * @lnum: logical eraseblock number + * @buf: data which was not written because of the write failure + * @offset: offset of the failed write + * @len: how many bytes should have been written + * + * This function is called in case of a write failure and moves all good data + * from the potentially bad physical eraseblock to a good physical eraseblock. + * This function also writes the data which was not written due to the failure. + * Returns new physical eraseblock number in case of success, and a negative + * error code in case of failure. + */ +static int recover_peb(struct ubi_device *ubi, int pnum, int vol_id, int lnum, + const void *buf, int offset, int len) +{ + int err, idx = vol_id2idx(ubi, vol_id), new_pnum, data_size, tries = 0; + struct ubi_volume *vol = ubi->volumes[idx]; + struct ubi_vid_hdr *vid_hdr; + + vid_hdr = ubi_zalloc_vid_hdr(ubi, GFP_NOFS); + if (!vid_hdr) + return -ENOMEM; + +retry: + new_pnum = ubi_wl_get_peb(ubi); + if (new_pnum < 0) { + ubi_free_vid_hdr(ubi, vid_hdr); + return new_pnum; + } + + ubi_msg("recover PEB %d, move data to PEB %d", pnum, new_pnum); + + err = ubi_io_read_vid_hdr(ubi, pnum, vid_hdr, 1); + if (err && err != UBI_IO_BITFLIPS) { + if (err > 0) + err = -EIO; + goto out_put; + } + + vid_hdr->sqnum = cpu_to_be64(ubi_next_sqnum(ubi)); + err = ubi_io_write_vid_hdr(ubi, new_pnum, vid_hdr); + if (err) + goto write_error; + + data_size = offset + len; + mutex_lock(&ubi->buf_mutex); + memset(ubi->peb_buf + offset, 0xFF, len); + + /* Read everything before the area where the write failure happened */ + if (offset > 0) { + err = ubi_io_read_data(ubi, ubi->peb_buf, pnum, 0, offset); + if (err && err != UBI_IO_BITFLIPS) + goto out_unlock; + } + + memcpy(ubi->peb_buf + offset, buf, len); + + err = ubi_io_write_data(ubi, ubi->peb_buf, new_pnum, 0, data_size); + if (err) { + mutex_unlock(&ubi->buf_mutex); + goto write_error; + } + + mutex_unlock(&ubi->buf_mutex); + ubi_free_vid_hdr(ubi, vid_hdr); + + down_read(&ubi->fm_sem); + vol->eba_tbl[lnum] = new_pnum; + up_read(&ubi->fm_sem); + ubi_wl_put_peb(ubi, vol_id, lnum, pnum, 1); + + ubi_msg("data was successfully recovered"); + return 0; + +out_unlock: + mutex_unlock(&ubi->buf_mutex); +out_put: + ubi_wl_put_peb(ubi, vol_id, lnum, new_pnum, 1); + ubi_free_vid_hdr(ubi, vid_hdr); + return err; + +write_error: + /* + * Bad luck? This physical eraseblock is bad too? Crud. Let's try to + * get another one. + */ + ubi_warn("failed to write to PEB %d", new_pnum); + ubi_wl_put_peb(ubi, vol_id, lnum, new_pnum, 1); + if (++tries > UBI_IO_RETRIES) { + ubi_free_vid_hdr(ubi, vid_hdr); + return err; + } + ubi_msg("try again"); + goto retry; +} + +/** + * ubi_eba_write_leb - write data to dynamic volume. + * @ubi: UBI device description object + * @vol: volume description object + * @lnum: logical eraseblock number + * @buf: the data to write + * @offset: offset within the logical eraseblock where to write + * @len: how many bytes to write + * + * This function writes data to logical eraseblock @lnum of a dynamic volume + * @vol. Returns zero in case of success and a negative error code in case + * of failure. In case of error, it is possible that something was still + * written to the flash media, but may be some garbage. + */ +int ubi_eba_write_leb(struct ubi_device *ubi, struct ubi_volume *vol, int lnum, + const void *buf, int offset, int len) +{ + int err, pnum, tries = 0, vol_id = vol->vol_id; + struct ubi_vid_hdr *vid_hdr; + + if (ubi->ro_mode) + return -EROFS; + + err = leb_write_lock(ubi, vol_id, lnum); + if (err) + return err; + + pnum = vol->eba_tbl[lnum]; + if (pnum >= 0) { + dbg_eba("write %d bytes at offset %d of LEB %d:%d, PEB %d", + len, offset, vol_id, lnum, pnum); + + err = ubi_io_write_data(ubi, buf, pnum, offset, len); + if (err) { + ubi_warn("failed to write data to PEB %d", pnum); + if (err == -EIO && ubi->bad_allowed) + err = recover_peb(ubi, pnum, vol_id, lnum, buf, + offset, len); + if (err) + ubi_ro_mode(ubi); + } + leb_write_unlock(ubi, vol_id, lnum); + return err; + } + + /* + * The logical eraseblock is not mapped. We have to get a free physical + * eraseblock and write the volume identifier header there first. + */ + vid_hdr = ubi_zalloc_vid_hdr(ubi, GFP_NOFS); + if (!vid_hdr) { + leb_write_unlock(ubi, vol_id, lnum); + return -ENOMEM; + } + + vid_hdr->vol_type = UBI_VID_DYNAMIC; + vid_hdr->sqnum = cpu_to_be64(ubi_next_sqnum(ubi)); + vid_hdr->vol_id = cpu_to_be32(vol_id); + vid_hdr->lnum = cpu_to_be32(lnum); + vid_hdr->compat = ubi_get_compat(ubi, vol_id); + vid_hdr->data_pad = cpu_to_be32(vol->data_pad); + +retry: + pnum = ubi_wl_get_peb(ubi); + if (pnum < 0) { + ubi_free_vid_hdr(ubi, vid_hdr); + leb_write_unlock(ubi, vol_id, lnum); + return pnum; + } + + dbg_eba("write VID hdr and %d bytes at offset %d of LEB %d:%d, PEB %d", + len, offset, vol_id, lnum, pnum); + + err = ubi_io_write_vid_hdr(ubi, pnum, vid_hdr); + if (err) { + ubi_warn("failed to write VID header to LEB %d:%d, PEB %d", + vol_id, lnum, pnum); + goto write_error; + } + + if (len) { + err = ubi_io_write_data(ubi, buf, pnum, offset, len); + if (err) { + ubi_warn("failed to write %d bytes at offset %d of LEB %d:%d, PEB %d", + len, offset, vol_id, lnum, pnum); + goto write_error; + } + } + + down_read(&ubi->fm_sem); + vol->eba_tbl[lnum] = pnum; + up_read(&ubi->fm_sem); + + leb_write_unlock(ubi, vol_id, lnum); + ubi_free_vid_hdr(ubi, vid_hdr); + return 0; + +write_error: + if (err != -EIO || !ubi->bad_allowed) { + ubi_ro_mode(ubi); + leb_write_unlock(ubi, vol_id, lnum); + ubi_free_vid_hdr(ubi, vid_hdr); + return err; + } + + /* + * Fortunately, this is the first write operation to this physical + * eraseblock, so just put it and request a new one. We assume that if + * this physical eraseblock went bad, the erase code will handle that. + */ + err = ubi_wl_put_peb(ubi, vol_id, lnum, pnum, 1); + if (err || ++tries > UBI_IO_RETRIES) { + ubi_ro_mode(ubi); + leb_write_unlock(ubi, vol_id, lnum); + ubi_free_vid_hdr(ubi, vid_hdr); + return err; + } + + vid_hdr->sqnum = cpu_to_be64(ubi_next_sqnum(ubi)); + ubi_msg("try another PEB"); + goto retry; +} + +/** + * ubi_eba_write_leb_st - write data to static volume. + * @ubi: UBI device description object + * @vol: volume description object + * @lnum: logical eraseblock number + * @buf: data to write + * @len: how many bytes to write + * @used_ebs: how many logical eraseblocks will this volume contain + * + * This function writes data to logical eraseblock @lnum of static volume + * @vol. The @used_ebs argument should contain total number of logical + * eraseblock in this static volume. + * + * When writing to the last logical eraseblock, the @len argument doesn't have + * to be aligned to the minimal I/O unit size. Instead, it has to be equivalent + * to the real data size, although the @buf buffer has to contain the + * alignment. In all other cases, @len has to be aligned. + * + * It is prohibited to write more than once to logical eraseblocks of static + * volumes. This function returns zero in case of success and a negative error + * code in case of failure. + */ +int ubi_eba_write_leb_st(struct ubi_device *ubi, struct ubi_volume *vol, + int lnum, const void *buf, int len, int used_ebs) +{ + int err, pnum, tries = 0, data_size = len, vol_id = vol->vol_id; + struct ubi_vid_hdr *vid_hdr; + uint32_t crc; + + if (ubi->ro_mode) + return -EROFS; + + if (lnum == used_ebs - 1) + /* If this is the last LEB @len may be unaligned */ + len = ALIGN(data_size, ubi->min_io_size); + else + ubi_assert(!(len & (ubi->min_io_size - 1))); + + vid_hdr = ubi_zalloc_vid_hdr(ubi, GFP_NOFS); + if (!vid_hdr) + return -ENOMEM; + + err = leb_write_lock(ubi, vol_id, lnum); + if (err) { + ubi_free_vid_hdr(ubi, vid_hdr); + return err; + } + + vid_hdr->sqnum = cpu_to_be64(ubi_next_sqnum(ubi)); + vid_hdr->vol_id = cpu_to_be32(vol_id); + vid_hdr->lnum = cpu_to_be32(lnum); + vid_hdr->compat = ubi_get_compat(ubi, vol_id); + vid_hdr->data_pad = cpu_to_be32(vol->data_pad); + + crc = crc32(UBI_CRC32_INIT, buf, data_size); + vid_hdr->vol_type = UBI_VID_STATIC; + vid_hdr->data_size = cpu_to_be32(data_size); + vid_hdr->used_ebs = cpu_to_be32(used_ebs); + vid_hdr->data_crc = cpu_to_be32(crc); + +retry: + pnum = ubi_wl_get_peb(ubi); + if (pnum < 0) { + ubi_free_vid_hdr(ubi, vid_hdr); + leb_write_unlock(ubi, vol_id, lnum); + return pnum; + } + + dbg_eba("write VID hdr and %d bytes at LEB %d:%d, PEB %d, used_ebs %d", + len, vol_id, lnum, pnum, used_ebs); + + err = ubi_io_write_vid_hdr(ubi, pnum, vid_hdr); + if (err) { + ubi_warn("failed to write VID header to LEB %d:%d, PEB %d", + vol_id, lnum, pnum); + goto write_error; + } + + err = ubi_io_write_data(ubi, buf, pnum, 0, len); + if (err) { + ubi_warn("failed to write %d bytes of data to PEB %d", + len, pnum); + goto write_error; + } + + ubi_assert(vol->eba_tbl[lnum] < 0); + down_read(&ubi->fm_sem); + vol->eba_tbl[lnum] = pnum; + up_read(&ubi->fm_sem); + + leb_write_unlock(ubi, vol_id, lnum); + ubi_free_vid_hdr(ubi, vid_hdr); + return 0; + +write_error: + if (err != -EIO || !ubi->bad_allowed) { + /* + * This flash device does not admit of bad eraseblocks or + * something nasty and unexpected happened. Switch to read-only + * mode just in case. + */ + ubi_ro_mode(ubi); + leb_write_unlock(ubi, vol_id, lnum); + ubi_free_vid_hdr(ubi, vid_hdr); + return err; + } + + err = ubi_wl_put_peb(ubi, vol_id, lnum, pnum, 1); + if (err || ++tries > UBI_IO_RETRIES) { + ubi_ro_mode(ubi); + leb_write_unlock(ubi, vol_id, lnum); + ubi_free_vid_hdr(ubi, vid_hdr); + return err; + } + + vid_hdr->sqnum = cpu_to_be64(ubi_next_sqnum(ubi)); + ubi_msg("try another PEB"); + goto retry; +} + +/* + * ubi_eba_atomic_leb_change - change logical eraseblock atomically. + * @ubi: UBI device description object + * @vol: volume description object + * @lnum: logical eraseblock number + * @buf: data to write + * @len: how many bytes to write + * + * This function changes the contents of a logical eraseblock atomically. @buf + * has to contain new logical eraseblock data, and @len - the length of the + * data, which has to be aligned. This function guarantees that in case of an + * unclean reboot the old contents is preserved. Returns zero in case of + * success and a negative error code in case of failure. + * + * UBI reserves one LEB for the "atomic LEB change" operation, so only one + * LEB change may be done at a time. This is ensured by @ubi->alc_mutex. + */ +int ubi_eba_atomic_leb_change(struct ubi_device *ubi, struct ubi_volume *vol, + int lnum, const void *buf, int len) +{ + int err, pnum, tries = 0, vol_id = vol->vol_id; + struct ubi_vid_hdr *vid_hdr; + uint32_t crc; + + if (ubi->ro_mode) + return -EROFS; + + if (len == 0) { + /* + * Special case when data length is zero. In this case the LEB + * has to be unmapped and mapped somewhere else. + */ + err = ubi_eba_unmap_leb(ubi, vol, lnum); + if (err) + return err; + return ubi_eba_write_leb(ubi, vol, lnum, NULL, 0, 0); + } + + vid_hdr = ubi_zalloc_vid_hdr(ubi, GFP_NOFS); + if (!vid_hdr) + return -ENOMEM; + + mutex_lock(&ubi->alc_mutex); + err = leb_write_lock(ubi, vol_id, lnum); + if (err) + goto out_mutex; + + vid_hdr->sqnum = cpu_to_be64(ubi_next_sqnum(ubi)); + vid_hdr->vol_id = cpu_to_be32(vol_id); + vid_hdr->lnum = cpu_to_be32(lnum); + vid_hdr->compat = ubi_get_compat(ubi, vol_id); + vid_hdr->data_pad = cpu_to_be32(vol->data_pad); + + crc = crc32(UBI_CRC32_INIT, buf, len); + vid_hdr->vol_type = UBI_VID_DYNAMIC; + vid_hdr->data_size = cpu_to_be32(len); + vid_hdr->copy_flag = 1; + vid_hdr->data_crc = cpu_to_be32(crc); + +retry: + pnum = ubi_wl_get_peb(ubi); + if (pnum < 0) { + err = pnum; + goto out_leb_unlock; + } + + dbg_eba("change LEB %d:%d, PEB %d, write VID hdr to PEB %d", + vol_id, lnum, vol->eba_tbl[lnum], pnum); + + err = ubi_io_write_vid_hdr(ubi, pnum, vid_hdr); + if (err) { + ubi_warn("failed to write VID header to LEB %d:%d, PEB %d", + vol_id, lnum, pnum); + goto write_error; + } + + err = ubi_io_write_data(ubi, buf, pnum, 0, len); + if (err) { + ubi_warn("failed to write %d bytes of data to PEB %d", + len, pnum); + goto write_error; + } + + if (vol->eba_tbl[lnum] >= 0) { + err = ubi_wl_put_peb(ubi, vol_id, lnum, vol->eba_tbl[lnum], 0); + if (err) + goto out_leb_unlock; + } + + down_read(&ubi->fm_sem); + vol->eba_tbl[lnum] = pnum; + up_read(&ubi->fm_sem); + +out_leb_unlock: + leb_write_unlock(ubi, vol_id, lnum); +out_mutex: + mutex_unlock(&ubi->alc_mutex); + ubi_free_vid_hdr(ubi, vid_hdr); + return err; + +write_error: + if (err != -EIO || !ubi->bad_allowed) { + /* + * This flash device does not admit of bad eraseblocks or + * something nasty and unexpected happened. Switch to read-only + * mode just in case. + */ + ubi_ro_mode(ubi); + goto out_leb_unlock; + } + + err = ubi_wl_put_peb(ubi, vol_id, lnum, pnum, 1); + if (err || ++tries > UBI_IO_RETRIES) { + ubi_ro_mode(ubi); + goto out_leb_unlock; + } + + vid_hdr->sqnum = cpu_to_be64(ubi_next_sqnum(ubi)); + ubi_msg("try another PEB"); + goto retry; +} + +/** + * is_error_sane - check whether a read error is sane. + * @err: code of the error happened during reading + * + * This is a helper function for 'ubi_eba_copy_leb()' which is called when we + * cannot read data from the target PEB (an error @err happened). If the error + * code is sane, then we treat this error as non-fatal. Otherwise the error is + * fatal and UBI will be switched to R/O mode later. + * + * The idea is that we try not to switch to R/O mode if the read error is + * something which suggests there was a real read problem. E.g., %-EIO. Or a + * memory allocation failed (-%ENOMEM). Otherwise, it is safer to switch to R/O + * mode, simply because we do not know what happened at the MTD level, and we + * cannot handle this. E.g., the underlying driver may have become crazy, and + * it is safer to switch to R/O mode to preserve the data. + * + * And bear in mind, this is about reading from the target PEB, i.e. the PEB + * which we have just written. + */ +static int is_error_sane(int err) +{ + if (err == -EIO || err == -ENOMEM || err == UBI_IO_BAD_HDR || + err == UBI_IO_BAD_HDR_EBADMSG || err == -ETIMEDOUT) + return 0; + return 1; +} + +/** + * ubi_eba_copy_leb - copy logical eraseblock. + * @ubi: UBI device description object + * @from: physical eraseblock number from where to copy + * @to: physical eraseblock number where to copy + * @vid_hdr: VID header of the @from physical eraseblock + * + * This function copies logical eraseblock from physical eraseblock @from to + * physical eraseblock @to. The @vid_hdr buffer may be changed by this + * function. Returns: + * o %0 in case of success; + * o %MOVE_CANCEL_RACE, %MOVE_TARGET_WR_ERR, %MOVE_TARGET_BITFLIPS, etc; + * o a negative error code in case of failure. + */ +int ubi_eba_copy_leb(struct ubi_device *ubi, int from, int to, + struct ubi_vid_hdr *vid_hdr) +{ + int err, vol_id, lnum, data_size, aldata_size, idx; + struct ubi_volume *vol; + uint32_t crc; + + vol_id = be32_to_cpu(vid_hdr->vol_id); + lnum = be32_to_cpu(vid_hdr->lnum); + + dbg_wl("copy LEB %d:%d, PEB %d to PEB %d", vol_id, lnum, from, to); + + if (vid_hdr->vol_type == UBI_VID_STATIC) { + data_size = be32_to_cpu(vid_hdr->data_size); + aldata_size = ALIGN(data_size, ubi->min_io_size); + } else + data_size = aldata_size = + ubi->leb_size - be32_to_cpu(vid_hdr->data_pad); + + idx = vol_id2idx(ubi, vol_id); + spin_lock(&ubi->volumes_lock); + /* + * Note, we may race with volume deletion, which means that the volume + * this logical eraseblock belongs to might be being deleted. Since the + * volume deletion un-maps all the volume's logical eraseblocks, it will + * be locked in 'ubi_wl_put_peb()' and wait for the WL worker to finish. + */ + vol = ubi->volumes[idx]; + spin_unlock(&ubi->volumes_lock); + if (!vol) { + /* No need to do further work, cancel */ + dbg_wl("volume %d is being removed, cancel", vol_id); + return MOVE_CANCEL_RACE; + } + + /* + * We do not want anybody to write to this logical eraseblock while we + * are moving it, so lock it. + * + * Note, we are using non-waiting locking here, because we cannot sleep + * on the LEB, since it may cause deadlocks. Indeed, imagine a task is + * unmapping the LEB which is mapped to the PEB we are going to move + * (@from). This task locks the LEB and goes sleep in the + * 'ubi_wl_put_peb()' function on the @ubi->move_mutex. In turn, we are + * holding @ubi->move_mutex and go sleep on the LEB lock. So, if the + * LEB is already locked, we just do not move it and return + * %MOVE_RETRY. Note, we do not return %MOVE_CANCEL_RACE here because + * we do not know the reasons of the contention - it may be just a + * normal I/O on this LEB, so we want to re-try. + */ + err = leb_write_trylock(ubi, vol_id, lnum); + if (err) { + dbg_wl("contention on LEB %d:%d, cancel", vol_id, lnum); + return MOVE_RETRY; + } + + /* + * The LEB might have been put meanwhile, and the task which put it is + * probably waiting on @ubi->move_mutex. No need to continue the work, + * cancel it. + */ + if (vol->eba_tbl[lnum] != from) { + dbg_wl("LEB %d:%d is no longer mapped to PEB %d, mapped to PEB %d, cancel", + vol_id, lnum, from, vol->eba_tbl[lnum]); + err = MOVE_CANCEL_RACE; + goto out_unlock_leb; + } + + /* + * OK, now the LEB is locked and we can safely start moving it. Since + * this function utilizes the @ubi->peb_buf buffer which is shared + * with some other functions - we lock the buffer by taking the + * @ubi->buf_mutex. + */ + mutex_lock(&ubi->buf_mutex); + dbg_wl("read %d bytes of data", aldata_size); + err = ubi_io_read_data(ubi, ubi->peb_buf, from, 0, aldata_size); + if (err && err != UBI_IO_BITFLIPS) { + ubi_warn("error %d while reading data from PEB %d", + err, from); + err = MOVE_SOURCE_RD_ERR; + goto out_unlock_buf; + } + + /* + * Now we have got to calculate how much data we have to copy. In + * case of a static volume it is fairly easy - the VID header contains + * the data size. In case of a dynamic volume it is more difficult - we + * have to read the contents, cut 0xFF bytes from the end and copy only + * the first part. We must do this to avoid writing 0xFF bytes as it + * may have some side-effects. And not only this. It is important not + * to include those 0xFFs to CRC because later the they may be filled + * by data. + */ + if (vid_hdr->vol_type == UBI_VID_DYNAMIC) + aldata_size = data_size = + ubi_calc_data_len(ubi, ubi->peb_buf, data_size); + + cond_resched(); + crc = crc32(UBI_CRC32_INIT, ubi->peb_buf, data_size); + cond_resched(); + + /* + * It may turn out to be that the whole @from physical eraseblock + * contains only 0xFF bytes. Then we have to only write the VID header + * and do not write any data. This also means we should not set + * @vid_hdr->copy_flag, @vid_hdr->data_size, and @vid_hdr->data_crc. + */ + if (data_size > 0) { + vid_hdr->copy_flag = 1; + vid_hdr->data_size = cpu_to_be32(data_size); + vid_hdr->data_crc = cpu_to_be32(crc); + } + vid_hdr->sqnum = cpu_to_be64(ubi_next_sqnum(ubi)); + + err = ubi_io_write_vid_hdr(ubi, to, vid_hdr); + if (err) { + if (err == -EIO) + err = MOVE_TARGET_WR_ERR; + goto out_unlock_buf; + } + + cond_resched(); + + /* Read the VID header back and check if it was written correctly */ + err = ubi_io_read_vid_hdr(ubi, to, vid_hdr, 1); + if (err) { + if (err != UBI_IO_BITFLIPS) { + ubi_warn("error %d while reading VID header back from PEB %d", + err, to); + if (is_error_sane(err)) + err = MOVE_TARGET_RD_ERR; + } else + err = MOVE_TARGET_BITFLIPS; + goto out_unlock_buf; + } + + if (data_size > 0) { + err = ubi_io_write_data(ubi, ubi->peb_buf, to, 0, aldata_size); + if (err) { + if (err == -EIO) + err = MOVE_TARGET_WR_ERR; + goto out_unlock_buf; + } + + cond_resched(); + + /* + * We've written the data and are going to read it back to make + * sure it was written correctly. + */ + memset(ubi->peb_buf, 0xFF, aldata_size); + err = ubi_io_read_data(ubi, ubi->peb_buf, to, 0, aldata_size); + if (err) { + if (err != UBI_IO_BITFLIPS) { + ubi_warn("error %d while reading data back from PEB %d", + err, to); + if (is_error_sane(err)) + err = MOVE_TARGET_RD_ERR; + } else + err = MOVE_TARGET_BITFLIPS; + goto out_unlock_buf; + } + + cond_resched(); + + if (crc != crc32(UBI_CRC32_INIT, ubi->peb_buf, data_size)) { + ubi_warn("read data back from PEB %d and it is different", + to); + err = -EINVAL; + goto out_unlock_buf; + } + } + + ubi_assert(vol->eba_tbl[lnum] == from); + down_read(&ubi->fm_sem); + vol->eba_tbl[lnum] = to; + up_read(&ubi->fm_sem); + +out_unlock_buf: + mutex_unlock(&ubi->buf_mutex); +out_unlock_leb: + leb_write_unlock(ubi, vol_id, lnum); + return err; +} + +/** + * print_rsvd_warning - warn about not having enough reserved PEBs. + * @ubi: UBI device description object + * + * This is a helper function for 'ubi_eba_init()' which is called when UBI + * cannot reserve enough PEBs for bad block handling. This function makes a + * decision whether we have to print a warning or not. The algorithm is as + * follows: + * o if this is a new UBI image, then just print the warning + * o if this is an UBI image which has already been used for some time, print + * a warning only if we can reserve less than 10% of the expected amount of + * the reserved PEB. + * + * The idea is that when UBI is used, PEBs become bad, and the reserved pool + * of PEBs becomes smaller, which is normal and we do not want to scare users + * with a warning every time they attach the MTD device. This was an issue + * reported by real users. + */ +static void print_rsvd_warning(struct ubi_device *ubi, + struct ubi_attach_info *ai) +{ + /* + * The 1 << 18 (256KiB) number is picked randomly, just a reasonably + * large number to distinguish between newly flashed and used images. + */ + if (ai->max_sqnum > (1 << 18)) { + int min = ubi->beb_rsvd_level / 10; + + if (!min) + min = 1; + if (ubi->beb_rsvd_pebs > min) + return; + } + + ubi_warn("cannot reserve enough PEBs for bad PEB handling, reserved %d, need %d", + ubi->beb_rsvd_pebs, ubi->beb_rsvd_level); + if (ubi->corr_peb_count) + ubi_warn("%d PEBs are corrupted and not used", + ubi->corr_peb_count); +} + +/** + * self_check_eba - run a self check on the EBA table constructed by fastmap. + * @ubi: UBI device description object + * @ai_fastmap: UBI attach info object created by fastmap + * @ai_scan: UBI attach info object created by scanning + * + * Returns < 0 in case of an internal error, 0 otherwise. + * If a bad EBA table entry was found it will be printed out and + * ubi_assert() triggers. + */ +int self_check_eba(struct ubi_device *ubi, struct ubi_attach_info *ai_fastmap, + struct ubi_attach_info *ai_scan) +{ + int i, j, num_volumes, ret = 0; + int **scan_eba, **fm_eba; + struct ubi_ainf_volume *av; + struct ubi_volume *vol; + struct ubi_ainf_peb *aeb; + struct rb_node *rb; + + num_volumes = ubi->vtbl_slots + UBI_INT_VOL_COUNT; + + scan_eba = kmalloc(sizeof(*scan_eba) * num_volumes, GFP_KERNEL); + if (!scan_eba) + return -ENOMEM; + + fm_eba = kmalloc(sizeof(*fm_eba) * num_volumes, GFP_KERNEL); + if (!fm_eba) { + kfree(scan_eba); + return -ENOMEM; + } + + for (i = 0; i < num_volumes; i++) { + vol = ubi->volumes[i]; + if (!vol) + continue; + + scan_eba[i] = kmalloc(vol->reserved_pebs * sizeof(**scan_eba), + GFP_KERNEL); + if (!scan_eba[i]) { + ret = -ENOMEM; + goto out_free; + } + + fm_eba[i] = kmalloc(vol->reserved_pebs * sizeof(**fm_eba), + GFP_KERNEL); + if (!fm_eba[i]) { + ret = -ENOMEM; + goto out_free; + } + + for (j = 0; j < vol->reserved_pebs; j++) + scan_eba[i][j] = fm_eba[i][j] = UBI_LEB_UNMAPPED; + + av = ubi_find_av(ai_scan, idx2vol_id(ubi, i)); + if (!av) + continue; + + ubi_rb_for_each_entry(rb, aeb, &av->root, u.rb) + scan_eba[i][aeb->lnum] = aeb->pnum; + + av = ubi_find_av(ai_fastmap, idx2vol_id(ubi, i)); + if (!av) + continue; + + ubi_rb_for_each_entry(rb, aeb, &av->root, u.rb) + fm_eba[i][aeb->lnum] = aeb->pnum; + + for (j = 0; j < vol->reserved_pebs; j++) { + if (scan_eba[i][j] != fm_eba[i][j]) { + if (scan_eba[i][j] == UBI_LEB_UNMAPPED || + fm_eba[i][j] == UBI_LEB_UNMAPPED) + continue; + + ubi_err("LEB:%i:%i is PEB:%i instead of %i!", + vol->vol_id, i, fm_eba[i][j], + scan_eba[i][j]); + ubi_assert(0); + } + } + } + +out_free: + for (i = 0; i < num_volumes; i++) { + if (!ubi->volumes[i]) + continue; + + kfree(scan_eba[i]); + kfree(fm_eba[i]); + } + + kfree(scan_eba); + kfree(fm_eba); + return ret; +} + +/** + * ubi_eba_init - initialize the EBA sub-system using attaching information. + * @ubi: UBI device description object + * @ai: attaching information + * + * This function returns zero in case of success and a negative error code in + * case of failure. + */ +int ubi_eba_init(struct ubi_device *ubi, struct ubi_attach_info *ai) +{ + int i, j, err, num_volumes; + struct ubi_ainf_volume *av; + struct ubi_volume *vol; + struct ubi_ainf_peb *aeb; + struct rb_node *rb; + + dbg_eba("initialize EBA sub-system"); + + spin_lock_init(&ubi->ltree_lock); + mutex_init(&ubi->alc_mutex); + ubi->ltree = RB_ROOT; + + ubi->global_sqnum = ai->max_sqnum + 1; + num_volumes = ubi->vtbl_slots + UBI_INT_VOL_COUNT; + + for (i = 0; i < num_volumes; i++) { + vol = ubi->volumes[i]; + if (!vol) + continue; + + cond_resched(); + + vol->eba_tbl = kmalloc(vol->reserved_pebs * sizeof(int), + GFP_KERNEL); + if (!vol->eba_tbl) { + err = -ENOMEM; + goto out_free; + } + + for (j = 0; j < vol->reserved_pebs; j++) + vol->eba_tbl[j] = UBI_LEB_UNMAPPED; + + av = ubi_find_av(ai, idx2vol_id(ubi, i)); + if (!av) + continue; + + ubi_rb_for_each_entry(rb, aeb, &av->root, u.rb) { + if (aeb->lnum >= vol->reserved_pebs) + /* + * This may happen in case of an unclean reboot + * during re-size. + */ + ubi_move_aeb_to_list(av, aeb, &ai->erase); + vol->eba_tbl[aeb->lnum] = aeb->pnum; + } + } + + if (ubi->avail_pebs < EBA_RESERVED_PEBS) { + ubi_err("no enough physical eraseblocks (%d, need %d)", + ubi->avail_pebs, EBA_RESERVED_PEBS); + if (ubi->corr_peb_count) + ubi_err("%d PEBs are corrupted and not used", + ubi->corr_peb_count); + err = -ENOSPC; + goto out_free; + } + ubi->avail_pebs -= EBA_RESERVED_PEBS; + ubi->rsvd_pebs += EBA_RESERVED_PEBS; + + if (ubi->bad_allowed) { + ubi_calculate_reserved(ubi); + + if (ubi->avail_pebs < ubi->beb_rsvd_level) { + /* No enough free physical eraseblocks */ + ubi->beb_rsvd_pebs = ubi->avail_pebs; + print_rsvd_warning(ubi, ai); + } else + ubi->beb_rsvd_pebs = ubi->beb_rsvd_level; + + ubi->avail_pebs -= ubi->beb_rsvd_pebs; + ubi->rsvd_pebs += ubi->beb_rsvd_pebs; + } + + dbg_eba("EBA sub-system is initialized"); + return 0; + +out_free: + for (i = 0; i < num_volumes; i++) { + if (!ubi->volumes[i]) + continue; + kfree(ubi->volumes[i]->eba_tbl); + ubi->volumes[i]->eba_tbl = NULL; + } + return err; +} diff --git a/drivers/mtd/ubi/fastmap.c b/drivers/mtd/ubi/fastmap.c new file mode 100644 index 00000000000..0431b46d9fd --- /dev/null +++ b/drivers/mtd/ubi/fastmap.c @@ -0,0 +1,1567 @@ +/* + * Copyright (c) 2012 Linutronix GmbH + * Author: Richard Weinberger <richard@nod.at> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2. + * + * 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/crc32.h> +#include "ubi.h" + +/** + * ubi_calc_fm_size - calculates the fastmap size in bytes for an UBI device. + * @ubi: UBI device description object + */ +size_t ubi_calc_fm_size(struct ubi_device *ubi) +{ + size_t size; + + size = sizeof(struct ubi_fm_hdr) + \ + sizeof(struct ubi_fm_scan_pool) + \ + sizeof(struct ubi_fm_scan_pool) + \ + (ubi->peb_count * sizeof(struct ubi_fm_ec)) + \ + (sizeof(struct ubi_fm_eba) + \ + (ubi->peb_count * sizeof(__be32))) + \ + sizeof(struct ubi_fm_volhdr) * UBI_MAX_VOLUMES; + return roundup(size, ubi->leb_size); +} + + +/** + * new_fm_vhdr - allocate a new volume header for fastmap usage. + * @ubi: UBI device description object + * @vol_id: the VID of the new header + * + * Returns a new struct ubi_vid_hdr on success. + * NULL indicates out of memory. + */ +static struct ubi_vid_hdr *new_fm_vhdr(struct ubi_device *ubi, int vol_id) +{ + struct ubi_vid_hdr *new; + + new = ubi_zalloc_vid_hdr(ubi, GFP_KERNEL); + if (!new) + goto out; + + new->vol_type = UBI_VID_DYNAMIC; + new->vol_id = cpu_to_be32(vol_id); + + /* UBI implementations without fastmap support have to delete the + * fastmap. + */ + new->compat = UBI_COMPAT_DELETE; + +out: + return new; +} + +/** + * add_aeb - create and add a attach erase block to a given list. + * @ai: UBI attach info object + * @list: the target list + * @pnum: PEB number of the new attach erase block + * @ec: erease counter of the new LEB + * @scrub: scrub this PEB after attaching + * + * Returns 0 on success, < 0 indicates an internal error. + */ +static int add_aeb(struct ubi_attach_info *ai, struct list_head *list, + int pnum, int ec, int scrub) +{ + struct ubi_ainf_peb *aeb; + + aeb = kmem_cache_alloc(ai->aeb_slab_cache, GFP_KERNEL); + if (!aeb) + return -ENOMEM; + + aeb->pnum = pnum; + aeb->ec = ec; + aeb->lnum = -1; + aeb->scrub = scrub; + aeb->copy_flag = aeb->sqnum = 0; + + ai->ec_sum += aeb->ec; + ai->ec_count++; + + if (ai->max_ec < aeb->ec) + ai->max_ec = aeb->ec; + + if (ai->min_ec > aeb->ec) + ai->min_ec = aeb->ec; + + list_add_tail(&aeb->u.list, list); + + return 0; +} + +/** + * add_vol - create and add a new volume to ubi_attach_info. + * @ai: ubi_attach_info object + * @vol_id: VID of the new volume + * @used_ebs: number of used EBS + * @data_pad: data padding value of the new volume + * @vol_type: volume type + * @last_eb_bytes: number of bytes in the last LEB + * + * Returns the new struct ubi_ainf_volume on success. + * NULL indicates an error. + */ +static struct ubi_ainf_volume *add_vol(struct ubi_attach_info *ai, int vol_id, + int used_ebs, int data_pad, u8 vol_type, + int last_eb_bytes) +{ + struct ubi_ainf_volume *av; + struct rb_node **p = &ai->volumes.rb_node, *parent = NULL; + + while (*p) { + parent = *p; + av = rb_entry(parent, struct ubi_ainf_volume, rb); + + if (vol_id > av->vol_id) + p = &(*p)->rb_left; + else + p = &(*p)->rb_right; + } + + av = kmalloc(sizeof(struct ubi_ainf_volume), GFP_KERNEL); + if (!av) + goto out; + + av->highest_lnum = av->leb_count = 0; + av->vol_id = vol_id; + av->used_ebs = used_ebs; + av->data_pad = data_pad; + av->last_data_size = last_eb_bytes; + av->compat = 0; + av->vol_type = vol_type; + av->root = RB_ROOT; + + dbg_bld("found volume (ID %i)", vol_id); + + rb_link_node(&av->rb, parent, p); + rb_insert_color(&av->rb, &ai->volumes); + +out: + return av; +} + +/** + * assign_aeb_to_av - assigns a SEB to a given ainf_volume and removes it + * from it's original list. + * @ai: ubi_attach_info object + * @aeb: the to be assigned SEB + * @av: target scan volume + */ +static void assign_aeb_to_av(struct ubi_attach_info *ai, + struct ubi_ainf_peb *aeb, + struct ubi_ainf_volume *av) +{ + struct ubi_ainf_peb *tmp_aeb; + struct rb_node **p = &ai->volumes.rb_node, *parent = NULL; + + p = &av->root.rb_node; + while (*p) { + parent = *p; + + tmp_aeb = rb_entry(parent, struct ubi_ainf_peb, u.rb); + if (aeb->lnum != tmp_aeb->lnum) { + if (aeb->lnum < tmp_aeb->lnum) + p = &(*p)->rb_left; + else + p = &(*p)->rb_right; + + continue; + } else + break; + } + + list_del(&aeb->u.list); + av->leb_count++; + + rb_link_node(&aeb->u.rb, parent, p); + rb_insert_color(&aeb->u.rb, &av->root); +} + +/** + * update_vol - inserts or updates a LEB which was found a pool. + * @ubi: the UBI device object + * @ai: attach info object + * @av: the volume this LEB belongs to + * @new_vh: the volume header derived from new_aeb + * @new_aeb: the AEB to be examined + * + * Returns 0 on success, < 0 indicates an internal error. + */ +static int update_vol(struct ubi_device *ubi, struct ubi_attach_info *ai, + struct ubi_ainf_volume *av, struct ubi_vid_hdr *new_vh, + struct ubi_ainf_peb *new_aeb) +{ + struct rb_node **p = &av->root.rb_node, *parent = NULL; + struct ubi_ainf_peb *aeb, *victim; + int cmp_res; + + while (*p) { + parent = *p; + aeb = rb_entry(parent, struct ubi_ainf_peb, u.rb); + + if (be32_to_cpu(new_vh->lnum) != aeb->lnum) { + if (be32_to_cpu(new_vh->lnum) < aeb->lnum) + p = &(*p)->rb_left; + else + p = &(*p)->rb_right; + + continue; + } + + /* This case can happen if the fastmap gets written + * because of a volume change (creation, deletion, ..). + * Then a PEB can be within the persistent EBA and the pool. + */ + if (aeb->pnum == new_aeb->pnum) { + ubi_assert(aeb->lnum == new_aeb->lnum); + kmem_cache_free(ai->aeb_slab_cache, new_aeb); + + return 0; + } + + cmp_res = ubi_compare_lebs(ubi, aeb, new_aeb->pnum, new_vh); + if (cmp_res < 0) + return cmp_res; + + /* new_aeb is newer */ + if (cmp_res & 1) { + victim = kmem_cache_alloc(ai->aeb_slab_cache, + GFP_KERNEL); + if (!victim) + return -ENOMEM; + + victim->ec = aeb->ec; + victim->pnum = aeb->pnum; + list_add_tail(&victim->u.list, &ai->erase); + + if (av->highest_lnum == be32_to_cpu(new_vh->lnum)) + av->last_data_size = \ + be32_to_cpu(new_vh->data_size); + + dbg_bld("vol %i: AEB %i's PEB %i is the newer", + av->vol_id, aeb->lnum, new_aeb->pnum); + + aeb->ec = new_aeb->ec; + aeb->pnum = new_aeb->pnum; + aeb->copy_flag = new_vh->copy_flag; + aeb->scrub = new_aeb->scrub; + kmem_cache_free(ai->aeb_slab_cache, new_aeb); + + /* new_aeb is older */ + } else { + dbg_bld("vol %i: AEB %i's PEB %i is old, dropping it", + av->vol_id, aeb->lnum, new_aeb->pnum); + list_add_tail(&new_aeb->u.list, &ai->erase); + } + + return 0; + } + /* This LEB is new, let's add it to the volume */ + + if (av->highest_lnum <= be32_to_cpu(new_vh->lnum)) { + av->highest_lnum = be32_to_cpu(new_vh->lnum); + av->last_data_size = be32_to_cpu(new_vh->data_size); + } + + if (av->vol_type == UBI_STATIC_VOLUME) + av->used_ebs = be32_to_cpu(new_vh->used_ebs); + + av->leb_count++; + + rb_link_node(&new_aeb->u.rb, parent, p); + rb_insert_color(&new_aeb->u.rb, &av->root); + + return 0; +} + +/** + * process_pool_aeb - we found a non-empty PEB in a pool. + * @ubi: UBI device object + * @ai: attach info object + * @new_vh: the volume header derived from new_aeb + * @new_aeb: the AEB to be examined + * + * Returns 0 on success, < 0 indicates an internal error. + */ +static int process_pool_aeb(struct ubi_device *ubi, struct ubi_attach_info *ai, + struct ubi_vid_hdr *new_vh, + struct ubi_ainf_peb *new_aeb) +{ + struct ubi_ainf_volume *av, *tmp_av = NULL; + struct rb_node **p = &ai->volumes.rb_node, *parent = NULL; + int found = 0; + + if (be32_to_cpu(new_vh->vol_id) == UBI_FM_SB_VOLUME_ID || + be32_to_cpu(new_vh->vol_id) == UBI_FM_DATA_VOLUME_ID) { + kmem_cache_free(ai->aeb_slab_cache, new_aeb); + + return 0; + } + + /* Find the volume this SEB belongs to */ + while (*p) { + parent = *p; + tmp_av = rb_entry(parent, struct ubi_ainf_volume, rb); + + if (be32_to_cpu(new_vh->vol_id) > tmp_av->vol_id) + p = &(*p)->rb_left; + else if (be32_to_cpu(new_vh->vol_id) < tmp_av->vol_id) + p = &(*p)->rb_right; + else { + found = 1; + break; + } + } + + if (found) + av = tmp_av; + else { + ubi_err("orphaned volume in fastmap pool!"); + return UBI_BAD_FASTMAP; + } + + ubi_assert(be32_to_cpu(new_vh->vol_id) == av->vol_id); + + return update_vol(ubi, ai, av, new_vh, new_aeb); +} + +/** + * unmap_peb - unmap a PEB. + * If fastmap detects a free PEB in the pool it has to check whether + * this PEB has been unmapped after writing the fastmap. + * + * @ai: UBI attach info object + * @pnum: The PEB to be unmapped + */ +static void unmap_peb(struct ubi_attach_info *ai, int pnum) +{ + struct ubi_ainf_volume *av; + struct rb_node *node, *node2; + struct ubi_ainf_peb *aeb; + + for (node = rb_first(&ai->volumes); node; node = rb_next(node)) { + av = rb_entry(node, struct ubi_ainf_volume, rb); + + for (node2 = rb_first(&av->root); node2; + node2 = rb_next(node2)) { + aeb = rb_entry(node2, struct ubi_ainf_peb, u.rb); + if (aeb->pnum == pnum) { + rb_erase(&aeb->u.rb, &av->root); + kmem_cache_free(ai->aeb_slab_cache, aeb); + return; + } + } + } +} + +/** + * scan_pool - scans a pool for changed (no longer empty PEBs). + * @ubi: UBI device object + * @ai: attach info object + * @pebs: an array of all PEB numbers in the to be scanned pool + * @pool_size: size of the pool (number of entries in @pebs) + * @max_sqnum: pointer to the maximal sequence number + * @eba_orphans: list of PEBs which need to be scanned + * @free: list of PEBs which are most likely free (and go into @ai->free) + * + * Returns 0 on success, if the pool is unusable UBI_BAD_FASTMAP is returned. + * < 0 indicates an internal error. + */ +static int scan_pool(struct ubi_device *ubi, struct ubi_attach_info *ai, + int *pebs, int pool_size, unsigned long long *max_sqnum, + struct list_head *eba_orphans, struct list_head *free) +{ + struct ubi_vid_hdr *vh; + struct ubi_ec_hdr *ech; + struct ubi_ainf_peb *new_aeb, *tmp_aeb; + int i, pnum, err, found_orphan, ret = 0; + + ech = kzalloc(ubi->ec_hdr_alsize, GFP_KERNEL); + if (!ech) + return -ENOMEM; + + vh = ubi_zalloc_vid_hdr(ubi, GFP_KERNEL); + if (!vh) { + kfree(ech); + return -ENOMEM; + } + + dbg_bld("scanning fastmap pool: size = %i", pool_size); + + /* + * Now scan all PEBs in the pool to find changes which have been made + * after the creation of the fastmap + */ + for (i = 0; i < pool_size; i++) { + int scrub = 0; + int image_seq; + + pnum = be32_to_cpu(pebs[i]); + + if (ubi_io_is_bad(ubi, pnum)) { + ubi_err("bad PEB in fastmap pool!"); + ret = UBI_BAD_FASTMAP; + goto out; + } + + err = ubi_io_read_ec_hdr(ubi, pnum, ech, 0); + if (err && err != UBI_IO_BITFLIPS) { + ubi_err("unable to read EC header! PEB:%i err:%i", + pnum, err); + ret = err > 0 ? UBI_BAD_FASTMAP : err; + goto out; + } else if (err == UBI_IO_BITFLIPS) + scrub = 1; + + /* + * Older UBI implementations have image_seq set to zero, so + * we shouldn't fail if image_seq == 0. + */ + image_seq = be32_to_cpu(ech->image_seq); + + if (image_seq && (image_seq != ubi->image_seq)) { + ubi_err("bad image seq: 0x%x, expected: 0x%x", + be32_to_cpu(ech->image_seq), ubi->image_seq); + ret = UBI_BAD_FASTMAP; + goto out; + } + + err = ubi_io_read_vid_hdr(ubi, pnum, vh, 0); + if (err == UBI_IO_FF || err == UBI_IO_FF_BITFLIPS) { + unsigned long long ec = be64_to_cpu(ech->ec); + unmap_peb(ai, pnum); + dbg_bld("Adding PEB to free: %i", pnum); + if (err == UBI_IO_FF_BITFLIPS) + add_aeb(ai, free, pnum, ec, 1); + else + add_aeb(ai, free, pnum, ec, 0); + continue; + } else if (err == 0 || err == UBI_IO_BITFLIPS) { + dbg_bld("Found non empty PEB:%i in pool", pnum); + + if (err == UBI_IO_BITFLIPS) + scrub = 1; + + found_orphan = 0; + list_for_each_entry(tmp_aeb, eba_orphans, u.list) { + if (tmp_aeb->pnum == pnum) { + found_orphan = 1; + break; + } + } + if (found_orphan) { + list_del(&tmp_aeb->u.list); + kmem_cache_free(ai->aeb_slab_cache, tmp_aeb); + } + + new_aeb = kmem_cache_alloc(ai->aeb_slab_cache, + GFP_KERNEL); + if (!new_aeb) { + ret = -ENOMEM; + goto out; + } + + new_aeb->ec = be64_to_cpu(ech->ec); + new_aeb->pnum = pnum; + new_aeb->lnum = be32_to_cpu(vh->lnum); + new_aeb->sqnum = be64_to_cpu(vh->sqnum); + new_aeb->copy_flag = vh->copy_flag; + new_aeb->scrub = scrub; + + if (*max_sqnum < new_aeb->sqnum) + *max_sqnum = new_aeb->sqnum; + + err = process_pool_aeb(ubi, ai, vh, new_aeb); + if (err) { + ret = err > 0 ? UBI_BAD_FASTMAP : err; + goto out; + } + } else { + /* We are paranoid and fall back to scanning mode */ + ubi_err("fastmap pool PEBs contains damaged PEBs!"); + ret = err > 0 ? UBI_BAD_FASTMAP : err; + goto out; + } + + } + +out: + ubi_free_vid_hdr(ubi, vh); + kfree(ech); + return ret; +} + +/** + * count_fastmap_pebs - Counts the PEBs found by fastmap. + * @ai: The UBI attach info object + */ +static int count_fastmap_pebs(struct ubi_attach_info *ai) +{ + struct ubi_ainf_peb *aeb; + struct ubi_ainf_volume *av; + struct rb_node *rb1, *rb2; + int n = 0; + + list_for_each_entry(aeb, &ai->erase, u.list) + n++; + + list_for_each_entry(aeb, &ai->free, u.list) + n++; + + ubi_rb_for_each_entry(rb1, av, &ai->volumes, rb) + ubi_rb_for_each_entry(rb2, aeb, &av->root, u.rb) + n++; + + return n; +} + +/** + * ubi_attach_fastmap - creates ubi_attach_info from a fastmap. + * @ubi: UBI device object + * @ai: UBI attach info object + * @fm: the fastmap to be attached + * + * Returns 0 on success, UBI_BAD_FASTMAP if the found fastmap was unusable. + * < 0 indicates an internal error. + */ +static int ubi_attach_fastmap(struct ubi_device *ubi, + struct ubi_attach_info *ai, + struct ubi_fastmap_layout *fm) +{ + struct list_head used, eba_orphans, free; + struct ubi_ainf_volume *av; + struct ubi_ainf_peb *aeb, *tmp_aeb, *_tmp_aeb; + struct ubi_ec_hdr *ech; + struct ubi_fm_sb *fmsb; + struct ubi_fm_hdr *fmhdr; + struct ubi_fm_scan_pool *fmpl1, *fmpl2; + struct ubi_fm_ec *fmec; + struct ubi_fm_volhdr *fmvhdr; + struct ubi_fm_eba *fm_eba; + int ret, i, j, pool_size, wl_pool_size; + size_t fm_pos = 0, fm_size = ubi->fm_size; + unsigned long long max_sqnum = 0; + void *fm_raw = ubi->fm_buf; + + INIT_LIST_HEAD(&used); + INIT_LIST_HEAD(&free); + INIT_LIST_HEAD(&eba_orphans); + INIT_LIST_HEAD(&ai->corr); + INIT_LIST_HEAD(&ai->free); + INIT_LIST_HEAD(&ai->erase); + INIT_LIST_HEAD(&ai->alien); + ai->volumes = RB_ROOT; + ai->min_ec = UBI_MAX_ERASECOUNTER; + + ai->aeb_slab_cache = kmem_cache_create("ubi_ainf_peb_slab", + sizeof(struct ubi_ainf_peb), + 0, 0, NULL); + if (!ai->aeb_slab_cache) { + ret = -ENOMEM; + goto fail; + } + + fmsb = (struct ubi_fm_sb *)(fm_raw); + ai->max_sqnum = fmsb->sqnum; + fm_pos += sizeof(struct ubi_fm_sb); + if (fm_pos >= fm_size) + goto fail_bad; + + fmhdr = (struct ubi_fm_hdr *)(fm_raw + fm_pos); + fm_pos += sizeof(*fmhdr); + if (fm_pos >= fm_size) + goto fail_bad; + + if (be32_to_cpu(fmhdr->magic) != UBI_FM_HDR_MAGIC) { + ubi_err("bad fastmap header magic: 0x%x, expected: 0x%x", + be32_to_cpu(fmhdr->magic), UBI_FM_HDR_MAGIC); + goto fail_bad; + } + + fmpl1 = (struct ubi_fm_scan_pool *)(fm_raw + fm_pos); + fm_pos += sizeof(*fmpl1); + if (fm_pos >= fm_size) + goto fail_bad; + if (be32_to_cpu(fmpl1->magic) != UBI_FM_POOL_MAGIC) { + ubi_err("bad fastmap pool magic: 0x%x, expected: 0x%x", + be32_to_cpu(fmpl1->magic), UBI_FM_POOL_MAGIC); + goto fail_bad; + } + + fmpl2 = (struct ubi_fm_scan_pool *)(fm_raw + fm_pos); + fm_pos += sizeof(*fmpl2); + if (fm_pos >= fm_size) + goto fail_bad; + if (be32_to_cpu(fmpl2->magic) != UBI_FM_POOL_MAGIC) { + ubi_err("bad fastmap pool magic: 0x%x, expected: 0x%x", + be32_to_cpu(fmpl2->magic), UBI_FM_POOL_MAGIC); + goto fail_bad; + } + + pool_size = be16_to_cpu(fmpl1->size); + wl_pool_size = be16_to_cpu(fmpl2->size); + fm->max_pool_size = be16_to_cpu(fmpl1->max_size); + fm->max_wl_pool_size = be16_to_cpu(fmpl2->max_size); + + if (pool_size > UBI_FM_MAX_POOL_SIZE || pool_size < 0) { + ubi_err("bad pool size: %i", pool_size); + goto fail_bad; + } + + if (wl_pool_size > UBI_FM_MAX_POOL_SIZE || wl_pool_size < 0) { + ubi_err("bad WL pool size: %i", wl_pool_size); + goto fail_bad; + } + + + if (fm->max_pool_size > UBI_FM_MAX_POOL_SIZE || + fm->max_pool_size < 0) { + ubi_err("bad maximal pool size: %i", fm->max_pool_size); + goto fail_bad; + } + + if (fm->max_wl_pool_size > UBI_FM_MAX_POOL_SIZE || + fm->max_wl_pool_size < 0) { + ubi_err("bad maximal WL pool size: %i", fm->max_wl_pool_size); + goto fail_bad; + } + + /* read EC values from free list */ + for (i = 0; i < be32_to_cpu(fmhdr->free_peb_count); i++) { + fmec = (struct ubi_fm_ec *)(fm_raw + fm_pos); + fm_pos += sizeof(*fmec); + if (fm_pos >= fm_size) + goto fail_bad; + + add_aeb(ai, &ai->free, be32_to_cpu(fmec->pnum), + be32_to_cpu(fmec->ec), 0); + } + + /* read EC values from used list */ + for (i = 0; i < be32_to_cpu(fmhdr->used_peb_count); i++) { + fmec = (struct ubi_fm_ec *)(fm_raw + fm_pos); + fm_pos += sizeof(*fmec); + if (fm_pos >= fm_size) + goto fail_bad; + + add_aeb(ai, &used, be32_to_cpu(fmec->pnum), + be32_to_cpu(fmec->ec), 0); + } + + /* read EC values from scrub list */ + for (i = 0; i < be32_to_cpu(fmhdr->scrub_peb_count); i++) { + fmec = (struct ubi_fm_ec *)(fm_raw + fm_pos); + fm_pos += sizeof(*fmec); + if (fm_pos >= fm_size) + goto fail_bad; + + add_aeb(ai, &used, be32_to_cpu(fmec->pnum), + be32_to_cpu(fmec->ec), 1); + } + + /* read EC values from erase list */ + for (i = 0; i < be32_to_cpu(fmhdr->erase_peb_count); i++) { + fmec = (struct ubi_fm_ec *)(fm_raw + fm_pos); + fm_pos += sizeof(*fmec); + if (fm_pos >= fm_size) + goto fail_bad; + + add_aeb(ai, &ai->erase, be32_to_cpu(fmec->pnum), + be32_to_cpu(fmec->ec), 1); + } + + ai->mean_ec = div_u64(ai->ec_sum, ai->ec_count); + ai->bad_peb_count = be32_to_cpu(fmhdr->bad_peb_count); + + /* Iterate over all volumes and read their EBA table */ + for (i = 0; i < be32_to_cpu(fmhdr->vol_count); i++) { + fmvhdr = (struct ubi_fm_volhdr *)(fm_raw + fm_pos); + fm_pos += sizeof(*fmvhdr); + if (fm_pos >= fm_size) + goto fail_bad; + + if (be32_to_cpu(fmvhdr->magic) != UBI_FM_VHDR_MAGIC) { + ubi_err("bad fastmap vol header magic: 0x%x, " \ + "expected: 0x%x", + be32_to_cpu(fmvhdr->magic), UBI_FM_VHDR_MAGIC); + goto fail_bad; + } + + av = add_vol(ai, be32_to_cpu(fmvhdr->vol_id), + be32_to_cpu(fmvhdr->used_ebs), + be32_to_cpu(fmvhdr->data_pad), + fmvhdr->vol_type, + be32_to_cpu(fmvhdr->last_eb_bytes)); + + if (!av) + goto fail_bad; + + ai->vols_found++; + if (ai->highest_vol_id < be32_to_cpu(fmvhdr->vol_id)) + ai->highest_vol_id = be32_to_cpu(fmvhdr->vol_id); + + fm_eba = (struct ubi_fm_eba *)(fm_raw + fm_pos); + fm_pos += sizeof(*fm_eba); + fm_pos += (sizeof(__be32) * be32_to_cpu(fm_eba->reserved_pebs)); + if (fm_pos >= fm_size) + goto fail_bad; + + if (be32_to_cpu(fm_eba->magic) != UBI_FM_EBA_MAGIC) { + ubi_err("bad fastmap EBA header magic: 0x%x, " \ + "expected: 0x%x", + be32_to_cpu(fm_eba->magic), UBI_FM_EBA_MAGIC); + goto fail_bad; + } + + for (j = 0; j < be32_to_cpu(fm_eba->reserved_pebs); j++) { + int pnum = be32_to_cpu(fm_eba->pnum[j]); + + if ((int)be32_to_cpu(fm_eba->pnum[j]) < 0) + continue; + + aeb = NULL; + list_for_each_entry(tmp_aeb, &used, u.list) { + if (tmp_aeb->pnum == pnum) { + aeb = tmp_aeb; + break; + } + } + + /* This can happen if a PEB is already in an EBA known + * by this fastmap but the PEB itself is not in the used + * list. + * In this case the PEB can be within the fastmap pool + * or while writing the fastmap it was in the protection + * queue. + */ + if (!aeb) { + aeb = kmem_cache_alloc(ai->aeb_slab_cache, + GFP_KERNEL); + if (!aeb) { + ret = -ENOMEM; + + goto fail; + } + + aeb->lnum = j; + aeb->pnum = be32_to_cpu(fm_eba->pnum[j]); + aeb->ec = -1; + aeb->scrub = aeb->copy_flag = aeb->sqnum = 0; + list_add_tail(&aeb->u.list, &eba_orphans); + continue; + } + + aeb->lnum = j; + + if (av->highest_lnum <= aeb->lnum) + av->highest_lnum = aeb->lnum; + + assign_aeb_to_av(ai, aeb, av); + + dbg_bld("inserting PEB:%i (LEB %i) to vol %i", + aeb->pnum, aeb->lnum, av->vol_id); + } + + ech = kzalloc(ubi->ec_hdr_alsize, GFP_KERNEL); + if (!ech) { + ret = -ENOMEM; + goto fail; + } + + list_for_each_entry_safe(tmp_aeb, _tmp_aeb, &eba_orphans, + u.list) { + int err; + + if (ubi_io_is_bad(ubi, tmp_aeb->pnum)) { + ubi_err("bad PEB in fastmap EBA orphan list"); + ret = UBI_BAD_FASTMAP; + kfree(ech); + goto fail; + } + + err = ubi_io_read_ec_hdr(ubi, tmp_aeb->pnum, ech, 0); + if (err && err != UBI_IO_BITFLIPS) { + ubi_err("unable to read EC header! PEB:%i " \ + "err:%i", tmp_aeb->pnum, err); + ret = err > 0 ? UBI_BAD_FASTMAP : err; + kfree(ech); + + goto fail; + } else if (err == UBI_IO_BITFLIPS) + tmp_aeb->scrub = 1; + + tmp_aeb->ec = be64_to_cpu(ech->ec); + assign_aeb_to_av(ai, tmp_aeb, av); + } + + kfree(ech); + } + + ret = scan_pool(ubi, ai, fmpl1->pebs, pool_size, &max_sqnum, + &eba_orphans, &free); + if (ret) + goto fail; + + ret = scan_pool(ubi, ai, fmpl2->pebs, wl_pool_size, &max_sqnum, + &eba_orphans, &free); + if (ret) + goto fail; + + if (max_sqnum > ai->max_sqnum) + ai->max_sqnum = max_sqnum; + + list_for_each_entry_safe(tmp_aeb, _tmp_aeb, &free, u.list) + list_move_tail(&tmp_aeb->u.list, &ai->free); + + ubi_assert(list_empty(&used)); + ubi_assert(list_empty(&eba_orphans)); + ubi_assert(list_empty(&free)); + + /* + * If fastmap is leaking PEBs (must not happen), raise a + * fat warning and fall back to scanning mode. + * We do this here because in ubi_wl_init() it's too late + * and we cannot fall back to scanning. + */ + if (WARN_ON(count_fastmap_pebs(ai) != ubi->peb_count - + ai->bad_peb_count - fm->used_blocks)) + goto fail_bad; + + return 0; + +fail_bad: + ret = UBI_BAD_FASTMAP; +fail: + list_for_each_entry_safe(tmp_aeb, _tmp_aeb, &used, u.list) { + list_del(&tmp_aeb->u.list); + kmem_cache_free(ai->aeb_slab_cache, tmp_aeb); + } + list_for_each_entry_safe(tmp_aeb, _tmp_aeb, &eba_orphans, u.list) { + list_del(&tmp_aeb->u.list); + kmem_cache_free(ai->aeb_slab_cache, tmp_aeb); + } + list_for_each_entry_safe(tmp_aeb, _tmp_aeb, &free, u.list) { + list_del(&tmp_aeb->u.list); + kmem_cache_free(ai->aeb_slab_cache, tmp_aeb); + } + + return ret; +} + +/** + * ubi_scan_fastmap - scan the fastmap. + * @ubi: UBI device object + * @ai: UBI attach info to be filled + * @fm_anchor: The fastmap starts at this PEB + * + * Returns 0 on success, UBI_NO_FASTMAP if no fastmap was found, + * UBI_BAD_FASTMAP if one was found but is not usable. + * < 0 indicates an internal error. + */ +int ubi_scan_fastmap(struct ubi_device *ubi, struct ubi_attach_info *ai, + int fm_anchor) +{ + struct ubi_fm_sb *fmsb, *fmsb2; + struct ubi_vid_hdr *vh; + struct ubi_ec_hdr *ech; + struct ubi_fastmap_layout *fm; + int i, used_blocks, pnum, ret = 0; + size_t fm_size; + __be32 crc, tmp_crc; + unsigned long long sqnum = 0; + + mutex_lock(&ubi->fm_mutex); + memset(ubi->fm_buf, 0, ubi->fm_size); + + fmsb = kmalloc(sizeof(*fmsb), GFP_KERNEL); + if (!fmsb) { + ret = -ENOMEM; + goto out; + } + + fm = kzalloc(sizeof(*fm), GFP_KERNEL); + if (!fm) { + ret = -ENOMEM; + kfree(fmsb); + goto out; + } + + ret = ubi_io_read(ubi, fmsb, fm_anchor, ubi->leb_start, sizeof(*fmsb)); + if (ret && ret != UBI_IO_BITFLIPS) + goto free_fm_sb; + else if (ret == UBI_IO_BITFLIPS) + fm->to_be_tortured[0] = 1; + + if (be32_to_cpu(fmsb->magic) != UBI_FM_SB_MAGIC) { + ubi_err("bad super block magic: 0x%x, expected: 0x%x", + be32_to_cpu(fmsb->magic), UBI_FM_SB_MAGIC); + ret = UBI_BAD_FASTMAP; + goto free_fm_sb; + } + + if (fmsb->version != UBI_FM_FMT_VERSION) { + ubi_err("bad fastmap version: %i, expected: %i", + fmsb->version, UBI_FM_FMT_VERSION); + ret = UBI_BAD_FASTMAP; + goto free_fm_sb; + } + + used_blocks = be32_to_cpu(fmsb->used_blocks); + if (used_blocks > UBI_FM_MAX_BLOCKS || used_blocks < 1) { + ubi_err("number of fastmap blocks is invalid: %i", used_blocks); + ret = UBI_BAD_FASTMAP; + goto free_fm_sb; + } + + fm_size = ubi->leb_size * used_blocks; + if (fm_size != ubi->fm_size) { + ubi_err("bad fastmap size: %zi, expected: %zi", fm_size, + ubi->fm_size); + ret = UBI_BAD_FASTMAP; + goto free_fm_sb; + } + + ech = kzalloc(ubi->ec_hdr_alsize, GFP_KERNEL); + if (!ech) { + ret = -ENOMEM; + goto free_fm_sb; + } + + vh = ubi_zalloc_vid_hdr(ubi, GFP_KERNEL); + if (!vh) { + ret = -ENOMEM; + goto free_hdr; + } + + for (i = 0; i < used_blocks; i++) { + int image_seq; + + pnum = be32_to_cpu(fmsb->block_loc[i]); + + if (ubi_io_is_bad(ubi, pnum)) { + ret = UBI_BAD_FASTMAP; + goto free_hdr; + } + + ret = ubi_io_read_ec_hdr(ubi, pnum, ech, 0); + if (ret && ret != UBI_IO_BITFLIPS) { + ubi_err("unable to read fastmap block# %i EC (PEB: %i)", + i, pnum); + if (ret > 0) + ret = UBI_BAD_FASTMAP; + goto free_hdr; + } else if (ret == UBI_IO_BITFLIPS) + fm->to_be_tortured[i] = 1; + + image_seq = be32_to_cpu(ech->image_seq); + if (!ubi->image_seq) + ubi->image_seq = image_seq; + + /* + * Older UBI implementations have image_seq set to zero, so + * we shouldn't fail if image_seq == 0. + */ + if (image_seq && (image_seq != ubi->image_seq)) { + ubi_err("wrong image seq:%d instead of %d", + be32_to_cpu(ech->image_seq), ubi->image_seq); + ret = UBI_BAD_FASTMAP; + goto free_hdr; + } + + ret = ubi_io_read_vid_hdr(ubi, pnum, vh, 0); + if (ret && ret != UBI_IO_BITFLIPS) { + ubi_err("unable to read fastmap block# %i (PEB: %i)", + i, pnum); + goto free_hdr; + } + + if (i == 0) { + if (be32_to_cpu(vh->vol_id) != UBI_FM_SB_VOLUME_ID) { + ubi_err("bad fastmap anchor vol_id: 0x%x," \ + " expected: 0x%x", + be32_to_cpu(vh->vol_id), + UBI_FM_SB_VOLUME_ID); + ret = UBI_BAD_FASTMAP; + goto free_hdr; + } + } else { + if (be32_to_cpu(vh->vol_id) != UBI_FM_DATA_VOLUME_ID) { + ubi_err("bad fastmap data vol_id: 0x%x," \ + " expected: 0x%x", + be32_to_cpu(vh->vol_id), + UBI_FM_DATA_VOLUME_ID); + ret = UBI_BAD_FASTMAP; + goto free_hdr; + } + } + + if (sqnum < be64_to_cpu(vh->sqnum)) + sqnum = be64_to_cpu(vh->sqnum); + + ret = ubi_io_read(ubi, ubi->fm_buf + (ubi->leb_size * i), pnum, + ubi->leb_start, ubi->leb_size); + if (ret && ret != UBI_IO_BITFLIPS) { + ubi_err("unable to read fastmap block# %i (PEB: %i, " \ + "err: %i)", i, pnum, ret); + goto free_hdr; + } + } + + kfree(fmsb); + fmsb = NULL; + + fmsb2 = (struct ubi_fm_sb *)(ubi->fm_buf); + tmp_crc = be32_to_cpu(fmsb2->data_crc); + fmsb2->data_crc = 0; + crc = crc32(UBI_CRC32_INIT, ubi->fm_buf, fm_size); + if (crc != tmp_crc) { + ubi_err("fastmap data CRC is invalid"); + ubi_err("CRC should be: 0x%x, calc: 0x%x", tmp_crc, crc); + ret = UBI_BAD_FASTMAP; + goto free_hdr; + } + + fmsb2->sqnum = sqnum; + + fm->used_blocks = used_blocks; + + ret = ubi_attach_fastmap(ubi, ai, fm); + if (ret) { + if (ret > 0) + ret = UBI_BAD_FASTMAP; + goto free_hdr; + } + + for (i = 0; i < used_blocks; i++) { + struct ubi_wl_entry *e; + + e = kmem_cache_alloc(ubi_wl_entry_slab, GFP_KERNEL); + if (!e) { + while (i--) + kfree(fm->e[i]); + + ret = -ENOMEM; + goto free_hdr; + } + + e->pnum = be32_to_cpu(fmsb2->block_loc[i]); + e->ec = be32_to_cpu(fmsb2->block_ec[i]); + fm->e[i] = e; + } + + ubi->fm = fm; + ubi->fm_pool.max_size = ubi->fm->max_pool_size; + ubi->fm_wl_pool.max_size = ubi->fm->max_wl_pool_size; + ubi_msg("attached by fastmap"); + ubi_msg("fastmap pool size: %d", ubi->fm_pool.max_size); + ubi_msg("fastmap WL pool size: %d", ubi->fm_wl_pool.max_size); + ubi->fm_disabled = 0; + + ubi_free_vid_hdr(ubi, vh); + kfree(ech); +out: + mutex_unlock(&ubi->fm_mutex); + if (ret == UBI_BAD_FASTMAP) + ubi_err("Attach by fastmap failed, doing a full scan!"); + return ret; + +free_hdr: + ubi_free_vid_hdr(ubi, vh); + kfree(ech); +free_fm_sb: + kfree(fmsb); + kfree(fm); + goto out; +} + +/** + * ubi_write_fastmap - writes a fastmap. + * @ubi: UBI device object + * @new_fm: the to be written fastmap + * + * Returns 0 on success, < 0 indicates an internal error. + */ +static int ubi_write_fastmap(struct ubi_device *ubi, + struct ubi_fastmap_layout *new_fm) +{ + size_t fm_pos = 0; + void *fm_raw; + struct ubi_fm_sb *fmsb; + struct ubi_fm_hdr *fmh; + struct ubi_fm_scan_pool *fmpl1, *fmpl2; + struct ubi_fm_ec *fec; + struct ubi_fm_volhdr *fvh; + struct ubi_fm_eba *feba; + struct rb_node *node; + struct ubi_wl_entry *wl_e; + struct ubi_volume *vol; + struct ubi_vid_hdr *avhdr, *dvhdr; + struct ubi_work *ubi_wrk; + int ret, i, j, free_peb_count, used_peb_count, vol_count; + int scrub_peb_count, erase_peb_count; + + fm_raw = ubi->fm_buf; + memset(ubi->fm_buf, 0, ubi->fm_size); + + avhdr = new_fm_vhdr(ubi, UBI_FM_SB_VOLUME_ID); + if (!avhdr) { + ret = -ENOMEM; + goto out; + } + + dvhdr = new_fm_vhdr(ubi, UBI_FM_DATA_VOLUME_ID); + if (!dvhdr) { + ret = -ENOMEM; + goto out_kfree; + } + + spin_lock(&ubi->volumes_lock); + spin_lock(&ubi->wl_lock); + + fmsb = (struct ubi_fm_sb *)fm_raw; + fm_pos += sizeof(*fmsb); + ubi_assert(fm_pos <= ubi->fm_size); + + fmh = (struct ubi_fm_hdr *)(fm_raw + fm_pos); + fm_pos += sizeof(*fmh); + ubi_assert(fm_pos <= ubi->fm_size); + + fmsb->magic = cpu_to_be32(UBI_FM_SB_MAGIC); + fmsb->version = UBI_FM_FMT_VERSION; + fmsb->used_blocks = cpu_to_be32(new_fm->used_blocks); + /* the max sqnum will be filled in while *reading* the fastmap */ + fmsb->sqnum = 0; + + fmh->magic = cpu_to_be32(UBI_FM_HDR_MAGIC); + free_peb_count = 0; + used_peb_count = 0; + scrub_peb_count = 0; + erase_peb_count = 0; + vol_count = 0; + + fmpl1 = (struct ubi_fm_scan_pool *)(fm_raw + fm_pos); + fm_pos += sizeof(*fmpl1); + fmpl1->magic = cpu_to_be32(UBI_FM_POOL_MAGIC); + fmpl1->size = cpu_to_be16(ubi->fm_pool.size); + fmpl1->max_size = cpu_to_be16(ubi->fm_pool.max_size); + + for (i = 0; i < ubi->fm_pool.size; i++) + fmpl1->pebs[i] = cpu_to_be32(ubi->fm_pool.pebs[i]); + + fmpl2 = (struct ubi_fm_scan_pool *)(fm_raw + fm_pos); + fm_pos += sizeof(*fmpl2); + fmpl2->magic = cpu_to_be32(UBI_FM_POOL_MAGIC); + fmpl2->size = cpu_to_be16(ubi->fm_wl_pool.size); + fmpl2->max_size = cpu_to_be16(ubi->fm_wl_pool.max_size); + + for (i = 0; i < ubi->fm_wl_pool.size; i++) + fmpl2->pebs[i] = cpu_to_be32(ubi->fm_wl_pool.pebs[i]); + + for (node = rb_first(&ubi->free); node; node = rb_next(node)) { + wl_e = rb_entry(node, struct ubi_wl_entry, u.rb); + fec = (struct ubi_fm_ec *)(fm_raw + fm_pos); + + fec->pnum = cpu_to_be32(wl_e->pnum); + fec->ec = cpu_to_be32(wl_e->ec); + + free_peb_count++; + fm_pos += sizeof(*fec); + ubi_assert(fm_pos <= ubi->fm_size); + } + fmh->free_peb_count = cpu_to_be32(free_peb_count); + + for (node = rb_first(&ubi->used); node; node = rb_next(node)) { + wl_e = rb_entry(node, struct ubi_wl_entry, u.rb); + fec = (struct ubi_fm_ec *)(fm_raw + fm_pos); + + fec->pnum = cpu_to_be32(wl_e->pnum); + fec->ec = cpu_to_be32(wl_e->ec); + + used_peb_count++; + fm_pos += sizeof(*fec); + ubi_assert(fm_pos <= ubi->fm_size); + } + fmh->used_peb_count = cpu_to_be32(used_peb_count); + + for (node = rb_first(&ubi->scrub); node; node = rb_next(node)) { + wl_e = rb_entry(node, struct ubi_wl_entry, u.rb); + fec = (struct ubi_fm_ec *)(fm_raw + fm_pos); + + fec->pnum = cpu_to_be32(wl_e->pnum); + fec->ec = cpu_to_be32(wl_e->ec); + + scrub_peb_count++; + fm_pos += sizeof(*fec); + ubi_assert(fm_pos <= ubi->fm_size); + } + fmh->scrub_peb_count = cpu_to_be32(scrub_peb_count); + + + list_for_each_entry(ubi_wrk, &ubi->works, list) { + if (ubi_is_erase_work(ubi_wrk)) { + wl_e = ubi_wrk->e; + ubi_assert(wl_e); + + fec = (struct ubi_fm_ec *)(fm_raw + fm_pos); + + fec->pnum = cpu_to_be32(wl_e->pnum); + fec->ec = cpu_to_be32(wl_e->ec); + + erase_peb_count++; + fm_pos += sizeof(*fec); + ubi_assert(fm_pos <= ubi->fm_size); + } + } + fmh->erase_peb_count = cpu_to_be32(erase_peb_count); + + for (i = 0; i < UBI_MAX_VOLUMES + UBI_INT_VOL_COUNT; i++) { + vol = ubi->volumes[i]; + + if (!vol) + continue; + + vol_count++; + + fvh = (struct ubi_fm_volhdr *)(fm_raw + fm_pos); + fm_pos += sizeof(*fvh); + ubi_assert(fm_pos <= ubi->fm_size); + + fvh->magic = cpu_to_be32(UBI_FM_VHDR_MAGIC); + fvh->vol_id = cpu_to_be32(vol->vol_id); + fvh->vol_type = vol->vol_type; + fvh->used_ebs = cpu_to_be32(vol->used_ebs); + fvh->data_pad = cpu_to_be32(vol->data_pad); + fvh->last_eb_bytes = cpu_to_be32(vol->last_eb_bytes); + + ubi_assert(vol->vol_type == UBI_DYNAMIC_VOLUME || + vol->vol_type == UBI_STATIC_VOLUME); + + feba = (struct ubi_fm_eba *)(fm_raw + fm_pos); + fm_pos += sizeof(*feba) + (sizeof(__be32) * vol->reserved_pebs); + ubi_assert(fm_pos <= ubi->fm_size); + + for (j = 0; j < vol->reserved_pebs; j++) + feba->pnum[j] = cpu_to_be32(vol->eba_tbl[j]); + + feba->reserved_pebs = cpu_to_be32(j); + feba->magic = cpu_to_be32(UBI_FM_EBA_MAGIC); + } + fmh->vol_count = cpu_to_be32(vol_count); + fmh->bad_peb_count = cpu_to_be32(ubi->bad_peb_count); + + avhdr->sqnum = cpu_to_be64(ubi_next_sqnum(ubi)); + avhdr->lnum = 0; + + spin_unlock(&ubi->wl_lock); + spin_unlock(&ubi->volumes_lock); + + dbg_bld("writing fastmap SB to PEB %i", new_fm->e[0]->pnum); + ret = ubi_io_write_vid_hdr(ubi, new_fm->e[0]->pnum, avhdr); + if (ret) { + ubi_err("unable to write vid_hdr to fastmap SB!"); + goto out_kfree; + } + + for (i = 0; i < new_fm->used_blocks; i++) { + fmsb->block_loc[i] = cpu_to_be32(new_fm->e[i]->pnum); + fmsb->block_ec[i] = cpu_to_be32(new_fm->e[i]->ec); + } + + fmsb->data_crc = 0; + fmsb->data_crc = cpu_to_be32(crc32(UBI_CRC32_INIT, fm_raw, + ubi->fm_size)); + + for (i = 1; i < new_fm->used_blocks; i++) { + dvhdr->sqnum = cpu_to_be64(ubi_next_sqnum(ubi)); + dvhdr->lnum = cpu_to_be32(i); + dbg_bld("writing fastmap data to PEB %i sqnum %llu", + new_fm->e[i]->pnum, be64_to_cpu(dvhdr->sqnum)); + ret = ubi_io_write_vid_hdr(ubi, new_fm->e[i]->pnum, dvhdr); + if (ret) { + ubi_err("unable to write vid_hdr to PEB %i!", + new_fm->e[i]->pnum); + goto out_kfree; + } + } + + for (i = 0; i < new_fm->used_blocks; i++) { + ret = ubi_io_write(ubi, fm_raw + (i * ubi->leb_size), + new_fm->e[i]->pnum, ubi->leb_start, ubi->leb_size); + if (ret) { + ubi_err("unable to write fastmap to PEB %i!", + new_fm->e[i]->pnum); + goto out_kfree; + } + } + + ubi_assert(new_fm); + ubi->fm = new_fm; + + dbg_bld("fastmap written!"); + +out_kfree: + ubi_free_vid_hdr(ubi, avhdr); + ubi_free_vid_hdr(ubi, dvhdr); +out: + return ret; +} + +/** + * erase_block - Manually erase a PEB. + * @ubi: UBI device object + * @pnum: PEB to be erased + * + * Returns the new EC value on success, < 0 indicates an internal error. + */ +static int erase_block(struct ubi_device *ubi, int pnum) +{ + int ret; + struct ubi_ec_hdr *ec_hdr; + long long ec; + + ec_hdr = kzalloc(ubi->ec_hdr_alsize, GFP_KERNEL); + if (!ec_hdr) + return -ENOMEM; + + ret = ubi_io_read_ec_hdr(ubi, pnum, ec_hdr, 0); + if (ret < 0) + goto out; + else if (ret && ret != UBI_IO_BITFLIPS) { + ret = -EINVAL; + goto out; + } + + ret = ubi_io_sync_erase(ubi, pnum, 0); + if (ret < 0) + goto out; + + ec = be64_to_cpu(ec_hdr->ec); + ec += ret; + if (ec > UBI_MAX_ERASECOUNTER) { + ret = -EINVAL; + goto out; + } + + ec_hdr->ec = cpu_to_be64(ec); + ret = ubi_io_write_ec_hdr(ubi, pnum, ec_hdr); + if (ret < 0) + goto out; + + ret = ec; +out: + kfree(ec_hdr); + return ret; +} + +/** + * invalidate_fastmap - destroys a fastmap. + * @ubi: UBI device object + * @fm: the fastmap to be destroyed + * + * Returns 0 on success, < 0 indicates an internal error. + */ +static int invalidate_fastmap(struct ubi_device *ubi, + struct ubi_fastmap_layout *fm) +{ + int ret; + struct ubi_vid_hdr *vh; + + ret = erase_block(ubi, fm->e[0]->pnum); + if (ret < 0) + return ret; + + vh = new_fm_vhdr(ubi, UBI_FM_SB_VOLUME_ID); + if (!vh) + return -ENOMEM; + + /* deleting the current fastmap SB is not enough, an old SB may exist, + * so create a (corrupted) SB such that fastmap will find it and fall + * back to scanning mode in any case */ + vh->sqnum = cpu_to_be64(ubi_next_sqnum(ubi)); + ret = ubi_io_write_vid_hdr(ubi, fm->e[0]->pnum, vh); + + return ret; +} + +/** + * ubi_update_fastmap - will be called by UBI if a volume changes or + * a fastmap pool becomes full. + * @ubi: UBI device object + * + * Returns 0 on success, < 0 indicates an internal error. + */ +int ubi_update_fastmap(struct ubi_device *ubi) +{ + int ret, i; + struct ubi_fastmap_layout *new_fm, *old_fm; + struct ubi_wl_entry *tmp_e; + + mutex_lock(&ubi->fm_mutex); + + ubi_refill_pools(ubi); + + if (ubi->ro_mode || ubi->fm_disabled) { + mutex_unlock(&ubi->fm_mutex); + return 0; + } + + ret = ubi_ensure_anchor_pebs(ubi); + if (ret) { + mutex_unlock(&ubi->fm_mutex); + return ret; + } + + new_fm = kzalloc(sizeof(*new_fm), GFP_KERNEL); + if (!new_fm) { + mutex_unlock(&ubi->fm_mutex); + return -ENOMEM; + } + + new_fm->used_blocks = ubi->fm_size / ubi->leb_size; + + for (i = 0; i < new_fm->used_blocks; i++) { + new_fm->e[i] = kmem_cache_alloc(ubi_wl_entry_slab, GFP_KERNEL); + if (!new_fm->e[i]) { + while (i--) + kfree(new_fm->e[i]); + + kfree(new_fm); + mutex_unlock(&ubi->fm_mutex); + return -ENOMEM; + } + } + + old_fm = ubi->fm; + ubi->fm = NULL; + + if (new_fm->used_blocks > UBI_FM_MAX_BLOCKS) { + ubi_err("fastmap too large"); + ret = -ENOSPC; + goto err; + } + + for (i = 1; i < new_fm->used_blocks; i++) { + spin_lock(&ubi->wl_lock); + tmp_e = ubi_wl_get_fm_peb(ubi, 0); + spin_unlock(&ubi->wl_lock); + + if (!tmp_e && !old_fm) { + int j; + ubi_err("could not get any free erase block"); + + for (j = 1; j < i; j++) + ubi_wl_put_fm_peb(ubi, new_fm->e[j], j, 0); + + ret = -ENOSPC; + goto err; + } else if (!tmp_e && old_fm) { + ret = erase_block(ubi, old_fm->e[i]->pnum); + if (ret < 0) { + int j; + + for (j = 1; j < i; j++) + ubi_wl_put_fm_peb(ubi, new_fm->e[j], + j, 0); + + ubi_err("could not erase old fastmap PEB"); + goto err; + } + + new_fm->e[i]->pnum = old_fm->e[i]->pnum; + new_fm->e[i]->ec = old_fm->e[i]->ec; + } else { + new_fm->e[i]->pnum = tmp_e->pnum; + new_fm->e[i]->ec = tmp_e->ec; + + if (old_fm) + ubi_wl_put_fm_peb(ubi, old_fm->e[i], i, + old_fm->to_be_tortured[i]); + } + } + + spin_lock(&ubi->wl_lock); + tmp_e = ubi_wl_get_fm_peb(ubi, 1); + spin_unlock(&ubi->wl_lock); + + if (old_fm) { + /* no fresh anchor PEB was found, reuse the old one */ + if (!tmp_e) { + ret = erase_block(ubi, old_fm->e[0]->pnum); + if (ret < 0) { + int i; + ubi_err("could not erase old anchor PEB"); + + for (i = 1; i < new_fm->used_blocks; i++) + ubi_wl_put_fm_peb(ubi, new_fm->e[i], + i, 0); + goto err; + } + + new_fm->e[0]->pnum = old_fm->e[0]->pnum; + new_fm->e[0]->ec = ret; + } else { + /* we've got a new anchor PEB, return the old one */ + ubi_wl_put_fm_peb(ubi, old_fm->e[0], 0, + old_fm->to_be_tortured[0]); + + new_fm->e[0]->pnum = tmp_e->pnum; + new_fm->e[0]->ec = tmp_e->ec; + } + } else { + if (!tmp_e) { + int i; + ubi_err("could not find any anchor PEB"); + + for (i = 1; i < new_fm->used_blocks; i++) + ubi_wl_put_fm_peb(ubi, new_fm->e[i], i, 0); + + ret = -ENOSPC; + goto err; + } + + new_fm->e[0]->pnum = tmp_e->pnum; + new_fm->e[0]->ec = tmp_e->ec; + } + + down_write(&ubi->work_sem); + down_write(&ubi->fm_sem); + ret = ubi_write_fastmap(ubi, new_fm); + up_write(&ubi->fm_sem); + up_write(&ubi->work_sem); + + if (ret) + goto err; + +out_unlock: + mutex_unlock(&ubi->fm_mutex); + kfree(old_fm); + return ret; + +err: + kfree(new_fm); + + ubi_warn("Unable to write new fastmap, err=%i", ret); + + ret = 0; + if (old_fm) { + ret = invalidate_fastmap(ubi, old_fm); + if (ret < 0) + ubi_err("Unable to invalidiate current fastmap!"); + else if (ret) + ret = 0; + } + goto out_unlock; +} diff --git a/drivers/mtd/ubi/gluebi.c b/drivers/mtd/ubi/gluebi.c new file mode 100644 index 00000000000..b93807b4c45 --- /dev/null +++ b/drivers/mtd/ubi/gluebi.c @@ -0,0 +1,521 @@ +/* + * Copyright (c) International Business Machines Corp., 2006 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Artem Bityutskiy (Битюцкий Артём), Joern Engel + */ + +/* + * This is a small driver which implements fake MTD devices on top of UBI + * volumes. This sounds strange, but it is in fact quite useful to make + * MTD-oriented software (including all the legacy software) work on top of + * UBI. + * + * Gluebi emulates MTD devices of "MTD_UBIVOLUME" type. Their minimal I/O unit + * size (@mtd->writesize) is equivalent to the UBI minimal I/O unit. The + * eraseblock size is equivalent to the logical eraseblock size of the volume. + */ + +#include <linux/err.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/mtd/ubi.h> +#include <linux/mtd/mtd.h> +#include "ubi-media.h" + +#define err_msg(fmt, ...) \ + pr_err("gluebi (pid %d): %s: " fmt "\n", \ + current->pid, __func__, ##__VA_ARGS__) + +/** + * struct gluebi_device - a gluebi device description data structure. + * @mtd: emulated MTD device description object + * @refcnt: gluebi device reference count + * @desc: UBI volume descriptor + * @ubi_num: UBI device number this gluebi device works on + * @vol_id: ID of UBI volume this gluebi device works on + * @list: link in a list of gluebi devices + */ +struct gluebi_device { + struct mtd_info mtd; + int refcnt; + struct ubi_volume_desc *desc; + int ubi_num; + int vol_id; + struct list_head list; +}; + +/* List of all gluebi devices */ +static LIST_HEAD(gluebi_devices); +static DEFINE_MUTEX(devices_mutex); + +/** + * find_gluebi_nolock - find a gluebi device. + * @ubi_num: UBI device number + * @vol_id: volume ID + * + * This function seraches for gluebi device corresponding to UBI device + * @ubi_num and UBI volume @vol_id. Returns the gluebi device description + * object in case of success and %NULL in case of failure. The caller has to + * have the &devices_mutex locked. + */ +static struct gluebi_device *find_gluebi_nolock(int ubi_num, int vol_id) +{ + struct gluebi_device *gluebi; + + list_for_each_entry(gluebi, &gluebi_devices, list) + if (gluebi->ubi_num == ubi_num && gluebi->vol_id == vol_id) + return gluebi; + return NULL; +} + +/** + * gluebi_get_device - get MTD device reference. + * @mtd: the MTD device description object + * + * This function is called every time the MTD device is being opened and + * implements the MTD get_device() operation. Returns zero in case of success + * and a negative error code in case of failure. + */ +static int gluebi_get_device(struct mtd_info *mtd) +{ + struct gluebi_device *gluebi; + int ubi_mode = UBI_READONLY; + + if (!try_module_get(THIS_MODULE)) + return -ENODEV; + + if (mtd->flags & MTD_WRITEABLE) + ubi_mode = UBI_READWRITE; + + gluebi = container_of(mtd, struct gluebi_device, mtd); + mutex_lock(&devices_mutex); + if (gluebi->refcnt > 0) { + /* + * The MTD device is already referenced and this is just one + * more reference. MTD allows many users to open the same + * volume simultaneously and do not distinguish between + * readers/writers/exclusive openers as UBI does. So we do not + * open the UBI volume again - just increase the reference + * counter and return. + */ + gluebi->refcnt += 1; + mutex_unlock(&devices_mutex); + return 0; + } + + /* + * This is the first reference to this UBI volume via the MTD device + * interface. Open the corresponding volume in read-write mode. + */ + gluebi->desc = ubi_open_volume(gluebi->ubi_num, gluebi->vol_id, + ubi_mode); + if (IS_ERR(gluebi->desc)) { + mutex_unlock(&devices_mutex); + module_put(THIS_MODULE); + return PTR_ERR(gluebi->desc); + } + gluebi->refcnt += 1; + mutex_unlock(&devices_mutex); + return 0; +} + +/** + * gluebi_put_device - put MTD device reference. + * @mtd: the MTD device description object + * + * This function is called every time the MTD device is being put. Returns + * zero in case of success and a negative error code in case of failure. + */ +static void gluebi_put_device(struct mtd_info *mtd) +{ + struct gluebi_device *gluebi; + + gluebi = container_of(mtd, struct gluebi_device, mtd); + mutex_lock(&devices_mutex); + gluebi->refcnt -= 1; + if (gluebi->refcnt == 0) + ubi_close_volume(gluebi->desc); + module_put(THIS_MODULE); + mutex_unlock(&devices_mutex); +} + +/** + * gluebi_read - read operation of emulated MTD devices. + * @mtd: MTD device description object + * @from: absolute offset from where to read + * @len: how many bytes to read + * @retlen: count of read bytes is returned here + * @buf: buffer to store the read data + * + * This function returns zero in case of success and a negative error code in + * case of failure. + */ +static int gluebi_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, unsigned char *buf) +{ + int err = 0, lnum, offs, bytes_left; + struct gluebi_device *gluebi; + + gluebi = container_of(mtd, struct gluebi_device, mtd); + lnum = div_u64_rem(from, mtd->erasesize, &offs); + bytes_left = len; + while (bytes_left) { + size_t to_read = mtd->erasesize - offs; + + if (to_read > bytes_left) + to_read = bytes_left; + + err = ubi_read(gluebi->desc, lnum, buf, offs, to_read); + if (err) + break; + + lnum += 1; + offs = 0; + bytes_left -= to_read; + buf += to_read; + } + + *retlen = len - bytes_left; + return err; +} + +/** + * gluebi_write - write operation of emulated MTD devices. + * @mtd: MTD device description object + * @to: absolute offset where to write + * @len: how many bytes to write + * @retlen: count of written bytes is returned here + * @buf: buffer with data to write + * + * This function returns zero in case of success and a negative error code in + * case of failure. + */ +static int gluebi_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + int err = 0, lnum, offs, bytes_left; + struct gluebi_device *gluebi; + + gluebi = container_of(mtd, struct gluebi_device, mtd); + lnum = div_u64_rem(to, mtd->erasesize, &offs); + + if (len % mtd->writesize || offs % mtd->writesize) + return -EINVAL; + + bytes_left = len; + while (bytes_left) { + size_t to_write = mtd->erasesize - offs; + + if (to_write > bytes_left) + to_write = bytes_left; + + err = ubi_leb_write(gluebi->desc, lnum, buf, offs, to_write); + if (err) + break; + + lnum += 1; + offs = 0; + bytes_left -= to_write; + buf += to_write; + } + + *retlen = len - bytes_left; + return err; +} + +/** + * gluebi_erase - erase operation of emulated MTD devices. + * @mtd: the MTD device description object + * @instr: the erase operation description + * + * This function calls the erase callback when finishes. Returns zero in case + * of success and a negative error code in case of failure. + */ +static int gluebi_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + int err, i, lnum, count; + struct gluebi_device *gluebi; + + if (mtd_mod_by_ws(instr->addr, mtd) || mtd_mod_by_ws(instr->len, mtd)) + return -EINVAL; + + lnum = mtd_div_by_eb(instr->addr, mtd); + count = mtd_div_by_eb(instr->len, mtd); + gluebi = container_of(mtd, struct gluebi_device, mtd); + + for (i = 0; i < count - 1; i++) { + err = ubi_leb_unmap(gluebi->desc, lnum + i); + if (err) + goto out_err; + } + /* + * MTD erase operations are synchronous, so we have to make sure the + * physical eraseblock is wiped out. + * + * Thus, perform leb_erase instead of leb_unmap operation - leb_erase + * will wait for the end of operations + */ + err = ubi_leb_erase(gluebi->desc, lnum + i); + if (err) + goto out_err; + + instr->state = MTD_ERASE_DONE; + mtd_erase_callback(instr); + return 0; + +out_err: + instr->state = MTD_ERASE_FAILED; + instr->fail_addr = (long long)lnum * mtd->erasesize; + return err; +} + +/** + * gluebi_create - create a gluebi device for an UBI volume. + * @di: UBI device description object + * @vi: UBI volume description object + * + * This function is called when a new UBI volume is created in order to create + * corresponding fake MTD device. Returns zero in case of success and a + * negative error code in case of failure. + */ +static int gluebi_create(struct ubi_device_info *di, + struct ubi_volume_info *vi) +{ + struct gluebi_device *gluebi, *g; + struct mtd_info *mtd; + + gluebi = kzalloc(sizeof(struct gluebi_device), GFP_KERNEL); + if (!gluebi) + return -ENOMEM; + + mtd = &gluebi->mtd; + mtd->name = kmemdup(vi->name, vi->name_len + 1, GFP_KERNEL); + if (!mtd->name) { + kfree(gluebi); + return -ENOMEM; + } + + gluebi->vol_id = vi->vol_id; + gluebi->ubi_num = vi->ubi_num; + mtd->type = MTD_UBIVOLUME; + if (!di->ro_mode) + mtd->flags = MTD_WRITEABLE; + mtd->owner = THIS_MODULE; + mtd->writesize = di->min_io_size; + mtd->erasesize = vi->usable_leb_size; + mtd->_read = gluebi_read; + mtd->_write = gluebi_write; + mtd->_erase = gluebi_erase; + mtd->_get_device = gluebi_get_device; + mtd->_put_device = gluebi_put_device; + + /* + * In case of dynamic a volume, MTD device size is just volume size. In + * case of a static volume the size is equivalent to the amount of data + * bytes. + */ + if (vi->vol_type == UBI_DYNAMIC_VOLUME) + mtd->size = (unsigned long long)vi->usable_leb_size * vi->size; + else + mtd->size = vi->used_bytes; + + /* Just a sanity check - make sure this gluebi device does not exist */ + mutex_lock(&devices_mutex); + g = find_gluebi_nolock(vi->ubi_num, vi->vol_id); + if (g) + err_msg("gluebi MTD device %d form UBI device %d volume %d already exists", + g->mtd.index, vi->ubi_num, vi->vol_id); + mutex_unlock(&devices_mutex); + + if (mtd_device_register(mtd, NULL, 0)) { + err_msg("cannot add MTD device"); + kfree(mtd->name); + kfree(gluebi); + return -ENFILE; + } + + mutex_lock(&devices_mutex); + list_add_tail(&gluebi->list, &gluebi_devices); + mutex_unlock(&devices_mutex); + return 0; +} + +/** + * gluebi_remove - remove a gluebi device. + * @vi: UBI volume description object + * + * This function is called when an UBI volume is removed and it removes + * corresponding fake MTD device. Returns zero in case of success and a + * negative error code in case of failure. + */ +static int gluebi_remove(struct ubi_volume_info *vi) +{ + int err = 0; + struct mtd_info *mtd; + struct gluebi_device *gluebi; + + mutex_lock(&devices_mutex); + gluebi = find_gluebi_nolock(vi->ubi_num, vi->vol_id); + if (!gluebi) { + err_msg("got remove notification for unknown UBI device %d volume %d", + vi->ubi_num, vi->vol_id); + err = -ENOENT; + } else if (gluebi->refcnt) + err = -EBUSY; + else + list_del(&gluebi->list); + mutex_unlock(&devices_mutex); + if (err) + return err; + + mtd = &gluebi->mtd; + err = mtd_device_unregister(mtd); + if (err) { + err_msg("cannot remove fake MTD device %d, UBI device %d, volume %d, error %d", + mtd->index, gluebi->ubi_num, gluebi->vol_id, err); + mutex_lock(&devices_mutex); + list_add_tail(&gluebi->list, &gluebi_devices); + mutex_unlock(&devices_mutex); + return err; + } + + kfree(mtd->name); + kfree(gluebi); + return 0; +} + +/** + * gluebi_updated - UBI volume was updated notifier. + * @vi: volume info structure + * + * This function is called every time an UBI volume is updated. It does nothing + * if te volume @vol is dynamic, and changes MTD device size if the + * volume is static. This is needed because static volumes cannot be read past + * data they contain. This function returns zero in case of success and a + * negative error code in case of error. + */ +static int gluebi_updated(struct ubi_volume_info *vi) +{ + struct gluebi_device *gluebi; + + mutex_lock(&devices_mutex); + gluebi = find_gluebi_nolock(vi->ubi_num, vi->vol_id); + if (!gluebi) { + mutex_unlock(&devices_mutex); + err_msg("got update notification for unknown UBI device %d volume %d", + vi->ubi_num, vi->vol_id); + return -ENOENT; + } + + if (vi->vol_type == UBI_STATIC_VOLUME) + gluebi->mtd.size = vi->used_bytes; + mutex_unlock(&devices_mutex); + return 0; +} + +/** + * gluebi_resized - UBI volume was re-sized notifier. + * @vi: volume info structure + * + * This function is called every time an UBI volume is re-size. It changes the + * corresponding fake MTD device size. This function returns zero in case of + * success and a negative error code in case of error. + */ +static int gluebi_resized(struct ubi_volume_info *vi) +{ + struct gluebi_device *gluebi; + + mutex_lock(&devices_mutex); + gluebi = find_gluebi_nolock(vi->ubi_num, vi->vol_id); + if (!gluebi) { + mutex_unlock(&devices_mutex); + err_msg("got update notification for unknown UBI device %d volume %d", + vi->ubi_num, vi->vol_id); + return -ENOENT; + } + gluebi->mtd.size = vi->used_bytes; + mutex_unlock(&devices_mutex); + return 0; +} + +/** + * gluebi_notify - UBI notification handler. + * @nb: registered notifier block + * @l: notification type + * @ptr: pointer to the &struct ubi_notification object + */ +static int gluebi_notify(struct notifier_block *nb, unsigned long l, + void *ns_ptr) +{ + struct ubi_notification *nt = ns_ptr; + + switch (l) { + case UBI_VOLUME_ADDED: + gluebi_create(&nt->di, &nt->vi); + break; + case UBI_VOLUME_REMOVED: + gluebi_remove(&nt->vi); + break; + case UBI_VOLUME_RESIZED: + gluebi_resized(&nt->vi); + break; + case UBI_VOLUME_UPDATED: + gluebi_updated(&nt->vi); + break; + default: + break; + } + return NOTIFY_OK; +} + +static struct notifier_block gluebi_notifier = { + .notifier_call = gluebi_notify, +}; + +static int __init ubi_gluebi_init(void) +{ + return ubi_register_volume_notifier(&gluebi_notifier, 0); +} + +static void __exit ubi_gluebi_exit(void) +{ + struct gluebi_device *gluebi, *g; + + list_for_each_entry_safe(gluebi, g, &gluebi_devices, list) { + int err; + struct mtd_info *mtd = &gluebi->mtd; + + err = mtd_device_unregister(mtd); + if (err) + err_msg("error %d while removing gluebi MTD device %d, UBI device %d, volume %d - ignoring", + err, mtd->index, gluebi->ubi_num, + gluebi->vol_id); + kfree(mtd->name); + kfree(gluebi); + } + ubi_unregister_volume_notifier(&gluebi_notifier); +} + +module_init(ubi_gluebi_init); +module_exit(ubi_gluebi_exit); +MODULE_DESCRIPTION("MTD emulation layer over UBI volumes"); +MODULE_AUTHOR("Artem Bityutskiy, Joern Engel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c new file mode 100644 index 00000000000..d36134925d3 --- /dev/null +++ b/drivers/mtd/ubi/io.c @@ -0,0 +1,1426 @@ +/* + * Copyright (c) International Business Machines Corp., 2006 + * Copyright (c) Nokia Corporation, 2006, 2007 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Artem Bityutskiy (Битюцкий Артём) + */ + +/* + * UBI input/output sub-system. + * + * This sub-system provides a uniform way to work with all kinds of the + * underlying MTD devices. It also implements handy functions for reading and + * writing UBI headers. + * + * We are trying to have a paranoid mindset and not to trust to what we read + * from the flash media in order to be more secure and robust. So this + * sub-system validates every single header it reads from the flash media. + * + * Some words about how the eraseblock headers are stored. + * + * The erase counter header is always stored at offset zero. By default, the + * VID header is stored after the EC header at the closest aligned offset + * (i.e. aligned to the minimum I/O unit size). Data starts next to the VID + * header at the closest aligned offset. But this default layout may be + * changed. For example, for different reasons (e.g., optimization) UBI may be + * asked to put the VID header at further offset, and even at an unaligned + * offset. Of course, if the offset of the VID header is unaligned, UBI adds + * proper padding in front of it. Data offset may also be changed but it has to + * be aligned. + * + * About minimal I/O units. In general, UBI assumes flash device model where + * there is only one minimal I/O unit size. E.g., in case of NOR flash it is 1, + * in case of NAND flash it is a NAND page, etc. This is reported by MTD in the + * @ubi->mtd->writesize field. But as an exception, UBI admits of using another + * (smaller) minimal I/O unit size for EC and VID headers to make it possible + * to do different optimizations. + * + * This is extremely useful in case of NAND flashes which admit of several + * write operations to one NAND page. In this case UBI can fit EC and VID + * headers at one NAND page. Thus, UBI may use "sub-page" size as the minimal + * I/O unit for the headers (the @ubi->hdrs_min_io_size field). But it still + * reports NAND page size (@ubi->min_io_size) as a minimal I/O unit for the UBI + * users. + * + * Example: some Samsung NANDs with 2KiB pages allow 4x 512-byte writes, so + * although the minimal I/O unit is 2K, UBI uses 512 bytes for EC and VID + * headers. + * + * Q: why not just to treat sub-page as a minimal I/O unit of this flash + * device, e.g., make @ubi->min_io_size = 512 in the example above? + * + * A: because when writing a sub-page, MTD still writes a full 2K page but the + * bytes which are not relevant to the sub-page are 0xFF. So, basically, + * writing 4x512 sub-pages is 4 times slower than writing one 2KiB NAND page. + * Thus, we prefer to use sub-pages only for EC and VID headers. + * + * As it was noted above, the VID header may start at a non-aligned offset. + * For example, in case of a 2KiB page NAND flash with a 512 bytes sub-page, + * the VID header may reside at offset 1984 which is the last 64 bytes of the + * last sub-page (EC header is always at offset zero). This causes some + * difficulties when reading and writing VID headers. + * + * Suppose we have a 64-byte buffer and we read a VID header at it. We change + * the data and want to write this VID header out. As we can only write in + * 512-byte chunks, we have to allocate one more buffer and copy our VID header + * to offset 448 of this buffer. + * + * The I/O sub-system does the following trick in order to avoid this extra + * copy. It always allocates a @ubi->vid_hdr_alsize bytes buffer for the VID + * header and returns a pointer to offset @ubi->vid_hdr_shift of this buffer. + * When the VID header is being written out, it shifts the VID header pointer + * back and writes the whole sub-page. + */ + +#include <linux/crc32.h> +#include <linux/err.h> +#include <linux/slab.h> +#include "ubi.h" + +static int self_check_not_bad(const struct ubi_device *ubi, int pnum); +static int self_check_peb_ec_hdr(const struct ubi_device *ubi, int pnum); +static int self_check_ec_hdr(const struct ubi_device *ubi, int pnum, + const struct ubi_ec_hdr *ec_hdr); +static int self_check_peb_vid_hdr(const struct ubi_device *ubi, int pnum); +static int self_check_vid_hdr(const struct ubi_device *ubi, int pnum, + const struct ubi_vid_hdr *vid_hdr); +static int self_check_write(struct ubi_device *ubi, const void *buf, int pnum, + int offset, int len); + +/** + * ubi_io_read - read data from a physical eraseblock. + * @ubi: UBI device description object + * @buf: buffer where to store the read data + * @pnum: physical eraseblock number to read from + * @offset: offset within the physical eraseblock from where to read + * @len: how many bytes to read + * + * This function reads data from offset @offset of physical eraseblock @pnum + * and stores the read data in the @buf buffer. The following return codes are + * possible: + * + * o %0 if all the requested data were successfully read; + * o %UBI_IO_BITFLIPS if all the requested data were successfully read, but + * correctable bit-flips were detected; this is harmless but may indicate + * that this eraseblock may become bad soon (but do not have to); + * o %-EBADMSG if the MTD subsystem reported about data integrity problems, for + * example it can be an ECC error in case of NAND; this most probably means + * that the data is corrupted; + * o %-EIO if some I/O error occurred; + * o other negative error codes in case of other errors. + */ +int ubi_io_read(const struct ubi_device *ubi, void *buf, int pnum, int offset, + int len) +{ + int err, retries = 0; + size_t read; + loff_t addr; + + dbg_io("read %d bytes from PEB %d:%d", len, pnum, offset); + + ubi_assert(pnum >= 0 && pnum < ubi->peb_count); + ubi_assert(offset >= 0 && offset + len <= ubi->peb_size); + ubi_assert(len > 0); + + err = self_check_not_bad(ubi, pnum); + if (err) + return err; + + /* + * Deliberately corrupt the buffer to improve robustness. Indeed, if we + * do not do this, the following may happen: + * 1. The buffer contains data from previous operation, e.g., read from + * another PEB previously. The data looks like expected, e.g., if we + * just do not read anything and return - the caller would not + * notice this. E.g., if we are reading a VID header, the buffer may + * contain a valid VID header from another PEB. + * 2. The driver is buggy and returns us success or -EBADMSG or + * -EUCLEAN, but it does not actually put any data to the buffer. + * + * This may confuse UBI or upper layers - they may think the buffer + * contains valid data while in fact it is just old data. This is + * especially possible because UBI (and UBIFS) relies on CRC, and + * treats data as correct even in case of ECC errors if the CRC is + * correct. + * + * Try to prevent this situation by changing the first byte of the + * buffer. + */ + *((uint8_t *)buf) ^= 0xFF; + + addr = (loff_t)pnum * ubi->peb_size + offset; +retry: + err = mtd_read(ubi->mtd, addr, len, &read, buf); + if (err) { + const char *errstr = mtd_is_eccerr(err) ? " (ECC error)" : ""; + + if (mtd_is_bitflip(err)) { + /* + * -EUCLEAN is reported if there was a bit-flip which + * was corrected, so this is harmless. + * + * We do not report about it here unless debugging is + * enabled. A corresponding message will be printed + * later, when it is has been scrubbed. + */ + ubi_msg("fixable bit-flip detected at PEB %d", pnum); + ubi_assert(len == read); + return UBI_IO_BITFLIPS; + } + + if (retries++ < UBI_IO_RETRIES) { + ubi_warn("error %d%s while reading %d bytes from PEB %d:%d, read only %zd bytes, retry", + err, errstr, len, pnum, offset, read); + yield(); + goto retry; + } + + ubi_err("error %d%s while reading %d bytes from PEB %d:%d, read %zd bytes", + err, errstr, len, pnum, offset, read); + dump_stack(); + + /* + * The driver should never return -EBADMSG if it failed to read + * all the requested data. But some buggy drivers might do + * this, so we change it to -EIO. + */ + if (read != len && mtd_is_eccerr(err)) { + ubi_assert(0); + err = -EIO; + } + } else { + ubi_assert(len == read); + + if (ubi_dbg_is_bitflip(ubi)) { + dbg_gen("bit-flip (emulated)"); + err = UBI_IO_BITFLIPS; + } + } + + return err; +} + +/** + * ubi_io_write - write data to a physical eraseblock. + * @ubi: UBI device description object + * @buf: buffer with the data to write + * @pnum: physical eraseblock number to write to + * @offset: offset within the physical eraseblock where to write + * @len: how many bytes to write + * + * This function writes @len bytes of data from buffer @buf to offset @offset + * of physical eraseblock @pnum. If all the data were successfully written, + * zero is returned. If an error occurred, this function returns a negative + * error code. If %-EIO is returned, the physical eraseblock most probably went + * bad. + * + * Note, in case of an error, it is possible that something was still written + * to the flash media, but may be some garbage. + */ +int ubi_io_write(struct ubi_device *ubi, const void *buf, int pnum, int offset, + int len) +{ + int err; + size_t written; + loff_t addr; + + dbg_io("write %d bytes to PEB %d:%d", len, pnum, offset); + + ubi_assert(pnum >= 0 && pnum < ubi->peb_count); + ubi_assert(offset >= 0 && offset + len <= ubi->peb_size); + ubi_assert(offset % ubi->hdrs_min_io_size == 0); + ubi_assert(len > 0 && len % ubi->hdrs_min_io_size == 0); + + if (ubi->ro_mode) { + ubi_err("read-only mode"); + return -EROFS; + } + + err = self_check_not_bad(ubi, pnum); + if (err) + return err; + + /* The area we are writing to has to contain all 0xFF bytes */ + err = ubi_self_check_all_ff(ubi, pnum, offset, len); + if (err) + return err; + + if (offset >= ubi->leb_start) { + /* + * We write to the data area of the physical eraseblock. Make + * sure it has valid EC and VID headers. + */ + err = self_check_peb_ec_hdr(ubi, pnum); + if (err) + return err; + err = self_check_peb_vid_hdr(ubi, pnum); + if (err) + return err; + } + + if (ubi_dbg_is_write_failure(ubi)) { + ubi_err("cannot write %d bytes to PEB %d:%d (emulated)", + len, pnum, offset); + dump_stack(); + return -EIO; + } + + addr = (loff_t)pnum * ubi->peb_size + offset; + err = mtd_write(ubi->mtd, addr, len, &written, buf); + if (err) { + ubi_err("error %d while writing %d bytes to PEB %d:%d, written %zd bytes", + err, len, pnum, offset, written); + dump_stack(); + ubi_dump_flash(ubi, pnum, offset, len); + } else + ubi_assert(written == len); + + if (!err) { + err = self_check_write(ubi, buf, pnum, offset, len); + if (err) + return err; + + /* + * Since we always write sequentially, the rest of the PEB has + * to contain only 0xFF bytes. + */ + offset += len; + len = ubi->peb_size - offset; + if (len) + err = ubi_self_check_all_ff(ubi, pnum, offset, len); + } + + return err; +} + +/** + * erase_callback - MTD erasure call-back. + * @ei: MTD erase information object. + * + * Note, even though MTD erase interface is asynchronous, all the current + * implementations are synchronous anyway. + */ +static void erase_callback(struct erase_info *ei) +{ + wake_up_interruptible((wait_queue_head_t *)ei->priv); +} + +/** + * do_sync_erase - synchronously erase a physical eraseblock. + * @ubi: UBI device description object + * @pnum: the physical eraseblock number to erase + * + * This function synchronously erases physical eraseblock @pnum and returns + * zero in case of success and a negative error code in case of failure. If + * %-EIO is returned, the physical eraseblock most probably went bad. + */ +static int do_sync_erase(struct ubi_device *ubi, int pnum) +{ + int err, retries = 0; + struct erase_info ei; + wait_queue_head_t wq; + + dbg_io("erase PEB %d", pnum); + ubi_assert(pnum >= 0 && pnum < ubi->peb_count); + + if (ubi->ro_mode) { + ubi_err("read-only mode"); + return -EROFS; + } + +retry: + init_waitqueue_head(&wq); + memset(&ei, 0, sizeof(struct erase_info)); + + ei.mtd = ubi->mtd; + ei.addr = (loff_t)pnum * ubi->peb_size; + ei.len = ubi->peb_size; + ei.callback = erase_callback; + ei.priv = (unsigned long)&wq; + + err = mtd_erase(ubi->mtd, &ei); + if (err) { + if (retries++ < UBI_IO_RETRIES) { + ubi_warn("error %d while erasing PEB %d, retry", + err, pnum); + yield(); + goto retry; + } + ubi_err("cannot erase PEB %d, error %d", pnum, err); + dump_stack(); + return err; + } + + err = wait_event_interruptible(wq, ei.state == MTD_ERASE_DONE || + ei.state == MTD_ERASE_FAILED); + if (err) { + ubi_err("interrupted PEB %d erasure", pnum); + return -EINTR; + } + + if (ei.state == MTD_ERASE_FAILED) { + if (retries++ < UBI_IO_RETRIES) { + ubi_warn("error while erasing PEB %d, retry", pnum); + yield(); + goto retry; + } + ubi_err("cannot erase PEB %d", pnum); + dump_stack(); + return -EIO; + } + + err = ubi_self_check_all_ff(ubi, pnum, 0, ubi->peb_size); + if (err) + return err; + + if (ubi_dbg_is_erase_failure(ubi)) { + ubi_err("cannot erase PEB %d (emulated)", pnum); + return -EIO; + } + + return 0; +} + +/* Patterns to write to a physical eraseblock when torturing it */ +static uint8_t patterns[] = {0xa5, 0x5a, 0x0}; + +/** + * torture_peb - test a supposedly bad physical eraseblock. + * @ubi: UBI device description object + * @pnum: the physical eraseblock number to test + * + * This function returns %-EIO if the physical eraseblock did not pass the + * test, a positive number of erase operations done if the test was + * successfully passed, and other negative error codes in case of other errors. + */ +static int torture_peb(struct ubi_device *ubi, int pnum) +{ + int err, i, patt_count; + + ubi_msg("run torture test for PEB %d", pnum); + patt_count = ARRAY_SIZE(patterns); + ubi_assert(patt_count > 0); + + mutex_lock(&ubi->buf_mutex); + for (i = 0; i < patt_count; i++) { + err = do_sync_erase(ubi, pnum); + if (err) + goto out; + + /* Make sure the PEB contains only 0xFF bytes */ + err = ubi_io_read(ubi, ubi->peb_buf, pnum, 0, ubi->peb_size); + if (err) + goto out; + + err = ubi_check_pattern(ubi->peb_buf, 0xFF, ubi->peb_size); + if (err == 0) { + ubi_err("erased PEB %d, but a non-0xFF byte found", + pnum); + err = -EIO; + goto out; + } + + /* Write a pattern and check it */ + memset(ubi->peb_buf, patterns[i], ubi->peb_size); + err = ubi_io_write(ubi, ubi->peb_buf, pnum, 0, ubi->peb_size); + if (err) + goto out; + + memset(ubi->peb_buf, ~patterns[i], ubi->peb_size); + err = ubi_io_read(ubi, ubi->peb_buf, pnum, 0, ubi->peb_size); + if (err) + goto out; + + err = ubi_check_pattern(ubi->peb_buf, patterns[i], + ubi->peb_size); + if (err == 0) { + ubi_err("pattern %x checking failed for PEB %d", + patterns[i], pnum); + err = -EIO; + goto out; + } + } + + err = patt_count; + ubi_msg("PEB %d passed torture test, do not mark it as bad", pnum); + +out: + mutex_unlock(&ubi->buf_mutex); + if (err == UBI_IO_BITFLIPS || mtd_is_eccerr(err)) { + /* + * If a bit-flip or data integrity error was detected, the test + * has not passed because it happened on a freshly erased + * physical eraseblock which means something is wrong with it. + */ + ubi_err("read problems on freshly erased PEB %d, must be bad", + pnum); + err = -EIO; + } + return err; +} + +/** + * nor_erase_prepare - prepare a NOR flash PEB for erasure. + * @ubi: UBI device description object + * @pnum: physical eraseblock number to prepare + * + * NOR flash, or at least some of them, have peculiar embedded PEB erasure + * algorithm: the PEB is first filled with zeroes, then it is erased. And + * filling with zeroes starts from the end of the PEB. This was observed with + * Spansion S29GL512N NOR flash. + * + * This means that in case of a power cut we may end up with intact data at the + * beginning of the PEB, and all zeroes at the end of PEB. In other words, the + * EC and VID headers are OK, but a large chunk of data at the end of PEB is + * zeroed. This makes UBI mistakenly treat this PEB as used and associate it + * with an LEB, which leads to subsequent failures (e.g., UBIFS fails). + * + * This function is called before erasing NOR PEBs and it zeroes out EC and VID + * magic numbers in order to invalidate them and prevent the failures. Returns + * zero in case of success and a negative error code in case of failure. + */ +static int nor_erase_prepare(struct ubi_device *ubi, int pnum) +{ + int err; + size_t written; + loff_t addr; + uint32_t data = 0; + struct ubi_ec_hdr ec_hdr; + + /* + * Note, we cannot generally define VID header buffers on stack, + * because of the way we deal with these buffers (see the header + * comment in this file). But we know this is a NOR-specific piece of + * code, so we can do this. But yes, this is error-prone and we should + * (pre-)allocate VID header buffer instead. + */ + struct ubi_vid_hdr vid_hdr; + + /* + * If VID or EC is valid, we have to corrupt them before erasing. + * It is important to first invalidate the EC header, and then the VID + * header. Otherwise a power cut may lead to valid EC header and + * invalid VID header, in which case UBI will treat this PEB as + * corrupted and will try to preserve it, and print scary warnings. + */ + addr = (loff_t)pnum * ubi->peb_size; + err = ubi_io_read_ec_hdr(ubi, pnum, &ec_hdr, 0); + if (err != UBI_IO_BAD_HDR_EBADMSG && err != UBI_IO_BAD_HDR && + err != UBI_IO_FF){ + err = mtd_write(ubi->mtd, addr, 4, &written, (void *)&data); + if(err) + goto error; + } + + err = ubi_io_read_vid_hdr(ubi, pnum, &vid_hdr, 0); + if (err != UBI_IO_BAD_HDR_EBADMSG && err != UBI_IO_BAD_HDR && + err != UBI_IO_FF){ + addr += ubi->vid_hdr_aloffset; + err = mtd_write(ubi->mtd, addr, 4, &written, (void *)&data); + if (err) + goto error; + } + return 0; + +error: + /* + * The PEB contains a valid VID or EC header, but we cannot invalidate + * it. Supposedly the flash media or the driver is screwed up, so + * return an error. + */ + ubi_err("cannot invalidate PEB %d, write returned %d", pnum, err); + ubi_dump_flash(ubi, pnum, 0, ubi->peb_size); + return -EIO; +} + +/** + * ubi_io_sync_erase - synchronously erase a physical eraseblock. + * @ubi: UBI device description object + * @pnum: physical eraseblock number to erase + * @torture: if this physical eraseblock has to be tortured + * + * This function synchronously erases physical eraseblock @pnum. If @torture + * flag is not zero, the physical eraseblock is checked by means of writing + * different patterns to it and reading them back. If the torturing is enabled, + * the physical eraseblock is erased more than once. + * + * This function returns the number of erasures made in case of success, %-EIO + * if the erasure failed or the torturing test failed, and other negative error + * codes in case of other errors. Note, %-EIO means that the physical + * eraseblock is bad. + */ +int ubi_io_sync_erase(struct ubi_device *ubi, int pnum, int torture) +{ + int err, ret = 0; + + ubi_assert(pnum >= 0 && pnum < ubi->peb_count); + + err = self_check_not_bad(ubi, pnum); + if (err != 0) + return err; + + if (ubi->ro_mode) { + ubi_err("read-only mode"); + return -EROFS; + } + + if (ubi->nor_flash) { + err = nor_erase_prepare(ubi, pnum); + if (err) + return err; + } + + if (torture) { + ret = torture_peb(ubi, pnum); + if (ret < 0) + return ret; + } + + err = do_sync_erase(ubi, pnum); + if (err) + return err; + + return ret + 1; +} + +/** + * ubi_io_is_bad - check if a physical eraseblock is bad. + * @ubi: UBI device description object + * @pnum: the physical eraseblock number to check + * + * This function returns a positive number if the physical eraseblock is bad, + * zero if not, and a negative error code if an error occurred. + */ +int ubi_io_is_bad(const struct ubi_device *ubi, int pnum) +{ + struct mtd_info *mtd = ubi->mtd; + + ubi_assert(pnum >= 0 && pnum < ubi->peb_count); + + if (ubi->bad_allowed) { + int ret; + + ret = mtd_block_isbad(mtd, (loff_t)pnum * ubi->peb_size); + if (ret < 0) + ubi_err("error %d while checking if PEB %d is bad", + ret, pnum); + else if (ret) + dbg_io("PEB %d is bad", pnum); + return ret; + } + + return 0; +} + +/** + * ubi_io_mark_bad - mark a physical eraseblock as bad. + * @ubi: UBI device description object + * @pnum: the physical eraseblock number to mark + * + * This function returns zero in case of success and a negative error code in + * case of failure. + */ +int ubi_io_mark_bad(const struct ubi_device *ubi, int pnum) +{ + int err; + struct mtd_info *mtd = ubi->mtd; + + ubi_assert(pnum >= 0 && pnum < ubi->peb_count); + + if (ubi->ro_mode) { + ubi_err("read-only mode"); + return -EROFS; + } + + if (!ubi->bad_allowed) + return 0; + + err = mtd_block_markbad(mtd, (loff_t)pnum * ubi->peb_size); + if (err) + ubi_err("cannot mark PEB %d bad, error %d", pnum, err); + return err; +} + +/** + * validate_ec_hdr - validate an erase counter header. + * @ubi: UBI device description object + * @ec_hdr: the erase counter header to check + * + * This function returns zero if the erase counter header is OK, and %1 if + * not. + */ +static int validate_ec_hdr(const struct ubi_device *ubi, + const struct ubi_ec_hdr *ec_hdr) +{ + long long ec; + int vid_hdr_offset, leb_start; + + ec = be64_to_cpu(ec_hdr->ec); + vid_hdr_offset = be32_to_cpu(ec_hdr->vid_hdr_offset); + leb_start = be32_to_cpu(ec_hdr->data_offset); + + if (ec_hdr->version != UBI_VERSION) { + ubi_err("node with incompatible UBI version found: this UBI version is %d, image version is %d", + UBI_VERSION, (int)ec_hdr->version); + goto bad; + } + + if (vid_hdr_offset != ubi->vid_hdr_offset) { + ubi_err("bad VID header offset %d, expected %d", + vid_hdr_offset, ubi->vid_hdr_offset); + goto bad; + } + + if (leb_start != ubi->leb_start) { + ubi_err("bad data offset %d, expected %d", + leb_start, ubi->leb_start); + goto bad; + } + + if (ec < 0 || ec > UBI_MAX_ERASECOUNTER) { + ubi_err("bad erase counter %lld", ec); + goto bad; + } + + return 0; + +bad: + ubi_err("bad EC header"); + ubi_dump_ec_hdr(ec_hdr); + dump_stack(); + return 1; +} + +/** + * ubi_io_read_ec_hdr - read and check an erase counter header. + * @ubi: UBI device description object + * @pnum: physical eraseblock to read from + * @ec_hdr: a &struct ubi_ec_hdr object where to store the read erase counter + * header + * @verbose: be verbose if the header is corrupted or was not found + * + * This function reads erase counter header from physical eraseblock @pnum and + * stores it in @ec_hdr. This function also checks CRC checksum of the read + * erase counter header. The following codes may be returned: + * + * o %0 if the CRC checksum is correct and the header was successfully read; + * o %UBI_IO_BITFLIPS if the CRC is correct, but bit-flips were detected + * and corrected by the flash driver; this is harmless but may indicate that + * this eraseblock may become bad soon (but may be not); + * o %UBI_IO_BAD_HDR if the erase counter header is corrupted (a CRC error); + * o %UBI_IO_BAD_HDR_EBADMSG is the same as %UBI_IO_BAD_HDR, but there also was + * a data integrity error (uncorrectable ECC error in case of NAND); + * o %UBI_IO_FF if only 0xFF bytes were read (the PEB is supposedly empty) + * o a negative error code in case of failure. + */ +int ubi_io_read_ec_hdr(struct ubi_device *ubi, int pnum, + struct ubi_ec_hdr *ec_hdr, int verbose) +{ + int err, read_err; + uint32_t crc, magic, hdr_crc; + + dbg_io("read EC header from PEB %d", pnum); + ubi_assert(pnum >= 0 && pnum < ubi->peb_count); + + read_err = ubi_io_read(ubi, ec_hdr, pnum, 0, UBI_EC_HDR_SIZE); + if (read_err) { + if (read_err != UBI_IO_BITFLIPS && !mtd_is_eccerr(read_err)) + return read_err; + + /* + * We read all the data, but either a correctable bit-flip + * occurred, or MTD reported a data integrity error + * (uncorrectable ECC error in case of NAND). The former is + * harmless, the later may mean that the read data is + * corrupted. But we have a CRC check-sum and we will detect + * this. If the EC header is still OK, we just report this as + * there was a bit-flip, to force scrubbing. + */ + } + + magic = be32_to_cpu(ec_hdr->magic); + if (magic != UBI_EC_HDR_MAGIC) { + if (mtd_is_eccerr(read_err)) + return UBI_IO_BAD_HDR_EBADMSG; + + /* + * The magic field is wrong. Let's check if we have read all + * 0xFF. If yes, this physical eraseblock is assumed to be + * empty. + */ + if (ubi_check_pattern(ec_hdr, 0xFF, UBI_EC_HDR_SIZE)) { + /* The physical eraseblock is supposedly empty */ + if (verbose) + ubi_warn("no EC header found at PEB %d, only 0xFF bytes", + pnum); + dbg_bld("no EC header found at PEB %d, only 0xFF bytes", + pnum); + if (!read_err) + return UBI_IO_FF; + else + return UBI_IO_FF_BITFLIPS; + } + + /* + * This is not a valid erase counter header, and these are not + * 0xFF bytes. Report that the header is corrupted. + */ + if (verbose) { + ubi_warn("bad magic number at PEB %d: %08x instead of %08x", + pnum, magic, UBI_EC_HDR_MAGIC); + ubi_dump_ec_hdr(ec_hdr); + } + dbg_bld("bad magic number at PEB %d: %08x instead of %08x", + pnum, magic, UBI_EC_HDR_MAGIC); + return UBI_IO_BAD_HDR; + } + + crc = crc32(UBI_CRC32_INIT, ec_hdr, UBI_EC_HDR_SIZE_CRC); + hdr_crc = be32_to_cpu(ec_hdr->hdr_crc); + + if (hdr_crc != crc) { + if (verbose) { + ubi_warn("bad EC header CRC at PEB %d, calculated %#08x, read %#08x", + pnum, crc, hdr_crc); + ubi_dump_ec_hdr(ec_hdr); + } + dbg_bld("bad EC header CRC at PEB %d, calculated %#08x, read %#08x", + pnum, crc, hdr_crc); + + if (!read_err) + return UBI_IO_BAD_HDR; + else + return UBI_IO_BAD_HDR_EBADMSG; + } + + /* And of course validate what has just been read from the media */ + err = validate_ec_hdr(ubi, ec_hdr); + if (err) { + ubi_err("validation failed for PEB %d", pnum); + return -EINVAL; + } + + /* + * If there was %-EBADMSG, but the header CRC is still OK, report about + * a bit-flip to force scrubbing on this PEB. + */ + return read_err ? UBI_IO_BITFLIPS : 0; +} + +/** + * ubi_io_write_ec_hdr - write an erase counter header. + * @ubi: UBI device description object + * @pnum: physical eraseblock to write to + * @ec_hdr: the erase counter header to write + * + * This function writes erase counter header described by @ec_hdr to physical + * eraseblock @pnum. It also fills most fields of @ec_hdr before writing, so + * the caller do not have to fill them. Callers must only fill the @ec_hdr->ec + * field. + * + * This function returns zero in case of success and a negative error code in + * case of failure. If %-EIO is returned, the physical eraseblock most probably + * went bad. + */ +int ubi_io_write_ec_hdr(struct ubi_device *ubi, int pnum, + struct ubi_ec_hdr *ec_hdr) +{ + int err; + uint32_t crc; + + dbg_io("write EC header to PEB %d", pnum); + ubi_assert(pnum >= 0 && pnum < ubi->peb_count); + + ec_hdr->magic = cpu_to_be32(UBI_EC_HDR_MAGIC); + ec_hdr->version = UBI_VERSION; + ec_hdr->vid_hdr_offset = cpu_to_be32(ubi->vid_hdr_offset); + ec_hdr->data_offset = cpu_to_be32(ubi->leb_start); + ec_hdr->image_seq = cpu_to_be32(ubi->image_seq); + crc = crc32(UBI_CRC32_INIT, ec_hdr, UBI_EC_HDR_SIZE_CRC); + ec_hdr->hdr_crc = cpu_to_be32(crc); + + err = self_check_ec_hdr(ubi, pnum, ec_hdr); + if (err) + return err; + + err = ubi_io_write(ubi, ec_hdr, pnum, 0, ubi->ec_hdr_alsize); + return err; +} + +/** + * validate_vid_hdr - validate a volume identifier header. + * @ubi: UBI device description object + * @vid_hdr: the volume identifier header to check + * + * This function checks that data stored in the volume identifier header + * @vid_hdr. Returns zero if the VID header is OK and %1 if not. + */ +static int validate_vid_hdr(const struct ubi_device *ubi, + const struct ubi_vid_hdr *vid_hdr) +{ + int vol_type = vid_hdr->vol_type; + int copy_flag = vid_hdr->copy_flag; + int vol_id = be32_to_cpu(vid_hdr->vol_id); + int lnum = be32_to_cpu(vid_hdr->lnum); + int compat = vid_hdr->compat; + int data_size = be32_to_cpu(vid_hdr->data_size); + int used_ebs = be32_to_cpu(vid_hdr->used_ebs); + int data_pad = be32_to_cpu(vid_hdr->data_pad); + int data_crc = be32_to_cpu(vid_hdr->data_crc); + int usable_leb_size = ubi->leb_size - data_pad; + + if (copy_flag != 0 && copy_flag != 1) { + ubi_err("bad copy_flag"); + goto bad; + } + + if (vol_id < 0 || lnum < 0 || data_size < 0 || used_ebs < 0 || + data_pad < 0) { + ubi_err("negative values"); + goto bad; + } + + if (vol_id >= UBI_MAX_VOLUMES && vol_id < UBI_INTERNAL_VOL_START) { + ubi_err("bad vol_id"); + goto bad; + } + + if (vol_id < UBI_INTERNAL_VOL_START && compat != 0) { + ubi_err("bad compat"); + goto bad; + } + + if (vol_id >= UBI_INTERNAL_VOL_START && compat != UBI_COMPAT_DELETE && + compat != UBI_COMPAT_RO && compat != UBI_COMPAT_PRESERVE && + compat != UBI_COMPAT_REJECT) { + ubi_err("bad compat"); + goto bad; + } + + if (vol_type != UBI_VID_DYNAMIC && vol_type != UBI_VID_STATIC) { + ubi_err("bad vol_type"); + goto bad; + } + + if (data_pad >= ubi->leb_size / 2) { + ubi_err("bad data_pad"); + goto bad; + } + + if (vol_type == UBI_VID_STATIC) { + /* + * Although from high-level point of view static volumes may + * contain zero bytes of data, but no VID headers can contain + * zero at these fields, because they empty volumes do not have + * mapped logical eraseblocks. + */ + if (used_ebs == 0) { + ubi_err("zero used_ebs"); + goto bad; + } + if (data_size == 0) { + ubi_err("zero data_size"); + goto bad; + } + if (lnum < used_ebs - 1) { + if (data_size != usable_leb_size) { + ubi_err("bad data_size"); + goto bad; + } + } else if (lnum == used_ebs - 1) { + if (data_size == 0) { + ubi_err("bad data_size at last LEB"); + goto bad; + } + } else { + ubi_err("too high lnum"); + goto bad; + } + } else { + if (copy_flag == 0) { + if (data_crc != 0) { + ubi_err("non-zero data CRC"); + goto bad; + } + if (data_size != 0) { + ubi_err("non-zero data_size"); + goto bad; + } + } else { + if (data_size == 0) { + ubi_err("zero data_size of copy"); + goto bad; + } + } + if (used_ebs != 0) { + ubi_err("bad used_ebs"); + goto bad; + } + } + + return 0; + +bad: + ubi_err("bad VID header"); + ubi_dump_vid_hdr(vid_hdr); + dump_stack(); + return 1; +} + +/** + * ubi_io_read_vid_hdr - read and check a volume identifier header. + * @ubi: UBI device description object + * @pnum: physical eraseblock number to read from + * @vid_hdr: &struct ubi_vid_hdr object where to store the read volume + * identifier header + * @verbose: be verbose if the header is corrupted or wasn't found + * + * This function reads the volume identifier header from physical eraseblock + * @pnum and stores it in @vid_hdr. It also checks CRC checksum of the read + * volume identifier header. The error codes are the same as in + * 'ubi_io_read_ec_hdr()'. + * + * Note, the implementation of this function is also very similar to + * 'ubi_io_read_ec_hdr()', so refer commentaries in 'ubi_io_read_ec_hdr()'. + */ +int ubi_io_read_vid_hdr(struct ubi_device *ubi, int pnum, + struct ubi_vid_hdr *vid_hdr, int verbose) +{ + int err, read_err; + uint32_t crc, magic, hdr_crc; + void *p; + + dbg_io("read VID header from PEB %d", pnum); + ubi_assert(pnum >= 0 && pnum < ubi->peb_count); + + p = (char *)vid_hdr - ubi->vid_hdr_shift; + read_err = ubi_io_read(ubi, p, pnum, ubi->vid_hdr_aloffset, + ubi->vid_hdr_alsize); + if (read_err && read_err != UBI_IO_BITFLIPS && !mtd_is_eccerr(read_err)) + return read_err; + + magic = be32_to_cpu(vid_hdr->magic); + if (magic != UBI_VID_HDR_MAGIC) { + if (mtd_is_eccerr(read_err)) + return UBI_IO_BAD_HDR_EBADMSG; + + if (ubi_check_pattern(vid_hdr, 0xFF, UBI_VID_HDR_SIZE)) { + if (verbose) + ubi_warn("no VID header found at PEB %d, only 0xFF bytes", + pnum); + dbg_bld("no VID header found at PEB %d, only 0xFF bytes", + pnum); + if (!read_err) + return UBI_IO_FF; + else + return UBI_IO_FF_BITFLIPS; + } + + if (verbose) { + ubi_warn("bad magic number at PEB %d: %08x instead of %08x", + pnum, magic, UBI_VID_HDR_MAGIC); + ubi_dump_vid_hdr(vid_hdr); + } + dbg_bld("bad magic number at PEB %d: %08x instead of %08x", + pnum, magic, UBI_VID_HDR_MAGIC); + return UBI_IO_BAD_HDR; + } + + crc = crc32(UBI_CRC32_INIT, vid_hdr, UBI_VID_HDR_SIZE_CRC); + hdr_crc = be32_to_cpu(vid_hdr->hdr_crc); + + if (hdr_crc != crc) { + if (verbose) { + ubi_warn("bad CRC at PEB %d, calculated %#08x, read %#08x", + pnum, crc, hdr_crc); + ubi_dump_vid_hdr(vid_hdr); + } + dbg_bld("bad CRC at PEB %d, calculated %#08x, read %#08x", + pnum, crc, hdr_crc); + if (!read_err) + return UBI_IO_BAD_HDR; + else + return UBI_IO_BAD_HDR_EBADMSG; + } + + err = validate_vid_hdr(ubi, vid_hdr); + if (err) { + ubi_err("validation failed for PEB %d", pnum); + return -EINVAL; + } + + return read_err ? UBI_IO_BITFLIPS : 0; +} + +/** + * ubi_io_write_vid_hdr - write a volume identifier header. + * @ubi: UBI device description object + * @pnum: the physical eraseblock number to write to + * @vid_hdr: the volume identifier header to write + * + * This function writes the volume identifier header described by @vid_hdr to + * physical eraseblock @pnum. This function automatically fills the + * @vid_hdr->magic and the @vid_hdr->version fields, as well as calculates + * header CRC checksum and stores it at vid_hdr->hdr_crc. + * + * This function returns zero in case of success and a negative error code in + * case of failure. If %-EIO is returned, the physical eraseblock probably went + * bad. + */ +int ubi_io_write_vid_hdr(struct ubi_device *ubi, int pnum, + struct ubi_vid_hdr *vid_hdr) +{ + int err; + uint32_t crc; + void *p; + + dbg_io("write VID header to PEB %d", pnum); + ubi_assert(pnum >= 0 && pnum < ubi->peb_count); + + err = self_check_peb_ec_hdr(ubi, pnum); + if (err) + return err; + + vid_hdr->magic = cpu_to_be32(UBI_VID_HDR_MAGIC); + vid_hdr->version = UBI_VERSION; + crc = crc32(UBI_CRC32_INIT, vid_hdr, UBI_VID_HDR_SIZE_CRC); + vid_hdr->hdr_crc = cpu_to_be32(crc); + + err = self_check_vid_hdr(ubi, pnum, vid_hdr); + if (err) + return err; + + p = (char *)vid_hdr - ubi->vid_hdr_shift; + err = ubi_io_write(ubi, p, pnum, ubi->vid_hdr_aloffset, + ubi->vid_hdr_alsize); + return err; +} + +/** + * self_check_not_bad - ensure that a physical eraseblock is not bad. + * @ubi: UBI device description object + * @pnum: physical eraseblock number to check + * + * This function returns zero if the physical eraseblock is good, %-EINVAL if + * it is bad and a negative error code if an error occurred. + */ +static int self_check_not_bad(const struct ubi_device *ubi, int pnum) +{ + int err; + + if (!ubi_dbg_chk_io(ubi)) + return 0; + + err = ubi_io_is_bad(ubi, pnum); + if (!err) + return err; + + ubi_err("self-check failed for PEB %d", pnum); + dump_stack(); + return err > 0 ? -EINVAL : err; +} + +/** + * self_check_ec_hdr - check if an erase counter header is all right. + * @ubi: UBI device description object + * @pnum: physical eraseblock number the erase counter header belongs to + * @ec_hdr: the erase counter header to check + * + * This function returns zero if the erase counter header contains valid + * values, and %-EINVAL if not. + */ +static int self_check_ec_hdr(const struct ubi_device *ubi, int pnum, + const struct ubi_ec_hdr *ec_hdr) +{ + int err; + uint32_t magic; + + if (!ubi_dbg_chk_io(ubi)) + return 0; + + magic = be32_to_cpu(ec_hdr->magic); + if (magic != UBI_EC_HDR_MAGIC) { + ubi_err("bad magic %#08x, must be %#08x", + magic, UBI_EC_HDR_MAGIC); + goto fail; + } + + err = validate_ec_hdr(ubi, ec_hdr); + if (err) { + ubi_err("self-check failed for PEB %d", pnum); + goto fail; + } + + return 0; + +fail: + ubi_dump_ec_hdr(ec_hdr); + dump_stack(); + return -EINVAL; +} + +/** + * self_check_peb_ec_hdr - check erase counter header. + * @ubi: UBI device description object + * @pnum: the physical eraseblock number to check + * + * This function returns zero if the erase counter header is all right and and + * a negative error code if not or if an error occurred. + */ +static int self_check_peb_ec_hdr(const struct ubi_device *ubi, int pnum) +{ + int err; + uint32_t crc, hdr_crc; + struct ubi_ec_hdr *ec_hdr; + + if (!ubi_dbg_chk_io(ubi)) + return 0; + + ec_hdr = kzalloc(ubi->ec_hdr_alsize, GFP_NOFS); + if (!ec_hdr) + return -ENOMEM; + + err = ubi_io_read(ubi, ec_hdr, pnum, 0, UBI_EC_HDR_SIZE); + if (err && err != UBI_IO_BITFLIPS && !mtd_is_eccerr(err)) + goto exit; + + crc = crc32(UBI_CRC32_INIT, ec_hdr, UBI_EC_HDR_SIZE_CRC); + hdr_crc = be32_to_cpu(ec_hdr->hdr_crc); + if (hdr_crc != crc) { + ubi_err("bad CRC, calculated %#08x, read %#08x", crc, hdr_crc); + ubi_err("self-check failed for PEB %d", pnum); + ubi_dump_ec_hdr(ec_hdr); + dump_stack(); + err = -EINVAL; + goto exit; + } + + err = self_check_ec_hdr(ubi, pnum, ec_hdr); + +exit: + kfree(ec_hdr); + return err; +} + +/** + * self_check_vid_hdr - check that a volume identifier header is all right. + * @ubi: UBI device description object + * @pnum: physical eraseblock number the volume identifier header belongs to + * @vid_hdr: the volume identifier header to check + * + * This function returns zero if the volume identifier header is all right, and + * %-EINVAL if not. + */ +static int self_check_vid_hdr(const struct ubi_device *ubi, int pnum, + const struct ubi_vid_hdr *vid_hdr) +{ + int err; + uint32_t magic; + + if (!ubi_dbg_chk_io(ubi)) + return 0; + + magic = be32_to_cpu(vid_hdr->magic); + if (magic != UBI_VID_HDR_MAGIC) { + ubi_err("bad VID header magic %#08x at PEB %d, must be %#08x", + magic, pnum, UBI_VID_HDR_MAGIC); + goto fail; + } + + err = validate_vid_hdr(ubi, vid_hdr); + if (err) { + ubi_err("self-check failed for PEB %d", pnum); + goto fail; + } + + return err; + +fail: + ubi_err("self-check failed for PEB %d", pnum); + ubi_dump_vid_hdr(vid_hdr); + dump_stack(); + return -EINVAL; + +} + +/** + * self_check_peb_vid_hdr - check volume identifier header. + * @ubi: UBI device description object + * @pnum: the physical eraseblock number to check + * + * This function returns zero if the volume identifier header is all right, + * and a negative error code if not or if an error occurred. + */ +static int self_check_peb_vid_hdr(const struct ubi_device *ubi, int pnum) +{ + int err; + uint32_t crc, hdr_crc; + struct ubi_vid_hdr *vid_hdr; + void *p; + + if (!ubi_dbg_chk_io(ubi)) + return 0; + + vid_hdr = ubi_zalloc_vid_hdr(ubi, GFP_NOFS); + if (!vid_hdr) + return -ENOMEM; + + p = (char *)vid_hdr - ubi->vid_hdr_shift; + err = ubi_io_read(ubi, p, pnum, ubi->vid_hdr_aloffset, + ubi->vid_hdr_alsize); + if (err && err != UBI_IO_BITFLIPS && !mtd_is_eccerr(err)) + goto exit; + + crc = crc32(UBI_CRC32_INIT, vid_hdr, UBI_EC_HDR_SIZE_CRC); + hdr_crc = be32_to_cpu(vid_hdr->hdr_crc); + if (hdr_crc != crc) { + ubi_err("bad VID header CRC at PEB %d, calculated %#08x, read %#08x", + pnum, crc, hdr_crc); + ubi_err("self-check failed for PEB %d", pnum); + ubi_dump_vid_hdr(vid_hdr); + dump_stack(); + err = -EINVAL; + goto exit; + } + + err = self_check_vid_hdr(ubi, pnum, vid_hdr); + +exit: + ubi_free_vid_hdr(ubi, vid_hdr); + return err; +} + +/** + * self_check_write - make sure write succeeded. + * @ubi: UBI device description object + * @buf: buffer with data which were written + * @pnum: physical eraseblock number the data were written to + * @offset: offset within the physical eraseblock the data were written to + * @len: how many bytes were written + * + * This functions reads data which were recently written and compares it with + * the original data buffer - the data have to match. Returns zero if the data + * match and a negative error code if not or in case of failure. + */ +static int self_check_write(struct ubi_device *ubi, const void *buf, int pnum, + int offset, int len) +{ + int err, i; + size_t read; + void *buf1; + loff_t addr = (loff_t)pnum * ubi->peb_size + offset; + + if (!ubi_dbg_chk_io(ubi)) + return 0; + + buf1 = __vmalloc(len, GFP_NOFS, PAGE_KERNEL); + if (!buf1) { + ubi_err("cannot allocate memory to check writes"); + return 0; + } + + err = mtd_read(ubi->mtd, addr, len, &read, buf1); + if (err && !mtd_is_bitflip(err)) + goto out_free; + + for (i = 0; i < len; i++) { + uint8_t c = ((uint8_t *)buf)[i]; + uint8_t c1 = ((uint8_t *)buf1)[i]; + int dump_len; + + if (c == c1) + continue; + + ubi_err("self-check failed for PEB %d:%d, len %d", + pnum, offset, len); + ubi_msg("data differ at position %d", i); + dump_len = max_t(int, 128, len - i); + ubi_msg("hex dump of the original buffer from %d to %d", + i, i + dump_len); + print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 32, 1, + buf + i, dump_len, 1); + ubi_msg("hex dump of the read buffer from %d to %d", + i, i + dump_len); + print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 32, 1, + buf1 + i, dump_len, 1); + dump_stack(); + err = -EINVAL; + goto out_free; + } + + vfree(buf1); + return 0; + +out_free: + vfree(buf1); + return err; +} + +/** + * ubi_self_check_all_ff - check that a region of flash is empty. + * @ubi: UBI device description object + * @pnum: the physical eraseblock number to check + * @offset: the starting offset within the physical eraseblock to check + * @len: the length of the region to check + * + * This function returns zero if only 0xFF bytes are present at offset + * @offset of the physical eraseblock @pnum, and a negative error code if not + * or if an error occurred. + */ +int ubi_self_check_all_ff(struct ubi_device *ubi, int pnum, int offset, int len) +{ + size_t read; + int err; + void *buf; + loff_t addr = (loff_t)pnum * ubi->peb_size + offset; + + if (!ubi_dbg_chk_io(ubi)) + return 0; + + buf = __vmalloc(len, GFP_NOFS, PAGE_KERNEL); + if (!buf) { + ubi_err("cannot allocate memory to check for 0xFFs"); + return 0; + } + + err = mtd_read(ubi->mtd, addr, len, &read, buf); + if (err && !mtd_is_bitflip(err)) { + ubi_err("error %d while reading %d bytes from PEB %d:%d, read %zd bytes", + err, len, pnum, offset, read); + goto error; + } + + err = ubi_check_pattern(buf, 0xFF, len); + if (err == 0) { + ubi_err("flash region at PEB %d:%d, length %d does not contain all 0xFF bytes", + pnum, offset, len); + goto fail; + } + + vfree(buf); + return 0; + +fail: + ubi_err("self-check failed for PEB %d", pnum); + ubi_msg("hex dump of the %d-%d region", offset, offset + len); + print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 32, 1, buf, len, 1); + err = -EINVAL; +error: + dump_stack(); + vfree(buf); + return err; +} diff --git a/drivers/mtd/ubi/kapi.c b/drivers/mtd/ubi/kapi.c new file mode 100644 index 00000000000..3aac1acceeb --- /dev/null +++ b/drivers/mtd/ubi/kapi.c @@ -0,0 +1,788 @@ +/* + * Copyright (c) International Business Machines Corp., 2006 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Artem Bityutskiy (Битюцкий Артём) + */ + +/* This file mostly implements UBI kernel API functions */ + +#include <linux/module.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/namei.h> +#include <linux/fs.h> +#include <asm/div64.h> +#include "ubi.h" + +/** + * ubi_do_get_device_info - get information about UBI device. + * @ubi: UBI device description object + * @di: the information is stored here + * + * This function is the same as 'ubi_get_device_info()', but it assumes the UBI + * device is locked and cannot disappear. + */ +void ubi_do_get_device_info(struct ubi_device *ubi, struct ubi_device_info *di) +{ + di->ubi_num = ubi->ubi_num; + di->leb_size = ubi->leb_size; + di->leb_start = ubi->leb_start; + di->min_io_size = ubi->min_io_size; + di->max_write_size = ubi->max_write_size; + di->ro_mode = ubi->ro_mode; + di->cdev = ubi->cdev.dev; +} +EXPORT_SYMBOL_GPL(ubi_do_get_device_info); + +/** + * ubi_get_device_info - get information about UBI device. + * @ubi_num: UBI device number + * @di: the information is stored here + * + * This function returns %0 in case of success, %-EINVAL if the UBI device + * number is invalid, and %-ENODEV if there is no such UBI device. + */ +int ubi_get_device_info(int ubi_num, struct ubi_device_info *di) +{ + struct ubi_device *ubi; + + if (ubi_num < 0 || ubi_num >= UBI_MAX_DEVICES) + return -EINVAL; + ubi = ubi_get_device(ubi_num); + if (!ubi) + return -ENODEV; + ubi_do_get_device_info(ubi, di); + ubi_put_device(ubi); + return 0; +} +EXPORT_SYMBOL_GPL(ubi_get_device_info); + +/** + * ubi_do_get_volume_info - get information about UBI volume. + * @ubi: UBI device description object + * @vol: volume description object + * @vi: the information is stored here + */ +void ubi_do_get_volume_info(struct ubi_device *ubi, struct ubi_volume *vol, + struct ubi_volume_info *vi) +{ + vi->vol_id = vol->vol_id; + vi->ubi_num = ubi->ubi_num; + vi->size = vol->reserved_pebs; + vi->used_bytes = vol->used_bytes; + vi->vol_type = vol->vol_type; + vi->corrupted = vol->corrupted; + vi->upd_marker = vol->upd_marker; + vi->alignment = vol->alignment; + vi->usable_leb_size = vol->usable_leb_size; + vi->name_len = vol->name_len; + vi->name = vol->name; + vi->cdev = vol->cdev.dev; +} + +/** + * ubi_get_volume_info - get information about UBI volume. + * @desc: volume descriptor + * @vi: the information is stored here + */ +void ubi_get_volume_info(struct ubi_volume_desc *desc, + struct ubi_volume_info *vi) +{ + ubi_do_get_volume_info(desc->vol->ubi, desc->vol, vi); +} +EXPORT_SYMBOL_GPL(ubi_get_volume_info); + +/** + * ubi_open_volume - open UBI volume. + * @ubi_num: UBI device number + * @vol_id: volume ID + * @mode: open mode + * + * The @mode parameter specifies if the volume should be opened in read-only + * mode, read-write mode, or exclusive mode. The exclusive mode guarantees that + * nobody else will be able to open this volume. UBI allows to have many volume + * readers and one writer at a time. + * + * If a static volume is being opened for the first time since boot, it will be + * checked by this function, which means it will be fully read and the CRC + * checksum of each logical eraseblock will be checked. + * + * This function returns volume descriptor in case of success and a negative + * error code in case of failure. + */ +struct ubi_volume_desc *ubi_open_volume(int ubi_num, int vol_id, int mode) +{ + int err; + struct ubi_volume_desc *desc; + struct ubi_device *ubi; + struct ubi_volume *vol; + + dbg_gen("open device %d, volume %d, mode %d", ubi_num, vol_id, mode); + + if (ubi_num < 0 || ubi_num >= UBI_MAX_DEVICES) + return ERR_PTR(-EINVAL); + + if (mode != UBI_READONLY && mode != UBI_READWRITE && + mode != UBI_EXCLUSIVE) + return ERR_PTR(-EINVAL); + + /* + * First of all, we have to get the UBI device to prevent its removal. + */ + ubi = ubi_get_device(ubi_num); + if (!ubi) + return ERR_PTR(-ENODEV); + + if (vol_id < 0 || vol_id >= ubi->vtbl_slots) { + err = -EINVAL; + goto out_put_ubi; + } + + desc = kmalloc(sizeof(struct ubi_volume_desc), GFP_KERNEL); + if (!desc) { + err = -ENOMEM; + goto out_put_ubi; + } + + err = -ENODEV; + if (!try_module_get(THIS_MODULE)) + goto out_free; + + spin_lock(&ubi->volumes_lock); + vol = ubi->volumes[vol_id]; + if (!vol) + goto out_unlock; + + err = -EBUSY; + switch (mode) { + case UBI_READONLY: + if (vol->exclusive) + goto out_unlock; + vol->readers += 1; + break; + + case UBI_READWRITE: + if (vol->exclusive || vol->writers > 0) + goto out_unlock; + vol->writers += 1; + break; + + case UBI_EXCLUSIVE: + if (vol->exclusive || vol->writers || vol->readers) + goto out_unlock; + vol->exclusive = 1; + break; + } + get_device(&vol->dev); + vol->ref_count += 1; + spin_unlock(&ubi->volumes_lock); + + desc->vol = vol; + desc->mode = mode; + + mutex_lock(&ubi->ckvol_mutex); + if (!vol->checked) { + /* This is the first open - check the volume */ + err = ubi_check_volume(ubi, vol_id); + if (err < 0) { + mutex_unlock(&ubi->ckvol_mutex); + ubi_close_volume(desc); + return ERR_PTR(err); + } + if (err == 1) { + ubi_warn("volume %d on UBI device %d is corrupted", + vol_id, ubi->ubi_num); + vol->corrupted = 1; + } + vol->checked = 1; + } + mutex_unlock(&ubi->ckvol_mutex); + + return desc; + +out_unlock: + spin_unlock(&ubi->volumes_lock); + module_put(THIS_MODULE); +out_free: + kfree(desc); +out_put_ubi: + ubi_put_device(ubi); + ubi_err("cannot open device %d, volume %d, error %d", + ubi_num, vol_id, err); + return ERR_PTR(err); +} +EXPORT_SYMBOL_GPL(ubi_open_volume); + +/** + * ubi_open_volume_nm - open UBI volume by name. + * @ubi_num: UBI device number + * @name: volume name + * @mode: open mode + * + * This function is similar to 'ubi_open_volume()', but opens a volume by name. + */ +struct ubi_volume_desc *ubi_open_volume_nm(int ubi_num, const char *name, + int mode) +{ + int i, vol_id = -1, len; + struct ubi_device *ubi; + struct ubi_volume_desc *ret; + + dbg_gen("open device %d, volume %s, mode %d", ubi_num, name, mode); + + if (!name) + return ERR_PTR(-EINVAL); + + len = strnlen(name, UBI_VOL_NAME_MAX + 1); + if (len > UBI_VOL_NAME_MAX) + return ERR_PTR(-EINVAL); + + if (ubi_num < 0 || ubi_num >= UBI_MAX_DEVICES) + return ERR_PTR(-EINVAL); + + ubi = ubi_get_device(ubi_num); + if (!ubi) + return ERR_PTR(-ENODEV); + + spin_lock(&ubi->volumes_lock); + /* Walk all volumes of this UBI device */ + for (i = 0; i < ubi->vtbl_slots; i++) { + struct ubi_volume *vol = ubi->volumes[i]; + + if (vol && len == vol->name_len && !strcmp(name, vol->name)) { + vol_id = i; + break; + } + } + spin_unlock(&ubi->volumes_lock); + + if (vol_id >= 0) + ret = ubi_open_volume(ubi_num, vol_id, mode); + else + ret = ERR_PTR(-ENODEV); + + /* + * We should put the UBI device even in case of success, because + * 'ubi_open_volume()' took a reference as well. + */ + ubi_put_device(ubi); + return ret; +} +EXPORT_SYMBOL_GPL(ubi_open_volume_nm); + +/** + * ubi_open_volume_path - open UBI volume by its character device node path. + * @pathname: volume character device node path + * @mode: open mode + * + * This function is similar to 'ubi_open_volume()', but opens a volume the path + * to its character device node. + */ +struct ubi_volume_desc *ubi_open_volume_path(const char *pathname, int mode) +{ + int error, ubi_num, vol_id, mod; + struct inode *inode; + struct path path; + + dbg_gen("open volume %s, mode %d", pathname, mode); + + if (!pathname || !*pathname) + return ERR_PTR(-EINVAL); + + error = kern_path(pathname, LOOKUP_FOLLOW, &path); + if (error) + return ERR_PTR(error); + + inode = path.dentry->d_inode; + mod = inode->i_mode; + ubi_num = ubi_major2num(imajor(inode)); + vol_id = iminor(inode) - 1; + path_put(&path); + + if (!S_ISCHR(mod)) + return ERR_PTR(-EINVAL); + if (vol_id >= 0 && ubi_num >= 0) + return ubi_open_volume(ubi_num, vol_id, mode); + return ERR_PTR(-ENODEV); +} +EXPORT_SYMBOL_GPL(ubi_open_volume_path); + +/** + * ubi_close_volume - close UBI volume. + * @desc: volume descriptor + */ +void ubi_close_volume(struct ubi_volume_desc *desc) +{ + struct ubi_volume *vol = desc->vol; + struct ubi_device *ubi = vol->ubi; + + dbg_gen("close device %d, volume %d, mode %d", + ubi->ubi_num, vol->vol_id, desc->mode); + + spin_lock(&ubi->volumes_lock); + switch (desc->mode) { + case UBI_READONLY: + vol->readers -= 1; + break; + case UBI_READWRITE: + vol->writers -= 1; + break; + case UBI_EXCLUSIVE: + vol->exclusive = 0; + } + vol->ref_count -= 1; + spin_unlock(&ubi->volumes_lock); + + kfree(desc); + put_device(&vol->dev); + ubi_put_device(ubi); + module_put(THIS_MODULE); +} +EXPORT_SYMBOL_GPL(ubi_close_volume); + +/** + * ubi_leb_read - read data. + * @desc: volume descriptor + * @lnum: logical eraseblock number to read from + * @buf: buffer where to store the read data + * @offset: offset within the logical eraseblock to read from + * @len: how many bytes to read + * @check: whether UBI has to check the read data's CRC or not. + * + * This function reads data from offset @offset of logical eraseblock @lnum and + * stores the data at @buf. When reading from static volumes, @check specifies + * whether the data has to be checked or not. If yes, the whole logical + * eraseblock will be read and its CRC checksum will be checked (i.e., the CRC + * checksum is per-eraseblock). So checking may substantially slow down the + * read speed. The @check argument is ignored for dynamic volumes. + * + * In case of success, this function returns zero. In case of failure, this + * function returns a negative error code. + * + * %-EBADMSG error code is returned: + * o for both static and dynamic volumes if MTD driver has detected a data + * integrity problem (unrecoverable ECC checksum mismatch in case of NAND); + * o for static volumes in case of data CRC mismatch. + * + * If the volume is damaged because of an interrupted update this function just + * returns immediately with %-EBADF error code. + */ +int ubi_leb_read(struct ubi_volume_desc *desc, int lnum, char *buf, int offset, + int len, int check) +{ + struct ubi_volume *vol = desc->vol; + struct ubi_device *ubi = vol->ubi; + int err, vol_id = vol->vol_id; + + dbg_gen("read %d bytes from LEB %d:%d:%d", len, vol_id, lnum, offset); + + if (vol_id < 0 || vol_id >= ubi->vtbl_slots || lnum < 0 || + lnum >= vol->used_ebs || offset < 0 || len < 0 || + offset + len > vol->usable_leb_size) + return -EINVAL; + + if (vol->vol_type == UBI_STATIC_VOLUME) { + if (vol->used_ebs == 0) + /* Empty static UBI volume */ + return 0; + if (lnum == vol->used_ebs - 1 && + offset + len > vol->last_eb_bytes) + return -EINVAL; + } + + if (vol->upd_marker) + return -EBADF; + if (len == 0) + return 0; + + err = ubi_eba_read_leb(ubi, vol, lnum, buf, offset, len, check); + if (err && mtd_is_eccerr(err) && vol->vol_type == UBI_STATIC_VOLUME) { + ubi_warn("mark volume %d as corrupted", vol_id); + vol->corrupted = 1; + } + + return err; +} +EXPORT_SYMBOL_GPL(ubi_leb_read); + +/** + * ubi_leb_write - write data. + * @desc: volume descriptor + * @lnum: logical eraseblock number to write to + * @buf: data to write + * @offset: offset within the logical eraseblock where to write + * @len: how many bytes to write + * + * This function writes @len bytes of data from @buf to offset @offset of + * logical eraseblock @lnum. + * + * This function takes care of physical eraseblock write failures. If write to + * the physical eraseblock write operation fails, the logical eraseblock is + * re-mapped to another physical eraseblock, the data is recovered, and the + * write finishes. UBI has a pool of reserved physical eraseblocks for this. + * + * If all the data were successfully written, zero is returned. If an error + * occurred and UBI has not been able to recover from it, this function returns + * a negative error code. Note, in case of an error, it is possible that + * something was still written to the flash media, but that may be some + * garbage. + * + * If the volume is damaged because of an interrupted update this function just + * returns immediately with %-EBADF code. + */ +int ubi_leb_write(struct ubi_volume_desc *desc, int lnum, const void *buf, + int offset, int len) +{ + struct ubi_volume *vol = desc->vol; + struct ubi_device *ubi = vol->ubi; + int vol_id = vol->vol_id; + + dbg_gen("write %d bytes to LEB %d:%d:%d", len, vol_id, lnum, offset); + + if (vol_id < 0 || vol_id >= ubi->vtbl_slots) + return -EINVAL; + + if (desc->mode == UBI_READONLY || vol->vol_type == UBI_STATIC_VOLUME) + return -EROFS; + + if (lnum < 0 || lnum >= vol->reserved_pebs || offset < 0 || len < 0 || + offset + len > vol->usable_leb_size || + offset & (ubi->min_io_size - 1) || len & (ubi->min_io_size - 1)) + return -EINVAL; + + if (vol->upd_marker) + return -EBADF; + + if (len == 0) + return 0; + + return ubi_eba_write_leb(ubi, vol, lnum, buf, offset, len); +} +EXPORT_SYMBOL_GPL(ubi_leb_write); + +/* + * ubi_leb_change - change logical eraseblock atomically. + * @desc: volume descriptor + * @lnum: logical eraseblock number to change + * @buf: data to write + * @len: how many bytes to write + * + * This function changes the contents of a logical eraseblock atomically. @buf + * has to contain new logical eraseblock data, and @len - the length of the + * data, which has to be aligned. The length may be shorter than the logical + * eraseblock size, ant the logical eraseblock may be appended to more times + * later on. This function guarantees that in case of an unclean reboot the old + * contents is preserved. Returns zero in case of success and a negative error + * code in case of failure. + */ +int ubi_leb_change(struct ubi_volume_desc *desc, int lnum, const void *buf, + int len) +{ + struct ubi_volume *vol = desc->vol; + struct ubi_device *ubi = vol->ubi; + int vol_id = vol->vol_id; + + dbg_gen("atomically write %d bytes to LEB %d:%d", len, vol_id, lnum); + + if (vol_id < 0 || vol_id >= ubi->vtbl_slots) + return -EINVAL; + + if (desc->mode == UBI_READONLY || vol->vol_type == UBI_STATIC_VOLUME) + return -EROFS; + + if (lnum < 0 || lnum >= vol->reserved_pebs || len < 0 || + len > vol->usable_leb_size || len & (ubi->min_io_size - 1)) + return -EINVAL; + + if (vol->upd_marker) + return -EBADF; + + if (len == 0) + return 0; + + return ubi_eba_atomic_leb_change(ubi, vol, lnum, buf, len); +} +EXPORT_SYMBOL_GPL(ubi_leb_change); + +/** + * ubi_leb_erase - erase logical eraseblock. + * @desc: volume descriptor + * @lnum: logical eraseblock number + * + * This function un-maps logical eraseblock @lnum and synchronously erases the + * correspondent physical eraseblock. Returns zero in case of success and a + * negative error code in case of failure. + * + * If the volume is damaged because of an interrupted update this function just + * returns immediately with %-EBADF code. + */ +int ubi_leb_erase(struct ubi_volume_desc *desc, int lnum) +{ + struct ubi_volume *vol = desc->vol; + struct ubi_device *ubi = vol->ubi; + int err; + + dbg_gen("erase LEB %d:%d", vol->vol_id, lnum); + + if (desc->mode == UBI_READONLY || vol->vol_type == UBI_STATIC_VOLUME) + return -EROFS; + + if (lnum < 0 || lnum >= vol->reserved_pebs) + return -EINVAL; + + if (vol->upd_marker) + return -EBADF; + + err = ubi_eba_unmap_leb(ubi, vol, lnum); + if (err) + return err; + + return ubi_wl_flush(ubi, vol->vol_id, lnum); +} +EXPORT_SYMBOL_GPL(ubi_leb_erase); + +/** + * ubi_leb_unmap - un-map logical eraseblock. + * @desc: volume descriptor + * @lnum: logical eraseblock number + * + * This function un-maps logical eraseblock @lnum and schedules the + * corresponding physical eraseblock for erasure, so that it will eventually be + * physically erased in background. This operation is much faster than the + * erase operation. + * + * Unlike erase, the un-map operation does not guarantee that the logical + * eraseblock will contain all 0xFF bytes when UBI is initialized again. For + * example, if several logical eraseblocks are un-mapped, and an unclean reboot + * happens after this, the logical eraseblocks will not necessarily be + * un-mapped again when this MTD device is attached. They may actually be + * mapped to the same physical eraseblocks again. So, this function has to be + * used with care. + * + * In other words, when un-mapping a logical eraseblock, UBI does not store + * any information about this on the flash media, it just marks the logical + * eraseblock as "un-mapped" in RAM. If UBI is detached before the physical + * eraseblock is physically erased, it will be mapped again to the same logical + * eraseblock when the MTD device is attached again. + * + * The main and obvious use-case of this function is when the contents of a + * logical eraseblock has to be re-written. Then it is much more efficient to + * first un-map it, then write new data, rather than first erase it, then write + * new data. Note, once new data has been written to the logical eraseblock, + * UBI guarantees that the old contents has gone forever. In other words, if an + * unclean reboot happens after the logical eraseblock has been un-mapped and + * then written to, it will contain the last written data. + * + * This function returns zero in case of success and a negative error code in + * case of failure. If the volume is damaged because of an interrupted update + * this function just returns immediately with %-EBADF code. + */ +int ubi_leb_unmap(struct ubi_volume_desc *desc, int lnum) +{ + struct ubi_volume *vol = desc->vol; + struct ubi_device *ubi = vol->ubi; + + dbg_gen("unmap LEB %d:%d", vol->vol_id, lnum); + + if (desc->mode == UBI_READONLY || vol->vol_type == UBI_STATIC_VOLUME) + return -EROFS; + + if (lnum < 0 || lnum >= vol->reserved_pebs) + return -EINVAL; + + if (vol->upd_marker) + return -EBADF; + + return ubi_eba_unmap_leb(ubi, vol, lnum); +} +EXPORT_SYMBOL_GPL(ubi_leb_unmap); + +/** + * ubi_leb_map - map logical eraseblock to a physical eraseblock. + * @desc: volume descriptor + * @lnum: logical eraseblock number + * + * This function maps an un-mapped logical eraseblock @lnum to a physical + * eraseblock. This means, that after a successful invocation of this + * function the logical eraseblock @lnum will be empty (contain only %0xFF + * bytes) and be mapped to a physical eraseblock, even if an unclean reboot + * happens. + * + * This function returns zero in case of success, %-EBADF if the volume is + * damaged because of an interrupted update, %-EBADMSG if the logical + * eraseblock is already mapped, and other negative error codes in case of + * other failures. + */ +int ubi_leb_map(struct ubi_volume_desc *desc, int lnum) +{ + struct ubi_volume *vol = desc->vol; + struct ubi_device *ubi = vol->ubi; + + dbg_gen("unmap LEB %d:%d", vol->vol_id, lnum); + + if (desc->mode == UBI_READONLY || vol->vol_type == UBI_STATIC_VOLUME) + return -EROFS; + + if (lnum < 0 || lnum >= vol->reserved_pebs) + return -EINVAL; + + if (vol->upd_marker) + return -EBADF; + + if (vol->eba_tbl[lnum] >= 0) + return -EBADMSG; + + return ubi_eba_write_leb(ubi, vol, lnum, NULL, 0, 0); +} +EXPORT_SYMBOL_GPL(ubi_leb_map); + +/** + * ubi_is_mapped - check if logical eraseblock is mapped. + * @desc: volume descriptor + * @lnum: logical eraseblock number + * + * This function checks if logical eraseblock @lnum is mapped to a physical + * eraseblock. If a logical eraseblock is un-mapped, this does not necessarily + * mean it will still be un-mapped after the UBI device is re-attached. The + * logical eraseblock may become mapped to the physical eraseblock it was last + * mapped to. + * + * This function returns %1 if the LEB is mapped, %0 if not, and a negative + * error code in case of failure. If the volume is damaged because of an + * interrupted update this function just returns immediately with %-EBADF error + * code. + */ +int ubi_is_mapped(struct ubi_volume_desc *desc, int lnum) +{ + struct ubi_volume *vol = desc->vol; + + dbg_gen("test LEB %d:%d", vol->vol_id, lnum); + + if (lnum < 0 || lnum >= vol->reserved_pebs) + return -EINVAL; + + if (vol->upd_marker) + return -EBADF; + + return vol->eba_tbl[lnum] >= 0; +} +EXPORT_SYMBOL_GPL(ubi_is_mapped); + +/** + * ubi_sync - synchronize UBI device buffers. + * @ubi_num: UBI device to synchronize + * + * The underlying MTD device may cache data in hardware or in software. This + * function ensures the caches are flushed. Returns zero in case of success and + * a negative error code in case of failure. + */ +int ubi_sync(int ubi_num) +{ + struct ubi_device *ubi; + + ubi = ubi_get_device(ubi_num); + if (!ubi) + return -ENODEV; + + mtd_sync(ubi->mtd); + ubi_put_device(ubi); + return 0; +} +EXPORT_SYMBOL_GPL(ubi_sync); + +/** + * ubi_flush - flush UBI work queue. + * @ubi_num: UBI device to flush work queue + * @vol_id: volume id to flush for + * @lnum: logical eraseblock number to flush for + * + * This function executes all pending works for a particular volume id / logical + * eraseblock number pair. If either value is set to %UBI_ALL, then it acts as + * a wildcard for all of the corresponding volume numbers or logical + * eraseblock numbers. It returns zero in case of success and a negative error + * code in case of failure. + */ +int ubi_flush(int ubi_num, int vol_id, int lnum) +{ + struct ubi_device *ubi; + int err = 0; + + ubi = ubi_get_device(ubi_num); + if (!ubi) + return -ENODEV; + + err = ubi_wl_flush(ubi, vol_id, lnum); + ubi_put_device(ubi); + return err; +} +EXPORT_SYMBOL_GPL(ubi_flush); + +BLOCKING_NOTIFIER_HEAD(ubi_notifiers); + +/** + * ubi_register_volume_notifier - register a volume notifier. + * @nb: the notifier description object + * @ignore_existing: if non-zero, do not send "added" notification for all + * already existing volumes + * + * This function registers a volume notifier, which means that + * 'nb->notifier_call()' will be invoked when an UBI volume is created, + * removed, re-sized, re-named, or updated. The first argument of the function + * is the notification type. The second argument is pointer to a + * &struct ubi_notification object which describes the notification event. + * Using UBI API from the volume notifier is prohibited. + * + * This function returns zero in case of success and a negative error code + * in case of failure. + */ +int ubi_register_volume_notifier(struct notifier_block *nb, + int ignore_existing) +{ + int err; + + err = blocking_notifier_chain_register(&ubi_notifiers, nb); + if (err != 0) + return err; + if (ignore_existing) + return 0; + + /* + * We are going to walk all UBI devices and all volumes, and + * notify the user about existing volumes by the %UBI_VOLUME_ADDED + * event. We have to lock the @ubi_devices_mutex to make sure UBI + * devices do not disappear. + */ + mutex_lock(&ubi_devices_mutex); + ubi_enumerate_volumes(nb); + mutex_unlock(&ubi_devices_mutex); + + return err; +} +EXPORT_SYMBOL_GPL(ubi_register_volume_notifier); + +/** + * ubi_unregister_volume_notifier - unregister the volume notifier. + * @nb: the notifier description object + * + * This function unregisters volume notifier @nm and returns zero in case of + * success and a negative error code in case of failure. + */ +int ubi_unregister_volume_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&ubi_notifiers, nb); +} +EXPORT_SYMBOL_GPL(ubi_unregister_volume_notifier); diff --git a/drivers/mtd/ubi/misc.c b/drivers/mtd/ubi/misc.c new file mode 100644 index 00000000000..f913d701a5b --- /dev/null +++ b/drivers/mtd/ubi/misc.c @@ -0,0 +1,153 @@ +/* + * Copyright (c) International Business Machines Corp., 2006 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Artem Bityutskiy (Битюцкий Артём) + */ + +/* Here we keep miscellaneous functions which are used all over the UBI code */ + +#include "ubi.h" + +/** + * calc_data_len - calculate how much real data is stored in a buffer. + * @ubi: UBI device description object + * @buf: a buffer with the contents of the physical eraseblock + * @length: the buffer length + * + * This function calculates how much "real data" is stored in @buf and returnes + * the length. Continuous 0xFF bytes at the end of the buffer are not + * considered as "real data". + */ +int ubi_calc_data_len(const struct ubi_device *ubi, const void *buf, + int length) +{ + int i; + + ubi_assert(!(length & (ubi->min_io_size - 1))); + + for (i = length - 1; i >= 0; i--) + if (((const uint8_t *)buf)[i] != 0xFF) + break; + + /* The resulting length must be aligned to the minimum flash I/O size */ + length = ALIGN(i + 1, ubi->min_io_size); + return length; +} + +/** + * ubi_check_volume - check the contents of a static volume. + * @ubi: UBI device description object + * @vol_id: ID of the volume to check + * + * This function checks if static volume @vol_id is corrupted by fully reading + * it and checking data CRC. This function returns %0 if the volume is not + * corrupted, %1 if it is corrupted and a negative error code in case of + * failure. Dynamic volumes are not checked and zero is returned immediately. + */ +int ubi_check_volume(struct ubi_device *ubi, int vol_id) +{ + void *buf; + int err = 0, i; + struct ubi_volume *vol = ubi->volumes[vol_id]; + + if (vol->vol_type != UBI_STATIC_VOLUME) + return 0; + + buf = vmalloc(vol->usable_leb_size); + if (!buf) + return -ENOMEM; + + for (i = 0; i < vol->used_ebs; i++) { + int size; + + if (i == vol->used_ebs - 1) + size = vol->last_eb_bytes; + else + size = vol->usable_leb_size; + + err = ubi_eba_read_leb(ubi, vol, i, buf, 0, size, 1); + if (err) { + if (mtd_is_eccerr(err)) + err = 1; + break; + } + } + + vfree(buf); + return err; +} + +/** + * ubi_update_reserved - update bad eraseblock handling accounting data. + * @ubi: UBI device description object + * + * This function calculates the gap between current number of PEBs reserved for + * bad eraseblock handling and the required level of PEBs that must be + * reserved, and if necessary, reserves more PEBs to fill that gap, according + * to availability. Should be called with ubi->volumes_lock held. + */ +void ubi_update_reserved(struct ubi_device *ubi) +{ + int need = ubi->beb_rsvd_level - ubi->beb_rsvd_pebs; + + if (need <= 0 || ubi->avail_pebs == 0) + return; + + need = min_t(int, need, ubi->avail_pebs); + ubi->avail_pebs -= need; + ubi->rsvd_pebs += need; + ubi->beb_rsvd_pebs += need; + ubi_msg("reserved more %d PEBs for bad PEB handling", need); +} + +/** + * ubi_calculate_reserved - calculate how many PEBs must be reserved for bad + * eraseblock handling. + * @ubi: UBI device description object + */ +void ubi_calculate_reserved(struct ubi_device *ubi) +{ + /* + * Calculate the actual number of PEBs currently needed to be reserved + * for future bad eraseblock handling. + */ + ubi->beb_rsvd_level = ubi->bad_peb_limit - ubi->bad_peb_count; + if (ubi->beb_rsvd_level < 0) { + ubi->beb_rsvd_level = 0; + ubi_warn("number of bad PEBs (%d) is above the expected limit (%d), not reserving any PEBs for bad PEB handling, will use available PEBs (if any)", + ubi->bad_peb_count, ubi->bad_peb_limit); + } +} + +/** + * ubi_check_pattern - check if buffer contains only a certain byte pattern. + * @buf: buffer to check + * @patt: the pattern to check + * @size: buffer size in bytes + * + * This function returns %1 in there are only @patt bytes in @buf, and %0 if + * something else was also found. + */ +int ubi_check_pattern(const void *buf, uint8_t patt, int size) +{ + int i; + + for (i = 0; i < size; i++) + if (((const uint8_t *)buf)[i] != patt) + return 0; + return 1; +} diff --git a/drivers/mtd/ubi/ubi-media.h b/drivers/mtd/ubi/ubi-media.h new file mode 100644 index 00000000000..ac2b24d1783 --- /dev/null +++ b/drivers/mtd/ubi/ubi-media.h @@ -0,0 +1,515 @@ +/* + * Copyright (c) International Business Machines Corp., 2006 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Authors: Artem Bityutskiy (Битюцкий Артём) + * Thomas Gleixner + * Frank Haverkamp + * Oliver Lohmann + * Andreas Arnez + */ + +/* + * This file defines the layout of UBI headers and all the other UBI on-flash + * data structures. + */ + +#ifndef __UBI_MEDIA_H__ +#define __UBI_MEDIA_H__ + +#include <asm/byteorder.h> + +/* The version of UBI images supported by this implementation */ +#define UBI_VERSION 1 + +/* The highest erase counter value supported by this implementation */ +#define UBI_MAX_ERASECOUNTER 0x7FFFFFFF + +/* The initial CRC32 value used when calculating CRC checksums */ +#define UBI_CRC32_INIT 0xFFFFFFFFU + +/* Erase counter header magic number (ASCII "UBI#") */ +#define UBI_EC_HDR_MAGIC 0x55424923 +/* Volume identifier header magic number (ASCII "UBI!") */ +#define UBI_VID_HDR_MAGIC 0x55424921 + +/* + * Volume type constants used in the volume identifier header. + * + * @UBI_VID_DYNAMIC: dynamic volume + * @UBI_VID_STATIC: static volume + */ +enum { + UBI_VID_DYNAMIC = 1, + UBI_VID_STATIC = 2 +}; + +/* + * Volume flags used in the volume table record. + * + * @UBI_VTBL_AUTORESIZE_FLG: auto-resize this volume + * + * %UBI_VTBL_AUTORESIZE_FLG flag can be set only for one volume in the volume + * table. UBI automatically re-sizes the volume which has this flag and makes + * the volume to be of largest possible size. This means that if after the + * initialization UBI finds out that there are available physical eraseblocks + * present on the device, it automatically appends all of them to the volume + * (the physical eraseblocks reserved for bad eraseblocks handling and other + * reserved physical eraseblocks are not taken). So, if there is a volume with + * the %UBI_VTBL_AUTORESIZE_FLG flag set, the amount of available logical + * eraseblocks will be zero after UBI is loaded, because all of them will be + * reserved for this volume. Note, the %UBI_VTBL_AUTORESIZE_FLG bit is cleared + * after the volume had been initialized. + * + * The auto-resize feature is useful for device production purposes. For + * example, different NAND flash chips may have different amount of initial bad + * eraseblocks, depending of particular chip instance. Manufacturers of NAND + * chips usually guarantee that the amount of initial bad eraseblocks does not + * exceed certain percent, e.g. 2%. When one creates an UBI image which will be + * flashed to the end devices in production, he does not know the exact amount + * of good physical eraseblocks the NAND chip on the device will have, but this + * number is required to calculate the volume sized and put them to the volume + * table of the UBI image. In this case, one of the volumes (e.g., the one + * which will store the root file system) is marked as "auto-resizable", and + * UBI will adjust its size on the first boot if needed. + * + * Note, first UBI reserves some amount of physical eraseblocks for bad + * eraseblock handling, and then re-sizes the volume, not vice-versa. This + * means that the pool of reserved physical eraseblocks will always be present. + */ +enum { + UBI_VTBL_AUTORESIZE_FLG = 0x01, +}; + +/* + * Compatibility constants used by internal volumes. + * + * @UBI_COMPAT_DELETE: delete this internal volume before anything is written + * to the flash + * @UBI_COMPAT_RO: attach this device in read-only mode + * @UBI_COMPAT_PRESERVE: preserve this internal volume - do not touch its + * physical eraseblocks, don't allow the wear-leveling + * sub-system to move them + * @UBI_COMPAT_REJECT: reject this UBI image + */ +enum { + UBI_COMPAT_DELETE = 1, + UBI_COMPAT_RO = 2, + UBI_COMPAT_PRESERVE = 4, + UBI_COMPAT_REJECT = 5 +}; + +/* Sizes of UBI headers */ +#define UBI_EC_HDR_SIZE sizeof(struct ubi_ec_hdr) +#define UBI_VID_HDR_SIZE sizeof(struct ubi_vid_hdr) + +/* Sizes of UBI headers without the ending CRC */ +#define UBI_EC_HDR_SIZE_CRC (UBI_EC_HDR_SIZE - sizeof(__be32)) +#define UBI_VID_HDR_SIZE_CRC (UBI_VID_HDR_SIZE - sizeof(__be32)) + +/** + * struct ubi_ec_hdr - UBI erase counter header. + * @magic: erase counter header magic number (%UBI_EC_HDR_MAGIC) + * @version: version of UBI implementation which is supposed to accept this + * UBI image + * @padding1: reserved for future, zeroes + * @ec: the erase counter + * @vid_hdr_offset: where the VID header starts + * @data_offset: where the user data start + * @image_seq: image sequence number + * @padding2: reserved for future, zeroes + * @hdr_crc: erase counter header CRC checksum + * + * The erase counter header takes 64 bytes and has a plenty of unused space for + * future usage. The unused fields are zeroed. The @version field is used to + * indicate the version of UBI implementation which is supposed to be able to + * work with this UBI image. If @version is greater than the current UBI + * version, the image is rejected. This may be useful in future if something + * is changed radically. This field is duplicated in the volume identifier + * header. + * + * The @vid_hdr_offset and @data_offset fields contain the offset of the the + * volume identifier header and user data, relative to the beginning of the + * physical eraseblock. These values have to be the same for all physical + * eraseblocks. + * + * The @image_seq field is used to validate a UBI image that has been prepared + * for a UBI device. The @image_seq value can be any value, but it must be the + * same on all eraseblocks. UBI will ensure that all new erase counter headers + * also contain this value, and will check the value when attaching the flash. + * One way to make use of @image_seq is to increase its value by one every time + * an image is flashed over an existing image, then, if the flashing does not + * complete, UBI will detect the error when attaching the media. + */ +struct ubi_ec_hdr { + __be32 magic; + __u8 version; + __u8 padding1[3]; + __be64 ec; /* Warning: the current limit is 31-bit anyway! */ + __be32 vid_hdr_offset; + __be32 data_offset; + __be32 image_seq; + __u8 padding2[32]; + __be32 hdr_crc; +} __packed; + +/** + * struct ubi_vid_hdr - on-flash UBI volume identifier header. + * @magic: volume identifier header magic number (%UBI_VID_HDR_MAGIC) + * @version: UBI implementation version which is supposed to accept this UBI + * image (%UBI_VERSION) + * @vol_type: volume type (%UBI_VID_DYNAMIC or %UBI_VID_STATIC) + * @copy_flag: if this logical eraseblock was copied from another physical + * eraseblock (for wear-leveling reasons) + * @compat: compatibility of this volume (%0, %UBI_COMPAT_DELETE, + * %UBI_COMPAT_IGNORE, %UBI_COMPAT_PRESERVE, or %UBI_COMPAT_REJECT) + * @vol_id: ID of this volume + * @lnum: logical eraseblock number + * @padding1: reserved for future, zeroes + * @data_size: how many bytes of data this logical eraseblock contains + * @used_ebs: total number of used logical eraseblocks in this volume + * @data_pad: how many bytes at the end of this physical eraseblock are not + * used + * @data_crc: CRC checksum of the data stored in this logical eraseblock + * @padding2: reserved for future, zeroes + * @sqnum: sequence number + * @padding3: reserved for future, zeroes + * @hdr_crc: volume identifier header CRC checksum + * + * The @sqnum is the value of the global sequence counter at the time when this + * VID header was created. The global sequence counter is incremented each time + * UBI writes a new VID header to the flash, i.e. when it maps a logical + * eraseblock to a new physical eraseblock. The global sequence counter is an + * unsigned 64-bit integer and we assume it never overflows. The @sqnum + * (sequence number) is used to distinguish between older and newer versions of + * logical eraseblocks. + * + * There are 2 situations when there may be more than one physical eraseblock + * corresponding to the same logical eraseblock, i.e., having the same @vol_id + * and @lnum values in the volume identifier header. Suppose we have a logical + * eraseblock L and it is mapped to the physical eraseblock P. + * + * 1. Because UBI may erase physical eraseblocks asynchronously, the following + * situation is possible: L is asynchronously erased, so P is scheduled for + * erasure, then L is written to,i.e. mapped to another physical eraseblock P1, + * so P1 is written to, then an unclean reboot happens. Result - there are 2 + * physical eraseblocks P and P1 corresponding to the same logical eraseblock + * L. But P1 has greater sequence number, so UBI picks P1 when it attaches the + * flash. + * + * 2. From time to time UBI moves logical eraseblocks to other physical + * eraseblocks for wear-leveling reasons. If, for example, UBI moves L from P + * to P1, and an unclean reboot happens before P is physically erased, there + * are two physical eraseblocks P and P1 corresponding to L and UBI has to + * select one of them when the flash is attached. The @sqnum field says which + * PEB is the original (obviously P will have lower @sqnum) and the copy. But + * it is not enough to select the physical eraseblock with the higher sequence + * number, because the unclean reboot could have happen in the middle of the + * copying process, so the data in P is corrupted. It is also not enough to + * just select the physical eraseblock with lower sequence number, because the + * data there may be old (consider a case if more data was added to P1 after + * the copying). Moreover, the unclean reboot may happen when the erasure of P + * was just started, so it result in unstable P, which is "mostly" OK, but + * still has unstable bits. + * + * UBI uses the @copy_flag field to indicate that this logical eraseblock is a + * copy. UBI also calculates data CRC when the data is moved and stores it at + * the @data_crc field of the copy (P1). So when UBI needs to pick one physical + * eraseblock of two (P or P1), the @copy_flag of the newer one (P1) is + * examined. If it is cleared, the situation* is simple and the newer one is + * picked. If it is set, the data CRC of the copy (P1) is examined. If the CRC + * checksum is correct, this physical eraseblock is selected (P1). Otherwise + * the older one (P) is selected. + * + * There are 2 sorts of volumes in UBI: user volumes and internal volumes. + * Internal volumes are not seen from outside and are used for various internal + * UBI purposes. In this implementation there is only one internal volume - the + * layout volume. Internal volumes are the main mechanism of UBI extensions. + * For example, in future one may introduce a journal internal volume. Internal + * volumes have their own reserved range of IDs. + * + * The @compat field is only used for internal volumes and contains the "degree + * of their compatibility". It is always zero for user volumes. This field + * provides a mechanism to introduce UBI extensions and to be still compatible + * with older UBI binaries. For example, if someone introduced a journal in + * future, he would probably use %UBI_COMPAT_DELETE compatibility for the + * journal volume. And in this case, older UBI binaries, which know nothing + * about the journal volume, would just delete this volume and work perfectly + * fine. This is similar to what Ext2fs does when it is fed by an Ext3fs image + * - it just ignores the Ext3fs journal. + * + * The @data_crc field contains the CRC checksum of the contents of the logical + * eraseblock if this is a static volume. In case of dynamic volumes, it does + * not contain the CRC checksum as a rule. The only exception is when the + * data of the physical eraseblock was moved by the wear-leveling sub-system, + * then the wear-leveling sub-system calculates the data CRC and stores it in + * the @data_crc field. And of course, the @copy_flag is %in this case. + * + * The @data_size field is used only for static volumes because UBI has to know + * how many bytes of data are stored in this eraseblock. For dynamic volumes, + * this field usually contains zero. The only exception is when the data of the + * physical eraseblock was moved to another physical eraseblock for + * wear-leveling reasons. In this case, UBI calculates CRC checksum of the + * contents and uses both @data_crc and @data_size fields. In this case, the + * @data_size field contains data size. + * + * The @used_ebs field is used only for static volumes and indicates how many + * eraseblocks the data of the volume takes. For dynamic volumes this field is + * not used and always contains zero. + * + * The @data_pad is calculated when volumes are created using the alignment + * parameter. So, effectively, the @data_pad field reduces the size of logical + * eraseblocks of this volume. This is very handy when one uses block-oriented + * software (say, cramfs) on top of the UBI volume. + */ +struct ubi_vid_hdr { + __be32 magic; + __u8 version; + __u8 vol_type; + __u8 copy_flag; + __u8 compat; + __be32 vol_id; + __be32 lnum; + __u8 padding1[4]; + __be32 data_size; + __be32 used_ebs; + __be32 data_pad; + __be32 data_crc; + __u8 padding2[4]; + __be64 sqnum; + __u8 padding3[12]; + __be32 hdr_crc; +} __packed; + +/* Internal UBI volumes count */ +#define UBI_INT_VOL_COUNT 1 + +/* + * Starting ID of internal volumes: 0x7fffefff. + * There is reserved room for 4096 internal volumes. + */ +#define UBI_INTERNAL_VOL_START (0x7FFFFFFF - 4096) + +/* The layout volume contains the volume table */ + +#define UBI_LAYOUT_VOLUME_ID UBI_INTERNAL_VOL_START +#define UBI_LAYOUT_VOLUME_TYPE UBI_VID_DYNAMIC +#define UBI_LAYOUT_VOLUME_ALIGN 1 +#define UBI_LAYOUT_VOLUME_EBS 2 +#define UBI_LAYOUT_VOLUME_NAME "layout volume" +#define UBI_LAYOUT_VOLUME_COMPAT UBI_COMPAT_REJECT + +/* The maximum number of volumes per one UBI device */ +#define UBI_MAX_VOLUMES 128 + +/* The maximum volume name length */ +#define UBI_VOL_NAME_MAX 127 + +/* Size of the volume table record */ +#define UBI_VTBL_RECORD_SIZE sizeof(struct ubi_vtbl_record) + +/* Size of the volume table record without the ending CRC */ +#define UBI_VTBL_RECORD_SIZE_CRC (UBI_VTBL_RECORD_SIZE - sizeof(__be32)) + +/** + * struct ubi_vtbl_record - a record in the volume table. + * @reserved_pebs: how many physical eraseblocks are reserved for this volume + * @alignment: volume alignment + * @data_pad: how many bytes are unused at the end of the each physical + * eraseblock to satisfy the requested alignment + * @vol_type: volume type (%UBI_DYNAMIC_VOLUME or %UBI_STATIC_VOLUME) + * @upd_marker: if volume update was started but not finished + * @name_len: volume name length + * @name: the volume name + * @flags: volume flags (%UBI_VTBL_AUTORESIZE_FLG) + * @padding: reserved, zeroes + * @crc: a CRC32 checksum of the record + * + * The volume table records are stored in the volume table, which is stored in + * the layout volume. The layout volume consists of 2 logical eraseblock, each + * of which contains a copy of the volume table (i.e., the volume table is + * duplicated). The volume table is an array of &struct ubi_vtbl_record + * objects indexed by the volume ID. + * + * If the size of the logical eraseblock is large enough to fit + * %UBI_MAX_VOLUMES records, the volume table contains %UBI_MAX_VOLUMES + * records. Otherwise, it contains as many records as it can fit (i.e., size of + * logical eraseblock divided by sizeof(struct ubi_vtbl_record)). + * + * The @upd_marker flag is used to implement volume update. It is set to %1 + * before update and set to %0 after the update. So if the update operation was + * interrupted, UBI knows that the volume is corrupted. + * + * The @alignment field is specified when the volume is created and cannot be + * later changed. It may be useful, for example, when a block-oriented file + * system works on top of UBI. The @data_pad field is calculated using the + * logical eraseblock size and @alignment. The alignment must be multiple to the + * minimal flash I/O unit. If @alignment is 1, all the available space of + * the physical eraseblocks is used. + * + * Empty records contain all zeroes and the CRC checksum of those zeroes. + */ +struct ubi_vtbl_record { + __be32 reserved_pebs; + __be32 alignment; + __be32 data_pad; + __u8 vol_type; + __u8 upd_marker; + __be16 name_len; + __u8 name[UBI_VOL_NAME_MAX+1]; + __u8 flags; + __u8 padding[23]; + __be32 crc; +} __packed; + +/* UBI fastmap on-flash data structures */ + +#define UBI_FM_SB_VOLUME_ID (UBI_LAYOUT_VOLUME_ID + 1) +#define UBI_FM_DATA_VOLUME_ID (UBI_LAYOUT_VOLUME_ID + 2) + +/* fastmap on-flash data structure format version */ +#define UBI_FM_FMT_VERSION 1 + +#define UBI_FM_SB_MAGIC 0x7B11D69F +#define UBI_FM_HDR_MAGIC 0xD4B82EF7 +#define UBI_FM_VHDR_MAGIC 0xFA370ED1 +#define UBI_FM_POOL_MAGIC 0x67AF4D08 +#define UBI_FM_EBA_MAGIC 0xf0c040a8 + +/* A fastmap supber block can be located between PEB 0 and + * UBI_FM_MAX_START */ +#define UBI_FM_MAX_START 64 + +/* A fastmap can use up to UBI_FM_MAX_BLOCKS PEBs */ +#define UBI_FM_MAX_BLOCKS 32 + +/* 5% of the total number of PEBs have to be scanned while attaching + * from a fastmap. + * But the size of this pool is limited to be between UBI_FM_MIN_POOL_SIZE and + * UBI_FM_MAX_POOL_SIZE */ +#define UBI_FM_MIN_POOL_SIZE 8 +#define UBI_FM_MAX_POOL_SIZE 256 + +#define UBI_FM_WL_POOL_SIZE 25 + +/** + * struct ubi_fm_sb - UBI fastmap super block + * @magic: fastmap super block magic number (%UBI_FM_SB_MAGIC) + * @version: format version of this fastmap + * @data_crc: CRC over the fastmap data + * @used_blocks: number of PEBs used by this fastmap + * @block_loc: an array containing the location of all PEBs of the fastmap + * @block_ec: the erase counter of each used PEB + * @sqnum: highest sequence number value at the time while taking the fastmap + * + */ +struct ubi_fm_sb { + __be32 magic; + __u8 version; + __u8 padding1[3]; + __be32 data_crc; + __be32 used_blocks; + __be32 block_loc[UBI_FM_MAX_BLOCKS]; + __be32 block_ec[UBI_FM_MAX_BLOCKS]; + __be64 sqnum; + __u8 padding2[32]; +} __packed; + +/** + * struct ubi_fm_hdr - header of the fastmap data set + * @magic: fastmap header magic number (%UBI_FM_HDR_MAGIC) + * @free_peb_count: number of free PEBs known by this fastmap + * @used_peb_count: number of used PEBs known by this fastmap + * @scrub_peb_count: number of to be scrubbed PEBs known by this fastmap + * @bad_peb_count: number of bad PEBs known by this fastmap + * @erase_peb_count: number of bad PEBs which have to be erased + * @vol_count: number of UBI volumes known by this fastmap + */ +struct ubi_fm_hdr { + __be32 magic; + __be32 free_peb_count; + __be32 used_peb_count; + __be32 scrub_peb_count; + __be32 bad_peb_count; + __be32 erase_peb_count; + __be32 vol_count; + __u8 padding[4]; +} __packed; + +/* struct ubi_fm_hdr is followed by two struct ubi_fm_scan_pool */ + +/** + * struct ubi_fm_scan_pool - Fastmap pool PEBs to be scanned while attaching + * @magic: pool magic numer (%UBI_FM_POOL_MAGIC) + * @size: current pool size + * @max_size: maximal pool size + * @pebs: an array containing the location of all PEBs in this pool + */ +struct ubi_fm_scan_pool { + __be32 magic; + __be16 size; + __be16 max_size; + __be32 pebs[UBI_FM_MAX_POOL_SIZE]; + __be32 padding[4]; +} __packed; + +/* ubi_fm_scan_pool is followed by nfree+nused struct ubi_fm_ec records */ + +/** + * struct ubi_fm_ec - stores the erase counter of a PEB + * @pnum: PEB number + * @ec: ec of this PEB + */ +struct ubi_fm_ec { + __be32 pnum; + __be32 ec; +} __packed; + +/** + * struct ubi_fm_volhdr - Fastmap volume header + * it identifies the start of an eba table + * @magic: Fastmap volume header magic number (%UBI_FM_VHDR_MAGIC) + * @vol_id: volume id of the fastmapped volume + * @vol_type: type of the fastmapped volume + * @data_pad: data_pad value of the fastmapped volume + * @used_ebs: number of used LEBs within this volume + * @last_eb_bytes: number of bytes used in the last LEB + */ +struct ubi_fm_volhdr { + __be32 magic; + __be32 vol_id; + __u8 vol_type; + __u8 padding1[3]; + __be32 data_pad; + __be32 used_ebs; + __be32 last_eb_bytes; + __u8 padding2[8]; +} __packed; + +/* struct ubi_fm_volhdr is followed by one struct ubi_fm_eba records */ + +/** + * struct ubi_fm_eba - denotes an association beween a PEB and LEB + * @magic: EBA table magic number + * @reserved_pebs: number of table entries + * @pnum: PEB number of LEB (LEB is the index) + */ +struct ubi_fm_eba { + __be32 magic; + __be32 reserved_pebs; + __be32 pnum[0]; +} __packed; +#endif /* !__UBI_MEDIA_H__ */ diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h new file mode 100644 index 00000000000..7bf416329c1 --- /dev/null +++ b/drivers/mtd/ubi/ubi.h @@ -0,0 +1,1019 @@ +/* + * Copyright (c) International Business Machines Corp., 2006 + * Copyright (c) Nokia Corporation, 2006, 2007 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Artem Bityutskiy (Битюцкий Артём) + */ + +#ifndef __UBI_UBI_H__ +#define __UBI_UBI_H__ + +#include <linux/types.h> +#include <linux/list.h> +#include <linux/rbtree.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/mutex.h> +#include <linux/rwsem.h> +#include <linux/spinlock.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/vmalloc.h> +#include <linux/notifier.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/ubi.h> +#include <asm/pgtable.h> + +#include "ubi-media.h" + +/* Maximum number of supported UBI devices */ +#define UBI_MAX_DEVICES 32 + +/* UBI name used for character devices, sysfs, etc */ +#define UBI_NAME_STR "ubi" + +/* Normal UBI messages */ +#define ubi_msg(fmt, ...) pr_notice("UBI: " fmt "\n", ##__VA_ARGS__) +/* UBI warning messages */ +#define ubi_warn(fmt, ...) pr_warn("UBI warning: %s: " fmt "\n", \ + __func__, ##__VA_ARGS__) +/* UBI error messages */ +#define ubi_err(fmt, ...) pr_err("UBI error: %s: " fmt "\n", \ + __func__, ##__VA_ARGS__) + +/* Background thread name pattern */ +#define UBI_BGT_NAME_PATTERN "ubi_bgt%dd" + +/* + * This marker in the EBA table means that the LEB is um-mapped. + * NOTE! It has to have the same value as %UBI_ALL. + */ +#define UBI_LEB_UNMAPPED -1 + +/* + * In case of errors, UBI tries to repeat the operation several times before + * returning error. The below constant defines how many times UBI re-tries. + */ +#define UBI_IO_RETRIES 3 + +/* + * Length of the protection queue. The length is effectively equivalent to the + * number of (global) erase cycles PEBs are protected from the wear-leveling + * worker. + */ +#define UBI_PROT_QUEUE_LEN 10 + +/* The volume ID/LEB number/erase counter is unknown */ +#define UBI_UNKNOWN -1 + +/* + * The UBI debugfs directory name pattern and maximum name length (3 for "ubi" + * + 2 for the number plus 1 for the trailing zero byte. + */ +#define UBI_DFS_DIR_NAME "ubi%d" +#define UBI_DFS_DIR_LEN (3 + 2 + 1) + +/* + * Error codes returned by the I/O sub-system. + * + * UBI_IO_FF: the read region of flash contains only 0xFFs + * UBI_IO_FF_BITFLIPS: the same as %UBI_IO_FF, but also also there was a data + * integrity error reported by the MTD driver + * (uncorrectable ECC error in case of NAND) + * UBI_IO_BAD_HDR: the EC or VID header is corrupted (bad magic or CRC) + * UBI_IO_BAD_HDR_EBADMSG: the same as %UBI_IO_BAD_HDR, but also there was a + * data integrity error reported by the MTD driver + * (uncorrectable ECC error in case of NAND) + * UBI_IO_BITFLIPS: bit-flips were detected and corrected + * + * Note, it is probably better to have bit-flip and ebadmsg as flags which can + * be or'ed with other error code. But this is a big change because there are + * may callers, so it does not worth the risk of introducing a bug + */ +enum { + UBI_IO_FF = 1, + UBI_IO_FF_BITFLIPS, + UBI_IO_BAD_HDR, + UBI_IO_BAD_HDR_EBADMSG, + UBI_IO_BITFLIPS, +}; + +/* + * Return codes of the 'ubi_eba_copy_leb()' function. + * + * MOVE_CANCEL_RACE: canceled because the volume is being deleted, the source + * PEB was put meanwhile, or there is I/O on the source PEB + * MOVE_SOURCE_RD_ERR: canceled because there was a read error from the source + * PEB + * MOVE_TARGET_RD_ERR: canceled because there was a read error from the target + * PEB + * MOVE_TARGET_WR_ERR: canceled because there was a write error to the target + * PEB + * MOVE_TARGET_BITFLIPS: canceled because a bit-flip was detected in the + * target PEB + * MOVE_RETRY: retry scrubbing the PEB + */ +enum { + MOVE_CANCEL_RACE = 1, + MOVE_SOURCE_RD_ERR, + MOVE_TARGET_RD_ERR, + MOVE_TARGET_WR_ERR, + MOVE_TARGET_BITFLIPS, + MOVE_RETRY, +}; + +/* + * Return codes of the fastmap sub-system + * + * UBI_NO_FASTMAP: No fastmap super block was found + * UBI_BAD_FASTMAP: A fastmap was found but it's unusable + */ +enum { + UBI_NO_FASTMAP = 1, + UBI_BAD_FASTMAP, +}; + +/** + * struct ubi_wl_entry - wear-leveling entry. + * @u.rb: link in the corresponding (free/used) RB-tree + * @u.list: link in the protection queue + * @ec: erase counter + * @pnum: physical eraseblock number + * + * This data structure is used in the WL sub-system. Each physical eraseblock + * has a corresponding &struct wl_entry object which may be kept in different + * RB-trees. See WL sub-system for details. + */ +struct ubi_wl_entry { + union { + struct rb_node rb; + struct list_head list; + } u; + int ec; + int pnum; +}; + +/** + * struct ubi_ltree_entry - an entry in the lock tree. + * @rb: links RB-tree nodes + * @vol_id: volume ID of the locked logical eraseblock + * @lnum: locked logical eraseblock number + * @users: how many tasks are using this logical eraseblock or wait for it + * @mutex: read/write mutex to implement read/write access serialization to + * the (@vol_id, @lnum) logical eraseblock + * + * This data structure is used in the EBA sub-system to implement per-LEB + * locking. When a logical eraseblock is being locked - corresponding + * &struct ubi_ltree_entry object is inserted to the lock tree (@ubi->ltree). + * See EBA sub-system for details. + */ +struct ubi_ltree_entry { + struct rb_node rb; + int vol_id; + int lnum; + int users; + struct rw_semaphore mutex; +}; + +/** + * struct ubi_rename_entry - volume re-name description data structure. + * @new_name_len: new volume name length + * @new_name: new volume name + * @remove: if not zero, this volume should be removed, not re-named + * @desc: descriptor of the volume + * @list: links re-name entries into a list + * + * This data structure is utilized in the multiple volume re-name code. Namely, + * UBI first creates a list of &struct ubi_rename_entry objects from the + * &struct ubi_rnvol_req request object, and then utilizes this list to do all + * the job. + */ +struct ubi_rename_entry { + int new_name_len; + char new_name[UBI_VOL_NAME_MAX + 1]; + int remove; + struct ubi_volume_desc *desc; + struct list_head list; +}; + +struct ubi_volume_desc; + +/** + * struct ubi_fastmap_layout - in-memory fastmap data structure. + * @e: PEBs used by the current fastmap + * @to_be_tortured: if non-zero tortured this PEB + * @used_blocks: number of used PEBs + * @max_pool_size: maximal size of the user pool + * @max_wl_pool_size: maximal size of the pool used by the WL sub-system + */ +struct ubi_fastmap_layout { + struct ubi_wl_entry *e[UBI_FM_MAX_BLOCKS]; + int to_be_tortured[UBI_FM_MAX_BLOCKS]; + int used_blocks; + int max_pool_size; + int max_wl_pool_size; +}; + +/** + * struct ubi_fm_pool - in-memory fastmap pool + * @pebs: PEBs in this pool + * @used: number of used PEBs + * @size: total number of PEBs in this pool + * @max_size: maximal size of the pool + * + * A pool gets filled with up to max_size. + * If all PEBs within the pool are used a new fastmap will be written + * to the flash and the pool gets refilled with empty PEBs. + * + */ +struct ubi_fm_pool { + int pebs[UBI_FM_MAX_POOL_SIZE]; + int used; + int size; + int max_size; +}; + +/** + * struct ubi_volume - UBI volume description data structure. + * @dev: device object to make use of the the Linux device model + * @cdev: character device object to create character device + * @ubi: reference to the UBI device description object + * @vol_id: volume ID + * @ref_count: volume reference count + * @readers: number of users holding this volume in read-only mode + * @writers: number of users holding this volume in read-write mode + * @exclusive: whether somebody holds this volume in exclusive mode + * + * @reserved_pebs: how many physical eraseblocks are reserved for this volume + * @vol_type: volume type (%UBI_DYNAMIC_VOLUME or %UBI_STATIC_VOLUME) + * @usable_leb_size: logical eraseblock size without padding + * @used_ebs: how many logical eraseblocks in this volume contain data + * @last_eb_bytes: how many bytes are stored in the last logical eraseblock + * @used_bytes: how many bytes of data this volume contains + * @alignment: volume alignment + * @data_pad: how many bytes are not used at the end of physical eraseblocks to + * satisfy the requested alignment + * @name_len: volume name length + * @name: volume name + * + * @upd_ebs: how many eraseblocks are expected to be updated + * @ch_lnum: LEB number which is being changing by the atomic LEB change + * operation + * @upd_bytes: how many bytes are expected to be received for volume update or + * atomic LEB change + * @upd_received: how many bytes were already received for volume update or + * atomic LEB change + * @upd_buf: update buffer which is used to collect update data or data for + * atomic LEB change + * + * @eba_tbl: EBA table of this volume (LEB->PEB mapping) + * @checked: %1 if this static volume was checked + * @corrupted: %1 if the volume is corrupted (static volumes only) + * @upd_marker: %1 if the update marker is set for this volume + * @updating: %1 if the volume is being updated + * @changing_leb: %1 if the atomic LEB change ioctl command is in progress + * @direct_writes: %1 if direct writes are enabled for this volume + * + * The @corrupted field indicates that the volume's contents is corrupted. + * Since UBI protects only static volumes, this field is not relevant to + * dynamic volumes - it is user's responsibility to assure their data + * integrity. + * + * The @upd_marker flag indicates that this volume is either being updated at + * the moment or is damaged because of an unclean reboot. + */ +struct ubi_volume { + struct device dev; + struct cdev cdev; + struct ubi_device *ubi; + int vol_id; + int ref_count; + int readers; + int writers; + int exclusive; + + int reserved_pebs; + int vol_type; + int usable_leb_size; + int used_ebs; + int last_eb_bytes; + long long used_bytes; + int alignment; + int data_pad; + int name_len; + char name[UBI_VOL_NAME_MAX + 1]; + + int upd_ebs; + int ch_lnum; + long long upd_bytes; + long long upd_received; + void *upd_buf; + + int *eba_tbl; + unsigned int checked:1; + unsigned int corrupted:1; + unsigned int upd_marker:1; + unsigned int updating:1; + unsigned int changing_leb:1; + unsigned int direct_writes:1; +}; + +/** + * struct ubi_volume_desc - UBI volume descriptor returned when it is opened. + * @vol: reference to the corresponding volume description object + * @mode: open mode (%UBI_READONLY, %UBI_READWRITE, or %UBI_EXCLUSIVE) + */ +struct ubi_volume_desc { + struct ubi_volume *vol; + int mode; +}; + +struct ubi_wl_entry; + +/** + * struct ubi_debug_info - debugging information for an UBI device. + * + * @chk_gen: if UBI general extra checks are enabled + * @chk_io: if UBI I/O extra checks are enabled + * @disable_bgt: disable the background task for testing purposes + * @emulate_bitflips: emulate bit-flips for testing purposes + * @emulate_io_failures: emulate write/erase failures for testing purposes + * @dfs_dir_name: name of debugfs directory containing files of this UBI device + * @dfs_dir: direntry object of the UBI device debugfs directory + * @dfs_chk_gen: debugfs knob to enable UBI general extra checks + * @dfs_chk_io: debugfs knob to enable UBI I/O extra checks + * @dfs_disable_bgt: debugfs knob to disable the background task + * @dfs_emulate_bitflips: debugfs knob to emulate bit-flips + * @dfs_emulate_io_failures: debugfs knob to emulate write/erase failures + */ +struct ubi_debug_info { + unsigned int chk_gen:1; + unsigned int chk_io:1; + unsigned int disable_bgt:1; + unsigned int emulate_bitflips:1; + unsigned int emulate_io_failures:1; + char dfs_dir_name[UBI_DFS_DIR_LEN + 1]; + struct dentry *dfs_dir; + struct dentry *dfs_chk_gen; + struct dentry *dfs_chk_io; + struct dentry *dfs_disable_bgt; + struct dentry *dfs_emulate_bitflips; + struct dentry *dfs_emulate_io_failures; +}; + +/** + * struct ubi_device - UBI device description structure + * @dev: UBI device object to use the the Linux device model + * @cdev: character device object to create character device + * @ubi_num: UBI device number + * @ubi_name: UBI device name + * @vol_count: number of volumes in this UBI device + * @volumes: volumes of this UBI device + * @volumes_lock: protects @volumes, @rsvd_pebs, @avail_pebs, beb_rsvd_pebs, + * @beb_rsvd_level, @bad_peb_count, @good_peb_count, @vol_count, + * @vol->readers, @vol->writers, @vol->exclusive, + * @vol->ref_count, @vol->mapping and @vol->eba_tbl. + * @ref_count: count of references on the UBI device + * @image_seq: image sequence number recorded on EC headers + * + * @rsvd_pebs: count of reserved physical eraseblocks + * @avail_pebs: count of available physical eraseblocks + * @beb_rsvd_pebs: how many physical eraseblocks are reserved for bad PEB + * handling + * @beb_rsvd_level: normal level of PEBs reserved for bad PEB handling + * + * @autoresize_vol_id: ID of the volume which has to be auto-resized at the end + * of UBI initialization + * @vtbl_slots: how many slots are available in the volume table + * @vtbl_size: size of the volume table in bytes + * @vtbl: in-RAM volume table copy + * @device_mutex: protects on-flash volume table and serializes volume + * creation, deletion, update, re-size, re-name and set + * property + * + * @max_ec: current highest erase counter value + * @mean_ec: current mean erase counter value + * + * @global_sqnum: global sequence number + * @ltree_lock: protects the lock tree and @global_sqnum + * @ltree: the lock tree + * @alc_mutex: serializes "atomic LEB change" operations + * + * @fm_disabled: non-zero if fastmap is disabled (default) + * @fm: in-memory data structure of the currently used fastmap + * @fm_pool: in-memory data structure of the fastmap pool + * @fm_wl_pool: in-memory data structure of the fastmap pool used by the WL + * sub-system + * @fm_mutex: serializes ubi_update_fastmap() and protects @fm_buf + * @fm_buf: vmalloc()'d buffer which holds the raw fastmap + * @fm_size: fastmap size in bytes + * @fm_sem: allows ubi_update_fastmap() to block EBA table changes + * @fm_work: fastmap work queue + * + * @used: RB-tree of used physical eraseblocks + * @erroneous: RB-tree of erroneous used physical eraseblocks + * @free: RB-tree of free physical eraseblocks + * @free_count: Contains the number of elements in @free + * @scrub: RB-tree of physical eraseblocks which need scrubbing + * @pq: protection queue (contain physical eraseblocks which are temporarily + * protected from the wear-leveling worker) + * @pq_head: protection queue head + * @wl_lock: protects the @used, @free, @pq, @pq_head, @lookuptbl, @move_from, + * @move_to, @move_to_put @erase_pending, @wl_scheduled, @works, + * @erroneous, and @erroneous_peb_count fields + * @move_mutex: serializes eraseblock moves + * @work_sem: synchronizes the WL worker with use tasks + * @wl_scheduled: non-zero if the wear-leveling was scheduled + * @lookuptbl: a table to quickly find a &struct ubi_wl_entry object for any + * physical eraseblock + * @move_from: physical eraseblock from where the data is being moved + * @move_to: physical eraseblock where the data is being moved to + * @move_to_put: if the "to" PEB was put + * @works: list of pending works + * @works_count: count of pending works + * @bgt_thread: background thread description object + * @thread_enabled: if the background thread is enabled + * @bgt_name: background thread name + * + * @flash_size: underlying MTD device size (in bytes) + * @peb_count: count of physical eraseblocks on the MTD device + * @peb_size: physical eraseblock size + * @bad_peb_limit: top limit of expected bad physical eraseblocks + * @bad_peb_count: count of bad physical eraseblocks + * @good_peb_count: count of good physical eraseblocks + * @corr_peb_count: count of corrupted physical eraseblocks (preserved and not + * used by UBI) + * @erroneous_peb_count: count of erroneous physical eraseblocks in @erroneous + * @max_erroneous: maximum allowed amount of erroneous physical eraseblocks + * @min_io_size: minimal input/output unit size of the underlying MTD device + * @hdrs_min_io_size: minimal I/O unit size used for VID and EC headers + * @ro_mode: if the UBI device is in read-only mode + * @leb_size: logical eraseblock size + * @leb_start: starting offset of logical eraseblocks within physical + * eraseblocks + * @ec_hdr_alsize: size of the EC header aligned to @hdrs_min_io_size + * @vid_hdr_alsize: size of the VID header aligned to @hdrs_min_io_size + * @vid_hdr_offset: starting offset of the volume identifier header (might be + * unaligned) + * @vid_hdr_aloffset: starting offset of the VID header aligned to + * @hdrs_min_io_size + * @vid_hdr_shift: contains @vid_hdr_offset - @vid_hdr_aloffset + * @bad_allowed: whether the MTD device admits of bad physical eraseblocks or + * not + * @nor_flash: non-zero if working on top of NOR flash + * @max_write_size: maximum amount of bytes the underlying flash can write at a + * time (MTD write buffer size) + * @mtd: MTD device descriptor + * + * @peb_buf: a buffer of PEB size used for different purposes + * @buf_mutex: protects @peb_buf + * @ckvol_mutex: serializes static volume checking when opening + * + * @dbg: debugging information for this UBI device + */ +struct ubi_device { + struct cdev cdev; + struct device dev; + int ubi_num; + char ubi_name[sizeof(UBI_NAME_STR)+5]; + int vol_count; + struct ubi_volume *volumes[UBI_MAX_VOLUMES+UBI_INT_VOL_COUNT]; + spinlock_t volumes_lock; + int ref_count; + int image_seq; + + int rsvd_pebs; + int avail_pebs; + int beb_rsvd_pebs; + int beb_rsvd_level; + int bad_peb_limit; + + int autoresize_vol_id; + int vtbl_slots; + int vtbl_size; + struct ubi_vtbl_record *vtbl; + struct mutex device_mutex; + + int max_ec; + /* Note, mean_ec is not updated run-time - should be fixed */ + int mean_ec; + + /* EBA sub-system's stuff */ + unsigned long long global_sqnum; + spinlock_t ltree_lock; + struct rb_root ltree; + struct mutex alc_mutex; + + /* Fastmap stuff */ + int fm_disabled; + struct ubi_fastmap_layout *fm; + struct ubi_fm_pool fm_pool; + struct ubi_fm_pool fm_wl_pool; + struct rw_semaphore fm_sem; + struct mutex fm_mutex; + void *fm_buf; + size_t fm_size; + struct work_struct fm_work; + + /* Wear-leveling sub-system's stuff */ + struct rb_root used; + struct rb_root erroneous; + struct rb_root free; + int free_count; + struct rb_root scrub; + struct list_head pq[UBI_PROT_QUEUE_LEN]; + int pq_head; + spinlock_t wl_lock; + struct mutex move_mutex; + struct rw_semaphore work_sem; + int wl_scheduled; + struct ubi_wl_entry **lookuptbl; + struct ubi_wl_entry *move_from; + struct ubi_wl_entry *move_to; + int move_to_put; + struct list_head works; + int works_count; + struct task_struct *bgt_thread; + int thread_enabled; + char bgt_name[sizeof(UBI_BGT_NAME_PATTERN)+2]; + + /* I/O sub-system's stuff */ + long long flash_size; + int peb_count; + int peb_size; + int bad_peb_count; + int good_peb_count; + int corr_peb_count; + int erroneous_peb_count; + int max_erroneous; + int min_io_size; + int hdrs_min_io_size; + int ro_mode; + int leb_size; + int leb_start; + int ec_hdr_alsize; + int vid_hdr_alsize; + int vid_hdr_offset; + int vid_hdr_aloffset; + int vid_hdr_shift; + unsigned int bad_allowed:1; + unsigned int nor_flash:1; + int max_write_size; + struct mtd_info *mtd; + + void *peb_buf; + struct mutex buf_mutex; + struct mutex ckvol_mutex; + + struct ubi_debug_info dbg; +}; + +/** + * struct ubi_ainf_peb - attach information about a physical eraseblock. + * @ec: erase counter (%UBI_UNKNOWN if it is unknown) + * @pnum: physical eraseblock number + * @vol_id: ID of the volume this LEB belongs to + * @lnum: logical eraseblock number + * @scrub: if this physical eraseblock needs scrubbing + * @copy_flag: this LEB is a copy (@copy_flag is set in VID header of this LEB) + * @sqnum: sequence number + * @u: unions RB-tree or @list links + * @u.rb: link in the per-volume RB-tree of &struct ubi_ainf_peb objects + * @u.list: link in one of the eraseblock lists + * + * One object of this type is allocated for each physical eraseblock when + * attaching an MTD device. Note, if this PEB does not belong to any LEB / + * volume, the @vol_id and @lnum fields are initialized to %UBI_UNKNOWN. + */ +struct ubi_ainf_peb { + int ec; + int pnum; + int vol_id; + int lnum; + unsigned int scrub:1; + unsigned int copy_flag:1; + unsigned long long sqnum; + union { + struct rb_node rb; + struct list_head list; + } u; +}; + +/** + * struct ubi_ainf_volume - attaching information about a volume. + * @vol_id: volume ID + * @highest_lnum: highest logical eraseblock number in this volume + * @leb_count: number of logical eraseblocks in this volume + * @vol_type: volume type + * @used_ebs: number of used logical eraseblocks in this volume (only for + * static volumes) + * @last_data_size: amount of data in the last logical eraseblock of this + * volume (always equivalent to the usable logical eraseblock + * size in case of dynamic volumes) + * @data_pad: how many bytes at the end of logical eraseblocks of this volume + * are not used (due to volume alignment) + * @compat: compatibility flags of this volume + * @rb: link in the volume RB-tree + * @root: root of the RB-tree containing all the eraseblock belonging to this + * volume (&struct ubi_ainf_peb objects) + * + * One object of this type is allocated for each volume when attaching an MTD + * device. + */ +struct ubi_ainf_volume { + int vol_id; + int highest_lnum; + int leb_count; + int vol_type; + int used_ebs; + int last_data_size; + int data_pad; + int compat; + struct rb_node rb; + struct rb_root root; +}; + +/** + * struct ubi_attach_info - MTD device attaching information. + * @volumes: root of the volume RB-tree + * @corr: list of corrupted physical eraseblocks + * @free: list of free physical eraseblocks + * @erase: list of physical eraseblocks which have to be erased + * @alien: list of physical eraseblocks which should not be used by UBI (e.g., + * those belonging to "preserve"-compatible internal volumes) + * @corr_peb_count: count of PEBs in the @corr list + * @empty_peb_count: count of PEBs which are presumably empty (contain only + * 0xFF bytes) + * @alien_peb_count: count of PEBs in the @alien list + * @bad_peb_count: count of bad physical eraseblocks + * @maybe_bad_peb_count: count of bad physical eraseblocks which are not marked + * as bad yet, but which look like bad + * @vols_found: number of volumes found + * @highest_vol_id: highest volume ID + * @is_empty: flag indicating whether the MTD device is empty or not + * @min_ec: lowest erase counter value + * @max_ec: highest erase counter value + * @max_sqnum: highest sequence number value + * @mean_ec: mean erase counter value + * @ec_sum: a temporary variable used when calculating @mean_ec + * @ec_count: a temporary variable used when calculating @mean_ec + * @aeb_slab_cache: slab cache for &struct ubi_ainf_peb objects + * + * This data structure contains the result of attaching an MTD device and may + * be used by other UBI sub-systems to build final UBI data structures, further + * error-recovery and so on. + */ +struct ubi_attach_info { + struct rb_root volumes; + struct list_head corr; + struct list_head free; + struct list_head erase; + struct list_head alien; + int corr_peb_count; + int empty_peb_count; + int alien_peb_count; + int bad_peb_count; + int maybe_bad_peb_count; + int vols_found; + int highest_vol_id; + int is_empty; + int min_ec; + int max_ec; + unsigned long long max_sqnum; + int mean_ec; + uint64_t ec_sum; + int ec_count; + struct kmem_cache *aeb_slab_cache; +}; + +/** + * struct ubi_work - UBI work description data structure. + * @list: a link in the list of pending works + * @func: worker function + * @e: physical eraseblock to erase + * @vol_id: the volume ID on which this erasure is being performed + * @lnum: the logical eraseblock number + * @torture: if the physical eraseblock has to be tortured + * @anchor: produce a anchor PEB to by used by fastmap + * + * The @func pointer points to the worker function. If the @cancel argument is + * not zero, the worker has to free the resources and exit immediately. The + * worker has to return zero in case of success and a negative error code in + * case of failure. + */ +struct ubi_work { + struct list_head list; + int (*func)(struct ubi_device *ubi, struct ubi_work *wrk, int cancel); + /* The below fields are only relevant to erasure works */ + struct ubi_wl_entry *e; + int vol_id; + int lnum; + int torture; + int anchor; +}; + +#include "debug.h" + +extern struct kmem_cache *ubi_wl_entry_slab; +extern const struct file_operations ubi_ctrl_cdev_operations; +extern const struct file_operations ubi_cdev_operations; +extern const struct file_operations ubi_vol_cdev_operations; +extern struct class *ubi_class; +extern struct mutex ubi_devices_mutex; +extern struct blocking_notifier_head ubi_notifiers; + +/* attach.c */ +int ubi_add_to_av(struct ubi_device *ubi, struct ubi_attach_info *ai, int pnum, + int ec, const struct ubi_vid_hdr *vid_hdr, int bitflips); +struct ubi_ainf_volume *ubi_find_av(const struct ubi_attach_info *ai, + int vol_id); +void ubi_remove_av(struct ubi_attach_info *ai, struct ubi_ainf_volume *av); +struct ubi_ainf_peb *ubi_early_get_peb(struct ubi_device *ubi, + struct ubi_attach_info *ai); +int ubi_attach(struct ubi_device *ubi, int force_scan); +void ubi_destroy_ai(struct ubi_attach_info *ai); + +/* vtbl.c */ +int ubi_change_vtbl_record(struct ubi_device *ubi, int idx, + struct ubi_vtbl_record *vtbl_rec); +int ubi_vtbl_rename_volumes(struct ubi_device *ubi, + struct list_head *rename_list); +int ubi_read_volume_table(struct ubi_device *ubi, struct ubi_attach_info *ai); + +/* vmt.c */ +int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req); +int ubi_remove_volume(struct ubi_volume_desc *desc, int no_vtbl); +int ubi_resize_volume(struct ubi_volume_desc *desc, int reserved_pebs); +int ubi_rename_volumes(struct ubi_device *ubi, struct list_head *rename_list); +int ubi_add_volume(struct ubi_device *ubi, struct ubi_volume *vol); +void ubi_free_volume(struct ubi_device *ubi, struct ubi_volume *vol); + +/* upd.c */ +int ubi_start_update(struct ubi_device *ubi, struct ubi_volume *vol, + long long bytes); +int ubi_more_update_data(struct ubi_device *ubi, struct ubi_volume *vol, + const void __user *buf, int count); +int ubi_start_leb_change(struct ubi_device *ubi, struct ubi_volume *vol, + const struct ubi_leb_change_req *req); +int ubi_more_leb_change_data(struct ubi_device *ubi, struct ubi_volume *vol, + const void __user *buf, int count); + +/* misc.c */ +int ubi_calc_data_len(const struct ubi_device *ubi, const void *buf, + int length); +int ubi_check_volume(struct ubi_device *ubi, int vol_id); +void ubi_update_reserved(struct ubi_device *ubi); +void ubi_calculate_reserved(struct ubi_device *ubi); +int ubi_check_pattern(const void *buf, uint8_t patt, int size); + +/* eba.c */ +int ubi_eba_unmap_leb(struct ubi_device *ubi, struct ubi_volume *vol, + int lnum); +int ubi_eba_read_leb(struct ubi_device *ubi, struct ubi_volume *vol, int lnum, + void *buf, int offset, int len, int check); +int ubi_eba_write_leb(struct ubi_device *ubi, struct ubi_volume *vol, int lnum, + const void *buf, int offset, int len); +int ubi_eba_write_leb_st(struct ubi_device *ubi, struct ubi_volume *vol, + int lnum, const void *buf, int len, int used_ebs); +int ubi_eba_atomic_leb_change(struct ubi_device *ubi, struct ubi_volume *vol, + int lnum, const void *buf, int len); +int ubi_eba_copy_leb(struct ubi_device *ubi, int from, int to, + struct ubi_vid_hdr *vid_hdr); +int ubi_eba_init(struct ubi_device *ubi, struct ubi_attach_info *ai); +unsigned long long ubi_next_sqnum(struct ubi_device *ubi); +int self_check_eba(struct ubi_device *ubi, struct ubi_attach_info *ai_fastmap, + struct ubi_attach_info *ai_scan); + +/* wl.c */ +int ubi_wl_get_peb(struct ubi_device *ubi); +int ubi_wl_put_peb(struct ubi_device *ubi, int vol_id, int lnum, + int pnum, int torture); +int ubi_wl_flush(struct ubi_device *ubi, int vol_id, int lnum); +int ubi_wl_scrub_peb(struct ubi_device *ubi, int pnum); +int ubi_wl_init(struct ubi_device *ubi, struct ubi_attach_info *ai); +void ubi_wl_close(struct ubi_device *ubi); +int ubi_thread(void *u); +struct ubi_wl_entry *ubi_wl_get_fm_peb(struct ubi_device *ubi, int anchor); +int ubi_wl_put_fm_peb(struct ubi_device *ubi, struct ubi_wl_entry *used_e, + int lnum, int torture); +int ubi_is_erase_work(struct ubi_work *wrk); +void ubi_refill_pools(struct ubi_device *ubi); +int ubi_ensure_anchor_pebs(struct ubi_device *ubi); + +/* io.c */ +int ubi_io_read(const struct ubi_device *ubi, void *buf, int pnum, int offset, + int len); +int ubi_io_write(struct ubi_device *ubi, const void *buf, int pnum, int offset, + int len); +int ubi_io_sync_erase(struct ubi_device *ubi, int pnum, int torture); +int ubi_io_is_bad(const struct ubi_device *ubi, int pnum); +int ubi_io_mark_bad(const struct ubi_device *ubi, int pnum); +int ubi_io_read_ec_hdr(struct ubi_device *ubi, int pnum, + struct ubi_ec_hdr *ec_hdr, int verbose); +int ubi_io_write_ec_hdr(struct ubi_device *ubi, int pnum, + struct ubi_ec_hdr *ec_hdr); +int ubi_io_read_vid_hdr(struct ubi_device *ubi, int pnum, + struct ubi_vid_hdr *vid_hdr, int verbose); +int ubi_io_write_vid_hdr(struct ubi_device *ubi, int pnum, + struct ubi_vid_hdr *vid_hdr); + +/* build.c */ +int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, + int vid_hdr_offset, int max_beb_per1024); +int ubi_detach_mtd_dev(int ubi_num, int anyway); +struct ubi_device *ubi_get_device(int ubi_num); +void ubi_put_device(struct ubi_device *ubi); +struct ubi_device *ubi_get_by_major(int major); +int ubi_major2num(int major); +int ubi_volume_notify(struct ubi_device *ubi, struct ubi_volume *vol, + int ntype); +int ubi_notify_all(struct ubi_device *ubi, int ntype, + struct notifier_block *nb); +int ubi_enumerate_volumes(struct notifier_block *nb); +void ubi_free_internal_volumes(struct ubi_device *ubi); + +/* kapi.c */ +void ubi_do_get_device_info(struct ubi_device *ubi, struct ubi_device_info *di); +void ubi_do_get_volume_info(struct ubi_device *ubi, struct ubi_volume *vol, + struct ubi_volume_info *vi); +/* scan.c */ +int ubi_compare_lebs(struct ubi_device *ubi, const struct ubi_ainf_peb *aeb, + int pnum, const struct ubi_vid_hdr *vid_hdr); + +/* fastmap.c */ +size_t ubi_calc_fm_size(struct ubi_device *ubi); +int ubi_update_fastmap(struct ubi_device *ubi); +int ubi_scan_fastmap(struct ubi_device *ubi, struct ubi_attach_info *ai, + int fm_anchor); + +/* block.c */ +#ifdef CONFIG_MTD_UBI_BLOCK +int ubiblock_init(void); +void ubiblock_exit(void); +int ubiblock_create(struct ubi_volume_info *vi); +int ubiblock_remove(struct ubi_volume_info *vi); +#else +static inline int ubiblock_init(void) { return 0; } +static inline void ubiblock_exit(void) {} +static inline int ubiblock_create(struct ubi_volume_info *vi) +{ + return -ENOSYS; +} +static inline int ubiblock_remove(struct ubi_volume_info *vi) +{ + return -ENOSYS; +} +#endif + + +/* + * ubi_rb_for_each_entry - walk an RB-tree. + * @rb: a pointer to type 'struct rb_node' to use as a loop counter + * @pos: a pointer to RB-tree entry type to use as a loop counter + * @root: RB-tree's root + * @member: the name of the 'struct rb_node' within the RB-tree entry + */ +#define ubi_rb_for_each_entry(rb, pos, root, member) \ + for (rb = rb_first(root), \ + pos = (rb ? container_of(rb, typeof(*pos), member) : NULL); \ + rb; \ + rb = rb_next(rb), \ + pos = (rb ? container_of(rb, typeof(*pos), member) : NULL)) + +/* + * ubi_move_aeb_to_list - move a PEB from the volume tree to a list. + * + * @av: volume attaching information + * @aeb: attaching eraseblock information + * @list: the list to move to + */ +static inline void ubi_move_aeb_to_list(struct ubi_ainf_volume *av, + struct ubi_ainf_peb *aeb, + struct list_head *list) +{ + rb_erase(&aeb->u.rb, &av->root); + list_add_tail(&aeb->u.list, list); +} + +/** + * ubi_zalloc_vid_hdr - allocate a volume identifier header object. + * @ubi: UBI device description object + * @gfp_flags: GFP flags to allocate with + * + * This function returns a pointer to the newly allocated and zero-filled + * volume identifier header object in case of success and %NULL in case of + * failure. + */ +static inline struct ubi_vid_hdr * +ubi_zalloc_vid_hdr(const struct ubi_device *ubi, gfp_t gfp_flags) +{ + void *vid_hdr; + + vid_hdr = kzalloc(ubi->vid_hdr_alsize, gfp_flags); + if (!vid_hdr) + return NULL; + + /* + * VID headers may be stored at un-aligned flash offsets, so we shift + * the pointer. + */ + return vid_hdr + ubi->vid_hdr_shift; +} + +/** + * ubi_free_vid_hdr - free a volume identifier header object. + * @ubi: UBI device description object + * @vid_hdr: the object to free + */ +static inline void ubi_free_vid_hdr(const struct ubi_device *ubi, + struct ubi_vid_hdr *vid_hdr) +{ + void *p = vid_hdr; + + if (!p) + return; + + kfree(p - ubi->vid_hdr_shift); +} + +/* + * This function is equivalent to 'ubi_io_read()', but @offset is relative to + * the beginning of the logical eraseblock, not to the beginning of the + * physical eraseblock. + */ +static inline int ubi_io_read_data(const struct ubi_device *ubi, void *buf, + int pnum, int offset, int len) +{ + ubi_assert(offset >= 0); + return ubi_io_read(ubi, buf, pnum, offset + ubi->leb_start, len); +} + +/* + * This function is equivalent to 'ubi_io_write()', but @offset is relative to + * the beginning of the logical eraseblock, not to the beginning of the + * physical eraseblock. + */ +static inline int ubi_io_write_data(struct ubi_device *ubi, const void *buf, + int pnum, int offset, int len) +{ + ubi_assert(offset >= 0); + return ubi_io_write(ubi, buf, pnum, offset + ubi->leb_start, len); +} + +/** + * ubi_ro_mode - switch to read-only mode. + * @ubi: UBI device description object + */ +static inline void ubi_ro_mode(struct ubi_device *ubi) +{ + if (!ubi->ro_mode) { + ubi->ro_mode = 1; + ubi_warn("switch to read-only mode"); + dump_stack(); + } +} + +/** + * vol_id2idx - get table index by volume ID. + * @ubi: UBI device description object + * @vol_id: volume ID + */ +static inline int vol_id2idx(const struct ubi_device *ubi, int vol_id) +{ + if (vol_id >= UBI_INTERNAL_VOL_START) + return vol_id - UBI_INTERNAL_VOL_START + ubi->vtbl_slots; + else + return vol_id; +} + +/** + * idx2vol_id - get volume ID by table index. + * @ubi: UBI device description object + * @idx: table index + */ +static inline int idx2vol_id(const struct ubi_device *ubi, int idx) +{ + if (idx >= ubi->vtbl_slots) + return idx - ubi->vtbl_slots + UBI_INTERNAL_VOL_START; + else + return idx; +} + +#endif /* !__UBI_UBI_H__ */ diff --git a/drivers/mtd/ubi/upd.c b/drivers/mtd/ubi/upd.c new file mode 100644 index 00000000000..ec2c2dc1c1c --- /dev/null +++ b/drivers/mtd/ubi/upd.c @@ -0,0 +1,431 @@ +/* + * Copyright (c) International Business Machines Corp., 2006 + * Copyright (c) Nokia Corporation, 2006 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Artem Bityutskiy (Битюцкий Артём) + * + * Jan 2007: Alexander Schmidt, hacked per-volume update. + */ + +/* + * This file contains implementation of the volume update and atomic LEB change + * functionality. + * + * The update operation is based on the per-volume update marker which is + * stored in the volume table. The update marker is set before the update + * starts, and removed after the update has been finished. So if the update was + * interrupted by an unclean re-boot or due to some other reasons, the update + * marker stays on the flash media and UBI finds it when it attaches the MTD + * device next time. If the update marker is set for a volume, the volume is + * treated as damaged and most I/O operations are prohibited. Only a new update + * operation is allowed. + * + * Note, in general it is possible to implement the update operation as a + * transaction with a roll-back capability. + */ + +#include <linux/err.h> +#include <linux/uaccess.h> +#include <linux/math64.h> +#include "ubi.h" + +/** + * set_update_marker - set update marker. + * @ubi: UBI device description object + * @vol: volume description object + * + * This function sets the update marker flag for volume @vol. Returns zero + * in case of success and a negative error code in case of failure. + */ +static int set_update_marker(struct ubi_device *ubi, struct ubi_volume *vol) +{ + int err; + struct ubi_vtbl_record vtbl_rec; + + dbg_gen("set update marker for volume %d", vol->vol_id); + + if (vol->upd_marker) { + ubi_assert(ubi->vtbl[vol->vol_id].upd_marker); + dbg_gen("already set"); + return 0; + } + + vtbl_rec = ubi->vtbl[vol->vol_id]; + vtbl_rec.upd_marker = 1; + + mutex_lock(&ubi->device_mutex); + err = ubi_change_vtbl_record(ubi, vol->vol_id, &vtbl_rec); + vol->upd_marker = 1; + mutex_unlock(&ubi->device_mutex); + return err; +} + +/** + * clear_update_marker - clear update marker. + * @ubi: UBI device description object + * @vol: volume description object + * @bytes: new data size in bytes + * + * This function clears the update marker for volume @vol, sets new volume + * data size and clears the "corrupted" flag (static volumes only). Returns + * zero in case of success and a negative error code in case of failure. + */ +static int clear_update_marker(struct ubi_device *ubi, struct ubi_volume *vol, + long long bytes) +{ + int err; + struct ubi_vtbl_record vtbl_rec; + + dbg_gen("clear update marker for volume %d", vol->vol_id); + + vtbl_rec = ubi->vtbl[vol->vol_id]; + ubi_assert(vol->upd_marker && vtbl_rec.upd_marker); + vtbl_rec.upd_marker = 0; + + if (vol->vol_type == UBI_STATIC_VOLUME) { + vol->corrupted = 0; + vol->used_bytes = bytes; + vol->used_ebs = div_u64_rem(bytes, vol->usable_leb_size, + &vol->last_eb_bytes); + if (vol->last_eb_bytes) + vol->used_ebs += 1; + else + vol->last_eb_bytes = vol->usable_leb_size; + } + + mutex_lock(&ubi->device_mutex); + err = ubi_change_vtbl_record(ubi, vol->vol_id, &vtbl_rec); + vol->upd_marker = 0; + mutex_unlock(&ubi->device_mutex); + return err; +} + +/** + * ubi_start_update - start volume update. + * @ubi: UBI device description object + * @vol: volume description object + * @bytes: update bytes + * + * This function starts volume update operation. If @bytes is zero, the volume + * is just wiped out. Returns zero in case of success and a negative error code + * in case of failure. + */ +int ubi_start_update(struct ubi_device *ubi, struct ubi_volume *vol, + long long bytes) +{ + int i, err; + + dbg_gen("start update of volume %d, %llu bytes", vol->vol_id, bytes); + ubi_assert(!vol->updating && !vol->changing_leb); + vol->updating = 1; + + err = set_update_marker(ubi, vol); + if (err) + return err; + + /* Before updating - wipe out the volume */ + for (i = 0; i < vol->reserved_pebs; i++) { + err = ubi_eba_unmap_leb(ubi, vol, i); + if (err) + return err; + } + + if (bytes == 0) { + err = ubi_wl_flush(ubi, UBI_ALL, UBI_ALL); + if (err) + return err; + + err = clear_update_marker(ubi, vol, 0); + if (err) + return err; + vol->updating = 0; + return 0; + } + + vol->upd_buf = vmalloc(ubi->leb_size); + if (!vol->upd_buf) + return -ENOMEM; + + vol->upd_ebs = div_u64(bytes + vol->usable_leb_size - 1, + vol->usable_leb_size); + vol->upd_bytes = bytes; + vol->upd_received = 0; + return 0; +} + +/** + * ubi_start_leb_change - start atomic LEB change. + * @ubi: UBI device description object + * @vol: volume description object + * @req: operation request + * + * This function starts atomic LEB change operation. Returns zero in case of + * success and a negative error code in case of failure. + */ +int ubi_start_leb_change(struct ubi_device *ubi, struct ubi_volume *vol, + const struct ubi_leb_change_req *req) +{ + ubi_assert(!vol->updating && !vol->changing_leb); + + dbg_gen("start changing LEB %d:%d, %u bytes", + vol->vol_id, req->lnum, req->bytes); + if (req->bytes == 0) + return ubi_eba_atomic_leb_change(ubi, vol, req->lnum, NULL, 0); + + vol->upd_bytes = req->bytes; + vol->upd_received = 0; + vol->changing_leb = 1; + vol->ch_lnum = req->lnum; + + vol->upd_buf = vmalloc(req->bytes); + if (!vol->upd_buf) + return -ENOMEM; + + return 0; +} + +/** + * write_leb - write update data. + * @ubi: UBI device description object + * @vol: volume description object + * @lnum: logical eraseblock number + * @buf: data to write + * @len: data size + * @used_ebs: how many logical eraseblocks will this volume contain (static + * volumes only) + * + * This function writes update data to corresponding logical eraseblock. In + * case of dynamic volume, this function checks if the data contains 0xFF bytes + * at the end. If yes, the 0xFF bytes are cut and not written. So if the whole + * buffer contains only 0xFF bytes, the LEB is left unmapped. + * + * The reason why we skip the trailing 0xFF bytes in case of dynamic volume is + * that we want to make sure that more data may be appended to the logical + * eraseblock in future. Indeed, writing 0xFF bytes may have side effects and + * this PEB won't be writable anymore. So if one writes the file-system image + * to the UBI volume where 0xFFs mean free space - UBI makes sure this free + * space is writable after the update. + * + * We do not do this for static volumes because they are read-only. But this + * also cannot be done because we have to store per-LEB CRC and the correct + * data length. + * + * This function returns zero in case of success and a negative error code in + * case of failure. + */ +static int write_leb(struct ubi_device *ubi, struct ubi_volume *vol, int lnum, + void *buf, int len, int used_ebs) +{ + int err; + + if (vol->vol_type == UBI_DYNAMIC_VOLUME) { + int l = ALIGN(len, ubi->min_io_size); + + memset(buf + len, 0xFF, l - len); + len = ubi_calc_data_len(ubi, buf, l); + if (len == 0) { + dbg_gen("all %d bytes contain 0xFF - skip", len); + return 0; + } + + err = ubi_eba_write_leb(ubi, vol, lnum, buf, 0, len); + } else { + /* + * When writing static volume, and this is the last logical + * eraseblock, the length (@len) does not have to be aligned to + * the minimal flash I/O unit. The 'ubi_eba_write_leb_st()' + * function accepts exact (unaligned) length and stores it in + * the VID header. And it takes care of proper alignment by + * padding the buffer. Here we just make sure the padding will + * contain zeros, not random trash. + */ + memset(buf + len, 0, vol->usable_leb_size - len); + err = ubi_eba_write_leb_st(ubi, vol, lnum, buf, len, used_ebs); + } + + return err; +} + +/** + * ubi_more_update_data - write more update data. + * @ubi: UBI device description object + * @vol: volume description object + * @buf: write data (user-space memory buffer) + * @count: how much bytes to write + * + * This function writes more data to the volume which is being updated. It may + * be called arbitrary number of times until all the update data arriveis. This + * function returns %0 in case of success, number of bytes written during the + * last call if the whole volume update has been successfully finished, and a + * negative error code in case of failure. + */ +int ubi_more_update_data(struct ubi_device *ubi, struct ubi_volume *vol, + const void __user *buf, int count) +{ + int lnum, offs, err = 0, len, to_write = count; + + dbg_gen("write %d of %lld bytes, %lld already passed", + count, vol->upd_bytes, vol->upd_received); + + if (ubi->ro_mode) + return -EROFS; + + lnum = div_u64_rem(vol->upd_received, vol->usable_leb_size, &offs); + if (vol->upd_received + count > vol->upd_bytes) + to_write = count = vol->upd_bytes - vol->upd_received; + + /* + * When updating volumes, we accumulate whole logical eraseblock of + * data and write it at once. + */ + if (offs != 0) { + /* + * This is a write to the middle of the logical eraseblock. We + * copy the data to our update buffer and wait for more data or + * flush it if the whole eraseblock is written or the update + * is finished. + */ + + len = vol->usable_leb_size - offs; + if (len > count) + len = count; + + err = copy_from_user(vol->upd_buf + offs, buf, len); + if (err) + return -EFAULT; + + if (offs + len == vol->usable_leb_size || + vol->upd_received + len == vol->upd_bytes) { + int flush_len = offs + len; + + /* + * OK, we gathered either the whole eraseblock or this + * is the last chunk, it's time to flush the buffer. + */ + ubi_assert(flush_len <= vol->usable_leb_size); + err = write_leb(ubi, vol, lnum, vol->upd_buf, flush_len, + vol->upd_ebs); + if (err) + return err; + } + + vol->upd_received += len; + count -= len; + buf += len; + lnum += 1; + } + + /* + * If we've got more to write, let's continue. At this point we know we + * are starting from the beginning of an eraseblock. + */ + while (count) { + if (count > vol->usable_leb_size) + len = vol->usable_leb_size; + else + len = count; + + err = copy_from_user(vol->upd_buf, buf, len); + if (err) + return -EFAULT; + + if (len == vol->usable_leb_size || + vol->upd_received + len == vol->upd_bytes) { + err = write_leb(ubi, vol, lnum, vol->upd_buf, + len, vol->upd_ebs); + if (err) + break; + } + + vol->upd_received += len; + count -= len; + lnum += 1; + buf += len; + } + + ubi_assert(vol->upd_received <= vol->upd_bytes); + if (vol->upd_received == vol->upd_bytes) { + err = ubi_wl_flush(ubi, UBI_ALL, UBI_ALL); + if (err) + return err; + /* The update is finished, clear the update marker */ + err = clear_update_marker(ubi, vol, vol->upd_bytes); + if (err) + return err; + vol->updating = 0; + err = to_write; + vfree(vol->upd_buf); + } + + return err; +} + +/** + * ubi_more_leb_change_data - accept more data for atomic LEB change. + * @ubi: UBI device description object + * @vol: volume description object + * @buf: write data (user-space memory buffer) + * @count: how much bytes to write + * + * This function accepts more data to the volume which is being under the + * "atomic LEB change" operation. It may be called arbitrary number of times + * until all data arrives. This function returns %0 in case of success, number + * of bytes written during the last call if the whole "atomic LEB change" + * operation has been successfully finished, and a negative error code in case + * of failure. + */ +int ubi_more_leb_change_data(struct ubi_device *ubi, struct ubi_volume *vol, + const void __user *buf, int count) +{ + int err; + + dbg_gen("write %d of %lld bytes, %lld already passed", + count, vol->upd_bytes, vol->upd_received); + + if (ubi->ro_mode) + return -EROFS; + + if (vol->upd_received + count > vol->upd_bytes) + count = vol->upd_bytes - vol->upd_received; + + err = copy_from_user(vol->upd_buf + vol->upd_received, buf, count); + if (err) + return -EFAULT; + + vol->upd_received += count; + + if (vol->upd_received == vol->upd_bytes) { + int len = ALIGN((int)vol->upd_bytes, ubi->min_io_size); + + memset(vol->upd_buf + vol->upd_bytes, 0xFF, + len - vol->upd_bytes); + len = ubi_calc_data_len(ubi, vol->upd_buf, len); + err = ubi_eba_atomic_leb_change(ubi, vol, vol->ch_lnum, + vol->upd_buf, len); + if (err) + return err; + } + + ubi_assert(vol->upd_received <= vol->upd_bytes); + if (vol->upd_received == vol->upd_bytes) { + vol->changing_leb = 0; + err = count; + vfree(vol->upd_buf); + } + + return err; +} diff --git a/drivers/mtd/ubi/vmt.c b/drivers/mtd/ubi/vmt.c new file mode 100644 index 00000000000..8330703c098 --- /dev/null +++ b/drivers/mtd/ubi/vmt.c @@ -0,0 +1,860 @@ +/* + * Copyright (c) International Business Machines Corp., 2006 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Artem Bityutskiy (Битюцкий Артём) + */ + +/* + * This file contains implementation of volume creation, deletion, updating and + * resizing. + */ + +#include <linux/err.h> +#include <linux/math64.h> +#include <linux/slab.h> +#include <linux/export.h> +#include "ubi.h" + +static int self_check_volumes(struct ubi_device *ubi); + +static ssize_t vol_attribute_show(struct device *dev, + struct device_attribute *attr, char *buf); + +/* Device attributes corresponding to files in '/<sysfs>/class/ubi/ubiX_Y' */ +static struct device_attribute attr_vol_reserved_ebs = + __ATTR(reserved_ebs, S_IRUGO, vol_attribute_show, NULL); +static struct device_attribute attr_vol_type = + __ATTR(type, S_IRUGO, vol_attribute_show, NULL); +static struct device_attribute attr_vol_name = + __ATTR(name, S_IRUGO, vol_attribute_show, NULL); +static struct device_attribute attr_vol_corrupted = + __ATTR(corrupted, S_IRUGO, vol_attribute_show, NULL); +static struct device_attribute attr_vol_alignment = + __ATTR(alignment, S_IRUGO, vol_attribute_show, NULL); +static struct device_attribute attr_vol_usable_eb_size = + __ATTR(usable_eb_size, S_IRUGO, vol_attribute_show, NULL); +static struct device_attribute attr_vol_data_bytes = + __ATTR(data_bytes, S_IRUGO, vol_attribute_show, NULL); +static struct device_attribute attr_vol_upd_marker = + __ATTR(upd_marker, S_IRUGO, vol_attribute_show, NULL); + +/* + * "Show" method for files in '/<sysfs>/class/ubi/ubiX_Y/'. + * + * Consider a situation: + * A. process 1 opens a sysfs file related to volume Y, say + * /<sysfs>/class/ubi/ubiX_Y/reserved_ebs; + * B. process 2 removes volume Y; + * C. process 1 starts reading the /<sysfs>/class/ubi/ubiX_Y/reserved_ebs file; + * + * In this situation, this function will return %-ENODEV because it will find + * out that the volume was removed from the @ubi->volumes array. + */ +static ssize_t vol_attribute_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + struct ubi_volume *vol = container_of(dev, struct ubi_volume, dev); + struct ubi_device *ubi; + + ubi = ubi_get_device(vol->ubi->ubi_num); + if (!ubi) + return -ENODEV; + + spin_lock(&ubi->volumes_lock); + if (!ubi->volumes[vol->vol_id]) { + spin_unlock(&ubi->volumes_lock); + ubi_put_device(ubi); + return -ENODEV; + } + /* Take a reference to prevent volume removal */ + vol->ref_count += 1; + spin_unlock(&ubi->volumes_lock); + + if (attr == &attr_vol_reserved_ebs) + ret = sprintf(buf, "%d\n", vol->reserved_pebs); + else if (attr == &attr_vol_type) { + const char *tp; + + if (vol->vol_type == UBI_DYNAMIC_VOLUME) + tp = "dynamic"; + else + tp = "static"; + ret = sprintf(buf, "%s\n", tp); + } else if (attr == &attr_vol_name) + ret = sprintf(buf, "%s\n", vol->name); + else if (attr == &attr_vol_corrupted) + ret = sprintf(buf, "%d\n", vol->corrupted); + else if (attr == &attr_vol_alignment) + ret = sprintf(buf, "%d\n", vol->alignment); + else if (attr == &attr_vol_usable_eb_size) + ret = sprintf(buf, "%d\n", vol->usable_leb_size); + else if (attr == &attr_vol_data_bytes) + ret = sprintf(buf, "%lld\n", vol->used_bytes); + else if (attr == &attr_vol_upd_marker) + ret = sprintf(buf, "%d\n", vol->upd_marker); + else + /* This must be a bug */ + ret = -EINVAL; + + /* We've done the operation, drop volume and UBI device references */ + spin_lock(&ubi->volumes_lock); + vol->ref_count -= 1; + ubi_assert(vol->ref_count >= 0); + spin_unlock(&ubi->volumes_lock); + ubi_put_device(ubi); + return ret; +} + +/* Release method for volume devices */ +static void vol_release(struct device *dev) +{ + struct ubi_volume *vol = container_of(dev, struct ubi_volume, dev); + + kfree(vol->eba_tbl); + kfree(vol); +} + +/** + * volume_sysfs_init - initialize sysfs for new volume. + * @ubi: UBI device description object + * @vol: volume description object + * + * This function returns zero in case of success and a negative error code in + * case of failure. + * + * Note, this function does not free allocated resources in case of failure - + * the caller does it. This is because this would cause release() here and the + * caller would oops. + */ +static int volume_sysfs_init(struct ubi_device *ubi, struct ubi_volume *vol) +{ + int err; + + err = device_create_file(&vol->dev, &attr_vol_reserved_ebs); + if (err) + return err; + err = device_create_file(&vol->dev, &attr_vol_type); + if (err) + return err; + err = device_create_file(&vol->dev, &attr_vol_name); + if (err) + return err; + err = device_create_file(&vol->dev, &attr_vol_corrupted); + if (err) + return err; + err = device_create_file(&vol->dev, &attr_vol_alignment); + if (err) + return err; + err = device_create_file(&vol->dev, &attr_vol_usable_eb_size); + if (err) + return err; + err = device_create_file(&vol->dev, &attr_vol_data_bytes); + if (err) + return err; + err = device_create_file(&vol->dev, &attr_vol_upd_marker); + return err; +} + +/** + * volume_sysfs_close - close sysfs for a volume. + * @vol: volume description object + */ +static void volume_sysfs_close(struct ubi_volume *vol) +{ + device_remove_file(&vol->dev, &attr_vol_upd_marker); + device_remove_file(&vol->dev, &attr_vol_data_bytes); + device_remove_file(&vol->dev, &attr_vol_usable_eb_size); + device_remove_file(&vol->dev, &attr_vol_alignment); + device_remove_file(&vol->dev, &attr_vol_corrupted); + device_remove_file(&vol->dev, &attr_vol_name); + device_remove_file(&vol->dev, &attr_vol_type); + device_remove_file(&vol->dev, &attr_vol_reserved_ebs); + device_unregister(&vol->dev); +} + +/** + * ubi_create_volume - create volume. + * @ubi: UBI device description object + * @req: volume creation request + * + * This function creates volume described by @req. If @req->vol_id id + * %UBI_VOL_NUM_AUTO, this function automatically assign ID to the new volume + * and saves it in @req->vol_id. Returns zero in case of success and a negative + * error code in case of failure. Note, the caller has to have the + * @ubi->device_mutex locked. + */ +int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req) +{ + int i, err, vol_id = req->vol_id, do_free = 1; + struct ubi_volume *vol; + struct ubi_vtbl_record vtbl_rec; + dev_t dev; + + if (ubi->ro_mode) + return -EROFS; + + vol = kzalloc(sizeof(struct ubi_volume), GFP_KERNEL); + if (!vol) + return -ENOMEM; + + spin_lock(&ubi->volumes_lock); + if (vol_id == UBI_VOL_NUM_AUTO) { + /* Find unused volume ID */ + dbg_gen("search for vacant volume ID"); + for (i = 0; i < ubi->vtbl_slots; i++) + if (!ubi->volumes[i]) { + vol_id = i; + break; + } + + if (vol_id == UBI_VOL_NUM_AUTO) { + ubi_err("out of volume IDs"); + err = -ENFILE; + goto out_unlock; + } + req->vol_id = vol_id; + } + + dbg_gen("create device %d, volume %d, %llu bytes, type %d, name %s", + ubi->ubi_num, vol_id, (unsigned long long)req->bytes, + (int)req->vol_type, req->name); + + /* Ensure that this volume does not exist */ + err = -EEXIST; + if (ubi->volumes[vol_id]) { + ubi_err("volume %d already exists", vol_id); + goto out_unlock; + } + + /* Ensure that the name is unique */ + for (i = 0; i < ubi->vtbl_slots; i++) + if (ubi->volumes[i] && + ubi->volumes[i]->name_len == req->name_len && + !strcmp(ubi->volumes[i]->name, req->name)) { + ubi_err("volume \"%s\" exists (ID %d)", req->name, i); + goto out_unlock; + } + + /* Calculate how many eraseblocks are requested */ + vol->usable_leb_size = ubi->leb_size - ubi->leb_size % req->alignment; + vol->reserved_pebs += div_u64(req->bytes + vol->usable_leb_size - 1, + vol->usable_leb_size); + + /* Reserve physical eraseblocks */ + if (vol->reserved_pebs > ubi->avail_pebs) { + ubi_err("not enough PEBs, only %d available", ubi->avail_pebs); + if (ubi->corr_peb_count) + ubi_err("%d PEBs are corrupted and not used", + ubi->corr_peb_count); + err = -ENOSPC; + goto out_unlock; + } + ubi->avail_pebs -= vol->reserved_pebs; + ubi->rsvd_pebs += vol->reserved_pebs; + spin_unlock(&ubi->volumes_lock); + + vol->vol_id = vol_id; + vol->alignment = req->alignment; + vol->data_pad = ubi->leb_size % vol->alignment; + vol->vol_type = req->vol_type; + vol->name_len = req->name_len; + memcpy(vol->name, req->name, vol->name_len); + vol->ubi = ubi; + + /* + * Finish all pending erases because there may be some LEBs belonging + * to the same volume ID. + */ + err = ubi_wl_flush(ubi, vol_id, UBI_ALL); + if (err) + goto out_acc; + + vol->eba_tbl = kmalloc(vol->reserved_pebs * sizeof(int), GFP_KERNEL); + if (!vol->eba_tbl) { + err = -ENOMEM; + goto out_acc; + } + + for (i = 0; i < vol->reserved_pebs; i++) + vol->eba_tbl[i] = UBI_LEB_UNMAPPED; + + if (vol->vol_type == UBI_DYNAMIC_VOLUME) { + vol->used_ebs = vol->reserved_pebs; + vol->last_eb_bytes = vol->usable_leb_size; + vol->used_bytes = + (long long)vol->used_ebs * vol->usable_leb_size; + } else { + vol->used_ebs = div_u64_rem(vol->used_bytes, + vol->usable_leb_size, + &vol->last_eb_bytes); + if (vol->last_eb_bytes != 0) + vol->used_ebs += 1; + else + vol->last_eb_bytes = vol->usable_leb_size; + } + + /* Register character device for the volume */ + cdev_init(&vol->cdev, &ubi_vol_cdev_operations); + vol->cdev.owner = THIS_MODULE; + dev = MKDEV(MAJOR(ubi->cdev.dev), vol_id + 1); + err = cdev_add(&vol->cdev, dev, 1); + if (err) { + ubi_err("cannot add character device"); + goto out_mapping; + } + + vol->dev.release = vol_release; + vol->dev.parent = &ubi->dev; + vol->dev.devt = dev; + vol->dev.class = ubi_class; + + dev_set_name(&vol->dev, "%s_%d", ubi->ubi_name, vol->vol_id); + err = device_register(&vol->dev); + if (err) { + ubi_err("cannot register device"); + goto out_cdev; + } + + err = volume_sysfs_init(ubi, vol); + if (err) + goto out_sysfs; + + /* Fill volume table record */ + memset(&vtbl_rec, 0, sizeof(struct ubi_vtbl_record)); + vtbl_rec.reserved_pebs = cpu_to_be32(vol->reserved_pebs); + vtbl_rec.alignment = cpu_to_be32(vol->alignment); + vtbl_rec.data_pad = cpu_to_be32(vol->data_pad); + vtbl_rec.name_len = cpu_to_be16(vol->name_len); + if (vol->vol_type == UBI_DYNAMIC_VOLUME) + vtbl_rec.vol_type = UBI_VID_DYNAMIC; + else + vtbl_rec.vol_type = UBI_VID_STATIC; + memcpy(vtbl_rec.name, vol->name, vol->name_len); + + err = ubi_change_vtbl_record(ubi, vol_id, &vtbl_rec); + if (err) + goto out_sysfs; + + spin_lock(&ubi->volumes_lock); + ubi->volumes[vol_id] = vol; + ubi->vol_count += 1; + spin_unlock(&ubi->volumes_lock); + + ubi_volume_notify(ubi, vol, UBI_VOLUME_ADDED); + self_check_volumes(ubi); + return err; + +out_sysfs: + /* + * We have registered our device, we should not free the volume + * description object in this function in case of an error - it is + * freed by the release function. + * + * Get device reference to prevent the release function from being + * called just after sysfs has been closed. + */ + do_free = 0; + get_device(&vol->dev); + volume_sysfs_close(vol); +out_cdev: + cdev_del(&vol->cdev); +out_mapping: + if (do_free) + kfree(vol->eba_tbl); +out_acc: + spin_lock(&ubi->volumes_lock); + ubi->rsvd_pebs -= vol->reserved_pebs; + ubi->avail_pebs += vol->reserved_pebs; +out_unlock: + spin_unlock(&ubi->volumes_lock); + if (do_free) + kfree(vol); + else + put_device(&vol->dev); + ubi_err("cannot create volume %d, error %d", vol_id, err); + return err; +} + +/** + * ubi_remove_volume - remove volume. + * @desc: volume descriptor + * @no_vtbl: do not change volume table if not zero + * + * This function removes volume described by @desc. The volume has to be opened + * in "exclusive" mode. Returns zero in case of success and a negative error + * code in case of failure. The caller has to have the @ubi->device_mutex + * locked. + */ +int ubi_remove_volume(struct ubi_volume_desc *desc, int no_vtbl) +{ + struct ubi_volume *vol = desc->vol; + struct ubi_device *ubi = vol->ubi; + int i, err, vol_id = vol->vol_id, reserved_pebs = vol->reserved_pebs; + + dbg_gen("remove device %d, volume %d", ubi->ubi_num, vol_id); + ubi_assert(desc->mode == UBI_EXCLUSIVE); + ubi_assert(vol == ubi->volumes[vol_id]); + + if (ubi->ro_mode) + return -EROFS; + + spin_lock(&ubi->volumes_lock); + if (vol->ref_count > 1) { + /* + * The volume is busy, probably someone is reading one of its + * sysfs files. + */ + err = -EBUSY; + goto out_unlock; + } + ubi->volumes[vol_id] = NULL; + spin_unlock(&ubi->volumes_lock); + + if (!no_vtbl) { + err = ubi_change_vtbl_record(ubi, vol_id, NULL); + if (err) + goto out_err; + } + + for (i = 0; i < vol->reserved_pebs; i++) { + err = ubi_eba_unmap_leb(ubi, vol, i); + if (err) + goto out_err; + } + + cdev_del(&vol->cdev); + volume_sysfs_close(vol); + + spin_lock(&ubi->volumes_lock); + ubi->rsvd_pebs -= reserved_pebs; + ubi->avail_pebs += reserved_pebs; + ubi_update_reserved(ubi); + ubi->vol_count -= 1; + spin_unlock(&ubi->volumes_lock); + + ubi_volume_notify(ubi, vol, UBI_VOLUME_REMOVED); + if (!no_vtbl) + self_check_volumes(ubi); + + return err; + +out_err: + ubi_err("cannot remove volume %d, error %d", vol_id, err); + spin_lock(&ubi->volumes_lock); + ubi->volumes[vol_id] = vol; +out_unlock: + spin_unlock(&ubi->volumes_lock); + return err; +} + +/** + * ubi_resize_volume - re-size volume. + * @desc: volume descriptor + * @reserved_pebs: new size in physical eraseblocks + * + * This function re-sizes the volume and returns zero in case of success, and a + * negative error code in case of failure. The caller has to have the + * @ubi->device_mutex locked. + */ +int ubi_resize_volume(struct ubi_volume_desc *desc, int reserved_pebs) +{ + int i, err, pebs, *new_mapping; + struct ubi_volume *vol = desc->vol; + struct ubi_device *ubi = vol->ubi; + struct ubi_vtbl_record vtbl_rec; + int vol_id = vol->vol_id; + + if (ubi->ro_mode) + return -EROFS; + + dbg_gen("re-size device %d, volume %d to from %d to %d PEBs", + ubi->ubi_num, vol_id, vol->reserved_pebs, reserved_pebs); + + if (vol->vol_type == UBI_STATIC_VOLUME && + reserved_pebs < vol->used_ebs) { + ubi_err("too small size %d, %d LEBs contain data", + reserved_pebs, vol->used_ebs); + return -EINVAL; + } + + /* If the size is the same, we have nothing to do */ + if (reserved_pebs == vol->reserved_pebs) + return 0; + + new_mapping = kmalloc(reserved_pebs * sizeof(int), GFP_KERNEL); + if (!new_mapping) + return -ENOMEM; + + for (i = 0; i < reserved_pebs; i++) + new_mapping[i] = UBI_LEB_UNMAPPED; + + spin_lock(&ubi->volumes_lock); + if (vol->ref_count > 1) { + spin_unlock(&ubi->volumes_lock); + err = -EBUSY; + goto out_free; + } + spin_unlock(&ubi->volumes_lock); + + /* Reserve physical eraseblocks */ + pebs = reserved_pebs - vol->reserved_pebs; + if (pebs > 0) { + spin_lock(&ubi->volumes_lock); + if (pebs > ubi->avail_pebs) { + ubi_err("not enough PEBs: requested %d, available %d", + pebs, ubi->avail_pebs); + if (ubi->corr_peb_count) + ubi_err("%d PEBs are corrupted and not used", + ubi->corr_peb_count); + spin_unlock(&ubi->volumes_lock); + err = -ENOSPC; + goto out_free; + } + ubi->avail_pebs -= pebs; + ubi->rsvd_pebs += pebs; + for (i = 0; i < vol->reserved_pebs; i++) + new_mapping[i] = vol->eba_tbl[i]; + kfree(vol->eba_tbl); + vol->eba_tbl = new_mapping; + spin_unlock(&ubi->volumes_lock); + } + + /* Change volume table record */ + vtbl_rec = ubi->vtbl[vol_id]; + vtbl_rec.reserved_pebs = cpu_to_be32(reserved_pebs); + err = ubi_change_vtbl_record(ubi, vol_id, &vtbl_rec); + if (err) + goto out_acc; + + if (pebs < 0) { + for (i = 0; i < -pebs; i++) { + err = ubi_eba_unmap_leb(ubi, vol, reserved_pebs + i); + if (err) + goto out_acc; + } + spin_lock(&ubi->volumes_lock); + ubi->rsvd_pebs += pebs; + ubi->avail_pebs -= pebs; + ubi_update_reserved(ubi); + for (i = 0; i < reserved_pebs; i++) + new_mapping[i] = vol->eba_tbl[i]; + kfree(vol->eba_tbl); + vol->eba_tbl = new_mapping; + spin_unlock(&ubi->volumes_lock); + } + + vol->reserved_pebs = reserved_pebs; + if (vol->vol_type == UBI_DYNAMIC_VOLUME) { + vol->used_ebs = reserved_pebs; + vol->last_eb_bytes = vol->usable_leb_size; + vol->used_bytes = + (long long)vol->used_ebs * vol->usable_leb_size; + } + + ubi_volume_notify(ubi, vol, UBI_VOLUME_RESIZED); + self_check_volumes(ubi); + return err; + +out_acc: + if (pebs > 0) { + spin_lock(&ubi->volumes_lock); + ubi->rsvd_pebs -= pebs; + ubi->avail_pebs += pebs; + spin_unlock(&ubi->volumes_lock); + } +out_free: + kfree(new_mapping); + return err; +} + +/** + * ubi_rename_volumes - re-name UBI volumes. + * @ubi: UBI device description object + * @rename_list: list of &struct ubi_rename_entry objects + * + * This function re-names or removes volumes specified in the re-name list. + * Returns zero in case of success and a negative error code in case of + * failure. + */ +int ubi_rename_volumes(struct ubi_device *ubi, struct list_head *rename_list) +{ + int err; + struct ubi_rename_entry *re; + + err = ubi_vtbl_rename_volumes(ubi, rename_list); + if (err) + return err; + + list_for_each_entry(re, rename_list, list) { + if (re->remove) { + err = ubi_remove_volume(re->desc, 1); + if (err) + break; + } else { + struct ubi_volume *vol = re->desc->vol; + + spin_lock(&ubi->volumes_lock); + vol->name_len = re->new_name_len; + memcpy(vol->name, re->new_name, re->new_name_len + 1); + spin_unlock(&ubi->volumes_lock); + ubi_volume_notify(ubi, vol, UBI_VOLUME_RENAMED); + } + } + + if (!err) + self_check_volumes(ubi); + return err; +} + +/** + * ubi_add_volume - add volume. + * @ubi: UBI device description object + * @vol: volume description object + * + * This function adds an existing volume and initializes all its data + * structures. Returns zero in case of success and a negative error code in + * case of failure. + */ +int ubi_add_volume(struct ubi_device *ubi, struct ubi_volume *vol) +{ + int err, vol_id = vol->vol_id; + dev_t dev; + + dbg_gen("add volume %d", vol_id); + + /* Register character device for the volume */ + cdev_init(&vol->cdev, &ubi_vol_cdev_operations); + vol->cdev.owner = THIS_MODULE; + dev = MKDEV(MAJOR(ubi->cdev.dev), vol->vol_id + 1); + err = cdev_add(&vol->cdev, dev, 1); + if (err) { + ubi_err("cannot add character device for volume %d, error %d", + vol_id, err); + return err; + } + + vol->dev.release = vol_release; + vol->dev.parent = &ubi->dev; + vol->dev.devt = dev; + vol->dev.class = ubi_class; + dev_set_name(&vol->dev, "%s_%d", ubi->ubi_name, vol->vol_id); + err = device_register(&vol->dev); + if (err) + goto out_cdev; + + err = volume_sysfs_init(ubi, vol); + if (err) { + cdev_del(&vol->cdev); + volume_sysfs_close(vol); + return err; + } + + self_check_volumes(ubi); + return err; + +out_cdev: + cdev_del(&vol->cdev); + return err; +} + +/** + * ubi_free_volume - free volume. + * @ubi: UBI device description object + * @vol: volume description object + * + * This function frees all resources for volume @vol but does not remove it. + * Used only when the UBI device is detached. + */ +void ubi_free_volume(struct ubi_device *ubi, struct ubi_volume *vol) +{ + dbg_gen("free volume %d", vol->vol_id); + + ubi->volumes[vol->vol_id] = NULL; + cdev_del(&vol->cdev); + volume_sysfs_close(vol); +} + +/** + * self_check_volume - check volume information. + * @ubi: UBI device description object + * @vol_id: volume ID + * + * Returns zero if volume is all right and a a negative error code if not. + */ +static int self_check_volume(struct ubi_device *ubi, int vol_id) +{ + int idx = vol_id2idx(ubi, vol_id); + int reserved_pebs, alignment, data_pad, vol_type, name_len, upd_marker; + const struct ubi_volume *vol; + long long n; + const char *name; + + spin_lock(&ubi->volumes_lock); + reserved_pebs = be32_to_cpu(ubi->vtbl[vol_id].reserved_pebs); + vol = ubi->volumes[idx]; + + if (!vol) { + if (reserved_pebs) { + ubi_err("no volume info, but volume exists"); + goto fail; + } + spin_unlock(&ubi->volumes_lock); + return 0; + } + + if (vol->reserved_pebs < 0 || vol->alignment < 0 || vol->data_pad < 0 || + vol->name_len < 0) { + ubi_err("negative values"); + goto fail; + } + if (vol->alignment > ubi->leb_size || vol->alignment == 0) { + ubi_err("bad alignment"); + goto fail; + } + + n = vol->alignment & (ubi->min_io_size - 1); + if (vol->alignment != 1 && n) { + ubi_err("alignment is not multiple of min I/O unit"); + goto fail; + } + + n = ubi->leb_size % vol->alignment; + if (vol->data_pad != n) { + ubi_err("bad data_pad, has to be %lld", n); + goto fail; + } + + if (vol->vol_type != UBI_DYNAMIC_VOLUME && + vol->vol_type != UBI_STATIC_VOLUME) { + ubi_err("bad vol_type"); + goto fail; + } + + if (vol->upd_marker && vol->corrupted) { + ubi_err("update marker and corrupted simultaneously"); + goto fail; + } + + if (vol->reserved_pebs > ubi->good_peb_count) { + ubi_err("too large reserved_pebs"); + goto fail; + } + + n = ubi->leb_size - vol->data_pad; + if (vol->usable_leb_size != ubi->leb_size - vol->data_pad) { + ubi_err("bad usable_leb_size, has to be %lld", n); + goto fail; + } + + if (vol->name_len > UBI_VOL_NAME_MAX) { + ubi_err("too long volume name, max is %d", UBI_VOL_NAME_MAX); + goto fail; + } + + n = strnlen(vol->name, vol->name_len + 1); + if (n != vol->name_len) { + ubi_err("bad name_len %lld", n); + goto fail; + } + + n = (long long)vol->used_ebs * vol->usable_leb_size; + if (vol->vol_type == UBI_DYNAMIC_VOLUME) { + if (vol->corrupted) { + ubi_err("corrupted dynamic volume"); + goto fail; + } + if (vol->used_ebs != vol->reserved_pebs) { + ubi_err("bad used_ebs"); + goto fail; + } + if (vol->last_eb_bytes != vol->usable_leb_size) { + ubi_err("bad last_eb_bytes"); + goto fail; + } + if (vol->used_bytes != n) { + ubi_err("bad used_bytes"); + goto fail; + } + } else { + if (vol->used_ebs < 0 || vol->used_ebs > vol->reserved_pebs) { + ubi_err("bad used_ebs"); + goto fail; + } + if (vol->last_eb_bytes < 0 || + vol->last_eb_bytes > vol->usable_leb_size) { + ubi_err("bad last_eb_bytes"); + goto fail; + } + if (vol->used_bytes < 0 || vol->used_bytes > n || + vol->used_bytes < n - vol->usable_leb_size) { + ubi_err("bad used_bytes"); + goto fail; + } + } + + alignment = be32_to_cpu(ubi->vtbl[vol_id].alignment); + data_pad = be32_to_cpu(ubi->vtbl[vol_id].data_pad); + name_len = be16_to_cpu(ubi->vtbl[vol_id].name_len); + upd_marker = ubi->vtbl[vol_id].upd_marker; + name = &ubi->vtbl[vol_id].name[0]; + if (ubi->vtbl[vol_id].vol_type == UBI_VID_DYNAMIC) + vol_type = UBI_DYNAMIC_VOLUME; + else + vol_type = UBI_STATIC_VOLUME; + + if (alignment != vol->alignment || data_pad != vol->data_pad || + upd_marker != vol->upd_marker || vol_type != vol->vol_type || + name_len != vol->name_len || strncmp(name, vol->name, name_len)) { + ubi_err("volume info is different"); + goto fail; + } + + spin_unlock(&ubi->volumes_lock); + return 0; + +fail: + ubi_err("self-check failed for volume %d", vol_id); + if (vol) + ubi_dump_vol_info(vol); + ubi_dump_vtbl_record(&ubi->vtbl[vol_id], vol_id); + dump_stack(); + spin_unlock(&ubi->volumes_lock); + return -EINVAL; +} + +/** + * self_check_volumes - check information about all volumes. + * @ubi: UBI device description object + * + * Returns zero if volumes are all right and a a negative error code if not. + */ +static int self_check_volumes(struct ubi_device *ubi) +{ + int i, err = 0; + + if (!ubi_dbg_chk_gen(ubi)) + return 0; + + for (i = 0; i < ubi->vtbl_slots; i++) { + err = self_check_volume(ubi, i); + if (err) + break; + } + + return err; +} diff --git a/drivers/mtd/ubi/vtbl.c b/drivers/mtd/ubi/vtbl.c new file mode 100644 index 00000000000..d77b1c1d7c7 --- /dev/null +++ b/drivers/mtd/ubi/vtbl.c @@ -0,0 +1,868 @@ +/* + * Copyright (c) International Business Machines Corp., 2006 + * Copyright (c) Nokia Corporation, 2006, 2007 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Artem Bityutskiy (Битюцкий Артём) + */ + +/* + * This file includes volume table manipulation code. The volume table is an + * on-flash table containing volume meta-data like name, number of reserved + * physical eraseblocks, type, etc. The volume table is stored in the so-called + * "layout volume". + * + * The layout volume is an internal volume which is organized as follows. It + * consists of two logical eraseblocks - LEB 0 and LEB 1. Each logical + * eraseblock stores one volume table copy, i.e. LEB 0 and LEB 1 duplicate each + * other. This redundancy guarantees robustness to unclean reboots. The volume + * table is basically an array of volume table records. Each record contains + * full information about the volume and protected by a CRC checksum. + * + * The volume table is changed, it is first changed in RAM. Then LEB 0 is + * erased, and the updated volume table is written back to LEB 0. Then same for + * LEB 1. This scheme guarantees recoverability from unclean reboots. + * + * In this UBI implementation the on-flash volume table does not contain any + * information about how much data static volumes contain. + * + * But it would still be beneficial to store this information in the volume + * table. For example, suppose we have a static volume X, and all its physical + * eraseblocks became bad for some reasons. Suppose we are attaching the + * corresponding MTD device, for some reason we find no logical eraseblocks + * corresponding to the volume X. According to the volume table volume X does + * exist. So we don't know whether it is just empty or all its physical + * eraseblocks went bad. So we cannot alarm the user properly. + * + * The volume table also stores so-called "update marker", which is used for + * volume updates. Before updating the volume, the update marker is set, and + * after the update operation is finished, the update marker is cleared. So if + * the update operation was interrupted (e.g. by an unclean reboot) - the + * update marker is still there and we know that the volume's contents is + * damaged. + */ + +#include <linux/crc32.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <asm/div64.h> +#include "ubi.h" + +static void self_vtbl_check(const struct ubi_device *ubi); + +/* Empty volume table record */ +static struct ubi_vtbl_record empty_vtbl_record; + +/** + * ubi_change_vtbl_record - change volume table record. + * @ubi: UBI device description object + * @idx: table index to change + * @vtbl_rec: new volume table record + * + * This function changes volume table record @idx. If @vtbl_rec is %NULL, empty + * volume table record is written. The caller does not have to calculate CRC of + * the record as it is done by this function. Returns zero in case of success + * and a negative error code in case of failure. + */ +int ubi_change_vtbl_record(struct ubi_device *ubi, int idx, + struct ubi_vtbl_record *vtbl_rec) +{ + int i, err; + uint32_t crc; + struct ubi_volume *layout_vol; + + ubi_assert(idx >= 0 && idx < ubi->vtbl_slots); + layout_vol = ubi->volumes[vol_id2idx(ubi, UBI_LAYOUT_VOLUME_ID)]; + + if (!vtbl_rec) + vtbl_rec = &empty_vtbl_record; + else { + crc = crc32(UBI_CRC32_INIT, vtbl_rec, UBI_VTBL_RECORD_SIZE_CRC); + vtbl_rec->crc = cpu_to_be32(crc); + } + + memcpy(&ubi->vtbl[idx], vtbl_rec, sizeof(struct ubi_vtbl_record)); + for (i = 0; i < UBI_LAYOUT_VOLUME_EBS; i++) { + err = ubi_eba_unmap_leb(ubi, layout_vol, i); + if (err) + return err; + + err = ubi_eba_write_leb(ubi, layout_vol, i, ubi->vtbl, 0, + ubi->vtbl_size); + if (err) + return err; + } + + self_vtbl_check(ubi); + return 0; +} + +/** + * ubi_vtbl_rename_volumes - rename UBI volumes in the volume table. + * @ubi: UBI device description object + * @rename_list: list of &struct ubi_rename_entry objects + * + * This function re-names multiple volumes specified in @req in the volume + * table. Returns zero in case of success and a negative error code in case of + * failure. + */ +int ubi_vtbl_rename_volumes(struct ubi_device *ubi, + struct list_head *rename_list) +{ + int i, err; + struct ubi_rename_entry *re; + struct ubi_volume *layout_vol; + + list_for_each_entry(re, rename_list, list) { + uint32_t crc; + struct ubi_volume *vol = re->desc->vol; + struct ubi_vtbl_record *vtbl_rec = &ubi->vtbl[vol->vol_id]; + + if (re->remove) { + memcpy(vtbl_rec, &empty_vtbl_record, + sizeof(struct ubi_vtbl_record)); + continue; + } + + vtbl_rec->name_len = cpu_to_be16(re->new_name_len); + memcpy(vtbl_rec->name, re->new_name, re->new_name_len); + memset(vtbl_rec->name + re->new_name_len, 0, + UBI_VOL_NAME_MAX + 1 - re->new_name_len); + crc = crc32(UBI_CRC32_INIT, vtbl_rec, + UBI_VTBL_RECORD_SIZE_CRC); + vtbl_rec->crc = cpu_to_be32(crc); + } + + layout_vol = ubi->volumes[vol_id2idx(ubi, UBI_LAYOUT_VOLUME_ID)]; + for (i = 0; i < UBI_LAYOUT_VOLUME_EBS; i++) { + err = ubi_eba_unmap_leb(ubi, layout_vol, i); + if (err) + return err; + + err = ubi_eba_write_leb(ubi, layout_vol, i, ubi->vtbl, 0, + ubi->vtbl_size); + if (err) + return err; + } + + return 0; +} + +/** + * vtbl_check - check if volume table is not corrupted and sensible. + * @ubi: UBI device description object + * @vtbl: volume table + * + * This function returns zero if @vtbl is all right, %1 if CRC is incorrect, + * and %-EINVAL if it contains inconsistent data. + */ +static int vtbl_check(const struct ubi_device *ubi, + const struct ubi_vtbl_record *vtbl) +{ + int i, n, reserved_pebs, alignment, data_pad, vol_type, name_len; + int upd_marker, err; + uint32_t crc; + const char *name; + + for (i = 0; i < ubi->vtbl_slots; i++) { + cond_resched(); + + reserved_pebs = be32_to_cpu(vtbl[i].reserved_pebs); + alignment = be32_to_cpu(vtbl[i].alignment); + data_pad = be32_to_cpu(vtbl[i].data_pad); + upd_marker = vtbl[i].upd_marker; + vol_type = vtbl[i].vol_type; + name_len = be16_to_cpu(vtbl[i].name_len); + name = &vtbl[i].name[0]; + + crc = crc32(UBI_CRC32_INIT, &vtbl[i], UBI_VTBL_RECORD_SIZE_CRC); + if (be32_to_cpu(vtbl[i].crc) != crc) { + ubi_err("bad CRC at record %u: %#08x, not %#08x", + i, crc, be32_to_cpu(vtbl[i].crc)); + ubi_dump_vtbl_record(&vtbl[i], i); + return 1; + } + + if (reserved_pebs == 0) { + if (memcmp(&vtbl[i], &empty_vtbl_record, + UBI_VTBL_RECORD_SIZE)) { + err = 2; + goto bad; + } + continue; + } + + if (reserved_pebs < 0 || alignment < 0 || data_pad < 0 || + name_len < 0) { + err = 3; + goto bad; + } + + if (alignment > ubi->leb_size || alignment == 0) { + err = 4; + goto bad; + } + + n = alignment & (ubi->min_io_size - 1); + if (alignment != 1 && n) { + err = 5; + goto bad; + } + + n = ubi->leb_size % alignment; + if (data_pad != n) { + ubi_err("bad data_pad, has to be %d", n); + err = 6; + goto bad; + } + + if (vol_type != UBI_VID_DYNAMIC && vol_type != UBI_VID_STATIC) { + err = 7; + goto bad; + } + + if (upd_marker != 0 && upd_marker != 1) { + err = 8; + goto bad; + } + + if (reserved_pebs > ubi->good_peb_count) { + ubi_err("too large reserved_pebs %d, good PEBs %d", + reserved_pebs, ubi->good_peb_count); + err = 9; + goto bad; + } + + if (name_len > UBI_VOL_NAME_MAX) { + err = 10; + goto bad; + } + + if (name[0] == '\0') { + err = 11; + goto bad; + } + + if (name_len != strnlen(name, name_len + 1)) { + err = 12; + goto bad; + } + } + + /* Checks that all names are unique */ + for (i = 0; i < ubi->vtbl_slots - 1; i++) { + for (n = i + 1; n < ubi->vtbl_slots; n++) { + int len1 = be16_to_cpu(vtbl[i].name_len); + int len2 = be16_to_cpu(vtbl[n].name_len); + + if (len1 > 0 && len1 == len2 && + !strncmp(vtbl[i].name, vtbl[n].name, len1)) { + ubi_err("volumes %d and %d have the same name \"%s\"", + i, n, vtbl[i].name); + ubi_dump_vtbl_record(&vtbl[i], i); + ubi_dump_vtbl_record(&vtbl[n], n); + return -EINVAL; + } + } + } + + return 0; + +bad: + ubi_err("volume table check failed: record %d, error %d", i, err); + ubi_dump_vtbl_record(&vtbl[i], i); + return -EINVAL; +} + +/** + * create_vtbl - create a copy of volume table. + * @ubi: UBI device description object + * @ai: attaching information + * @copy: number of the volume table copy + * @vtbl: contents of the volume table + * + * This function returns zero in case of success and a negative error code in + * case of failure. + */ +static int create_vtbl(struct ubi_device *ubi, struct ubi_attach_info *ai, + int copy, void *vtbl) +{ + int err, tries = 0; + struct ubi_vid_hdr *vid_hdr; + struct ubi_ainf_peb *new_aeb; + + dbg_gen("create volume table (copy #%d)", copy + 1); + + vid_hdr = ubi_zalloc_vid_hdr(ubi, GFP_KERNEL); + if (!vid_hdr) + return -ENOMEM; + +retry: + new_aeb = ubi_early_get_peb(ubi, ai); + if (IS_ERR(new_aeb)) { + err = PTR_ERR(new_aeb); + goto out_free; + } + + vid_hdr->vol_type = UBI_LAYOUT_VOLUME_TYPE; + vid_hdr->vol_id = cpu_to_be32(UBI_LAYOUT_VOLUME_ID); + vid_hdr->compat = UBI_LAYOUT_VOLUME_COMPAT; + vid_hdr->data_size = vid_hdr->used_ebs = + vid_hdr->data_pad = cpu_to_be32(0); + vid_hdr->lnum = cpu_to_be32(copy); + vid_hdr->sqnum = cpu_to_be64(++ai->max_sqnum); + + /* The EC header is already there, write the VID header */ + err = ubi_io_write_vid_hdr(ubi, new_aeb->pnum, vid_hdr); + if (err) + goto write_error; + + /* Write the layout volume contents */ + err = ubi_io_write_data(ubi, vtbl, new_aeb->pnum, 0, ubi->vtbl_size); + if (err) + goto write_error; + + /* + * And add it to the attaching information. Don't delete the old version + * of this LEB as it will be deleted and freed in 'ubi_add_to_av()'. + */ + err = ubi_add_to_av(ubi, ai, new_aeb->pnum, new_aeb->ec, vid_hdr, 0); + kmem_cache_free(ai->aeb_slab_cache, new_aeb); + ubi_free_vid_hdr(ubi, vid_hdr); + return err; + +write_error: + if (err == -EIO && ++tries <= 5) { + /* + * Probably this physical eraseblock went bad, try to pick + * another one. + */ + list_add(&new_aeb->u.list, &ai->erase); + goto retry; + } + kmem_cache_free(ai->aeb_slab_cache, new_aeb); +out_free: + ubi_free_vid_hdr(ubi, vid_hdr); + return err; + +} + +/** + * process_lvol - process the layout volume. + * @ubi: UBI device description object + * @ai: attaching information + * @av: layout volume attaching information + * + * This function is responsible for reading the layout volume, ensuring it is + * not corrupted, and recovering from corruptions if needed. Returns volume + * table in case of success and a negative error code in case of failure. + */ +static struct ubi_vtbl_record *process_lvol(struct ubi_device *ubi, + struct ubi_attach_info *ai, + struct ubi_ainf_volume *av) +{ + int err; + struct rb_node *rb; + struct ubi_ainf_peb *aeb; + struct ubi_vtbl_record *leb[UBI_LAYOUT_VOLUME_EBS] = { NULL, NULL }; + int leb_corrupted[UBI_LAYOUT_VOLUME_EBS] = {1, 1}; + + /* + * UBI goes through the following steps when it changes the layout + * volume: + * a. erase LEB 0; + * b. write new data to LEB 0; + * c. erase LEB 1; + * d. write new data to LEB 1. + * + * Before the change, both LEBs contain the same data. + * + * Due to unclean reboots, the contents of LEB 0 may be lost, but there + * should LEB 1. So it is OK if LEB 0 is corrupted while LEB 1 is not. + * Similarly, LEB 1 may be lost, but there should be LEB 0. And + * finally, unclean reboots may result in a situation when neither LEB + * 0 nor LEB 1 are corrupted, but they are different. In this case, LEB + * 0 contains more recent information. + * + * So the plan is to first check LEB 0. Then + * a. if LEB 0 is OK, it must be containing the most recent data; then + * we compare it with LEB 1, and if they are different, we copy LEB + * 0 to LEB 1; + * b. if LEB 0 is corrupted, but LEB 1 has to be OK, and we copy LEB 1 + * to LEB 0. + */ + + dbg_gen("check layout volume"); + + /* Read both LEB 0 and LEB 1 into memory */ + ubi_rb_for_each_entry(rb, aeb, &av->root, u.rb) { + leb[aeb->lnum] = vzalloc(ubi->vtbl_size); + if (!leb[aeb->lnum]) { + err = -ENOMEM; + goto out_free; + } + + err = ubi_io_read_data(ubi, leb[aeb->lnum], aeb->pnum, 0, + ubi->vtbl_size); + if (err == UBI_IO_BITFLIPS || mtd_is_eccerr(err)) + /* + * Scrub the PEB later. Note, -EBADMSG indicates an + * uncorrectable ECC error, but we have our own CRC and + * the data will be checked later. If the data is OK, + * the PEB will be scrubbed (because we set + * aeb->scrub). If the data is not OK, the contents of + * the PEB will be recovered from the second copy, and + * aeb->scrub will be cleared in + * 'ubi_add_to_av()'. + */ + aeb->scrub = 1; + else if (err) + goto out_free; + } + + err = -EINVAL; + if (leb[0]) { + leb_corrupted[0] = vtbl_check(ubi, leb[0]); + if (leb_corrupted[0] < 0) + goto out_free; + } + + if (!leb_corrupted[0]) { + /* LEB 0 is OK */ + if (leb[1]) + leb_corrupted[1] = memcmp(leb[0], leb[1], + ubi->vtbl_size); + if (leb_corrupted[1]) { + ubi_warn("volume table copy #2 is corrupted"); + err = create_vtbl(ubi, ai, 1, leb[0]); + if (err) + goto out_free; + ubi_msg("volume table was restored"); + } + + /* Both LEB 1 and LEB 2 are OK and consistent */ + vfree(leb[1]); + return leb[0]; + } else { + /* LEB 0 is corrupted or does not exist */ + if (leb[1]) { + leb_corrupted[1] = vtbl_check(ubi, leb[1]); + if (leb_corrupted[1] < 0) + goto out_free; + } + if (leb_corrupted[1]) { + /* Both LEB 0 and LEB 1 are corrupted */ + ubi_err("both volume tables are corrupted"); + goto out_free; + } + + ubi_warn("volume table copy #1 is corrupted"); + err = create_vtbl(ubi, ai, 0, leb[1]); + if (err) + goto out_free; + ubi_msg("volume table was restored"); + + vfree(leb[0]); + return leb[1]; + } + +out_free: + vfree(leb[0]); + vfree(leb[1]); + return ERR_PTR(err); +} + +/** + * create_empty_lvol - create empty layout volume. + * @ubi: UBI device description object + * @ai: attaching information + * + * This function returns volume table contents in case of success and a + * negative error code in case of failure. + */ +static struct ubi_vtbl_record *create_empty_lvol(struct ubi_device *ubi, + struct ubi_attach_info *ai) +{ + int i; + struct ubi_vtbl_record *vtbl; + + vtbl = vzalloc(ubi->vtbl_size); + if (!vtbl) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < ubi->vtbl_slots; i++) + memcpy(&vtbl[i], &empty_vtbl_record, UBI_VTBL_RECORD_SIZE); + + for (i = 0; i < UBI_LAYOUT_VOLUME_EBS; i++) { + int err; + + err = create_vtbl(ubi, ai, i, vtbl); + if (err) { + vfree(vtbl); + return ERR_PTR(err); + } + } + + return vtbl; +} + +/** + * init_volumes - initialize volume information for existing volumes. + * @ubi: UBI device description object + * @ai: scanning information + * @vtbl: volume table + * + * This function allocates volume description objects for existing volumes. + * Returns zero in case of success and a negative error code in case of + * failure. + */ +static int init_volumes(struct ubi_device *ubi, + const struct ubi_attach_info *ai, + const struct ubi_vtbl_record *vtbl) +{ + int i, reserved_pebs = 0; + struct ubi_ainf_volume *av; + struct ubi_volume *vol; + + for (i = 0; i < ubi->vtbl_slots; i++) { + cond_resched(); + + if (be32_to_cpu(vtbl[i].reserved_pebs) == 0) + continue; /* Empty record */ + + vol = kzalloc(sizeof(struct ubi_volume), GFP_KERNEL); + if (!vol) + return -ENOMEM; + + vol->reserved_pebs = be32_to_cpu(vtbl[i].reserved_pebs); + vol->alignment = be32_to_cpu(vtbl[i].alignment); + vol->data_pad = be32_to_cpu(vtbl[i].data_pad); + vol->upd_marker = vtbl[i].upd_marker; + vol->vol_type = vtbl[i].vol_type == UBI_VID_DYNAMIC ? + UBI_DYNAMIC_VOLUME : UBI_STATIC_VOLUME; + vol->name_len = be16_to_cpu(vtbl[i].name_len); + vol->usable_leb_size = ubi->leb_size - vol->data_pad; + memcpy(vol->name, vtbl[i].name, vol->name_len); + vol->name[vol->name_len] = '\0'; + vol->vol_id = i; + + if (vtbl[i].flags & UBI_VTBL_AUTORESIZE_FLG) { + /* Auto re-size flag may be set only for one volume */ + if (ubi->autoresize_vol_id != -1) { + ubi_err("more than one auto-resize volume (%d and %d)", + ubi->autoresize_vol_id, i); + kfree(vol); + return -EINVAL; + } + + ubi->autoresize_vol_id = i; + } + + ubi_assert(!ubi->volumes[i]); + ubi->volumes[i] = vol; + ubi->vol_count += 1; + vol->ubi = ubi; + reserved_pebs += vol->reserved_pebs; + + /* + * In case of dynamic volume UBI knows nothing about how many + * data is stored there. So assume the whole volume is used. + */ + if (vol->vol_type == UBI_DYNAMIC_VOLUME) { + vol->used_ebs = vol->reserved_pebs; + vol->last_eb_bytes = vol->usable_leb_size; + vol->used_bytes = + (long long)vol->used_ebs * vol->usable_leb_size; + continue; + } + + /* Static volumes only */ + av = ubi_find_av(ai, i); + if (!av) { + /* + * No eraseblocks belonging to this volume found. We + * don't actually know whether this static volume is + * completely corrupted or just contains no data. And + * we cannot know this as long as data size is not + * stored on flash. So we just assume the volume is + * empty. FIXME: this should be handled. + */ + continue; + } + + if (av->leb_count != av->used_ebs) { + /* + * We found a static volume which misses several + * eraseblocks. Treat it as corrupted. + */ + ubi_warn("static volume %d misses %d LEBs - corrupted", + av->vol_id, av->used_ebs - av->leb_count); + vol->corrupted = 1; + continue; + } + + vol->used_ebs = av->used_ebs; + vol->used_bytes = + (long long)(vol->used_ebs - 1) * vol->usable_leb_size; + vol->used_bytes += av->last_data_size; + vol->last_eb_bytes = av->last_data_size; + } + + /* And add the layout volume */ + vol = kzalloc(sizeof(struct ubi_volume), GFP_KERNEL); + if (!vol) + return -ENOMEM; + + vol->reserved_pebs = UBI_LAYOUT_VOLUME_EBS; + vol->alignment = UBI_LAYOUT_VOLUME_ALIGN; + vol->vol_type = UBI_DYNAMIC_VOLUME; + vol->name_len = sizeof(UBI_LAYOUT_VOLUME_NAME) - 1; + memcpy(vol->name, UBI_LAYOUT_VOLUME_NAME, vol->name_len + 1); + vol->usable_leb_size = ubi->leb_size; + vol->used_ebs = vol->reserved_pebs; + vol->last_eb_bytes = vol->reserved_pebs; + vol->used_bytes = + (long long)vol->used_ebs * (ubi->leb_size - vol->data_pad); + vol->vol_id = UBI_LAYOUT_VOLUME_ID; + vol->ref_count = 1; + + ubi_assert(!ubi->volumes[i]); + ubi->volumes[vol_id2idx(ubi, vol->vol_id)] = vol; + reserved_pebs += vol->reserved_pebs; + ubi->vol_count += 1; + vol->ubi = ubi; + + if (reserved_pebs > ubi->avail_pebs) { + ubi_err("not enough PEBs, required %d, available %d", + reserved_pebs, ubi->avail_pebs); + if (ubi->corr_peb_count) + ubi_err("%d PEBs are corrupted and not used", + ubi->corr_peb_count); + } + ubi->rsvd_pebs += reserved_pebs; + ubi->avail_pebs -= reserved_pebs; + + return 0; +} + +/** + * check_av - check volume attaching information. + * @vol: UBI volume description object + * @av: volume attaching information + * + * This function returns zero if the volume attaching information is consistent + * to the data read from the volume tabla, and %-EINVAL if not. + */ +static int check_av(const struct ubi_volume *vol, + const struct ubi_ainf_volume *av) +{ + int err; + + if (av->highest_lnum >= vol->reserved_pebs) { + err = 1; + goto bad; + } + if (av->leb_count > vol->reserved_pebs) { + err = 2; + goto bad; + } + if (av->vol_type != vol->vol_type) { + err = 3; + goto bad; + } + if (av->used_ebs > vol->reserved_pebs) { + err = 4; + goto bad; + } + if (av->data_pad != vol->data_pad) { + err = 5; + goto bad; + } + return 0; + +bad: + ubi_err("bad attaching information, error %d", err); + ubi_dump_av(av); + ubi_dump_vol_info(vol); + return -EINVAL; +} + +/** + * check_attaching_info - check that attaching information. + * @ubi: UBI device description object + * @ai: attaching information + * + * Even though we protect on-flash data by CRC checksums, we still don't trust + * the media. This function ensures that attaching information is consistent to + * the information read from the volume table. Returns zero if the attaching + * information is OK and %-EINVAL if it is not. + */ +static int check_attaching_info(const struct ubi_device *ubi, + struct ubi_attach_info *ai) +{ + int err, i; + struct ubi_ainf_volume *av; + struct ubi_volume *vol; + + if (ai->vols_found > UBI_INT_VOL_COUNT + ubi->vtbl_slots) { + ubi_err("found %d volumes while attaching, maximum is %d + %d", + ai->vols_found, UBI_INT_VOL_COUNT, ubi->vtbl_slots); + return -EINVAL; + } + + if (ai->highest_vol_id >= ubi->vtbl_slots + UBI_INT_VOL_COUNT && + ai->highest_vol_id < UBI_INTERNAL_VOL_START) { + ubi_err("too large volume ID %d found", ai->highest_vol_id); + return -EINVAL; + } + + for (i = 0; i < ubi->vtbl_slots + UBI_INT_VOL_COUNT; i++) { + cond_resched(); + + av = ubi_find_av(ai, i); + vol = ubi->volumes[i]; + if (!vol) { + if (av) + ubi_remove_av(ai, av); + continue; + } + + if (vol->reserved_pebs == 0) { + ubi_assert(i < ubi->vtbl_slots); + + if (!av) + continue; + + /* + * During attaching we found a volume which does not + * exist according to the information in the volume + * table. This must have happened due to an unclean + * reboot while the volume was being removed. Discard + * these eraseblocks. + */ + ubi_msg("finish volume %d removal", av->vol_id); + ubi_remove_av(ai, av); + } else if (av) { + err = check_av(vol, av); + if (err) + return err; + } + } + + return 0; +} + +/** + * ubi_read_volume_table - read the volume table. + * @ubi: UBI device description object + * @ai: attaching information + * + * This function reads volume table, checks it, recover from errors if needed, + * or creates it if needed. Returns zero in case of success and a negative + * error code in case of failure. + */ +int ubi_read_volume_table(struct ubi_device *ubi, struct ubi_attach_info *ai) +{ + int i, err; + struct ubi_ainf_volume *av; + + empty_vtbl_record.crc = cpu_to_be32(0xf116c36b); + + /* + * The number of supported volumes is limited by the eraseblock size + * and by the UBI_MAX_VOLUMES constant. + */ + ubi->vtbl_slots = ubi->leb_size / UBI_VTBL_RECORD_SIZE; + if (ubi->vtbl_slots > UBI_MAX_VOLUMES) + ubi->vtbl_slots = UBI_MAX_VOLUMES; + + ubi->vtbl_size = ubi->vtbl_slots * UBI_VTBL_RECORD_SIZE; + ubi->vtbl_size = ALIGN(ubi->vtbl_size, ubi->min_io_size); + + av = ubi_find_av(ai, UBI_LAYOUT_VOLUME_ID); + if (!av) { + /* + * No logical eraseblocks belonging to the layout volume were + * found. This could mean that the flash is just empty. In + * this case we create empty layout volume. + * + * But if flash is not empty this must be a corruption or the + * MTD device just contains garbage. + */ + if (ai->is_empty) { + ubi->vtbl = create_empty_lvol(ubi, ai); + if (IS_ERR(ubi->vtbl)) + return PTR_ERR(ubi->vtbl); + } else { + ubi_err("the layout volume was not found"); + return -EINVAL; + } + } else { + if (av->leb_count > UBI_LAYOUT_VOLUME_EBS) { + /* This must not happen with proper UBI images */ + ubi_err("too many LEBs (%d) in layout volume", + av->leb_count); + return -EINVAL; + } + + ubi->vtbl = process_lvol(ubi, ai, av); + if (IS_ERR(ubi->vtbl)) + return PTR_ERR(ubi->vtbl); + } + + ubi->avail_pebs = ubi->good_peb_count - ubi->corr_peb_count; + + /* + * The layout volume is OK, initialize the corresponding in-RAM data + * structures. + */ + err = init_volumes(ubi, ai, ubi->vtbl); + if (err) + goto out_free; + + /* + * Make sure that the attaching information is consistent to the + * information stored in the volume table. + */ + err = check_attaching_info(ubi, ai); + if (err) + goto out_free; + + return 0; + +out_free: + vfree(ubi->vtbl); + for (i = 0; i < ubi->vtbl_slots + UBI_INT_VOL_COUNT; i++) { + kfree(ubi->volumes[i]); + ubi->volumes[i] = NULL; + } + return err; +} + +/** + * self_vtbl_check - check volume table. + * @ubi: UBI device description object + */ +static void self_vtbl_check(const struct ubi_device *ubi) +{ + if (!ubi_dbg_chk_gen(ubi)) + return; + + if (vtbl_check(ubi, ubi->vtbl)) { + ubi_err("self-check failed"); + BUG(); + } +} diff --git a/drivers/mtd/ubi/wl.c b/drivers/mtd/ubi/wl.c new file mode 100644 index 00000000000..0f3425dac91 --- /dev/null +++ b/drivers/mtd/ubi/wl.c @@ -0,0 +1,2135 @@ +/* + * Copyright (c) International Business Machines Corp., 2006 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Authors: Artem Bityutskiy (Битюцкий Артём), Thomas Gleixner + */ + +/* + * UBI wear-leveling sub-system. + * + * This sub-system is responsible for wear-leveling. It works in terms of + * physical eraseblocks and erase counters and knows nothing about logical + * eraseblocks, volumes, etc. From this sub-system's perspective all physical + * eraseblocks are of two types - used and free. Used physical eraseblocks are + * those that were "get" by the 'ubi_wl_get_peb()' function, and free physical + * eraseblocks are those that were put by the 'ubi_wl_put_peb()' function. + * + * Physical eraseblocks returned by 'ubi_wl_get_peb()' have only erase counter + * header. The rest of the physical eraseblock contains only %0xFF bytes. + * + * When physical eraseblocks are returned to the WL sub-system by means of the + * 'ubi_wl_put_peb()' function, they are scheduled for erasure. The erasure is + * done asynchronously in context of the per-UBI device background thread, + * which is also managed by the WL sub-system. + * + * The wear-leveling is ensured by means of moving the contents of used + * physical eraseblocks with low erase counter to free physical eraseblocks + * with high erase counter. + * + * If the WL sub-system fails to erase a physical eraseblock, it marks it as + * bad. + * + * This sub-system is also responsible for scrubbing. If a bit-flip is detected + * in a physical eraseblock, it has to be moved. Technically this is the same + * as moving it for wear-leveling reasons. + * + * As it was said, for the UBI sub-system all physical eraseblocks are either + * "free" or "used". Free eraseblock are kept in the @wl->free RB-tree, while + * used eraseblocks are kept in @wl->used, @wl->erroneous, or @wl->scrub + * RB-trees, as well as (temporarily) in the @wl->pq queue. + * + * When the WL sub-system returns a physical eraseblock, the physical + * eraseblock is protected from being moved for some "time". For this reason, + * the physical eraseblock is not directly moved from the @wl->free tree to the + * @wl->used tree. There is a protection queue in between where this + * physical eraseblock is temporarily stored (@wl->pq). + * + * All this protection stuff is needed because: + * o we don't want to move physical eraseblocks just after we have given them + * to the user; instead, we first want to let users fill them up with data; + * + * o there is a chance that the user will put the physical eraseblock very + * soon, so it makes sense not to move it for some time, but wait. + * + * Physical eraseblocks stay protected only for limited time. But the "time" is + * measured in erase cycles in this case. This is implemented with help of the + * protection queue. Eraseblocks are put to the tail of this queue when they + * are returned by the 'ubi_wl_get_peb()', and eraseblocks are removed from the + * head of the queue on each erase operation (for any eraseblock). So the + * length of the queue defines how may (global) erase cycles PEBs are protected. + * + * To put it differently, each physical eraseblock has 2 main states: free and + * used. The former state corresponds to the @wl->free tree. The latter state + * is split up on several sub-states: + * o the WL movement is allowed (@wl->used tree); + * o the WL movement is disallowed (@wl->erroneous) because the PEB is + * erroneous - e.g., there was a read error; + * o the WL movement is temporarily prohibited (@wl->pq queue); + * o scrubbing is needed (@wl->scrub tree). + * + * Depending on the sub-state, wear-leveling entries of the used physical + * eraseblocks may be kept in one of those structures. + * + * Note, in this implementation, we keep a small in-RAM object for each physical + * eraseblock. This is surely not a scalable solution. But it appears to be good + * enough for moderately large flashes and it is simple. In future, one may + * re-work this sub-system and make it more scalable. + * + * At the moment this sub-system does not utilize the sequence number, which + * was introduced relatively recently. But it would be wise to do this because + * the sequence number of a logical eraseblock characterizes how old is it. For + * example, when we move a PEB with low erase counter, and we need to pick the + * target PEB, we pick a PEB with the highest EC if our PEB is "old" and we + * pick target PEB with an average EC if our PEB is not very "old". This is a + * room for future re-works of the WL sub-system. + */ + +#include <linux/slab.h> +#include <linux/crc32.h> +#include <linux/freezer.h> +#include <linux/kthread.h> +#include "ubi.h" + +/* Number of physical eraseblocks reserved for wear-leveling purposes */ +#define WL_RESERVED_PEBS 1 + +/* + * Maximum difference between two erase counters. If this threshold is + * exceeded, the WL sub-system starts moving data from used physical + * eraseblocks with low erase counter to free physical eraseblocks with high + * erase counter. + */ +#define UBI_WL_THRESHOLD CONFIG_MTD_UBI_WL_THRESHOLD + +/* + * When a physical eraseblock is moved, the WL sub-system has to pick the target + * physical eraseblock to move to. The simplest way would be just to pick the + * one with the highest erase counter. But in certain workloads this could lead + * to an unlimited wear of one or few physical eraseblock. Indeed, imagine a + * situation when the picked physical eraseblock is constantly erased after the + * data is written to it. So, we have a constant which limits the highest erase + * counter of the free physical eraseblock to pick. Namely, the WL sub-system + * does not pick eraseblocks with erase counter greater than the lowest erase + * counter plus %WL_FREE_MAX_DIFF. + */ +#define WL_FREE_MAX_DIFF (2*UBI_WL_THRESHOLD) + +/* + * Maximum number of consecutive background thread failures which is enough to + * switch to read-only mode. + */ +#define WL_MAX_FAILURES 32 + +static int self_check_ec(struct ubi_device *ubi, int pnum, int ec); +static int self_check_in_wl_tree(const struct ubi_device *ubi, + struct ubi_wl_entry *e, struct rb_root *root); +static int self_check_in_pq(const struct ubi_device *ubi, + struct ubi_wl_entry *e); + +#ifdef CONFIG_MTD_UBI_FASTMAP +/** + * update_fastmap_work_fn - calls ubi_update_fastmap from a work queue + * @wrk: the work description object + */ +static void update_fastmap_work_fn(struct work_struct *wrk) +{ + struct ubi_device *ubi = container_of(wrk, struct ubi_device, fm_work); + ubi_update_fastmap(ubi); +} + +/** + * ubi_ubi_is_fm_block - returns 1 if a PEB is currently used in a fastmap. + * @ubi: UBI device description object + * @pnum: the to be checked PEB + */ +static int ubi_is_fm_block(struct ubi_device *ubi, int pnum) +{ + int i; + + if (!ubi->fm) + return 0; + + for (i = 0; i < ubi->fm->used_blocks; i++) + if (ubi->fm->e[i]->pnum == pnum) + return 1; + + return 0; +} +#else +static int ubi_is_fm_block(struct ubi_device *ubi, int pnum) +{ + return 0; +} +#endif + +/** + * wl_tree_add - add a wear-leveling entry to a WL RB-tree. + * @e: the wear-leveling entry to add + * @root: the root of the tree + * + * Note, we use (erase counter, physical eraseblock number) pairs as keys in + * the @ubi->used and @ubi->free RB-trees. + */ +static void wl_tree_add(struct ubi_wl_entry *e, struct rb_root *root) +{ + struct rb_node **p, *parent = NULL; + + p = &root->rb_node; + while (*p) { + struct ubi_wl_entry *e1; + + parent = *p; + e1 = rb_entry(parent, struct ubi_wl_entry, u.rb); + + if (e->ec < e1->ec) + p = &(*p)->rb_left; + else if (e->ec > e1->ec) + p = &(*p)->rb_right; + else { + ubi_assert(e->pnum != e1->pnum); + if (e->pnum < e1->pnum) + p = &(*p)->rb_left; + else + p = &(*p)->rb_right; + } + } + + rb_link_node(&e->u.rb, parent, p); + rb_insert_color(&e->u.rb, root); +} + +/** + * do_work - do one pending work. + * @ubi: UBI device description object + * + * This function returns zero in case of success and a negative error code in + * case of failure. + */ +static int do_work(struct ubi_device *ubi) +{ + int err; + struct ubi_work *wrk; + + cond_resched(); + + /* + * @ubi->work_sem is used to synchronize with the workers. Workers take + * it in read mode, so many of them may be doing works at a time. But + * the queue flush code has to be sure the whole queue of works is + * done, and it takes the mutex in write mode. + */ + down_read(&ubi->work_sem); + spin_lock(&ubi->wl_lock); + if (list_empty(&ubi->works)) { + spin_unlock(&ubi->wl_lock); + up_read(&ubi->work_sem); + return 0; + } + + wrk = list_entry(ubi->works.next, struct ubi_work, list); + list_del(&wrk->list); + ubi->works_count -= 1; + ubi_assert(ubi->works_count >= 0); + spin_unlock(&ubi->wl_lock); + + /* + * Call the worker function. Do not touch the work structure + * after this call as it will have been freed or reused by that + * time by the worker function. + */ + err = wrk->func(ubi, wrk, 0); + if (err) + ubi_err("work failed with error code %d", err); + up_read(&ubi->work_sem); + + return err; +} + +/** + * produce_free_peb - produce a free physical eraseblock. + * @ubi: UBI device description object + * + * This function tries to make a free PEB by means of synchronous execution of + * pending works. This may be needed if, for example the background thread is + * disabled. Returns zero in case of success and a negative error code in case + * of failure. + */ +static int produce_free_peb(struct ubi_device *ubi) +{ + int err; + + while (!ubi->free.rb_node) { + spin_unlock(&ubi->wl_lock); + + dbg_wl("do one work synchronously"); + err = do_work(ubi); + + spin_lock(&ubi->wl_lock); + if (err) + return err; + } + + return 0; +} + +/** + * in_wl_tree - check if wear-leveling entry is present in a WL RB-tree. + * @e: the wear-leveling entry to check + * @root: the root of the tree + * + * This function returns non-zero if @e is in the @root RB-tree and zero if it + * is not. + */ +static int in_wl_tree(struct ubi_wl_entry *e, struct rb_root *root) +{ + struct rb_node *p; + + p = root->rb_node; + while (p) { + struct ubi_wl_entry *e1; + + e1 = rb_entry(p, struct ubi_wl_entry, u.rb); + + if (e->pnum == e1->pnum) { + ubi_assert(e == e1); + return 1; + } + + if (e->ec < e1->ec) + p = p->rb_left; + else if (e->ec > e1->ec) + p = p->rb_right; + else { + ubi_assert(e->pnum != e1->pnum); + if (e->pnum < e1->pnum) + p = p->rb_left; + else + p = p->rb_right; + } + } + + return 0; +} + +/** + * prot_queue_add - add physical eraseblock to the protection queue. + * @ubi: UBI device description object + * @e: the physical eraseblock to add + * + * This function adds @e to the tail of the protection queue @ubi->pq, where + * @e will stay for %UBI_PROT_QUEUE_LEN erase operations and will be + * temporarily protected from the wear-leveling worker. Note, @wl->lock has to + * be locked. + */ +static void prot_queue_add(struct ubi_device *ubi, struct ubi_wl_entry *e) +{ + int pq_tail = ubi->pq_head - 1; + + if (pq_tail < 0) + pq_tail = UBI_PROT_QUEUE_LEN - 1; + ubi_assert(pq_tail >= 0 && pq_tail < UBI_PROT_QUEUE_LEN); + list_add_tail(&e->u.list, &ubi->pq[pq_tail]); + dbg_wl("added PEB %d EC %d to the protection queue", e->pnum, e->ec); +} + +/** + * find_wl_entry - find wear-leveling entry closest to certain erase counter. + * @ubi: UBI device description object + * @root: the RB-tree where to look for + * @diff: maximum possible difference from the smallest erase counter + * + * This function looks for a wear leveling entry with erase counter closest to + * min + @diff, where min is the smallest erase counter. + */ +static struct ubi_wl_entry *find_wl_entry(struct ubi_device *ubi, + struct rb_root *root, int diff) +{ + struct rb_node *p; + struct ubi_wl_entry *e, *prev_e = NULL; + int max; + + e = rb_entry(rb_first(root), struct ubi_wl_entry, u.rb); + max = e->ec + diff; + + p = root->rb_node; + while (p) { + struct ubi_wl_entry *e1; + + e1 = rb_entry(p, struct ubi_wl_entry, u.rb); + if (e1->ec >= max) + p = p->rb_left; + else { + p = p->rb_right; + prev_e = e; + e = e1; + } + } + + /* If no fastmap has been written and this WL entry can be used + * as anchor PEB, hold it back and return the second best WL entry + * such that fastmap can use the anchor PEB later. */ + if (prev_e && !ubi->fm_disabled && + !ubi->fm && e->pnum < UBI_FM_MAX_START) + return prev_e; + + return e; +} + +/** + * find_mean_wl_entry - find wear-leveling entry with medium erase counter. + * @ubi: UBI device description object + * @root: the RB-tree where to look for + * + * This function looks for a wear leveling entry with medium erase counter, + * but not greater or equivalent than the lowest erase counter plus + * %WL_FREE_MAX_DIFF/2. + */ +static struct ubi_wl_entry *find_mean_wl_entry(struct ubi_device *ubi, + struct rb_root *root) +{ + struct ubi_wl_entry *e, *first, *last; + + first = rb_entry(rb_first(root), struct ubi_wl_entry, u.rb); + last = rb_entry(rb_last(root), struct ubi_wl_entry, u.rb); + + if (last->ec - first->ec < WL_FREE_MAX_DIFF) { + e = rb_entry(root->rb_node, struct ubi_wl_entry, u.rb); + +#ifdef CONFIG_MTD_UBI_FASTMAP + /* If no fastmap has been written and this WL entry can be used + * as anchor PEB, hold it back and return the second best + * WL entry such that fastmap can use the anchor PEB later. */ + if (e && !ubi->fm_disabled && !ubi->fm && + e->pnum < UBI_FM_MAX_START) + e = rb_entry(rb_next(root->rb_node), + struct ubi_wl_entry, u.rb); +#endif + } else + e = find_wl_entry(ubi, root, WL_FREE_MAX_DIFF/2); + + return e; +} + +#ifdef CONFIG_MTD_UBI_FASTMAP +/** + * find_anchor_wl_entry - find wear-leveling entry to used as anchor PEB. + * @root: the RB-tree where to look for + */ +static struct ubi_wl_entry *find_anchor_wl_entry(struct rb_root *root) +{ + struct rb_node *p; + struct ubi_wl_entry *e, *victim = NULL; + int max_ec = UBI_MAX_ERASECOUNTER; + + ubi_rb_for_each_entry(p, e, root, u.rb) { + if (e->pnum < UBI_FM_MAX_START && e->ec < max_ec) { + victim = e; + max_ec = e->ec; + } + } + + return victim; +} + +static int anchor_pebs_avalible(struct rb_root *root) +{ + struct rb_node *p; + struct ubi_wl_entry *e; + + ubi_rb_for_each_entry(p, e, root, u.rb) + if (e->pnum < UBI_FM_MAX_START) + return 1; + + return 0; +} + +/** + * ubi_wl_get_fm_peb - find a physical erase block with a given maximal number. + * @ubi: UBI device description object + * @anchor: This PEB will be used as anchor PEB by fastmap + * + * The function returns a physical erase block with a given maximal number + * and removes it from the wl subsystem. + * Must be called with wl_lock held! + */ +struct ubi_wl_entry *ubi_wl_get_fm_peb(struct ubi_device *ubi, int anchor) +{ + struct ubi_wl_entry *e = NULL; + + if (!ubi->free.rb_node || (ubi->free_count - ubi->beb_rsvd_pebs < 1)) + goto out; + + if (anchor) + e = find_anchor_wl_entry(&ubi->free); + else + e = find_mean_wl_entry(ubi, &ubi->free); + + if (!e) + goto out; + + self_check_in_wl_tree(ubi, e, &ubi->free); + + /* remove it from the free list, + * the wl subsystem does no longer know this erase block */ + rb_erase(&e->u.rb, &ubi->free); + ubi->free_count--; +out: + return e; +} +#endif + +/** + * __wl_get_peb - get a physical eraseblock. + * @ubi: UBI device description object + * + * This function returns a physical eraseblock in case of success and a + * negative error code in case of failure. + */ +static int __wl_get_peb(struct ubi_device *ubi) +{ + int err; + struct ubi_wl_entry *e; + +retry: + if (!ubi->free.rb_node) { + if (ubi->works_count == 0) { + ubi_err("no free eraseblocks"); + ubi_assert(list_empty(&ubi->works)); + return -ENOSPC; + } + + err = produce_free_peb(ubi); + if (err < 0) + return err; + goto retry; + } + + e = find_mean_wl_entry(ubi, &ubi->free); + if (!e) { + ubi_err("no free eraseblocks"); + return -ENOSPC; + } + + self_check_in_wl_tree(ubi, e, &ubi->free); + + /* + * Move the physical eraseblock to the protection queue where it will + * be protected from being moved for some time. + */ + rb_erase(&e->u.rb, &ubi->free); + ubi->free_count--; + dbg_wl("PEB %d EC %d", e->pnum, e->ec); +#ifndef CONFIG_MTD_UBI_FASTMAP + /* We have to enqueue e only if fastmap is disabled, + * is fastmap enabled prot_queue_add() will be called by + * ubi_wl_get_peb() after removing e from the pool. */ + prot_queue_add(ubi, e); +#endif + return e->pnum; +} + +#ifdef CONFIG_MTD_UBI_FASTMAP +/** + * return_unused_pool_pebs - returns unused PEB to the free tree. + * @ubi: UBI device description object + * @pool: fastmap pool description object + */ +static void return_unused_pool_pebs(struct ubi_device *ubi, + struct ubi_fm_pool *pool) +{ + int i; + struct ubi_wl_entry *e; + + for (i = pool->used; i < pool->size; i++) { + e = ubi->lookuptbl[pool->pebs[i]]; + wl_tree_add(e, &ubi->free); + ubi->free_count++; + } +} + +/** + * refill_wl_pool - refills all the fastmap pool used by the + * WL sub-system. + * @ubi: UBI device description object + */ +static void refill_wl_pool(struct ubi_device *ubi) +{ + struct ubi_wl_entry *e; + struct ubi_fm_pool *pool = &ubi->fm_wl_pool; + + return_unused_pool_pebs(ubi, pool); + + for (pool->size = 0; pool->size < pool->max_size; pool->size++) { + if (!ubi->free.rb_node || + (ubi->free_count - ubi->beb_rsvd_pebs < 5)) + break; + + e = find_wl_entry(ubi, &ubi->free, WL_FREE_MAX_DIFF); + self_check_in_wl_tree(ubi, e, &ubi->free); + rb_erase(&e->u.rb, &ubi->free); + ubi->free_count--; + + pool->pebs[pool->size] = e->pnum; + } + pool->used = 0; +} + +/** + * refill_wl_user_pool - refills all the fastmap pool used by ubi_wl_get_peb. + * @ubi: UBI device description object + */ +static void refill_wl_user_pool(struct ubi_device *ubi) +{ + struct ubi_fm_pool *pool = &ubi->fm_pool; + + return_unused_pool_pebs(ubi, pool); + + for (pool->size = 0; pool->size < pool->max_size; pool->size++) { + pool->pebs[pool->size] = __wl_get_peb(ubi); + if (pool->pebs[pool->size] < 0) + break; + } + pool->used = 0; +} + +/** + * ubi_refill_pools - refills all fastmap PEB pools. + * @ubi: UBI device description object + */ +void ubi_refill_pools(struct ubi_device *ubi) +{ + spin_lock(&ubi->wl_lock); + refill_wl_pool(ubi); + refill_wl_user_pool(ubi); + spin_unlock(&ubi->wl_lock); +} + +/* ubi_wl_get_peb - works exaclty like __wl_get_peb but keeps track of + * the fastmap pool. + */ +int ubi_wl_get_peb(struct ubi_device *ubi) +{ + int ret; + struct ubi_fm_pool *pool = &ubi->fm_pool; + struct ubi_fm_pool *wl_pool = &ubi->fm_wl_pool; + + if (!pool->size || !wl_pool->size || pool->used == pool->size || + wl_pool->used == wl_pool->size) + ubi_update_fastmap(ubi); + + /* we got not a single free PEB */ + if (!pool->size) + ret = -ENOSPC; + else { + spin_lock(&ubi->wl_lock); + ret = pool->pebs[pool->used++]; + prot_queue_add(ubi, ubi->lookuptbl[ret]); + spin_unlock(&ubi->wl_lock); + } + + return ret; +} + +/* get_peb_for_wl - returns a PEB to be used internally by the WL sub-system. + * + * @ubi: UBI device description object + */ +static struct ubi_wl_entry *get_peb_for_wl(struct ubi_device *ubi) +{ + struct ubi_fm_pool *pool = &ubi->fm_wl_pool; + int pnum; + + if (pool->used == pool->size || !pool->size) { + /* We cannot update the fastmap here because this + * function is called in atomic context. + * Let's fail here and refill/update it as soon as possible. */ + schedule_work(&ubi->fm_work); + return NULL; + } else { + pnum = pool->pebs[pool->used++]; + return ubi->lookuptbl[pnum]; + } +} +#else +static struct ubi_wl_entry *get_peb_for_wl(struct ubi_device *ubi) +{ + struct ubi_wl_entry *e; + + e = find_wl_entry(ubi, &ubi->free, WL_FREE_MAX_DIFF); + self_check_in_wl_tree(ubi, e, &ubi->free); + ubi->free_count--; + ubi_assert(ubi->free_count >= 0); + rb_erase(&e->u.rb, &ubi->free); + + return e; +} + +int ubi_wl_get_peb(struct ubi_device *ubi) +{ + int peb, err; + + spin_lock(&ubi->wl_lock); + peb = __wl_get_peb(ubi); + spin_unlock(&ubi->wl_lock); + + if (peb < 0) + return peb; + + err = ubi_self_check_all_ff(ubi, peb, ubi->vid_hdr_aloffset, + ubi->peb_size - ubi->vid_hdr_aloffset); + if (err) { + ubi_err("new PEB %d does not contain all 0xFF bytes", peb); + return err; + } + + return peb; +} +#endif + +/** + * prot_queue_del - remove a physical eraseblock from the protection queue. + * @ubi: UBI device description object + * @pnum: the physical eraseblock to remove + * + * This function deletes PEB @pnum from the protection queue and returns zero + * in case of success and %-ENODEV if the PEB was not found. + */ +static int prot_queue_del(struct ubi_device *ubi, int pnum) +{ + struct ubi_wl_entry *e; + + e = ubi->lookuptbl[pnum]; + if (!e) + return -ENODEV; + + if (self_check_in_pq(ubi, e)) + return -ENODEV; + + list_del(&e->u.list); + dbg_wl("deleted PEB %d from the protection queue", e->pnum); + return 0; +} + +/** + * sync_erase - synchronously erase a physical eraseblock. + * @ubi: UBI device description object + * @e: the the physical eraseblock to erase + * @torture: if the physical eraseblock has to be tortured + * + * This function returns zero in case of success and a negative error code in + * case of failure. + */ +static int sync_erase(struct ubi_device *ubi, struct ubi_wl_entry *e, + int torture) +{ + int err; + struct ubi_ec_hdr *ec_hdr; + unsigned long long ec = e->ec; + + dbg_wl("erase PEB %d, old EC %llu", e->pnum, ec); + + err = self_check_ec(ubi, e->pnum, e->ec); + if (err) + return -EINVAL; + + ec_hdr = kzalloc(ubi->ec_hdr_alsize, GFP_NOFS); + if (!ec_hdr) + return -ENOMEM; + + err = ubi_io_sync_erase(ubi, e->pnum, torture); + if (err < 0) + goto out_free; + + ec += err; + if (ec > UBI_MAX_ERASECOUNTER) { + /* + * Erase counter overflow. Upgrade UBI and use 64-bit + * erase counters internally. + */ + ubi_err("erase counter overflow at PEB %d, EC %llu", + e->pnum, ec); + err = -EINVAL; + goto out_free; + } + + dbg_wl("erased PEB %d, new EC %llu", e->pnum, ec); + + ec_hdr->ec = cpu_to_be64(ec); + + err = ubi_io_write_ec_hdr(ubi, e->pnum, ec_hdr); + if (err) + goto out_free; + + e->ec = ec; + spin_lock(&ubi->wl_lock); + if (e->ec > ubi->max_ec) + ubi->max_ec = e->ec; + spin_unlock(&ubi->wl_lock); + +out_free: + kfree(ec_hdr); + return err; +} + +/** + * serve_prot_queue - check if it is time to stop protecting PEBs. + * @ubi: UBI device description object + * + * This function is called after each erase operation and removes PEBs from the + * tail of the protection queue. These PEBs have been protected for long enough + * and should be moved to the used tree. + */ +static void serve_prot_queue(struct ubi_device *ubi) +{ + struct ubi_wl_entry *e, *tmp; + int count; + + /* + * There may be several protected physical eraseblock to remove, + * process them all. + */ +repeat: + count = 0; + spin_lock(&ubi->wl_lock); + list_for_each_entry_safe(e, tmp, &ubi->pq[ubi->pq_head], u.list) { + dbg_wl("PEB %d EC %d protection over, move to used tree", + e->pnum, e->ec); + + list_del(&e->u.list); + wl_tree_add(e, &ubi->used); + if (count++ > 32) { + /* + * Let's be nice and avoid holding the spinlock for + * too long. + */ + spin_unlock(&ubi->wl_lock); + cond_resched(); + goto repeat; + } + } + + ubi->pq_head += 1; + if (ubi->pq_head == UBI_PROT_QUEUE_LEN) + ubi->pq_head = 0; + ubi_assert(ubi->pq_head >= 0 && ubi->pq_head < UBI_PROT_QUEUE_LEN); + spin_unlock(&ubi->wl_lock); +} + +/** + * __schedule_ubi_work - schedule a work. + * @ubi: UBI device description object + * @wrk: the work to schedule + * + * This function adds a work defined by @wrk to the tail of the pending works + * list. Can only be used of ubi->work_sem is already held in read mode! + */ +static void __schedule_ubi_work(struct ubi_device *ubi, struct ubi_work *wrk) +{ + spin_lock(&ubi->wl_lock); + list_add_tail(&wrk->list, &ubi->works); + ubi_assert(ubi->works_count >= 0); + ubi->works_count += 1; + if (ubi->thread_enabled && !ubi_dbg_is_bgt_disabled(ubi)) + wake_up_process(ubi->bgt_thread); + spin_unlock(&ubi->wl_lock); +} + +/** + * schedule_ubi_work - schedule a work. + * @ubi: UBI device description object + * @wrk: the work to schedule + * + * This function adds a work defined by @wrk to the tail of the pending works + * list. + */ +static void schedule_ubi_work(struct ubi_device *ubi, struct ubi_work *wrk) +{ + down_read(&ubi->work_sem); + __schedule_ubi_work(ubi, wrk); + up_read(&ubi->work_sem); +} + +static int erase_worker(struct ubi_device *ubi, struct ubi_work *wl_wrk, + int cancel); + +#ifdef CONFIG_MTD_UBI_FASTMAP +/** + * ubi_is_erase_work - checks whether a work is erase work. + * @wrk: The work object to be checked + */ +int ubi_is_erase_work(struct ubi_work *wrk) +{ + return wrk->func == erase_worker; +} +#endif + +/** + * schedule_erase - schedule an erase work. + * @ubi: UBI device description object + * @e: the WL entry of the physical eraseblock to erase + * @vol_id: the volume ID that last used this PEB + * @lnum: the last used logical eraseblock number for the PEB + * @torture: if the physical eraseblock has to be tortured + * + * This function returns zero in case of success and a %-ENOMEM in case of + * failure. + */ +static int schedule_erase(struct ubi_device *ubi, struct ubi_wl_entry *e, + int vol_id, int lnum, int torture) +{ + struct ubi_work *wl_wrk; + + ubi_assert(e); + ubi_assert(!ubi_is_fm_block(ubi, e->pnum)); + + dbg_wl("schedule erasure of PEB %d, EC %d, torture %d", + e->pnum, e->ec, torture); + + wl_wrk = kmalloc(sizeof(struct ubi_work), GFP_NOFS); + if (!wl_wrk) + return -ENOMEM; + + wl_wrk->func = &erase_worker; + wl_wrk->e = e; + wl_wrk->vol_id = vol_id; + wl_wrk->lnum = lnum; + wl_wrk->torture = torture; + + schedule_ubi_work(ubi, wl_wrk); + return 0; +} + +/** + * do_sync_erase - run the erase worker synchronously. + * @ubi: UBI device description object + * @e: the WL entry of the physical eraseblock to erase + * @vol_id: the volume ID that last used this PEB + * @lnum: the last used logical eraseblock number for the PEB + * @torture: if the physical eraseblock has to be tortured + * + */ +static int do_sync_erase(struct ubi_device *ubi, struct ubi_wl_entry *e, + int vol_id, int lnum, int torture) +{ + struct ubi_work *wl_wrk; + + dbg_wl("sync erase of PEB %i", e->pnum); + + wl_wrk = kmalloc(sizeof(struct ubi_work), GFP_NOFS); + if (!wl_wrk) + return -ENOMEM; + + wl_wrk->e = e; + wl_wrk->vol_id = vol_id; + wl_wrk->lnum = lnum; + wl_wrk->torture = torture; + + return erase_worker(ubi, wl_wrk, 0); +} + +#ifdef CONFIG_MTD_UBI_FASTMAP +/** + * ubi_wl_put_fm_peb - returns a PEB used in a fastmap to the wear-leveling + * sub-system. + * see: ubi_wl_put_peb() + * + * @ubi: UBI device description object + * @fm_e: physical eraseblock to return + * @lnum: the last used logical eraseblock number for the PEB + * @torture: if this physical eraseblock has to be tortured + */ +int ubi_wl_put_fm_peb(struct ubi_device *ubi, struct ubi_wl_entry *fm_e, + int lnum, int torture) +{ + struct ubi_wl_entry *e; + int vol_id, pnum = fm_e->pnum; + + dbg_wl("PEB %d", pnum); + + ubi_assert(pnum >= 0); + ubi_assert(pnum < ubi->peb_count); + + spin_lock(&ubi->wl_lock); + e = ubi->lookuptbl[pnum]; + + /* This can happen if we recovered from a fastmap the very + * first time and writing now a new one. In this case the wl system + * has never seen any PEB used by the original fastmap. + */ + if (!e) { + e = fm_e; + ubi_assert(e->ec >= 0); + ubi->lookuptbl[pnum] = e; + } else { + e->ec = fm_e->ec; + kfree(fm_e); + } + + spin_unlock(&ubi->wl_lock); + + vol_id = lnum ? UBI_FM_DATA_VOLUME_ID : UBI_FM_SB_VOLUME_ID; + return schedule_erase(ubi, e, vol_id, lnum, torture); +} +#endif + +/** + * wear_leveling_worker - wear-leveling worker function. + * @ubi: UBI device description object + * @wrk: the work object + * @cancel: non-zero if the worker has to free memory and exit + * + * This function copies a more worn out physical eraseblock to a less worn out + * one. Returns zero in case of success and a negative error code in case of + * failure. + */ +static int wear_leveling_worker(struct ubi_device *ubi, struct ubi_work *wrk, + int cancel) +{ + int err, scrubbing = 0, torture = 0, protect = 0, erroneous = 0; + int vol_id = -1, uninitialized_var(lnum); +#ifdef CONFIG_MTD_UBI_FASTMAP + int anchor = wrk->anchor; +#endif + struct ubi_wl_entry *e1, *e2; + struct ubi_vid_hdr *vid_hdr; + + kfree(wrk); + if (cancel) + return 0; + + vid_hdr = ubi_zalloc_vid_hdr(ubi, GFP_NOFS); + if (!vid_hdr) + return -ENOMEM; + + mutex_lock(&ubi->move_mutex); + spin_lock(&ubi->wl_lock); + ubi_assert(!ubi->move_from && !ubi->move_to); + ubi_assert(!ubi->move_to_put); + + if (!ubi->free.rb_node || + (!ubi->used.rb_node && !ubi->scrub.rb_node)) { + /* + * No free physical eraseblocks? Well, they must be waiting in + * the queue to be erased. Cancel movement - it will be + * triggered again when a free physical eraseblock appears. + * + * No used physical eraseblocks? They must be temporarily + * protected from being moved. They will be moved to the + * @ubi->used tree later and the wear-leveling will be + * triggered again. + */ + dbg_wl("cancel WL, a list is empty: free %d, used %d", + !ubi->free.rb_node, !ubi->used.rb_node); + goto out_cancel; + } + +#ifdef CONFIG_MTD_UBI_FASTMAP + /* Check whether we need to produce an anchor PEB */ + if (!anchor) + anchor = !anchor_pebs_avalible(&ubi->free); + + if (anchor) { + e1 = find_anchor_wl_entry(&ubi->used); + if (!e1) + goto out_cancel; + e2 = get_peb_for_wl(ubi); + if (!e2) + goto out_cancel; + + self_check_in_wl_tree(ubi, e1, &ubi->used); + rb_erase(&e1->u.rb, &ubi->used); + dbg_wl("anchor-move PEB %d to PEB %d", e1->pnum, e2->pnum); + } else if (!ubi->scrub.rb_node) { +#else + if (!ubi->scrub.rb_node) { +#endif + /* + * Now pick the least worn-out used physical eraseblock and a + * highly worn-out free physical eraseblock. If the erase + * counters differ much enough, start wear-leveling. + */ + e1 = rb_entry(rb_first(&ubi->used), struct ubi_wl_entry, u.rb); + e2 = get_peb_for_wl(ubi); + if (!e2) + goto out_cancel; + + if (!(e2->ec - e1->ec >= UBI_WL_THRESHOLD)) { + dbg_wl("no WL needed: min used EC %d, max free EC %d", + e1->ec, e2->ec); + + /* Give the unused PEB back */ + wl_tree_add(e2, &ubi->free); + ubi->free_count++; + goto out_cancel; + } + self_check_in_wl_tree(ubi, e1, &ubi->used); + rb_erase(&e1->u.rb, &ubi->used); + dbg_wl("move PEB %d EC %d to PEB %d EC %d", + e1->pnum, e1->ec, e2->pnum, e2->ec); + } else { + /* Perform scrubbing */ + scrubbing = 1; + e1 = rb_entry(rb_first(&ubi->scrub), struct ubi_wl_entry, u.rb); + e2 = get_peb_for_wl(ubi); + if (!e2) + goto out_cancel; + + self_check_in_wl_tree(ubi, e1, &ubi->scrub); + rb_erase(&e1->u.rb, &ubi->scrub); + dbg_wl("scrub PEB %d to PEB %d", e1->pnum, e2->pnum); + } + + ubi->move_from = e1; + ubi->move_to = e2; + spin_unlock(&ubi->wl_lock); + + /* + * Now we are going to copy physical eraseblock @e1->pnum to @e2->pnum. + * We so far do not know which logical eraseblock our physical + * eraseblock (@e1) belongs to. We have to read the volume identifier + * header first. + * + * Note, we are protected from this PEB being unmapped and erased. The + * 'ubi_wl_put_peb()' would wait for moving to be finished if the PEB + * which is being moved was unmapped. + */ + + err = ubi_io_read_vid_hdr(ubi, e1->pnum, vid_hdr, 0); + if (err && err != UBI_IO_BITFLIPS) { + if (err == UBI_IO_FF) { + /* + * We are trying to move PEB without a VID header. UBI + * always write VID headers shortly after the PEB was + * given, so we have a situation when it has not yet + * had a chance to write it, because it was preempted. + * So add this PEB to the protection queue so far, + * because presumably more data will be written there + * (including the missing VID header), and then we'll + * move it. + */ + dbg_wl("PEB %d has no VID header", e1->pnum); + protect = 1; + goto out_not_moved; + } else if (err == UBI_IO_FF_BITFLIPS) { + /* + * The same situation as %UBI_IO_FF, but bit-flips were + * detected. It is better to schedule this PEB for + * scrubbing. + */ + dbg_wl("PEB %d has no VID header but has bit-flips", + e1->pnum); + scrubbing = 1; + goto out_not_moved; + } + + ubi_err("error %d while reading VID header from PEB %d", + err, e1->pnum); + goto out_error; + } + + vol_id = be32_to_cpu(vid_hdr->vol_id); + lnum = be32_to_cpu(vid_hdr->lnum); + + err = ubi_eba_copy_leb(ubi, e1->pnum, e2->pnum, vid_hdr); + if (err) { + if (err == MOVE_CANCEL_RACE) { + /* + * The LEB has not been moved because the volume is + * being deleted or the PEB has been put meanwhile. We + * should prevent this PEB from being selected for + * wear-leveling movement again, so put it to the + * protection queue. + */ + protect = 1; + goto out_not_moved; + } + if (err == MOVE_RETRY) { + scrubbing = 1; + goto out_not_moved; + } + if (err == MOVE_TARGET_BITFLIPS || err == MOVE_TARGET_WR_ERR || + err == MOVE_TARGET_RD_ERR) { + /* + * Target PEB had bit-flips or write error - torture it. + */ + torture = 1; + goto out_not_moved; + } + + if (err == MOVE_SOURCE_RD_ERR) { + /* + * An error happened while reading the source PEB. Do + * not switch to R/O mode in this case, and give the + * upper layers a possibility to recover from this, + * e.g. by unmapping corresponding LEB. Instead, just + * put this PEB to the @ubi->erroneous list to prevent + * UBI from trying to move it over and over again. + */ + if (ubi->erroneous_peb_count > ubi->max_erroneous) { + ubi_err("too many erroneous eraseblocks (%d)", + ubi->erroneous_peb_count); + goto out_error; + } + erroneous = 1; + goto out_not_moved; + } + + if (err < 0) + goto out_error; + + ubi_assert(0); + } + + /* The PEB has been successfully moved */ + if (scrubbing) + ubi_msg("scrubbed PEB %d (LEB %d:%d), data moved to PEB %d", + e1->pnum, vol_id, lnum, e2->pnum); + ubi_free_vid_hdr(ubi, vid_hdr); + + spin_lock(&ubi->wl_lock); + if (!ubi->move_to_put) { + wl_tree_add(e2, &ubi->used); + e2 = NULL; + } + ubi->move_from = ubi->move_to = NULL; + ubi->move_to_put = ubi->wl_scheduled = 0; + spin_unlock(&ubi->wl_lock); + + err = do_sync_erase(ubi, e1, vol_id, lnum, 0); + if (err) { + kmem_cache_free(ubi_wl_entry_slab, e1); + if (e2) + kmem_cache_free(ubi_wl_entry_slab, e2); + goto out_ro; + } + + if (e2) { + /* + * Well, the target PEB was put meanwhile, schedule it for + * erasure. + */ + dbg_wl("PEB %d (LEB %d:%d) was put meanwhile, erase", + e2->pnum, vol_id, lnum); + err = do_sync_erase(ubi, e2, vol_id, lnum, 0); + if (err) { + kmem_cache_free(ubi_wl_entry_slab, e2); + goto out_ro; + } + } + + dbg_wl("done"); + mutex_unlock(&ubi->move_mutex); + return 0; + + /* + * For some reasons the LEB was not moved, might be an error, might be + * something else. @e1 was not changed, so return it back. @e2 might + * have been changed, schedule it for erasure. + */ +out_not_moved: + if (vol_id != -1) + dbg_wl("cancel moving PEB %d (LEB %d:%d) to PEB %d (%d)", + e1->pnum, vol_id, lnum, e2->pnum, err); + else + dbg_wl("cancel moving PEB %d to PEB %d (%d)", + e1->pnum, e2->pnum, err); + spin_lock(&ubi->wl_lock); + if (protect) + prot_queue_add(ubi, e1); + else if (erroneous) { + wl_tree_add(e1, &ubi->erroneous); + ubi->erroneous_peb_count += 1; + } else if (scrubbing) + wl_tree_add(e1, &ubi->scrub); + else + wl_tree_add(e1, &ubi->used); + ubi_assert(!ubi->move_to_put); + ubi->move_from = ubi->move_to = NULL; + ubi->wl_scheduled = 0; + spin_unlock(&ubi->wl_lock); + + ubi_free_vid_hdr(ubi, vid_hdr); + err = do_sync_erase(ubi, e2, vol_id, lnum, torture); + if (err) { + kmem_cache_free(ubi_wl_entry_slab, e2); + goto out_ro; + } + mutex_unlock(&ubi->move_mutex); + return 0; + +out_error: + if (vol_id != -1) + ubi_err("error %d while moving PEB %d to PEB %d", + err, e1->pnum, e2->pnum); + else + ubi_err("error %d while moving PEB %d (LEB %d:%d) to PEB %d", + err, e1->pnum, vol_id, lnum, e2->pnum); + spin_lock(&ubi->wl_lock); + ubi->move_from = ubi->move_to = NULL; + ubi->move_to_put = ubi->wl_scheduled = 0; + spin_unlock(&ubi->wl_lock); + + ubi_free_vid_hdr(ubi, vid_hdr); + kmem_cache_free(ubi_wl_entry_slab, e1); + kmem_cache_free(ubi_wl_entry_slab, e2); + +out_ro: + ubi_ro_mode(ubi); + mutex_unlock(&ubi->move_mutex); + ubi_assert(err != 0); + return err < 0 ? err : -EIO; + +out_cancel: + ubi->wl_scheduled = 0; + spin_unlock(&ubi->wl_lock); + mutex_unlock(&ubi->move_mutex); + ubi_free_vid_hdr(ubi, vid_hdr); + return 0; +} + +/** + * ensure_wear_leveling - schedule wear-leveling if it is needed. + * @ubi: UBI device description object + * @nested: set to non-zero if this function is called from UBI worker + * + * This function checks if it is time to start wear-leveling and schedules it + * if yes. This function returns zero in case of success and a negative error + * code in case of failure. + */ +static int ensure_wear_leveling(struct ubi_device *ubi, int nested) +{ + int err = 0; + struct ubi_wl_entry *e1; + struct ubi_wl_entry *e2; + struct ubi_work *wrk; + + spin_lock(&ubi->wl_lock); + if (ubi->wl_scheduled) + /* Wear-leveling is already in the work queue */ + goto out_unlock; + + /* + * If the ubi->scrub tree is not empty, scrubbing is needed, and the + * the WL worker has to be scheduled anyway. + */ + if (!ubi->scrub.rb_node) { + if (!ubi->used.rb_node || !ubi->free.rb_node) + /* No physical eraseblocks - no deal */ + goto out_unlock; + + /* + * We schedule wear-leveling only if the difference between the + * lowest erase counter of used physical eraseblocks and a high + * erase counter of free physical eraseblocks is greater than + * %UBI_WL_THRESHOLD. + */ + e1 = rb_entry(rb_first(&ubi->used), struct ubi_wl_entry, u.rb); + e2 = find_wl_entry(ubi, &ubi->free, WL_FREE_MAX_DIFF); + + if (!(e2->ec - e1->ec >= UBI_WL_THRESHOLD)) + goto out_unlock; + dbg_wl("schedule wear-leveling"); + } else + dbg_wl("schedule scrubbing"); + + ubi->wl_scheduled = 1; + spin_unlock(&ubi->wl_lock); + + wrk = kmalloc(sizeof(struct ubi_work), GFP_NOFS); + if (!wrk) { + err = -ENOMEM; + goto out_cancel; + } + + wrk->anchor = 0; + wrk->func = &wear_leveling_worker; + if (nested) + __schedule_ubi_work(ubi, wrk); + else + schedule_ubi_work(ubi, wrk); + return err; + +out_cancel: + spin_lock(&ubi->wl_lock); + ubi->wl_scheduled = 0; +out_unlock: + spin_unlock(&ubi->wl_lock); + return err; +} + +#ifdef CONFIG_MTD_UBI_FASTMAP +/** + * ubi_ensure_anchor_pebs - schedule wear-leveling to produce an anchor PEB. + * @ubi: UBI device description object + */ +int ubi_ensure_anchor_pebs(struct ubi_device *ubi) +{ + struct ubi_work *wrk; + + spin_lock(&ubi->wl_lock); + if (ubi->wl_scheduled) { + spin_unlock(&ubi->wl_lock); + return 0; + } + ubi->wl_scheduled = 1; + spin_unlock(&ubi->wl_lock); + + wrk = kmalloc(sizeof(struct ubi_work), GFP_NOFS); + if (!wrk) { + spin_lock(&ubi->wl_lock); + ubi->wl_scheduled = 0; + spin_unlock(&ubi->wl_lock); + return -ENOMEM; + } + + wrk->anchor = 1; + wrk->func = &wear_leveling_worker; + schedule_ubi_work(ubi, wrk); + return 0; +} +#endif + +/** + * erase_worker - physical eraseblock erase worker function. + * @ubi: UBI device description object + * @wl_wrk: the work object + * @cancel: non-zero if the worker has to free memory and exit + * + * This function erases a physical eraseblock and perform torture testing if + * needed. It also takes care about marking the physical eraseblock bad if + * needed. Returns zero in case of success and a negative error code in case of + * failure. + */ +static int erase_worker(struct ubi_device *ubi, struct ubi_work *wl_wrk, + int cancel) +{ + struct ubi_wl_entry *e = wl_wrk->e; + int pnum = e->pnum; + int vol_id = wl_wrk->vol_id; + int lnum = wl_wrk->lnum; + int err, available_consumed = 0; + + if (cancel) { + dbg_wl("cancel erasure of PEB %d EC %d", pnum, e->ec); + kfree(wl_wrk); + kmem_cache_free(ubi_wl_entry_slab, e); + return 0; + } + + dbg_wl("erase PEB %d EC %d LEB %d:%d", + pnum, e->ec, wl_wrk->vol_id, wl_wrk->lnum); + + ubi_assert(!ubi_is_fm_block(ubi, e->pnum)); + + err = sync_erase(ubi, e, wl_wrk->torture); + if (!err) { + /* Fine, we've erased it successfully */ + kfree(wl_wrk); + + spin_lock(&ubi->wl_lock); + wl_tree_add(e, &ubi->free); + ubi->free_count++; + spin_unlock(&ubi->wl_lock); + + /* + * One more erase operation has happened, take care about + * protected physical eraseblocks. + */ + serve_prot_queue(ubi); + + /* And take care about wear-leveling */ + err = ensure_wear_leveling(ubi, 1); + return err; + } + + ubi_err("failed to erase PEB %d, error %d", pnum, err); + kfree(wl_wrk); + + if (err == -EINTR || err == -ENOMEM || err == -EAGAIN || + err == -EBUSY) { + int err1; + + /* Re-schedule the LEB for erasure */ + err1 = schedule_erase(ubi, e, vol_id, lnum, 0); + if (err1) { + err = err1; + goto out_ro; + } + return err; + } + + kmem_cache_free(ubi_wl_entry_slab, e); + if (err != -EIO) + /* + * If this is not %-EIO, we have no idea what to do. Scheduling + * this physical eraseblock for erasure again would cause + * errors again and again. Well, lets switch to R/O mode. + */ + goto out_ro; + + /* It is %-EIO, the PEB went bad */ + + if (!ubi->bad_allowed) { + ubi_err("bad physical eraseblock %d detected", pnum); + goto out_ro; + } + + spin_lock(&ubi->volumes_lock); + if (ubi->beb_rsvd_pebs == 0) { + if (ubi->avail_pebs == 0) { + spin_unlock(&ubi->volumes_lock); + ubi_err("no reserved/available physical eraseblocks"); + goto out_ro; + } + ubi->avail_pebs -= 1; + available_consumed = 1; + } + spin_unlock(&ubi->volumes_lock); + + ubi_msg("mark PEB %d as bad", pnum); + err = ubi_io_mark_bad(ubi, pnum); + if (err) + goto out_ro; + + spin_lock(&ubi->volumes_lock); + if (ubi->beb_rsvd_pebs > 0) { + if (available_consumed) { + /* + * The amount of reserved PEBs increased since we last + * checked. + */ + ubi->avail_pebs += 1; + available_consumed = 0; + } + ubi->beb_rsvd_pebs -= 1; + } + ubi->bad_peb_count += 1; + ubi->good_peb_count -= 1; + ubi_calculate_reserved(ubi); + if (available_consumed) + ubi_warn("no PEBs in the reserved pool, used an available PEB"); + else if (ubi->beb_rsvd_pebs) + ubi_msg("%d PEBs left in the reserve", ubi->beb_rsvd_pebs); + else + ubi_warn("last PEB from the reserve was used"); + spin_unlock(&ubi->volumes_lock); + + return err; + +out_ro: + if (available_consumed) { + spin_lock(&ubi->volumes_lock); + ubi->avail_pebs += 1; + spin_unlock(&ubi->volumes_lock); + } + ubi_ro_mode(ubi); + return err; +} + +/** + * ubi_wl_put_peb - return a PEB to the wear-leveling sub-system. + * @ubi: UBI device description object + * @vol_id: the volume ID that last used this PEB + * @lnum: the last used logical eraseblock number for the PEB + * @pnum: physical eraseblock to return + * @torture: if this physical eraseblock has to be tortured + * + * This function is called to return physical eraseblock @pnum to the pool of + * free physical eraseblocks. The @torture flag has to be set if an I/O error + * occurred to this @pnum and it has to be tested. This function returns zero + * in case of success, and a negative error code in case of failure. + */ +int ubi_wl_put_peb(struct ubi_device *ubi, int vol_id, int lnum, + int pnum, int torture) +{ + int err; + struct ubi_wl_entry *e; + + dbg_wl("PEB %d", pnum); + ubi_assert(pnum >= 0); + ubi_assert(pnum < ubi->peb_count); + +retry: + spin_lock(&ubi->wl_lock); + e = ubi->lookuptbl[pnum]; + if (e == ubi->move_from) { + /* + * User is putting the physical eraseblock which was selected to + * be moved. It will be scheduled for erasure in the + * wear-leveling worker. + */ + dbg_wl("PEB %d is being moved, wait", pnum); + spin_unlock(&ubi->wl_lock); + + /* Wait for the WL worker by taking the @ubi->move_mutex */ + mutex_lock(&ubi->move_mutex); + mutex_unlock(&ubi->move_mutex); + goto retry; + } else if (e == ubi->move_to) { + /* + * User is putting the physical eraseblock which was selected + * as the target the data is moved to. It may happen if the EBA + * sub-system already re-mapped the LEB in 'ubi_eba_copy_leb()' + * but the WL sub-system has not put the PEB to the "used" tree + * yet, but it is about to do this. So we just set a flag which + * will tell the WL worker that the PEB is not needed anymore + * and should be scheduled for erasure. + */ + dbg_wl("PEB %d is the target of data moving", pnum); + ubi_assert(!ubi->move_to_put); + ubi->move_to_put = 1; + spin_unlock(&ubi->wl_lock); + return 0; + } else { + if (in_wl_tree(e, &ubi->used)) { + self_check_in_wl_tree(ubi, e, &ubi->used); + rb_erase(&e->u.rb, &ubi->used); + } else if (in_wl_tree(e, &ubi->scrub)) { + self_check_in_wl_tree(ubi, e, &ubi->scrub); + rb_erase(&e->u.rb, &ubi->scrub); + } else if (in_wl_tree(e, &ubi->erroneous)) { + self_check_in_wl_tree(ubi, e, &ubi->erroneous); + rb_erase(&e->u.rb, &ubi->erroneous); + ubi->erroneous_peb_count -= 1; + ubi_assert(ubi->erroneous_peb_count >= 0); + /* Erroneous PEBs should be tortured */ + torture = 1; + } else { + err = prot_queue_del(ubi, e->pnum); + if (err) { + ubi_err("PEB %d not found", pnum); + ubi_ro_mode(ubi); + spin_unlock(&ubi->wl_lock); + return err; + } + } + } + spin_unlock(&ubi->wl_lock); + + err = schedule_erase(ubi, e, vol_id, lnum, torture); + if (err) { + spin_lock(&ubi->wl_lock); + wl_tree_add(e, &ubi->used); + spin_unlock(&ubi->wl_lock); + } + + return err; +} + +/** + * ubi_wl_scrub_peb - schedule a physical eraseblock for scrubbing. + * @ubi: UBI device description object + * @pnum: the physical eraseblock to schedule + * + * If a bit-flip in a physical eraseblock is detected, this physical eraseblock + * needs scrubbing. This function schedules a physical eraseblock for + * scrubbing which is done in background. This function returns zero in case of + * success and a negative error code in case of failure. + */ +int ubi_wl_scrub_peb(struct ubi_device *ubi, int pnum) +{ + struct ubi_wl_entry *e; + + ubi_msg("schedule PEB %d for scrubbing", pnum); + +retry: + spin_lock(&ubi->wl_lock); + e = ubi->lookuptbl[pnum]; + if (e == ubi->move_from || in_wl_tree(e, &ubi->scrub) || + in_wl_tree(e, &ubi->erroneous)) { + spin_unlock(&ubi->wl_lock); + return 0; + } + + if (e == ubi->move_to) { + /* + * This physical eraseblock was used to move data to. The data + * was moved but the PEB was not yet inserted to the proper + * tree. We should just wait a little and let the WL worker + * proceed. + */ + spin_unlock(&ubi->wl_lock); + dbg_wl("the PEB %d is not in proper tree, retry", pnum); + yield(); + goto retry; + } + + if (in_wl_tree(e, &ubi->used)) { + self_check_in_wl_tree(ubi, e, &ubi->used); + rb_erase(&e->u.rb, &ubi->used); + } else { + int err; + + err = prot_queue_del(ubi, e->pnum); + if (err) { + ubi_err("PEB %d not found", pnum); + ubi_ro_mode(ubi); + spin_unlock(&ubi->wl_lock); + return err; + } + } + + wl_tree_add(e, &ubi->scrub); + spin_unlock(&ubi->wl_lock); + + /* + * Technically scrubbing is the same as wear-leveling, so it is done + * by the WL worker. + */ + return ensure_wear_leveling(ubi, 0); +} + +/** + * ubi_wl_flush - flush all pending works. + * @ubi: UBI device description object + * @vol_id: the volume id to flush for + * @lnum: the logical eraseblock number to flush for + * + * This function executes all pending works for a particular volume id / + * logical eraseblock number pair. If either value is set to %UBI_ALL, then it + * acts as a wildcard for all of the corresponding volume numbers or logical + * eraseblock numbers. It returns zero in case of success and a negative error + * code in case of failure. + */ +int ubi_wl_flush(struct ubi_device *ubi, int vol_id, int lnum) +{ + int err = 0; + int found = 1; + + /* + * Erase while the pending works queue is not empty, but not more than + * the number of currently pending works. + */ + dbg_wl("flush pending work for LEB %d:%d (%d pending works)", + vol_id, lnum, ubi->works_count); + + while (found) { + struct ubi_work *wrk; + found = 0; + + down_read(&ubi->work_sem); + spin_lock(&ubi->wl_lock); + list_for_each_entry(wrk, &ubi->works, list) { + if ((vol_id == UBI_ALL || wrk->vol_id == vol_id) && + (lnum == UBI_ALL || wrk->lnum == lnum)) { + list_del(&wrk->list); + ubi->works_count -= 1; + ubi_assert(ubi->works_count >= 0); + spin_unlock(&ubi->wl_lock); + + err = wrk->func(ubi, wrk, 0); + if (err) { + up_read(&ubi->work_sem); + return err; + } + + spin_lock(&ubi->wl_lock); + found = 1; + break; + } + } + spin_unlock(&ubi->wl_lock); + up_read(&ubi->work_sem); + } + + /* + * Make sure all the works which have been done in parallel are + * finished. + */ + down_write(&ubi->work_sem); + up_write(&ubi->work_sem); + + return err; +} + +/** + * tree_destroy - destroy an RB-tree. + * @root: the root of the tree to destroy + */ +static void tree_destroy(struct rb_root *root) +{ + struct rb_node *rb; + struct ubi_wl_entry *e; + + rb = root->rb_node; + while (rb) { + if (rb->rb_left) + rb = rb->rb_left; + else if (rb->rb_right) + rb = rb->rb_right; + else { + e = rb_entry(rb, struct ubi_wl_entry, u.rb); + + rb = rb_parent(rb); + if (rb) { + if (rb->rb_left == &e->u.rb) + rb->rb_left = NULL; + else + rb->rb_right = NULL; + } + + kmem_cache_free(ubi_wl_entry_slab, e); + } + } +} + +/** + * ubi_thread - UBI background thread. + * @u: the UBI device description object pointer + */ +int ubi_thread(void *u) +{ + int failures = 0; + struct ubi_device *ubi = u; + + ubi_msg("background thread \"%s\" started, PID %d", + ubi->bgt_name, task_pid_nr(current)); + + set_freezable(); + for (;;) { + int err; + + if (kthread_should_stop()) + break; + + if (try_to_freeze()) + continue; + + spin_lock(&ubi->wl_lock); + if (list_empty(&ubi->works) || ubi->ro_mode || + !ubi->thread_enabled || ubi_dbg_is_bgt_disabled(ubi)) { + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock(&ubi->wl_lock); + schedule(); + continue; + } + spin_unlock(&ubi->wl_lock); + + err = do_work(ubi); + if (err) { + ubi_err("%s: work failed with error code %d", + ubi->bgt_name, err); + if (failures++ > WL_MAX_FAILURES) { + /* + * Too many failures, disable the thread and + * switch to read-only mode. + */ + ubi_msg("%s: %d consecutive failures", + ubi->bgt_name, WL_MAX_FAILURES); + ubi_ro_mode(ubi); + ubi->thread_enabled = 0; + continue; + } + } else + failures = 0; + + cond_resched(); + } + + dbg_wl("background thread \"%s\" is killed", ubi->bgt_name); + return 0; +} + +/** + * cancel_pending - cancel all pending works. + * @ubi: UBI device description object + */ +static void cancel_pending(struct ubi_device *ubi) +{ + while (!list_empty(&ubi->works)) { + struct ubi_work *wrk; + + wrk = list_entry(ubi->works.next, struct ubi_work, list); + list_del(&wrk->list); + wrk->func(ubi, wrk, 1); + ubi->works_count -= 1; + ubi_assert(ubi->works_count >= 0); + } +} + +/** + * ubi_wl_init - initialize the WL sub-system using attaching information. + * @ubi: UBI device description object + * @ai: attaching information + * + * This function returns zero in case of success, and a negative error code in + * case of failure. + */ +int ubi_wl_init(struct ubi_device *ubi, struct ubi_attach_info *ai) +{ + int err, i, reserved_pebs, found_pebs = 0; + struct rb_node *rb1, *rb2; + struct ubi_ainf_volume *av; + struct ubi_ainf_peb *aeb, *tmp; + struct ubi_wl_entry *e; + + ubi->used = ubi->erroneous = ubi->free = ubi->scrub = RB_ROOT; + spin_lock_init(&ubi->wl_lock); + mutex_init(&ubi->move_mutex); + init_rwsem(&ubi->work_sem); + ubi->max_ec = ai->max_ec; + INIT_LIST_HEAD(&ubi->works); +#ifdef CONFIG_MTD_UBI_FASTMAP + INIT_WORK(&ubi->fm_work, update_fastmap_work_fn); +#endif + + sprintf(ubi->bgt_name, UBI_BGT_NAME_PATTERN, ubi->ubi_num); + + err = -ENOMEM; + ubi->lookuptbl = kzalloc(ubi->peb_count * sizeof(void *), GFP_KERNEL); + if (!ubi->lookuptbl) + return err; + + for (i = 0; i < UBI_PROT_QUEUE_LEN; i++) + INIT_LIST_HEAD(&ubi->pq[i]); + ubi->pq_head = 0; + + list_for_each_entry_safe(aeb, tmp, &ai->erase, u.list) { + cond_resched(); + + e = kmem_cache_alloc(ubi_wl_entry_slab, GFP_KERNEL); + if (!e) + goto out_free; + + e->pnum = aeb->pnum; + e->ec = aeb->ec; + ubi_assert(!ubi_is_fm_block(ubi, e->pnum)); + ubi->lookuptbl[e->pnum] = e; + if (schedule_erase(ubi, e, aeb->vol_id, aeb->lnum, 0)) { + kmem_cache_free(ubi_wl_entry_slab, e); + goto out_free; + } + + found_pebs++; + } + + ubi->free_count = 0; + list_for_each_entry(aeb, &ai->free, u.list) { + cond_resched(); + + e = kmem_cache_alloc(ubi_wl_entry_slab, GFP_KERNEL); + if (!e) + goto out_free; + + e->pnum = aeb->pnum; + e->ec = aeb->ec; + ubi_assert(e->ec >= 0); + ubi_assert(!ubi_is_fm_block(ubi, e->pnum)); + + wl_tree_add(e, &ubi->free); + ubi->free_count++; + + ubi->lookuptbl[e->pnum] = e; + + found_pebs++; + } + + ubi_rb_for_each_entry(rb1, av, &ai->volumes, rb) { + ubi_rb_for_each_entry(rb2, aeb, &av->root, u.rb) { + cond_resched(); + + e = kmem_cache_alloc(ubi_wl_entry_slab, GFP_KERNEL); + if (!e) + goto out_free; + + e->pnum = aeb->pnum; + e->ec = aeb->ec; + ubi->lookuptbl[e->pnum] = e; + + if (!aeb->scrub) { + dbg_wl("add PEB %d EC %d to the used tree", + e->pnum, e->ec); + wl_tree_add(e, &ubi->used); + } else { + dbg_wl("add PEB %d EC %d to the scrub tree", + e->pnum, e->ec); + wl_tree_add(e, &ubi->scrub); + } + + found_pebs++; + } + } + + dbg_wl("found %i PEBs", found_pebs); + + if (ubi->fm) + ubi_assert(ubi->good_peb_count == \ + found_pebs + ubi->fm->used_blocks); + else + ubi_assert(ubi->good_peb_count == found_pebs); + + reserved_pebs = WL_RESERVED_PEBS; +#ifdef CONFIG_MTD_UBI_FASTMAP + /* Reserve enough LEBs to store two fastmaps. */ + reserved_pebs += (ubi->fm_size / ubi->leb_size) * 2; +#endif + + if (ubi->avail_pebs < reserved_pebs) { + ubi_err("no enough physical eraseblocks (%d, need %d)", + ubi->avail_pebs, reserved_pebs); + if (ubi->corr_peb_count) + ubi_err("%d PEBs are corrupted and not used", + ubi->corr_peb_count); + goto out_free; + } + ubi->avail_pebs -= reserved_pebs; + ubi->rsvd_pebs += reserved_pebs; + + /* Schedule wear-leveling if needed */ + err = ensure_wear_leveling(ubi, 0); + if (err) + goto out_free; + + return 0; + +out_free: + cancel_pending(ubi); + tree_destroy(&ubi->used); + tree_destroy(&ubi->free); + tree_destroy(&ubi->scrub); + kfree(ubi->lookuptbl); + return err; +} + +/** + * protection_queue_destroy - destroy the protection queue. + * @ubi: UBI device description object + */ +static void protection_queue_destroy(struct ubi_device *ubi) +{ + int i; + struct ubi_wl_entry *e, *tmp; + + for (i = 0; i < UBI_PROT_QUEUE_LEN; ++i) { + list_for_each_entry_safe(e, tmp, &ubi->pq[i], u.list) { + list_del(&e->u.list); + kmem_cache_free(ubi_wl_entry_slab, e); + } + } +} + +/** + * ubi_wl_close - close the wear-leveling sub-system. + * @ubi: UBI device description object + */ +void ubi_wl_close(struct ubi_device *ubi) +{ + dbg_wl("close the WL sub-system"); + cancel_pending(ubi); + protection_queue_destroy(ubi); + tree_destroy(&ubi->used); + tree_destroy(&ubi->erroneous); + tree_destroy(&ubi->free); + tree_destroy(&ubi->scrub); + kfree(ubi->lookuptbl); +} + +/** + * self_check_ec - make sure that the erase counter of a PEB is correct. + * @ubi: UBI device description object + * @pnum: the physical eraseblock number to check + * @ec: the erase counter to check + * + * This function returns zero if the erase counter of physical eraseblock @pnum + * is equivalent to @ec, and a negative error code if not or if an error + * occurred. + */ +static int self_check_ec(struct ubi_device *ubi, int pnum, int ec) +{ + int err; + long long read_ec; + struct ubi_ec_hdr *ec_hdr; + + if (!ubi_dbg_chk_gen(ubi)) + return 0; + + ec_hdr = kzalloc(ubi->ec_hdr_alsize, GFP_NOFS); + if (!ec_hdr) + return -ENOMEM; + + err = ubi_io_read_ec_hdr(ubi, pnum, ec_hdr, 0); + if (err && err != UBI_IO_BITFLIPS) { + /* The header does not have to exist */ + err = 0; + goto out_free; + } + + read_ec = be64_to_cpu(ec_hdr->ec); + if (ec != read_ec && read_ec - ec > 1) { + ubi_err("self-check failed for PEB %d", pnum); + ubi_err("read EC is %lld, should be %d", read_ec, ec); + dump_stack(); + err = 1; + } else + err = 0; + +out_free: + kfree(ec_hdr); + return err; +} + +/** + * self_check_in_wl_tree - check that wear-leveling entry is in WL RB-tree. + * @ubi: UBI device description object + * @e: the wear-leveling entry to check + * @root: the root of the tree + * + * This function returns zero if @e is in the @root RB-tree and %-EINVAL if it + * is not. + */ +static int self_check_in_wl_tree(const struct ubi_device *ubi, + struct ubi_wl_entry *e, struct rb_root *root) +{ + if (!ubi_dbg_chk_gen(ubi)) + return 0; + + if (in_wl_tree(e, root)) + return 0; + + ubi_err("self-check failed for PEB %d, EC %d, RB-tree %p ", + e->pnum, e->ec, root); + dump_stack(); + return -EINVAL; +} + +/** + * self_check_in_pq - check if wear-leveling entry is in the protection + * queue. + * @ubi: UBI device description object + * @e: the wear-leveling entry to check + * + * This function returns zero if @e is in @ubi->pq and %-EINVAL if it is not. + */ +static int self_check_in_pq(const struct ubi_device *ubi, + struct ubi_wl_entry *e) +{ + struct ubi_wl_entry *p; + int i; + + if (!ubi_dbg_chk_gen(ubi)) + return 0; + + for (i = 0; i < UBI_PROT_QUEUE_LEN; ++i) + list_for_each_entry(p, &ubi->pq[i], u.list) + if (p == e) + return 0; + + ubi_err("self-check failed for PEB %d, EC %d, Protect queue", + e->pnum, e->ec); + dump_stack(); + return -EINVAL; +} |
