From 93db446a424cee9387b532995e6b516667079555 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Mon, 5 Feb 2018 23:02:04 +0100 Subject: mtd: nand: move raw NAND related code to the raw/ subdir As part of the process of sharing more code between different NAND based devices, we need to move all raw NAND related code to the raw/ subdirectory. Signed-off-by: Boris Brezillon --- MAINTAINERS | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 3bdc260e36b7..34b2e9ed6d3d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1710,7 +1710,7 @@ F: drivers/input/keyboard/w90p910_keypad.c F: drivers/input/touchscreen/w90p910_ts.c F: drivers/watchdog/nuc900_wdt.c F: drivers/net/ethernet/nuvoton/w90p910_ether.c -F: drivers/mtd/nand/nuc900_nand.c +F: drivers/mtd/nand/raw/nuc900_nand.c F: drivers/rtc/rtc-nuc900.c F: drivers/spi/spi-nuc900.c F: drivers/usb/host/ehci-w90x900.c @@ -3014,7 +3014,7 @@ M: Kamal Dasu L: linux-mtd@lists.infradead.org L: bcm-kernel-feedback-list@broadcom.com S: Maintained -F: drivers/mtd/nand/brcmnand/ +F: drivers/mtd/nand/raw/brcmnand/ BROADCOM STB DPFE DRIVER M: Markus Mayer @@ -4116,7 +4116,7 @@ DENALI NAND DRIVER M: Masahiro Yamada L: linux-mtd@lists.infradead.org S: Supported -F: drivers/mtd/nand/denali* +F: drivers/mtd/nand/raw/denali* DESIGNWARE USB2 DRD IP DRIVER M: John Youn @@ -5646,7 +5646,7 @@ FREESCALE GPMI NAND DRIVER M: Han Xu L: linux-mtd@lists.infradead.org S: Maintained -F: drivers/mtd/nand/gpmi-nand/* +F: drivers/mtd/nand/raw/gpmi-nand/* FREESCALE I2C CPM DRIVER M: Jochen Friedrich @@ -6955,7 +6955,7 @@ INGENIC JZ4780 NAND DRIVER M: Harvey Hunt L: linux-mtd@lists.infradead.org S: Maintained -F: drivers/mtd/nand/jz4780_* +F: drivers/mtd/nand/raw/jz4780_* INOTIFY M: Jan Kara @@ -8474,7 +8474,7 @@ MARVELL NAND CONTROLLER DRIVER M: Miquel Raynal L: linux-mtd@lists.infradead.org S: Maintained -F: drivers/mtd/nand/marvell_nand.c +F: drivers/mtd/nand/raw/marvell_nand.c F: Documentation/devicetree/bindings/mtd/marvell-nand.txt MARVELL SOC MMC/SD/SDIO CONTROLLER DRIVER @@ -9136,7 +9136,7 @@ M: Wenyou Yang M: Josh Wu L: linux-mtd@lists.infradead.org S: Supported -F: drivers/mtd/nand/atmel/* +F: drivers/mtd/nand/raw/atmel/* F: Documentation/devicetree/bindings/mtd/atmel-nand.txt MICROCHIP KSZ SERIES ETHERNET SWITCH DRIVER @@ -11331,7 +11331,7 @@ PXA3xx NAND FLASH DRIVER M: Ezequiel Garcia L: linux-mtd@lists.infradead.org S: Maintained -F: drivers/mtd/nand/pxa3xx_nand.c +F: drivers/mtd/nand/raw/pxa3xx_nand.c QAT DRIVER M: Giovanni Cabiddu @@ -11815,8 +11815,8 @@ F: drivers/memstick/host/r592.* RICOH SMARTMEDIA/XD DRIVER M: Maxim Levitsky S: Maintained -F: drivers/mtd/nand/r852.c -F: drivers/mtd/nand/r852.h +F: drivers/mtd/nand/raw/r852.c +F: drivers/mtd/nand/raw/r852.h RISC-V ARCHITECTURE M: Palmer Dabbelt @@ -14635,7 +14635,7 @@ VF610 NAND DRIVER M: Stefan Agner L: linux-mtd@lists.infradead.org S: Supported -F: drivers/mtd/nand/vf610_nfc.c +F: drivers/mtd/nand/raw/vf610_nfc.c VFAT/FAT/MSDOS FILESYSTEM M: OGAWA Hirofumi -- cgit v1.2.3 From 645be665a5932829239eea151c042dd5a6481e62 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 19 Feb 2018 23:57:59 +0100 Subject: MAINTAINERS: Update email address for Miquel Raynal Free Electrons is now Bootlin. Signed-off-by: Miquel Raynal Signed-off-by: Boris Brezillon --- .mailmap | 1 + MAINTAINERS | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/.mailmap b/.mailmap index e18cab73e209..6cc2d5a94cc2 100644 --- a/.mailmap +++ b/.mailmap @@ -126,6 +126,7 @@ Mayuresh Janorkar Michael Buesch Michel Dänzer Miodrag Dinic +Miquel Raynal Mitesh shah Mohit Kumar Morten Welinder diff --git a/MAINTAINERS b/MAINTAINERS index 34b2e9ed6d3d..ab9c367f03c7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8471,7 +8471,7 @@ S: Odd Fixes F: drivers/net/wireless/marvell/mwl8k.c MARVELL NAND CONTROLLER DRIVER -M: Miquel Raynal +M: Miquel Raynal L: linux-mtd@lists.infradead.org S: Maintained F: drivers/mtd/nand/raw/marvell_nand.c -- cgit v1.2.3 From d8757f4c59c9fafe5a1ce75a237164f268904b18 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Fri, 16 Feb 2018 11:44:49 +0100 Subject: Update Boris Brezillon email address Free Electrons is now Bootlin. Signed-off-by: Boris Brezillon --- .mailmap | 6 +++--- MAINTAINERS | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'MAINTAINERS') diff --git a/.mailmap b/.mailmap index 6cc2d5a94cc2..85b28e8eb8ee 100644 --- a/.mailmap +++ b/.mailmap @@ -33,9 +33,9 @@ Axel Lin Ben Gardner Ben M Cahill Björn Steinbrink -Boris Brezillon -Boris Brezillon -Boris Brezillon +Boris Brezillon +Boris Brezillon +Boris Brezillon Brian Avery Brian King Christoph Hellwig diff --git a/MAINTAINERS b/MAINTAINERS index ab9c367f03c7..14890e5d3708 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1232,7 +1232,7 @@ F: arch/arm/boot/dts/aspeed-* F: drivers/*/*aspeed* ARM/ATMEL AT91 Clock Support -M: Boris Brezillon +M: Boris Brezillon S: Maintained F: drivers/clk/at91 @@ -4644,7 +4644,7 @@ F: Documentation/gpu/meson.rst T: git git://anongit.freedesktop.org/drm/drm-misc DRM DRIVERS FOR ATMEL HLCDC -M: Boris Brezillon +M: Boris Brezillon L: dri-devel@lists.freedesktop.org S: Supported F: drivers/gpu/drm/atmel-hlcdc/ @@ -8412,7 +8412,7 @@ F: include/uapi/drm/armada_drm.h F: Documentation/devicetree/bindings/display/armada/ MARVELL CRYPTO DRIVER -M: Boris Brezillon +M: Boris Brezillon M: Arnaud Ebalard F: drivers/crypto/marvell/ S: Maintained @@ -9034,7 +9034,7 @@ F: mm/ MEMORY TECHNOLOGY DEVICES (MTD) M: David Woodhouse M: Brian Norris -M: Boris Brezillon +M: Boris Brezillon M: Marek Vasut M: Richard Weinberger M: Cyrille Pitchen @@ -9452,7 +9452,7 @@ S: Supported F: drivers/net/ethernet/myricom/myri10ge/ NAND FLASH SUBSYSTEM -M: Boris Brezillon +M: Boris Brezillon R: Richard Weinberger L: linux-mtd@lists.infradead.org W: http://www.linux-mtd.infradead.org/ -- cgit v1.2.3 From 26777d37216c976cf6fd196700133a38aa2c4b0f Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Sun, 18 Feb 2018 17:05:16 +0100 Subject: mtd: Move onenand code base to drivers/mtd/nand/onenand Move onenand code base to the drivers/mtd/nand directory in the hope that someday someone will patch it to use the generic NAND helpers. If it never happens, at least we'll have all NAND related support in a single directory and not spread over the drivers/mtd/ directory. Signed-off-by: Boris Brezillon --- MAINTAINERS | 2 +- drivers/mtd/Kconfig | 2 - drivers/mtd/Makefile | 2 +- drivers/mtd/nand/Kconfig | 2 + drivers/mtd/nand/Makefile | 1 + drivers/mtd/nand/onenand/Kconfig | 71 + drivers/mtd/nand/onenand/Makefile | 14 + drivers/mtd/nand/onenand/generic.c | 116 + drivers/mtd/nand/onenand/omap2.c | 660 +++++ drivers/mtd/nand/onenand/onenand_base.c | 4031 +++++++++++++++++++++++++++++++ drivers/mtd/nand/onenand/onenand_bbt.c | 248 ++ drivers/mtd/nand/onenand/samsung.c | 1012 ++++++++ drivers/mtd/nand/onenand/samsung.h | 59 + drivers/mtd/onenand/Kconfig | 71 - drivers/mtd/onenand/Makefile | 14 - drivers/mtd/onenand/generic.c | 116 - drivers/mtd/onenand/omap2.c | 660 ----- drivers/mtd/onenand/onenand_base.c | 4031 ------------------------------- drivers/mtd/onenand/onenand_bbt.c | 248 -- drivers/mtd/onenand/samsung.c | 1012 -------- drivers/mtd/onenand/samsung.h | 59 - 21 files changed, 6216 insertions(+), 6215 deletions(-) create mode 100644 drivers/mtd/nand/onenand/Kconfig create mode 100644 drivers/mtd/nand/onenand/Makefile create mode 100644 drivers/mtd/nand/onenand/generic.c create mode 100644 drivers/mtd/nand/onenand/omap2.c create mode 100644 drivers/mtd/nand/onenand/onenand_base.c create mode 100644 drivers/mtd/nand/onenand/onenand_bbt.c create mode 100644 drivers/mtd/nand/onenand/samsung.c create mode 100644 drivers/mtd/nand/onenand/samsung.h delete mode 100644 drivers/mtd/onenand/Kconfig delete mode 100644 drivers/mtd/onenand/Makefile delete mode 100644 drivers/mtd/onenand/generic.c delete mode 100644 drivers/mtd/onenand/omap2.c delete mode 100644 drivers/mtd/onenand/onenand_base.c delete mode 100644 drivers/mtd/onenand/onenand_bbt.c delete mode 100644 drivers/mtd/onenand/samsung.c delete mode 100644 drivers/mtd/onenand/samsung.h (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index d5ebb8a1a7c2..d262f177c65b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10206,7 +10206,7 @@ ONENAND FLASH DRIVER M: Kyungmin Park L: linux-mtd@lists.infradead.org S: Maintained -F: drivers/mtd/onenand/ +F: drivers/mtd/nand/onenand/ F: include/linux/mtd/onenand*.h ONSTREAM SCSI TAPE DRIVER diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index 2a8ac6829d42..46ab7feec6b6 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -333,8 +333,6 @@ source "drivers/mtd/devices/Kconfig" source "drivers/mtd/nand/Kconfig" -source "drivers/mtd/onenand/Kconfig" - source "drivers/mtd/lpddr/Kconfig" source "drivers/mtd/spi-nor/Kconfig" diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile index d6f8f625e1ff..93473d215a38 100644 --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile @@ -32,7 +32,7 @@ obj-$(CONFIG_MTD_SWAP) += mtdswap.o nftl-objs := nftlcore.o nftlmount.o inftl-objs := inftlcore.o inftlmount.o -obj-y += chips/ lpddr/ maps/ devices/ nand/ onenand/ tests/ +obj-y += chips/ lpddr/ maps/ devices/ nand/ tests/ obj-$(CONFIG_MTD_SPI_NOR) += spi-nor/ obj-$(CONFIG_MTD_UBI) += ubi/ diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 1c1a1f487e20..88c7d3b4ff8b 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -1,4 +1,6 @@ config MTD_NAND_CORE tristate +source "drivers/mtd/nand/onenand/Kconfig" + source "drivers/mtd/nand/raw/Kconfig" diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index a72d3cb0f325..3f0cb87f1a57 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -3,4 +3,5 @@ nandcore-objs := core.o bbt.o obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o +obj-y += onenand/ obj-y += raw/ diff --git a/drivers/mtd/nand/onenand/Kconfig b/drivers/mtd/nand/onenand/Kconfig new file mode 100644 index 000000000000..9dc15748947b --- /dev/null +++ b/drivers/mtd/nand/onenand/Kconfig @@ -0,0 +1,71 @@ +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. + +if MTD_ONENAND + +config MTD_ONENAND_VERIFY_WRITE + bool "Verify OneNAND page writes" + 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 accidentally due to device wear or something else. + +config MTD_ONENAND_GENERIC + tristate "OneNAND Flash device via platform device driver" + 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 + depends on OF || COMPILE_TEST + help + Support for a OneNAND flash device connected to an OMAP2/OMAP3 SoC + via the GPMC memory controller. + Enable dmaengine and gpiolib for better performance. + +config MTD_ONENAND_SAMSUNG + tristate "OneNAND on Samsung SOC controller support" + depends on ARCH_S3C64XX || ARCH_S5PV210 || ARCH_EXYNOS4 + help + Support for a OneNAND flash device connected to an Samsung SOC. + S3C64XX uses command mapping method. + S5PC110/S5PC210 use generic OneNAND method. + +config MTD_ONENAND_OTP + bool "OneNAND OTP Support" + help + One Block of the NAND Flash Array memory is reserved as + a One-Time Programmable Block memory area. + Also, 1st Block of NAND Flash Array can be used as OTP. + + The OTP block can be read, programmed and locked using the same + operations as any other NAND Flash Array memory block. + OTP block cannot be erased. + + OTP block is fully-guaranteed to be a valid block. + +config MTD_ONENAND_2X_PROGRAM + bool "OneNAND 2X program support" + help + 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 + +endif # MTD_ONENAND diff --git a/drivers/mtd/nand/onenand/Makefile b/drivers/mtd/nand/onenand/Makefile new file mode 100644 index 000000000000..f8b624aca9cc --- /dev/null +++ b/drivers/mtd/nand/onenand/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the OneNAND MTD +# + +# Core functionality. +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/nand/onenand/generic.c b/drivers/mtd/nand/onenand/generic.c new file mode 100644 index 000000000000..d5ccaf943b91 --- /dev/null +++ b/drivers/mtd/nand/onenand/generic.c @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2005 Samsung Electronics + * Kyungmin Park + * + * 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 OneNAND flash for generic boards. + */ + +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 onenand_chip onenand; +}; + +static int generic_onenand_probe(struct platform_device *pdev) +{ + struct onenand_info *info; + struct onenand_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct resource *res = pdev->resource; + unsigned long size = resource_size(res); + int err; + + info = kzalloc(sizeof(struct onenand_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if (!request_mem_region(res->start, size, dev_name(&pdev->dev))) { + err = -EBUSY; + goto out_free_info; + } + + info->onenand.base = ioremap(res->start, size); + if (!info->onenand.base) { + err = -ENOMEM; + goto out_release_mem_region; + } + + info->onenand.mmcontrol = pdata ? pdata->mmcontrol : NULL; + info->onenand.irq = platform_get_irq(pdev, 0); + + info->mtd.dev.parent = &pdev->dev; + info->mtd.priv = &info->onenand; + + if (onenand_scan(&info->mtd, 1)) { + err = -ENXIO; + goto out_iounmap; + } + + err = mtd_device_parse_register(&info->mtd, NULL, NULL, + pdata ? pdata->parts : NULL, + pdata ? pdata->nr_parts : 0); + + platform_set_drvdata(pdev, info); + + return 0; + +out_iounmap: + iounmap(info->onenand.base); +out_release_mem_region: + release_mem_region(res->start, size); +out_free_info: + kfree(info); + + return err; +} + +static int generic_onenand_remove(struct platform_device *pdev) +{ + struct onenand_info *info = platform_get_drvdata(pdev); + struct resource *res = pdev->resource; + unsigned long size = resource_size(res); + + if (info) { + onenand_release(&info->mtd); + release_mem_region(res->start, size); + iounmap(info->onenand.base); + kfree(info); + } + + return 0; +} + +static struct platform_driver generic_onenand_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .probe = generic_onenand_probe, + .remove = generic_onenand_remove, +}; + +module_platform_driver(generic_onenand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kyungmin Park "); +MODULE_DESCRIPTION("Glue layer for OneNAND flash on generic boards"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/mtd/nand/onenand/omap2.c b/drivers/mtd/nand/onenand/omap2.c new file mode 100644 index 000000000000..9c159f0dd9a6 --- /dev/null +++ b/drivers/mtd/nand/onenand/omap2.c @@ -0,0 +1,660 @@ +/* + * OneNAND driver for OMAP2 / OMAP3 + * + * Copyright © 2005-2006 Nokia Corporation + * + * Author: Jarkko Lavinen 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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; + struct gpio_desc *int_gpiod; + struct mtd_info mtd; + struct onenand_chip onenand; + struct completion irq_done; + struct completion dma_done; + struct dma_chan *dma_chan; +}; + +static void omap2_onenand_dma_complete_func(void *completion) +{ + complete(completion); +} + +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 int omap2_onenand_set_cfg(struct omap2_onenand *c, + bool sr, bool sw, + int latency, int burst_len) +{ + unsigned short reg = ONENAND_SYS_CFG1_RDY | ONENAND_SYS_CFG1_INT; + + reg |= latency << ONENAND_SYS_CFG1_BRL_SHIFT; + + switch (burst_len) { + case 0: /* continuous */ + break; + case 4: + reg |= ONENAND_SYS_CFG1_BL_4; + break; + case 8: + reg |= ONENAND_SYS_CFG1_BL_8; + break; + case 16: + reg |= ONENAND_SYS_CFG1_BL_16; + break; + case 32: + reg |= ONENAND_SYS_CFG1_BL_32; + break; + default: + return -EINVAL; + } + + if (latency > 5) + reg |= ONENAND_SYS_CFG1_HF; + if (latency > 7) + reg |= ONENAND_SYS_CFG1_VHF; + if (sr) + reg |= ONENAND_SYS_CFG1_SYNC_READ; + if (sw) + reg |= ONENAND_SYS_CFG1_SYNC_WRITE; + + write_reg(c, reg, ONENAND_REG_SYS_CFG1); + + return 0; +} + +static int omap2_onenand_get_freq(int ver) +{ + switch ((ver >> 4) & 0xf) { + case 0: + return 40; + case 1: + return 54; + case 2: + return 66; + case 3: + return 83; + case 4: + return 104; + } + + return -EINVAL; +} + +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); + /* Add a delay to let GPIO settle */ + syscfg = read_reg(c, ONENAND_REG_SYS_CFG1); + } + + reinit_completion(&c->irq_done); + result = gpiod_get_value(c->int_gpiod); + if (result < 0) { + ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); + intr = read_reg(c, ONENAND_REG_INTERRUPT); + wait_err("gpio error", state, ctrl, intr); + return result; + } else if (result == 0) { + int retry_cnt = 0; +retry: + if (!wait_for_completion_io_timeout(&c->irq_done, + msecs_to_jiffies(20))) { + /* 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; +} + +static inline int omap2_onenand_dma_transfer(struct omap2_onenand *c, + dma_addr_t src, dma_addr_t dst, + size_t count) +{ + struct dma_async_tx_descriptor *tx; + dma_cookie_t cookie; + + tx = dmaengine_prep_dma_memcpy(c->dma_chan, dst, src, count, 0); + if (!tx) { + dev_err(&c->pdev->dev, "Failed to prepare DMA memcpy\n"); + return -EIO; + } + + reinit_completion(&c->dma_done); + + tx->callback = omap2_onenand_dma_complete_func; + tx->callback_param = &c->dma_done; + + cookie = tx->tx_submit(tx); + if (dma_submit_error(cookie)) { + dev_err(&c->pdev->dev, "Failed to do DMA tx_submit\n"); + return -EIO; + } + + dma_async_issue_pending(c->dma_chan); + + if (!wait_for_completion_io_timeout(&c->dma_done, + msecs_to_jiffies(20))) { + dmaengine_terminate_sync(c->dma_chan); + return -ETIMEDOUT; + } + + return 0; +} + +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; + void *buf = (void *)buffer; + size_t xtra; + int ret; + + 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; + } + + ret = omap2_onenand_dma_transfer(c, dma_src, dma_dst, count); + dma_unmap_single(&c->pdev->dev, dma_dst, count, DMA_FROM_DEVICE); + + if (ret) { + 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 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; + void *buf = (void *)buffer; + int ret; + + 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; + } + + ret = omap2_onenand_dma_transfer(c, dma_src, dma_dst, count); + dma_unmap_single(&c->pdev->dev, dma_src, count, DMA_TO_DEVICE); + + if (ret) { + 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; +} + +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_probe(struct platform_device *pdev) +{ + u32 val; + dma_cap_mask_t mask; + int freq, latency, r; + struct resource *res; + struct omap2_onenand *c; + struct gpmc_onenand_info info; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "error getting memory resource\n"); + return -EINVAL; + } + + r = of_property_read_u32(np, "reg", &val); + if (r) { + dev_err(dev, "reg not found in DT\n"); + return r; + } + + c = devm_kzalloc(dev, sizeof(struct omap2_onenand), GFP_KERNEL); + if (!c) + return -ENOMEM; + + init_completion(&c->irq_done); + init_completion(&c->dma_done); + c->gpmc_cs = val; + c->phys_base = res->start; + + c->onenand.base = devm_ioremap_resource(dev, res); + if (IS_ERR(c->onenand.base)) + return PTR_ERR(c->onenand.base); + + c->int_gpiod = devm_gpiod_get_optional(dev, "int", GPIOD_IN); + if (IS_ERR(c->int_gpiod)) { + r = PTR_ERR(c->int_gpiod); + /* Just try again if this happens */ + if (r != -EPROBE_DEFER) + dev_err(dev, "error getting gpio: %d\n", r); + return r; + } + + if (c->int_gpiod) { + r = devm_request_irq(dev, gpiod_to_irq(c->int_gpiod), + omap2_onenand_interrupt, + IRQF_TRIGGER_RISING, "onenand", c); + if (r) + return r; + + c->onenand.wait = omap2_onenand_wait; + } + + dma_cap_zero(mask); + dma_cap_set(DMA_MEMCPY, mask); + + c->dma_chan = dma_request_channel(mask, NULL, NULL); + if (c->dma_chan) { + c->onenand.read_bufferram = omap2_onenand_read_bufferram; + c->onenand.write_bufferram = omap2_onenand_write_bufferram; + } + + c->pdev = pdev; + c->mtd.priv = &c->onenand; + c->mtd.dev.parent = dev; + mtd_set_of_node(&c->mtd, dev->of_node); + + dev_info(dev, "initializing on CS%d (0x%08lx), va %p, %s mode\n", + c->gpmc_cs, c->phys_base, c->onenand.base, + c->dma_chan ? "DMA" : "PIO"); + + if ((r = onenand_scan(&c->mtd, 1)) < 0) + goto err_release_dma; + + freq = omap2_onenand_get_freq(c->onenand.version_id); + if (freq > 0) { + switch (freq) { + case 104: + latency = 7; + break; + case 83: + latency = 6; + break; + case 66: + latency = 5; + break; + case 56: + latency = 4; + break; + default: /* 40 MHz or lower */ + latency = 3; + break; + } + + r = gpmc_omap_onenand_set_timings(dev, c->gpmc_cs, + freq, latency, &info); + if (r) + goto err_release_onenand; + + r = omap2_onenand_set_cfg(c, info.sync_read, info.sync_write, + latency, info.burst_len); + if (r) + goto err_release_onenand; + + if (info.sync_read || info.sync_write) + dev_info(dev, "optimized timings for %d MHz\n", freq); + } + + r = mtd_device_register(&c->mtd, NULL, 0); + if (r) + goto err_release_onenand; + + platform_set_drvdata(pdev, c); + + return 0; + +err_release_onenand: + onenand_release(&c->mtd); +err_release_dma: + if (c->dma_chan) + dma_release_channel(c->dma_chan); + + return r; +} + +static int omap2_onenand_remove(struct platform_device *pdev) +{ + struct omap2_onenand *c = dev_get_drvdata(&pdev->dev); + + onenand_release(&c->mtd); + if (c->dma_chan) + dma_release_channel(c->dma_chan); + omap2_onenand_shutdown(pdev); + + return 0; +} + +static const struct of_device_id omap2_onenand_id_table[] = { + { .compatible = "ti,omap2-onenand", }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap2_onenand_id_table); + +static struct platform_driver omap2_onenand_driver = { + .probe = omap2_onenand_probe, + .remove = omap2_onenand_remove, + .shutdown = omap2_onenand_shutdown, + .driver = { + .name = DRIVER_NAME, + .of_match_table = omap2_onenand_id_table, + }, +}; + +module_platform_driver(omap2_onenand_driver); + +MODULE_ALIAS("platform:" DRIVER_NAME); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jarkko Lavinen "); +MODULE_DESCRIPTION("Glue layer for OneNAND flash on OMAP2 / OMAP3"); diff --git a/drivers/mtd/nand/onenand/onenand_base.c b/drivers/mtd/nand/onenand/onenand_base.c new file mode 100644 index 000000000000..05907b1b4098 --- /dev/null +++ b/drivers/mtd/nand/onenand/onenand_base.c @@ -0,0 +1,4031 @@ +/* + * Copyright © 2005-2009 Samsung Electronics + * Copyright © 2007 Nokia Corporation + * + * Kyungmin Park + * + * Credits: + * Adrian Hunter : + * auto-placement support, read-while load support, various fixes + * + * Vishak G , Rohit Hagargundgi + * Flex-OneNAND support + * Amul Kumar Saha + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* + * 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 int flexonenand_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section > 7) + return -ERANGE; + + oobregion->offset = (section * 16) + 6; + oobregion->length = 10; + + return 0; +} + +static int flexonenand_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section > 7) + return -ERANGE; + + oobregion->offset = (section * 16) + 2; + oobregion->length = 4; + + return 0; +} + +static const struct mtd_ooblayout_ops flexonenand_ooblayout_ops = { + .ecc = flexonenand_ooblayout_ecc, + .free = flexonenand_ooblayout_free, +}; + +/* + * 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 + * + */ +static int onenand_ooblayout_128_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section > 7) + return -ERANGE; + + oobregion->offset = (section * 16) + 7; + oobregion->length = 9; + + return 0; +} + +static int onenand_ooblayout_128_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section >= 8) + return -ERANGE; + + /* + * free bytes are using the spare area fields marked as + * "Managed by internal ECC logic for Logical Sector Number area" + */ + oobregion->offset = (section * 16) + 2; + oobregion->length = 3; + + return 0; +} + +static const struct mtd_ooblayout_ops onenand_oob_128_ooblayout_ops = { + .ecc = onenand_ooblayout_128_ecc, + .free = onenand_ooblayout_128_free, +}; + +/** + * onenand_oob_32_64 - oob info for large (2KB) page + */ +static int onenand_ooblayout_32_64_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section > 3) + return -ERANGE; + + oobregion->offset = (section * 16) + 8; + oobregion->length = 5; + + return 0; +} + +static int onenand_ooblayout_32_64_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + int sections = (mtd->oobsize / 32) * 2; + + if (section >= sections) + return -ERANGE; + + if (section & 1) { + oobregion->offset = ((section - 1) * 16) + 14; + oobregion->length = 2; + } else { + oobregion->offset = (section * 16) + 2; + oobregion->length = 3; + } + + return 0; +} + +static const struct mtd_ooblayout_ops onenand_oob_32_64_ooblayout_ops = { + .ecc = onenand_ooblayout_32_64_ecc, + .free = onenand_ooblayout_32_64_free, +}; + +static const unsigned char ffchars[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 16 */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 32 */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 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 */ +}; + +/** + * onenand_readw - [OneNAND Interface] Read OneNAND register + * @param addr address to read + * + * Read OneNAND register + */ +static unsigned short onenand_readw(void __iomem *addr) +{ + return readw(addr); +} + +/** + * onenand_writew - [OneNAND Interface] Write OneNAND register with value + * @param value value to write + * @param addr address to write + * + * Write OneNAND register with value + */ +static void onenand_writew(unsigned short value, void __iomem *addr) +{ + writew(value, addr); +} + +/** + * onenand_block_address - [DEFAULT] Get block address + * @param this onenand chip data structure + * @param block the block + * @return translated block address if DDP, otherwise same + * + * Setup Start Address 1 Register (F100h) + */ +static int onenand_block_address(struct onenand_chip *this, int block) +{ + /* Device Flash Core select, NAND Flash Block Address */ + if (block & this->density_mask) + return ONENAND_DDP_CHIP1 | (block ^ this->density_mask); + + return block; +} + +/** + * onenand_bufferram_address - [DEFAULT] Get bufferram address + * @param this onenand chip data structure + * @param block the block + * @return set DBS value if DDP, otherwise 0 + * + * Setup Start Address 2 Register (F101h) for DDP + */ +static int onenand_bufferram_address(struct onenand_chip *this, int block) +{ + /* Device BufferRAM Select */ + if (block & this->density_mask) + return ONENAND_DDP_CHIP1; + + return ONENAND_DDP_CHIP0; +} + +/** + * onenand_page_address - [DEFAULT] Get page address + * @param page the page address + * @param sector the sector address + * @return combined page and sector address + * + * Setup Start Address 8 Register (F107h) + */ +static int onenand_page_address(int page, int sector) +{ + /* Flash Page Address, Flash Sector Address */ + int fpa, fsa; + + fpa = page & ONENAND_FPA_MASK; + fsa = sector & ONENAND_FSA_MASK; + + return ((fpa << ONENAND_FPA_SHIFT) | fsa); +} + +/** + * onenand_buffer_address - [DEFAULT] Get buffer address + * @param dataram1 DataRAM index + * @param sectors the sector address + * @param count the number of sectors + * @return the start buffer value + * + * Setup Start Buffer Register (F200h) + */ +static int onenand_buffer_address(int dataram1, int sectors, int count) +{ + int bsa, bsc; + + /* BufferRAM Sector Address */ + bsa = sectors & ONENAND_BSA_MASK; + + if (dataram1) + bsa |= ONENAND_BSA_DATARAM1; /* DataRAM1 */ + else + bsa |= ONENAND_BSA_DATARAM0; /* DataRAM0 */ + + /* BufferRAM Sector Count */ + bsc = count & ONENAND_BSC_MASK; + + return ((bsa << ONENAND_BSA_SHIFT) | bsc); +} + +/** + * 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 + * @param addr offset to read from or write to + * @param len number of bytes to read or write + * + * Send command to OneNAND device. This function is used for middle/large page + * devices (1KB/2KB Bytes per page) + */ +static int onenand_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_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 = 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 = 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; + } + + /* NOTE: The setting order of the registers is very important! */ + if (cmd == ONENAND_CMD_BUFFERRAM) { + /* Select DataRAM for DDP */ + value = onenand_bufferram_address(this, block); + this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); + + 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; + } + + if (block != -1) { + /* Write 'DFS, FBA' of Flash */ + 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); + } + + 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: + 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; + } + + /* 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_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 + * + * Wait for command done. This applies to all OneNAND command + * Read can take up to 30us, erase up to 2ms and program up to 350us + * according to general OneNAND specs + */ +static int onenand_wait(struct mtd_info *mtd, int state) +{ + struct onenand_chip * this = mtd->priv; + unsigned long timeout; + unsigned int flags = ONENAND_INT_MASTER; + unsigned int interrupt = 0; + unsigned int ctrl; + + /* 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 & flags) + break; + + if (state != FL_READING && state != FL_PREPARING_ERASE) + cond_resched(); + } + /* 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); + + /* + * 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 (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_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 + * @param area BufferRAM area + * @return offset given area + * + * Return BufferRAM offset given area + */ +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 this->writesize; + if (area == ONENAND_SPARERAM) + return mtd->oobsize; + } + + return 0; +} + +/** + * onenand_read_bufferram - [OneNAND Interface] Read the bufferram area + * @param mtd MTD data structure + * @param area BufferRAM area + * @param buffer the databuffer to put/get data + * @param offset offset to read from or write to + * @param count number of bytes to read/write + * + * Read the BufferRAM area + */ +static int onenand_read_bufferram(struct mtd_info *mtd, int area, + unsigned char *buffer, int offset, size_t count) +{ + struct onenand_chip *this = mtd->priv; + void __iomem *bufferram; + + bufferram = this->base + area; + + bufferram += onenand_bufferram_offset(mtd, area); + + if (ONENAND_CHECK_BYTE_ACCESS(count)) { + unsigned short word; + + /* Align with word(16-bit) size */ + count--; + + /* Read word and save byte */ + word = this->read_word(bufferram + offset + count); + buffer[count] = (word & 0xff); + } + + memcpy(buffer, bufferram + offset, count); + + return 0; +} + +/** + * onenand_sync_read_bufferram - [OneNAND Interface] Read the bufferram area with Sync. Burst mode + * @param mtd MTD data structure + * @param area BufferRAM area + * @param buffer the databuffer to put/get data + * @param offset offset to read from or write to + * @param count number of bytes to read/write + * + * Read the BufferRAM area with Sync. Burst Mode + */ +static int onenand_sync_read_bufferram(struct mtd_info *mtd, int area, + unsigned char *buffer, int offset, size_t count) +{ + struct onenand_chip *this = mtd->priv; + void __iomem *bufferram; + + bufferram = this->base + area; + + bufferram += onenand_bufferram_offset(mtd, area); + + this->mmcontrol(mtd, ONENAND_SYS_CFG1_SYNC_READ); + + if (ONENAND_CHECK_BYTE_ACCESS(count)) { + unsigned short word; + + /* Align with word(16-bit) size */ + count--; + + /* Read word and save byte */ + word = this->read_word(bufferram + offset + count); + buffer[count] = (word & 0xff); + } + + memcpy(buffer, bufferram + offset, count); + + this->mmcontrol(mtd, 0); + + return 0; +} + +/** + * onenand_write_bufferram - [OneNAND Interface] Write the bufferram area + * @param mtd MTD data structure + * @param area BufferRAM area + * @param buffer the databuffer to put/get data + * @param offset offset to read from or write to + * @param count number of bytes to read/write + * + * Write the BufferRAM area + */ +static int onenand_write_bufferram(struct mtd_info *mtd, int area, + const unsigned char *buffer, int offset, size_t count) +{ + struct onenand_chip *this = mtd->priv; + void __iomem *bufferram; + + bufferram = this->base + area; + + bufferram += onenand_bufferram_offset(mtd, area); + + if (ONENAND_CHECK_BYTE_ACCESS(count)) { + unsigned short word; + int byte_offset; + + /* Align with word(16-bit) size */ + count--; + + /* Calculate byte access offset */ + byte_offset = offset + count; + + /* Read word and save byte */ + word = this->read_word(bufferram + byte_offset); + word = (word & ~0xff) | buffer[count]; + this->write_word(word, bufferram + byte_offset); + } + + memcpy(bufferram + offset, buffer, count); + + return 0; +} + +/** + * 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 + * @return 1 if there are valid data, otherwise 0 + * + * Check bufferram if there is data we required + */ +static int onenand_check_bufferram(struct mtd_info *mtd, loff_t addr) +{ + struct onenand_chip *this = mtd->priv; + int blockpage, found = 0; + unsigned int i; + + 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; + } + } + + 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 found; +} + +/** + * onenand_update_bufferram - [GENERIC] Update BufferRAM information + * @param mtd MTD data structure + * @param addr address to update + * @param valid valid flag + * + * Update BufferRAM information + */ +static void onenand_update_bufferram(struct mtd_info *mtd, loff_t addr, + int valid) +{ + struct onenand_chip *this = mtd->priv; + int blockpage; + unsigned int i; + + if (ONENAND_IS_2PLANE(this)) + blockpage = onenand_get_2x_blockpage(mtd, addr); + else + blockpage = (int) (addr >> this->page_shift); + + /* 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); + if (valid) + this->bufferram[i].blockpage = blockpage; + else + this->bufferram[i].blockpage = -1; +} + +/** + * 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; + } +} + +/** + * onenand_get_device - [GENERIC] Get chip for selected access + * @param mtd MTD device structure + * @param new_state the state which is requested + * + * Get the device and lock it for exclusive access + */ +static int onenand_get_device(struct mtd_info *mtd, int new_state) +{ + struct onenand_chip *this = mtd->priv; + DECLARE_WAITQUEUE(wait, current); + + /* + * Grab the lock and see if the device is available + */ + while (1) { + spin_lock(&this->chip_lock); + 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) { + spin_unlock(&this->chip_lock); + return (this->state == FL_PM_SUSPENDED) ? 0 : -EAGAIN; + } + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&this->wq, &wait); + spin_unlock(&this->chip_lock); + schedule(); + remove_wait_queue(&this->wq, &wait); + } + + return 0; +} + +/** + * onenand_release_device - [GENERIC] release chip + * @param mtd MTD device structure + * + * Deselect, release chip lock and wake up anyone waiting on the device + */ +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; + wake_up(&this->wq); + spin_unlock(&this->chip_lock); +} + +/** + * 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; + int ret; + + this->read_bufferram(mtd, ONENAND_SPARERAM, this->oob_buf, 0, + mtd->oobsize); + ret = mtd_ooblayout_get_databytes(mtd, buf, this->oob_buf, + column, thislen); + if (ret) + return ret; + + 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 ops: oob operation description structure + * + * 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; + 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; + + pr_debug("%s: from = 0x%08x, len = %i\n", __func__, (unsigned int)from, + (int)len); + + oobsize = mtd_oobavail(mtd, ops); + 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; + + while (read < len) { + cond_resched(); + + thislen = min_t(int, writesize, len - read); + + column = from & (writesize - 1); + if (column + thislen > writesize) + thislen = writesize - column; + + if (!onenand_check_bufferram(mtd, from)) { + this->command(mtd, ONENAND_CMD_READ, from, writesize); + + ret = this->wait(mtd, FL_READING); + 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); + + 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; + + from += thislen; + buf += thislen; + } + + /* + * 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); + + oobsize = mtd_oobavail(mtd, ops); + 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 + */ + 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_oob_nolock - [MTD Interface] OneNAND read out-of-band + * @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 + */ +static int onenand_read_oob_nolock(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct onenand_chip *this = mtd->priv; + 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; + + pr_debug("%s: from = 0x%08x, len = %i\n", __func__, (unsigned int)from, + (int)len); + + /* Initialize return length value */ + ops->oobretlen = 0; + + if (mode == MTD_OPS_AUTO_OOB) + oobsize = mtd->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; + } + + stats = mtd->ecc_stats; + + readcmd = ONENAND_IS_4KB_PAGE(this) ? ONENAND_CMD_READ : ONENAND_CMD_READOOB; + + while (read < len) { + cond_resched(); + + thislen = oobsize - column; + thislen = min_t(int, thislen, len); + + this->command(mtd, readcmd, from, mtd->oobsize); + + onenand_update_bufferram(mtd, from, 0); + + ret = this->wait(mtd, FL_READING); + if (unlikely(ret)) + ret = onenand_recover_lsb(mtd, from, ret); + + 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; + + buf += thislen; + + /* Read more? */ + if (read < len) { + /* Page size */ + from += mtd->writesize; + column = 0; + } + } + + ops->oobretlen = read; + + if (ret) + return ret; + + if (mtd->ecc_stats.failed - stats.failed) + return -EBADMSG; + + return 0; +} + +/** + * 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) +{ + 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); + + 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 +/** + * onenand_verify_oob - [GENERIC] verify the oob contents after a write + * @param mtd MTD device structure + * @param buf the databuffer to verify + * @param to offset to read from + */ +static int onenand_verify_oob(struct mtd_info *mtd, const u_char *buf, loff_t to) +{ + struct onenand_chip *this = mtd->priv; + 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, 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, 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 - [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(struct mtd_info *mtd, const u_char *buf, loff_t addr, size_t len) +{ + struct onenand_chip *this = mtd->priv; + int ret = 0; + int thislen, column; + + column = addr & (this->writesize - 1); + + while (len != 0) { + thislen = min_t(int, this->writesize - column, len); + + this->command(mtd, ONENAND_CMD_READ, addr, this->writesize); + + onenand_update_bufferram(mtd, addr, 0); + + 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(...) (0) +#define onenand_verify_oob(...) (0) +#endif + +#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_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 + * @param retlen pointer to variable to store the number of written bytes + * @param buf the data to write + * + * Write with ECC + */ +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; + + if (this->state == FL_PM_SUSPENDED) + return -EBUSY; + + /* Wait for any existing operation to clear */ + onenand_panic_wait(mtd); + + 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) || NOTALIGNED(len))) { + printk(KERN_ERR "%s: Attempt to write not page aligned data\n", + __func__); + return -EINVAL; + } + + column = to & (mtd->writesize - 1); + + /* Loop until all data write */ + while (written < len) { + int thislen = min_t(int, mtd->writesize - column, len - written); + u_char *wbuf = (u_char *) buf; + + 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); + this->write_bufferram(mtd, ONENAND_SPARERAM, ffchars, 0, mtd->oobsize); + + this->command(mtd, ONENAND_CMD_PROG, to, mtd->writesize); + + onenand_panic_wait(mtd); + + /* In partial page write we don't update bufferram */ + onenand_update_bufferram(mtd, to, !subpage); + if (ONENAND_IS_2PLANE(this)) { + ONENAND_SET_BUFFERRAM1(this); + onenand_update_bufferram(mtd, to + this->writesize, !subpage); + } + + written += thislen; + + if (written == len) + break; + + column = 0; + to += thislen; + buf += thislen; + } + + *retlen = written; + return 0; +} + +/** + * 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) +{ + return mtd_ooblayout_set_databytes(mtd, buf, oob_buf, column, thislen); +} + +/** + * 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; + oobsize = mtd_oobavail(mtd, ops); + 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_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_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, oobcmd; + u_char *oobbuf; + size_t len = ops->ooblen; + const u_char *buf = ops->oobbuf; + unsigned int mode = ops->mode; + + 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 */ + ops->oobretlen = 0; + + if (mode == MTD_OPS_AUTO_OOB) + oobsize = mtd->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; + } + + /* 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; + } + + 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, oobsize, len - written); + + cond_resched(); + + this->command(mtd, ONENAND_CMD_BUFFERRAM, to, mtd->oobsize); + + /* We send data to spare ram with oobsize + * to prevent byte access */ + 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, 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) { + printk(KERN_ERR "%s: write failed %d\n", __func__, ret); + break; + } + + ret = onenand_verify_oob(mtd, oobbuf, to); + if (ret) { + printk(KERN_ERR "%s: verify failed %d\n", + __func__, ret); + break; + } + + written += thislen; + if (written == len) + break; + + to += mtd->writesize; + buf += thislen; + column = 0; + } + + ops->oobretlen = written; + + return ret; +} + +/** + * onenand_write_oob - [MTD Interface] NAND write data and/or out-of-band + * @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) +{ + 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 ret; +} + +/** + * onenand_block_isbad_nolock - [GENERIC] Check if a block is marked bad + * @param mtd MTD device structure + * @param ofs offset from device start + * @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_isbad_nolock(struct mtd_info *mtd, loff_t ofs, int allowbbt) +{ + struct onenand_chip *this = mtd->priv; + struct bbm_info *bbm = this->bbm; + + /* Return info from the table */ + 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_multiblock_erase - [INTERN] erase block(s) using multiblock erase + * @param mtd MTD device structure + * @param instr erase instruction + * @param region erase region + * + * Erase one or more blocks up to 64 block at a time + */ +static int onenand_multiblock_erase(struct mtd_info *mtd, + struct erase_info *instr, + unsigned int block_size) +{ + struct onenand_chip *this = mtd->priv; + loff_t addr = instr->addr; + int len = instr->len; + int eb_count = 0; + int ret = 0; + int bdry_block = 0; + + instr->state = MTD_ERASING; + + 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; + } + + /* 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; + } + + 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; +} + + +/** + * 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; + + 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_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; + } + + 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 = addr; + 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; +} + +/** + * 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; + + pr_debug("%s: start=0x%012llx, len=%llu\n", __func__, + (unsigned long long)instr->addr, + (unsigned long long)instr->len); + + 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; +} + +/** + * onenand_sync - [MTD Interface] sync + * @param mtd MTD device structure + * + * Sync is actually a wait for chip ready function + */ +static void onenand_sync(struct mtd_info *mtd) +{ + pr_debug("%s: called\n", __func__); + + /* Grab the lock and see if the device is available */ + onenand_get_device(mtd, FL_SYNCING); + + /* Release it and go back */ + onenand_release_device(mtd); +} + +/** + * onenand_block_isbad - [MTD Interface] Check whether the block at the given offset is bad + * @param mtd MTD device structure + * @param ofs offset relative to mtd start + * + * Check whether the block is bad + */ +static int onenand_block_isbad(struct mtd_info *mtd, loff_t ofs) +{ + int ret; + + onenand_get_device(mtd, FL_READING); + ret = onenand_block_isbad_nolock(mtd, ofs, 0); + onenand_release_device(mtd); + return ret; +} + +/** + * onenand_default_block_markbad - [DEFAULT] mark a block bad + * @param mtd MTD device structure + * @param ofs offset from device start + * + * This is the default implementation, which can be overridden by + * a hardware specific driver. + */ +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}; + struct mtd_oob_ops ops = { + .mode = MTD_OPS_PLACE_OOB, + .ooblen = 2, + .oobbuf = buf, + .ooboffs = 0, + }; + int block; + + /* Get block number */ + block = onenand_block(this, ofs); + if (bbm->bbt) + bbm->bbt[block >> 2] |= 0x01 << ((block & 0x03) << 1); + + /* We write two bytes, so we don't have to mess with 16-bit access */ + ofs += mtd->oobsize + (bbm->badblockpos & ~0x01); + /* 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); +} + +/** + * onenand_block_markbad - [MTD Interface] Mark the block at the given offset as bad + * @param mtd MTD device structure + * @param ofs offset relative to mtd start + * + * Mark the block as bad + */ +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); + if (ret) { + /* If it was bad already, return success and do nothing */ + if (ret > 0) + return 0; + return ret; + } + + onenand_get_device(mtd, FL_WRITING); + ret = this->block_markbad(mtd, ofs); + onenand_release_device(mtd); + return ret; +} + +/** + * 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 lock or unlock + * @param cmd lock or unlock command + * + * Lock or unlock one or more blocks + */ +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 = 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_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, 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_LOCKING); + + /* Sanity check */ + while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS) + & ONENAND_CTRL_ONGO) + continue; + + /* Check lock status */ + status = this->read_word(this->base + ONENAND_REG_WP_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 + 1; 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); + /* Write lock command */ + this->command(mtd, cmd, 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; + + /* Check lock status */ + status = this->read_word(this->base + ONENAND_REG_WP_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 + +/** + * 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); + +/** + * do_otp_read - [DEFAULT] Read OTP block area + * @param mtd MTD device structure + * @param from The offset to read + * @param len number of bytes to read + * @param retlen pointer to variable to store the number of readbytes + * @param buf the databuffer to put/get data + * + * Read OTP block area. + */ +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 = 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); + this->wait(mtd, FL_RESETING); + + return ret; +} + +/** + * do_otp_write - [DEFAULT] Write OTP block area + * @param mtd MTD device structure + * @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 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) { + memcpy(this->page_buf, buf, len); + memset(this->page_buf + len, 0xff, mtd->writesize - len); + pbuf = this->page_buf; + len = mtd->writesize; + } + + /* Enter OTP access mode */ + this->command(mtd, ONENAND_CMD_OTP_ACCESS, 0, 0); + this->wait(mtd, FL_OTPING); + + 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); + this->wait(mtd, FL_RESETING); + + return ret; +} + +/** + * do_otp_lock - [DEFAULT] Lock OTP block area + * @param mtd MTD device structure + * @param from The offset to lock + * @param len number of bytes to lock + * @param retlen pointer to variable to store the number of lock bytes + * @param buf the databuffer to put/get data + * + * Lock OTP block area. + */ +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; + + 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; +} + +/** + * onenand_otp_walk - [DEFAULT] Handle OTP operation + * @param mtd MTD device structure + * @param from The offset to read/write + * @param len number of bytes to read/write + * @param retlen pointer to variable to store the number of read bytes + * @param buf the databuffer to put/get data + * @param action do given action + * @param mode specify user and factory + * + * Handle OTP operation. + */ +static int onenand_otp_walk(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf, + otp_op_t action, int mode) +{ + struct onenand_chip *this = mtd->priv; + int otp_pages; + int density; + int ret = 0; + + *retlen = 0; + + density = onenand_get_density(this->device_id); + if (density < ONENAND_DEVICE_DENSITY_512Mb) + otp_pages = 20; + else + otp_pages = 50; + + if (mode == MTD_OTP_FACTORY) { + from += mtd->writesize * otp_pages; + otp_pages = ONENAND_PAGES_PER_BLOCK - otp_pages; + } + + /* Check User/Factory boundary */ + 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) { + ret = -ENOSPC; + break; + } + + otpinfo = (struct otp_info *) buf; + otpinfo->start = from; + otpinfo->length = mtd->writesize; + otpinfo->locked = 0; + + from += mtd->writesize; + buf += sizeof(struct otp_info); + *retlen += sizeof(struct otp_info); + } else { + size_t tmp_retlen; + + ret = action(mtd, from, len, &tmp_retlen, buf); + if (ret) + break; + + buf += tmp_retlen; + len -= tmp_retlen; + *retlen += tmp_retlen; + + } + otp_pages--; + } + onenand_release_device(mtd); + + return ret; +} + +/** + * onenand_get_fact_prot_info - [MTD Interface] Read factory OTP info + * @param mtd MTD device structure + * @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, size_t len, + size_t *retlen, struct otp_info *buf) +{ + return onenand_otp_walk(mtd, 0, len, retlen, (u_char *) buf, NULL, + MTD_OTP_FACTORY); +} + +/** + * onenand_read_fact_prot_reg - [MTD Interface] Read factory OTP area + * @param mtd MTD device structure + * @param from The offset to read + * @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 area. + */ +static int onenand_read_fact_prot_reg(struct mtd_info *mtd, loff_t from, + size_t len, size_t *retlen, u_char *buf) +{ + return onenand_otp_walk(mtd, from, len, retlen, buf, do_otp_read, MTD_OTP_FACTORY); +} + +/** + * onenand_get_user_prot_info - [MTD Interface] Read user OTP info + * @param mtd MTD device structure + * @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, size_t len, + size_t *retlen, struct otp_info *buf) +{ + return onenand_otp_walk(mtd, 0, len, retlen, (u_char *) buf, NULL, + MTD_OTP_USER); +} + +/** + * onenand_read_user_prot_reg - [MTD Interface] Read user OTP area + * @param mtd MTD device structure + * @param from The offset to read + * @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 user OTP area. + */ +static int onenand_read_user_prot_reg(struct mtd_info *mtd, loff_t from, + size_t len, size_t *retlen, u_char *buf) +{ + return onenand_otp_walk(mtd, from, len, retlen, buf, do_otp_read, MTD_OTP_USER); +} + +/** + * onenand_write_user_prot_reg - [MTD Interface] Write user OTP area + * @param mtd MTD device structure + * @param from 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 user OTP area. + */ +static int onenand_write_user_prot_reg(struct mtd_info *mtd, loff_t from, + size_t len, size_t *retlen, u_char *buf) +{ + return onenand_otp_walk(mtd, from, len, retlen, buf, do_otp_write, MTD_OTP_USER); +} + +/** + * onenand_lock_user_prot_reg - [MTD Interface] Lock user OTP area + * @param mtd MTD device structure + * @param from The offset to lock + * @param len number of bytes to unlock + * + * Write lock mark on spare area in page 0 in OTP block + */ +static int onenand_lock_user_prot_reg(struct mtd_info *mtd, loff_t from, + size_t len) +{ + 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(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 = 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; + + /* 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_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 & version ID + */ +static void onenand_print_device_info(int device, int version) +{ + 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 = 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"}, +}; + +/** + * onenand_check_maf - Check manufacturer ID + * @param manuf manufacturer ID + * + * Check manufacturer ID + */ +static int onenand_check_maf(int manuf) +{ + int size = ARRAY_SIZE(onenand_manuf_ids); + char *name; + int i; + + for (i = 0; i < size; i++) + if (manuf == onenand_manuf_ids[i].id) + break; + + if (i < size) + name = onenand_manuf_ids[i].name; + else + name = "Unknown"; + + printk(KERN_DEBUG "OneNAND Manufacturer: %s (0x%0x)\n", name, manuf); + + return (i == size); +} + +/** +* 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 values from command with ones from register + */ +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 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); + + /* Read manufacturer and device IDs from BootRAM */ + 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; + + /* 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); + + /* Check OneNAND device */ + 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, 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; + } + + /* + * For Flex-OneNAND, chipsize represents maximum possible device size. + * mtd->size represents the actual device size. + */ + this->chipsize = (16 << density) << 20; + + /* 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; + /* 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->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; + + /* REVISIT: Multichip handling */ + + if (FLEXONENAND(this)) + flexonenand_get_size(mtd); + else + mtd->size = this->chipsize; + + /* + * 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; +} + +/** + * onenand_suspend - [MTD Interface] Suspend the OneNAND flash + * @param mtd MTD device structure + */ +static int onenand_suspend(struct mtd_info *mtd) +{ + return onenand_get_device(mtd, FL_PM_SUSPENDED); +} + +/** + * onenand_resume - [MTD Interface] Resume the OneNAND flash + * @param mtd MTD device structure + */ +static void onenand_resume(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + + if (this->state == FL_PM_SUSPENDED) + onenand_release_device(mtd); + else + printk(KERN_ERR "%s: resume() called for the chip which is not " + "in suspended state\n", __func__); +} + +/** + * onenand_scan - [OneNAND Interface] Scan for the OneNAND device + * @param mtd MTD device structure + * @param maxchips Number of chips to scan for + * + * This fills out all the not initialized function pointers + * with the defaults. + * The flash ID is read and the mtd/chip structures are + * filled with the appropriate values. + */ +int onenand_scan(struct mtd_info *mtd, int maxchips) +{ + int i, ret; + struct onenand_chip *this = mtd->priv; + + if (!this->read_word) + this->read_word = onenand_readw; + if (!this->write_word) + this->write_word = onenand_writew; + + if (!this->command) + this->command = onenand_command; + if (!this->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; + if (!this->write_bufferram) + this->write_bufferram = onenand_write_bufferram; + + if (!this->block_markbad) + this->block_markbad = onenand_default_block_markbad; + if (!this->scan_bbt) + this->scan_bbt = onenand_default_bbt; + + if (onenand_probe(mtd)) + return -ENXIO; + + /* Set Sync. Burst Read after probing */ + if (this->mmcontrol) { + printk(KERN_INFO "OneNAND Sync. Burst Read support\n"); + this->read_bufferram = onenand_sync_read_bufferram; + } + + /* Allocate buffers, if necessary */ + if (!this->page_buf) { + 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)) { + mtd_set_ooblayout(mtd, &flexonenand_ooblayout_ops); + mtd->subpage_sft = 0; + } else { + mtd_set_ooblayout(mtd, &onenand_oob_128_ooblayout_ops); + mtd->subpage_sft = 2; + } + if (ONENAND_IS_NOP_1(this)) + mtd->subpage_sft = 0; + break; + case 64: + mtd_set_ooblayout(mtd, &onenand_oob_32_64_ooblayout_ops); + mtd->subpage_sft = 2; + break; + + case 32: + mtd_set_ooblayout(mtd, &onenand_oob_32_64_ooblayout_ops); + mtd->subpage_sft = 1; + break; + + default: + printk(KERN_WARNING "%s: No OOB scheme defined for oobsize %d\n", + __func__, mtd->oobsize); + mtd->subpage_sft = 0; + /* To prevent kernel oops */ + mtd_set_ooblayout(mtd, &onenand_oob_32_64_ooblayout_ops); + 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 + */ + ret = mtd_ooblayout_count_freebytes(mtd); + if (ret < 0) + ret = 0; + + mtd->oobavail = ret; + + mtd->ecc_strength = 1; + + /* Fill in remaining MTD driver data */ + mtd->type = ONENAND_IS_MLC(this) ? MTD_MLCNANDFLASH : MTD_NANDFLASH; + mtd->flags = MTD_CAP_NANDFLASH; + mtd->_erase = onenand_erase; + mtd->_point = NULL; + mtd->_unpoint = NULL; + 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; +#endif + 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 */ + if (!(this->options & ONENAND_SKIP_INITIAL_UNLOCKING)) + this->unlock_all(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; +} + +/** + * onenand_release - [OneNAND Interface] Free resources held by the OneNAND device + * @param mtd MTD device structure + */ +void onenand_release(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + + /* Deregister partitions */ + mtd_device_unregister(mtd); + + /* Free bad block table memory, if allocated */ + if (this->bbm) { + struct bbm_info *bbm = this->bbm; + kfree(bbm->bbt); + kfree(this->bbm); + } + /* 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); +EXPORT_SYMBOL_GPL(onenand_release); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kyungmin Park "); +MODULE_DESCRIPTION("Generic OneNAND flash driver code"); diff --git a/drivers/mtd/nand/onenand/onenand_bbt.c b/drivers/mtd/nand/onenand/onenand_bbt.c new file mode 100644 index 000000000000..dde20487937d --- /dev/null +++ b/drivers/mtd/nand/onenand/onenand_bbt.c @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Bad Block Table support for the OneNAND driver + * + * Copyright(c) 2005 Samsung Electronics + * Kyungmin Park + * + * Derived from nand_bbt.c + * + * TODO: + * Split BBT core and chip specific BBT. + */ + +#include +#include +#include +#include + +/** + * check_short_pattern - [GENERIC] check if a pattern is in the buffer + * @param buf the buffer to search + * @param len the length of buffer to search + * @param paglen the pagelength + * @param 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 and the pattern is expected to start + * at offset 0. + * + */ +static int check_short_pattern(uint8_t *buf, int len, int paglen, struct nand_bbt_descr *td) +{ + int i; + uint8_t *p = buf; + + /* Compare the pattern */ + for (i = 0; i < td->len; i++) { + if (p[i] != td->pattern[i]) + return -1; + } + return 0; +} + +/** + * create_bbt - [GENERIC] Create a bad block table by scanning the device + * @param mtd MTD device structure + * @param buf temporary buffer + * @param bd descriptor for the good/bad block search pattern + * @param 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 + */ +static int create_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *bd, int chip) +{ + struct onenand_chip *this = mtd->priv; + struct bbm_info *bbm = this->bbm; + int i, j, numblocks, len, scanlen; + 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 = 2; + + /* We need only read few bytes from the OOB area */ + scanlen = ooblen = 0; + readlen = bd->len; + + /* chip == -1 case only */ + /* Note that numblocks is 2 * (real numblocks) here; + * see i += 2 below as it makses shifting and masking less painful + */ + 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++) { + /* No need to read pages fully, + * just read required OOB bytes */ + ret = onenand_bbt_read_oob(mtd, + from + j * this->writesize + bd->offs, &ops); + + /* If it is a initial bad block, just ignore it */ + if (ret == ONENAND_BBT_READ_FATAL_ERROR) + return -EIO; + + if (ret || check_short_pattern(&buf[j * scanlen], + scanlen, this->writesize, bd)) { + bbm->bbt[i >> 3] |= 0x03 << (i & 0x6); + printk(KERN_INFO "OneNAND eraseblock %d is an " + "initial bad block\n", i >> 1); + mtd->ecc_stats.badblocks++; + break; + } + } + i += 2; + + if (FLEXONENAND(this)) { + rgn = flexonenand_region(mtd, from); + from += mtd->eraseregions[rgn].erasesize; + } else + from += (1 << bbm->bbt_erase_shift); + } + + return 0; +} + + +/** + * onenand_memory_bbt - [GENERIC] create a memory based bad block table + * @param mtd MTD device structure + * @param 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 + */ +static inline int onenand_memory_bbt (struct mtd_info *mtd, struct nand_bbt_descr *bd) +{ + struct onenand_chip *this = mtd->priv; + + return create_bbt(mtd, this->page_buf, bd, -1); +} + +/** + * onenand_isbad_bbt - [OneNAND Interface] Check if a block is bad + * @param mtd MTD device structure + * @param offs offset in the device + * @param allowbbt allow access to bad block table region + */ +static int onenand_isbad_bbt(struct mtd_info *mtd, loff_t offs, int allowbbt) +{ + struct onenand_chip *this = mtd->priv; + struct bbm_info *bbm = this->bbm; + int block; + uint8_t res; + + /* Get block number * 2 */ + block = (int) (onenand_block(this, offs) << 1); + res = (bbm->bbt[block >> 3] >> (block & 0x06)) & 0x03; + + 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) { + case 0x00: return 0; + case 0x01: return 1; + case 0x02: return allowbbt ? 0 : 1; + } + + return 1; +} + +/** + * onenand_scan_bbt - [OneNAND Interface] scan, find, read and maybe create bad block table(s) + * @param mtd MTD device structure + * @param bd descriptor for the good/bad block search pattern + * + * 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 is freed + * by the onenand_release function. + * + */ +static int onenand_scan_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd) +{ + struct onenand_chip *this = mtd->priv; + struct bbm_info *bbm = this->bbm; + int len, ret = 0; + + 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; + + /* Set the bad block position */ + bbm->badblockpos = ONENAND_BADBLOCK_POS; + + /* Set erase shift */ + bbm->bbt_erase_shift = this->erase_shift; + + if (!bbm->isbad_bbt) + bbm->isbad_bbt = onenand_isbad_bbt; + + /* Scan the device to build a memory based bad block table */ + if ((ret = onenand_memory_bbt(mtd, bd))) { + printk(KERN_ERR "onenand_scan_bbt: Can't scan flash and build the RAM-based BBT\n"); + kfree(bbm->bbt); + bbm->bbt = NULL; + } + + return ret; +} + +/* + * 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 largepage_memorybased = { + .options = 0, + .offs = 0, + .len = 2, + .pattern = scan_ff_pattern, +}; + +/** + * onenand_default_bbt - [OneNAND Interface] Select a default bad block table for the device + * @param mtd MTD device structure + * + * This function selects the default bad block table + * support for the device and calls the onenand_scan_bbt function + */ +int onenand_default_bbt(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + struct bbm_info *bbm; + + this->bbm = kzalloc(sizeof(struct bbm_info), GFP_KERNEL); + if (!this->bbm) + return -ENOMEM; + + bbm = this->bbm; + + /* 1KB page has same configuration as 2KB page */ + if (!bbm->badblock_pattern) + bbm->badblock_pattern = &largepage_memorybased; + + return onenand_scan_bbt(mtd, bbm->badblock_pattern); +} diff --git a/drivers/mtd/nand/onenand/samsung.c b/drivers/mtd/nand/onenand/samsung.c new file mode 100644 index 000000000000..2e9d076e445a --- /dev/null +++ b/drivers/mtd/nand/onenand/samsung.c @@ -0,0 +1,1012 @@ +/* + * Samsung S3C64XX/S5PC1XX OneNAND driver + * + * Copyright © 2008-2010 Samsung Electronics + * Kyungmin Park + * Marek Szyprowski + * + * 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: emulate the pseudo BufferRAM + * S5PC110: use DMA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "samsung.h" + +enum soc_type { + TYPE_S3C6400, + TYPE_S3C6410, + 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 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 + +/* 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; + void __iomem *ahb_addr; + int bootram_command; + void *page_buf; + void *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; + 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 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 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 = onenand->page_buf; + s = 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 = onenand->page_buf; + if (index == 1) + p += this->writesize; + } else { + p = 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_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 = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); + if (!mtd) + return -ENOMEM; + + onenand = devm_kzalloc(&pdev->dev, sizeof(struct s3c_onenand), + GFP_KERNEL); + if (!onenand) + return -ENOMEM; + + this = (struct onenand_chip *) &mtd[1]; + mtd->priv = this; + mtd->dev.parent = &pdev->dev; + onenand->pdev = pdev; + onenand->type = platform_get_device_id(pdev)->driver_data; + + s3c_onenand_setup(mtd); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + onenand->base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(onenand->base)) + return PTR_ERR(onenand->base); + + onenand->phys_base = r->start; + + /* 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); + onenand->ahb_addr = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(onenand->ahb_addr)) + return PTR_ERR(onenand->ahb_addr); + + /* Allocate 4KiB BufferRAM */ + onenand->page_buf = devm_kzalloc(&pdev->dev, SZ_4K, + GFP_KERNEL); + if (!onenand->page_buf) + return -ENOMEM; + + /* Allocate 128 SpareRAM */ + onenand->oob_buf = devm_kzalloc(&pdev->dev, 128, GFP_KERNEL); + if (!onenand->oob_buf) + return -ENOMEM; + + /* S3C doesn't handle subpage write */ + mtd->subpage_sft = 0; + this->subpagesize = mtd->writesize; + + } else { /* S5PC110 */ + r = platform_get_resource(pdev, IORESOURCE_MEM, 1); + onenand->dma_addr = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(onenand->dma_addr)) + return PTR_ERR(onenand->dma_addr); + + 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 = devm_request_irq(&pdev->dev, r->start, + s5pc110_onenand_irq, + IRQF_SHARED, "onenand", + &onenand); + if (err) { + dev_err(&pdev->dev, "failed to get irq\n"); + return err; + } + } + } + + err = onenand_scan(mtd, 1); + if (err) + return err; + + 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); + if (err) { + dev_err(&pdev->dev, "failed to parse partitions and register the MTD device\n"); + onenand_release(mtd); + return err; + } + + platform_set_drvdata(pdev, mtd); + + return 0; +} + +static int s3c_onenand_remove(struct platform_device *pdev) +{ + struct mtd_info *mtd = platform_get_drvdata(pdev); + + onenand_release(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 const struct platform_device_id s3c_onenand_driver_ids[] = { + { + .name = "s3c6400-onenand", + .driver_data = TYPE_S3C6400, + }, { + .name = "s3c6410-onenand", + .driver_data = TYPE_S3C6410, + }, { + .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 "); +MODULE_DESCRIPTION("Samsung OneNAND controller support"); diff --git a/drivers/mtd/nand/onenand/samsung.h b/drivers/mtd/nand/onenand/samsung.h new file mode 100644 index 000000000000..9016dc0136a8 --- /dev/null +++ b/drivers/mtd/nand/onenand/samsung.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2008-2010 Samsung Electronics + * Kyungmin Park + * + * 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/onenand/Kconfig b/drivers/mtd/onenand/Kconfig deleted file mode 100644 index 9dc15748947b..000000000000 --- a/drivers/mtd/onenand/Kconfig +++ /dev/null @@ -1,71 +0,0 @@ -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. - -if MTD_ONENAND - -config MTD_ONENAND_VERIFY_WRITE - bool "Verify OneNAND page writes" - 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 accidentally due to device wear or something else. - -config MTD_ONENAND_GENERIC - tristate "OneNAND Flash device via platform device driver" - 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 - depends on OF || COMPILE_TEST - help - Support for a OneNAND flash device connected to an OMAP2/OMAP3 SoC - via the GPMC memory controller. - Enable dmaengine and gpiolib for better performance. - -config MTD_ONENAND_SAMSUNG - tristate "OneNAND on Samsung SOC controller support" - depends on ARCH_S3C64XX || ARCH_S5PV210 || ARCH_EXYNOS4 - help - Support for a OneNAND flash device connected to an Samsung SOC. - S3C64XX uses command mapping method. - S5PC110/S5PC210 use generic OneNAND method. - -config MTD_ONENAND_OTP - bool "OneNAND OTP Support" - help - One Block of the NAND Flash Array memory is reserved as - a One-Time Programmable Block memory area. - Also, 1st Block of NAND Flash Array can be used as OTP. - - The OTP block can be read, programmed and locked using the same - operations as any other NAND Flash Array memory block. - OTP block cannot be erased. - - OTP block is fully-guaranteed to be a valid block. - -config MTD_ONENAND_2X_PROGRAM - bool "OneNAND 2X program support" - help - 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 - -endif # MTD_ONENAND diff --git a/drivers/mtd/onenand/Makefile b/drivers/mtd/onenand/Makefile deleted file mode 100644 index f8b624aca9cc..000000000000 --- a/drivers/mtd/onenand/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# -# Makefile for the OneNAND MTD -# - -# Core functionality. -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 deleted file mode 100644 index d5ccaf943b91..000000000000 --- a/drivers/mtd/onenand/generic.c +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2005 Samsung Electronics - * Kyungmin Park - * - * 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 OneNAND flash for generic boards. - */ - -#include -#include -#include -#include -#include -#include -#include - -/* - * 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 onenand_chip onenand; -}; - -static int generic_onenand_probe(struct platform_device *pdev) -{ - struct onenand_info *info; - struct onenand_platform_data *pdata = dev_get_platdata(&pdev->dev); - struct resource *res = pdev->resource; - unsigned long size = resource_size(res); - int err; - - info = kzalloc(sizeof(struct onenand_info), GFP_KERNEL); - if (!info) - return -ENOMEM; - - if (!request_mem_region(res->start, size, dev_name(&pdev->dev))) { - err = -EBUSY; - goto out_free_info; - } - - info->onenand.base = ioremap(res->start, size); - if (!info->onenand.base) { - err = -ENOMEM; - goto out_release_mem_region; - } - - info->onenand.mmcontrol = pdata ? pdata->mmcontrol : NULL; - info->onenand.irq = platform_get_irq(pdev, 0); - - info->mtd.dev.parent = &pdev->dev; - info->mtd.priv = &info->onenand; - - if (onenand_scan(&info->mtd, 1)) { - err = -ENXIO; - goto out_iounmap; - } - - err = mtd_device_parse_register(&info->mtd, NULL, NULL, - pdata ? pdata->parts : NULL, - pdata ? pdata->nr_parts : 0); - - platform_set_drvdata(pdev, info); - - return 0; - -out_iounmap: - iounmap(info->onenand.base); -out_release_mem_region: - release_mem_region(res->start, size); -out_free_info: - kfree(info); - - return err; -} - -static int generic_onenand_remove(struct platform_device *pdev) -{ - struct onenand_info *info = platform_get_drvdata(pdev); - struct resource *res = pdev->resource; - unsigned long size = resource_size(res); - - if (info) { - onenand_release(&info->mtd); - release_mem_region(res->start, size); - iounmap(info->onenand.base); - kfree(info); - } - - return 0; -} - -static struct platform_driver generic_onenand_driver = { - .driver = { - .name = DRIVER_NAME, - }, - .probe = generic_onenand_probe, - .remove = generic_onenand_remove, -}; - -module_platform_driver(generic_onenand_driver); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Kyungmin Park "); -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 deleted file mode 100644 index 9c159f0dd9a6..000000000000 --- a/drivers/mtd/onenand/omap2.c +++ /dev/null @@ -1,660 +0,0 @@ -/* - * OneNAND driver for OMAP2 / OMAP3 - * - * Copyright © 2005-2006 Nokia Corporation - * - * Author: Jarkko Lavinen 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#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; - struct gpio_desc *int_gpiod; - struct mtd_info mtd; - struct onenand_chip onenand; - struct completion irq_done; - struct completion dma_done; - struct dma_chan *dma_chan; -}; - -static void omap2_onenand_dma_complete_func(void *completion) -{ - complete(completion); -} - -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 int omap2_onenand_set_cfg(struct omap2_onenand *c, - bool sr, bool sw, - int latency, int burst_len) -{ - unsigned short reg = ONENAND_SYS_CFG1_RDY | ONENAND_SYS_CFG1_INT; - - reg |= latency << ONENAND_SYS_CFG1_BRL_SHIFT; - - switch (burst_len) { - case 0: /* continuous */ - break; - case 4: - reg |= ONENAND_SYS_CFG1_BL_4; - break; - case 8: - reg |= ONENAND_SYS_CFG1_BL_8; - break; - case 16: - reg |= ONENAND_SYS_CFG1_BL_16; - break; - case 32: - reg |= ONENAND_SYS_CFG1_BL_32; - break; - default: - return -EINVAL; - } - - if (latency > 5) - reg |= ONENAND_SYS_CFG1_HF; - if (latency > 7) - reg |= ONENAND_SYS_CFG1_VHF; - if (sr) - reg |= ONENAND_SYS_CFG1_SYNC_READ; - if (sw) - reg |= ONENAND_SYS_CFG1_SYNC_WRITE; - - write_reg(c, reg, ONENAND_REG_SYS_CFG1); - - return 0; -} - -static int omap2_onenand_get_freq(int ver) -{ - switch ((ver >> 4) & 0xf) { - case 0: - return 40; - case 1: - return 54; - case 2: - return 66; - case 3: - return 83; - case 4: - return 104; - } - - return -EINVAL; -} - -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); - /* Add a delay to let GPIO settle */ - syscfg = read_reg(c, ONENAND_REG_SYS_CFG1); - } - - reinit_completion(&c->irq_done); - result = gpiod_get_value(c->int_gpiod); - if (result < 0) { - ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); - intr = read_reg(c, ONENAND_REG_INTERRUPT); - wait_err("gpio error", state, ctrl, intr); - return result; - } else if (result == 0) { - int retry_cnt = 0; -retry: - if (!wait_for_completion_io_timeout(&c->irq_done, - msecs_to_jiffies(20))) { - /* 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; -} - -static inline int omap2_onenand_dma_transfer(struct omap2_onenand *c, - dma_addr_t src, dma_addr_t dst, - size_t count) -{ - struct dma_async_tx_descriptor *tx; - dma_cookie_t cookie; - - tx = dmaengine_prep_dma_memcpy(c->dma_chan, dst, src, count, 0); - if (!tx) { - dev_err(&c->pdev->dev, "Failed to prepare DMA memcpy\n"); - return -EIO; - } - - reinit_completion(&c->dma_done); - - tx->callback = omap2_onenand_dma_complete_func; - tx->callback_param = &c->dma_done; - - cookie = tx->tx_submit(tx); - if (dma_submit_error(cookie)) { - dev_err(&c->pdev->dev, "Failed to do DMA tx_submit\n"); - return -EIO; - } - - dma_async_issue_pending(c->dma_chan); - - if (!wait_for_completion_io_timeout(&c->dma_done, - msecs_to_jiffies(20))) { - dmaengine_terminate_sync(c->dma_chan); - return -ETIMEDOUT; - } - - return 0; -} - -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; - void *buf = (void *)buffer; - size_t xtra; - int ret; - - 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; - } - - ret = omap2_onenand_dma_transfer(c, dma_src, dma_dst, count); - dma_unmap_single(&c->pdev->dev, dma_dst, count, DMA_FROM_DEVICE); - - if (ret) { - 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 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; - void *buf = (void *)buffer; - int ret; - - 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; - } - - ret = omap2_onenand_dma_transfer(c, dma_src, dma_dst, count); - dma_unmap_single(&c->pdev->dev, dma_src, count, DMA_TO_DEVICE); - - if (ret) { - 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; -} - -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_probe(struct platform_device *pdev) -{ - u32 val; - dma_cap_mask_t mask; - int freq, latency, r; - struct resource *res; - struct omap2_onenand *c; - struct gpmc_onenand_info info; - struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(dev, "error getting memory resource\n"); - return -EINVAL; - } - - r = of_property_read_u32(np, "reg", &val); - if (r) { - dev_err(dev, "reg not found in DT\n"); - return r; - } - - c = devm_kzalloc(dev, sizeof(struct omap2_onenand), GFP_KERNEL); - if (!c) - return -ENOMEM; - - init_completion(&c->irq_done); - init_completion(&c->dma_done); - c->gpmc_cs = val; - c->phys_base = res->start; - - c->onenand.base = devm_ioremap_resource(dev, res); - if (IS_ERR(c->onenand.base)) - return PTR_ERR(c->onenand.base); - - c->int_gpiod = devm_gpiod_get_optional(dev, "int", GPIOD_IN); - if (IS_ERR(c->int_gpiod)) { - r = PTR_ERR(c->int_gpiod); - /* Just try again if this happens */ - if (r != -EPROBE_DEFER) - dev_err(dev, "error getting gpio: %d\n", r); - return r; - } - - if (c->int_gpiod) { - r = devm_request_irq(dev, gpiod_to_irq(c->int_gpiod), - omap2_onenand_interrupt, - IRQF_TRIGGER_RISING, "onenand", c); - if (r) - return r; - - c->onenand.wait = omap2_onenand_wait; - } - - dma_cap_zero(mask); - dma_cap_set(DMA_MEMCPY, mask); - - c->dma_chan = dma_request_channel(mask, NULL, NULL); - if (c->dma_chan) { - c->onenand.read_bufferram = omap2_onenand_read_bufferram; - c->onenand.write_bufferram = omap2_onenand_write_bufferram; - } - - c->pdev = pdev; - c->mtd.priv = &c->onenand; - c->mtd.dev.parent = dev; - mtd_set_of_node(&c->mtd, dev->of_node); - - dev_info(dev, "initializing on CS%d (0x%08lx), va %p, %s mode\n", - c->gpmc_cs, c->phys_base, c->onenand.base, - c->dma_chan ? "DMA" : "PIO"); - - if ((r = onenand_scan(&c->mtd, 1)) < 0) - goto err_release_dma; - - freq = omap2_onenand_get_freq(c->onenand.version_id); - if (freq > 0) { - switch (freq) { - case 104: - latency = 7; - break; - case 83: - latency = 6; - break; - case 66: - latency = 5; - break; - case 56: - latency = 4; - break; - default: /* 40 MHz or lower */ - latency = 3; - break; - } - - r = gpmc_omap_onenand_set_timings(dev, c->gpmc_cs, - freq, latency, &info); - if (r) - goto err_release_onenand; - - r = omap2_onenand_set_cfg(c, info.sync_read, info.sync_write, - latency, info.burst_len); - if (r) - goto err_release_onenand; - - if (info.sync_read || info.sync_write) - dev_info(dev, "optimized timings for %d MHz\n", freq); - } - - r = mtd_device_register(&c->mtd, NULL, 0); - if (r) - goto err_release_onenand; - - platform_set_drvdata(pdev, c); - - return 0; - -err_release_onenand: - onenand_release(&c->mtd); -err_release_dma: - if (c->dma_chan) - dma_release_channel(c->dma_chan); - - return r; -} - -static int omap2_onenand_remove(struct platform_device *pdev) -{ - struct omap2_onenand *c = dev_get_drvdata(&pdev->dev); - - onenand_release(&c->mtd); - if (c->dma_chan) - dma_release_channel(c->dma_chan); - omap2_onenand_shutdown(pdev); - - return 0; -} - -static const struct of_device_id omap2_onenand_id_table[] = { - { .compatible = "ti,omap2-onenand", }, - {}, -}; -MODULE_DEVICE_TABLE(of, omap2_onenand_id_table); - -static struct platform_driver omap2_onenand_driver = { - .probe = omap2_onenand_probe, - .remove = omap2_onenand_remove, - .shutdown = omap2_onenand_shutdown, - .driver = { - .name = DRIVER_NAME, - .of_match_table = omap2_onenand_id_table, - }, -}; - -module_platform_driver(omap2_onenand_driver); - -MODULE_ALIAS("platform:" DRIVER_NAME); -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Jarkko Lavinen "); -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 deleted file mode 100644 index 05907b1b4098..000000000000 --- a/drivers/mtd/onenand/onenand_base.c +++ /dev/null @@ -1,4031 +0,0 @@ -/* - * Copyright © 2005-2009 Samsung Electronics - * Copyright © 2007 Nokia Corporation - * - * Kyungmin Park - * - * Credits: - * Adrian Hunter : - * auto-placement support, read-while load support, various fixes - * - * Vishak G , Rohit Hagargundgi - * Flex-OneNAND support - * Amul Kumar Saha - * 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. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -/* - * 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 int flexonenand_ooblayout_ecc(struct mtd_info *mtd, int section, - struct mtd_oob_region *oobregion) -{ - if (section > 7) - return -ERANGE; - - oobregion->offset = (section * 16) + 6; - oobregion->length = 10; - - return 0; -} - -static int flexonenand_ooblayout_free(struct mtd_info *mtd, int section, - struct mtd_oob_region *oobregion) -{ - if (section > 7) - return -ERANGE; - - oobregion->offset = (section * 16) + 2; - oobregion->length = 4; - - return 0; -} - -static const struct mtd_ooblayout_ops flexonenand_ooblayout_ops = { - .ecc = flexonenand_ooblayout_ecc, - .free = flexonenand_ooblayout_free, -}; - -/* - * 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 - * - */ -static int onenand_ooblayout_128_ecc(struct mtd_info *mtd, int section, - struct mtd_oob_region *oobregion) -{ - if (section > 7) - return -ERANGE; - - oobregion->offset = (section * 16) + 7; - oobregion->length = 9; - - return 0; -} - -static int onenand_ooblayout_128_free(struct mtd_info *mtd, int section, - struct mtd_oob_region *oobregion) -{ - if (section >= 8) - return -ERANGE; - - /* - * free bytes are using the spare area fields marked as - * "Managed by internal ECC logic for Logical Sector Number area" - */ - oobregion->offset = (section * 16) + 2; - oobregion->length = 3; - - return 0; -} - -static const struct mtd_ooblayout_ops onenand_oob_128_ooblayout_ops = { - .ecc = onenand_ooblayout_128_ecc, - .free = onenand_ooblayout_128_free, -}; - -/** - * onenand_oob_32_64 - oob info for large (2KB) page - */ -static int onenand_ooblayout_32_64_ecc(struct mtd_info *mtd, int section, - struct mtd_oob_region *oobregion) -{ - if (section > 3) - return -ERANGE; - - oobregion->offset = (section * 16) + 8; - oobregion->length = 5; - - return 0; -} - -static int onenand_ooblayout_32_64_free(struct mtd_info *mtd, int section, - struct mtd_oob_region *oobregion) -{ - int sections = (mtd->oobsize / 32) * 2; - - if (section >= sections) - return -ERANGE; - - if (section & 1) { - oobregion->offset = ((section - 1) * 16) + 14; - oobregion->length = 2; - } else { - oobregion->offset = (section * 16) + 2; - oobregion->length = 3; - } - - return 0; -} - -static const struct mtd_ooblayout_ops onenand_oob_32_64_ooblayout_ops = { - .ecc = onenand_ooblayout_32_64_ecc, - .free = onenand_ooblayout_32_64_free, -}; - -static const unsigned char ffchars[] = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 16 */ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 32 */ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 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 */ -}; - -/** - * onenand_readw - [OneNAND Interface] Read OneNAND register - * @param addr address to read - * - * Read OneNAND register - */ -static unsigned short onenand_readw(void __iomem *addr) -{ - return readw(addr); -} - -/** - * onenand_writew - [OneNAND Interface] Write OneNAND register with value - * @param value value to write - * @param addr address to write - * - * Write OneNAND register with value - */ -static void onenand_writew(unsigned short value, void __iomem *addr) -{ - writew(value, addr); -} - -/** - * onenand_block_address - [DEFAULT] Get block address - * @param this onenand chip data structure - * @param block the block - * @return translated block address if DDP, otherwise same - * - * Setup Start Address 1 Register (F100h) - */ -static int onenand_block_address(struct onenand_chip *this, int block) -{ - /* Device Flash Core select, NAND Flash Block Address */ - if (block & this->density_mask) - return ONENAND_DDP_CHIP1 | (block ^ this->density_mask); - - return block; -} - -/** - * onenand_bufferram_address - [DEFAULT] Get bufferram address - * @param this onenand chip data structure - * @param block the block - * @return set DBS value if DDP, otherwise 0 - * - * Setup Start Address 2 Register (F101h) for DDP - */ -static int onenand_bufferram_address(struct onenand_chip *this, int block) -{ - /* Device BufferRAM Select */ - if (block & this->density_mask) - return ONENAND_DDP_CHIP1; - - return ONENAND_DDP_CHIP0; -} - -/** - * onenand_page_address - [DEFAULT] Get page address - * @param page the page address - * @param sector the sector address - * @return combined page and sector address - * - * Setup Start Address 8 Register (F107h) - */ -static int onenand_page_address(int page, int sector) -{ - /* Flash Page Address, Flash Sector Address */ - int fpa, fsa; - - fpa = page & ONENAND_FPA_MASK; - fsa = sector & ONENAND_FSA_MASK; - - return ((fpa << ONENAND_FPA_SHIFT) | fsa); -} - -/** - * onenand_buffer_address - [DEFAULT] Get buffer address - * @param dataram1 DataRAM index - * @param sectors the sector address - * @param count the number of sectors - * @return the start buffer value - * - * Setup Start Buffer Register (F200h) - */ -static int onenand_buffer_address(int dataram1, int sectors, int count) -{ - int bsa, bsc; - - /* BufferRAM Sector Address */ - bsa = sectors & ONENAND_BSA_MASK; - - if (dataram1) - bsa |= ONENAND_BSA_DATARAM1; /* DataRAM1 */ - else - bsa |= ONENAND_BSA_DATARAM0; /* DataRAM0 */ - - /* BufferRAM Sector Count */ - bsc = count & ONENAND_BSC_MASK; - - return ((bsa << ONENAND_BSA_SHIFT) | bsc); -} - -/** - * 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 - * @param addr offset to read from or write to - * @param len number of bytes to read or write - * - * Send command to OneNAND device. This function is used for middle/large page - * devices (1KB/2KB Bytes per page) - */ -static int onenand_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_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 = 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 = 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; - } - - /* NOTE: The setting order of the registers is very important! */ - if (cmd == ONENAND_CMD_BUFFERRAM) { - /* Select DataRAM for DDP */ - value = onenand_bufferram_address(this, block); - this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); - - 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; - } - - if (block != -1) { - /* Write 'DFS, FBA' of Flash */ - 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); - } - - 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: - 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; - } - - /* 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_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 - * - * Wait for command done. This applies to all OneNAND command - * Read can take up to 30us, erase up to 2ms and program up to 350us - * according to general OneNAND specs - */ -static int onenand_wait(struct mtd_info *mtd, int state) -{ - struct onenand_chip * this = mtd->priv; - unsigned long timeout; - unsigned int flags = ONENAND_INT_MASTER; - unsigned int interrupt = 0; - unsigned int ctrl; - - /* 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 & flags) - break; - - if (state != FL_READING && state != FL_PREPARING_ERASE) - cond_resched(); - } - /* 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); - - /* - * 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 (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_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 - * @param area BufferRAM area - * @return offset given area - * - * Return BufferRAM offset given area - */ -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 this->writesize; - if (area == ONENAND_SPARERAM) - return mtd->oobsize; - } - - return 0; -} - -/** - * onenand_read_bufferram - [OneNAND Interface] Read the bufferram area - * @param mtd MTD data structure - * @param area BufferRAM area - * @param buffer the databuffer to put/get data - * @param offset offset to read from or write to - * @param count number of bytes to read/write - * - * Read the BufferRAM area - */ -static int onenand_read_bufferram(struct mtd_info *mtd, int area, - unsigned char *buffer, int offset, size_t count) -{ - struct onenand_chip *this = mtd->priv; - void __iomem *bufferram; - - bufferram = this->base + area; - - bufferram += onenand_bufferram_offset(mtd, area); - - if (ONENAND_CHECK_BYTE_ACCESS(count)) { - unsigned short word; - - /* Align with word(16-bit) size */ - count--; - - /* Read word and save byte */ - word = this->read_word(bufferram + offset + count); - buffer[count] = (word & 0xff); - } - - memcpy(buffer, bufferram + offset, count); - - return 0; -} - -/** - * onenand_sync_read_bufferram - [OneNAND Interface] Read the bufferram area with Sync. Burst mode - * @param mtd MTD data structure - * @param area BufferRAM area - * @param buffer the databuffer to put/get data - * @param offset offset to read from or write to - * @param count number of bytes to read/write - * - * Read the BufferRAM area with Sync. Burst Mode - */ -static int onenand_sync_read_bufferram(struct mtd_info *mtd, int area, - unsigned char *buffer, int offset, size_t count) -{ - struct onenand_chip *this = mtd->priv; - void __iomem *bufferram; - - bufferram = this->base + area; - - bufferram += onenand_bufferram_offset(mtd, area); - - this->mmcontrol(mtd, ONENAND_SYS_CFG1_SYNC_READ); - - if (ONENAND_CHECK_BYTE_ACCESS(count)) { - unsigned short word; - - /* Align with word(16-bit) size */ - count--; - - /* Read word and save byte */ - word = this->read_word(bufferram + offset + count); - buffer[count] = (word & 0xff); - } - - memcpy(buffer, bufferram + offset, count); - - this->mmcontrol(mtd, 0); - - return 0; -} - -/** - * onenand_write_bufferram - [OneNAND Interface] Write the bufferram area - * @param mtd MTD data structure - * @param area BufferRAM area - * @param buffer the databuffer to put/get data - * @param offset offset to read from or write to - * @param count number of bytes to read/write - * - * Write the BufferRAM area - */ -static int onenand_write_bufferram(struct mtd_info *mtd, int area, - const unsigned char *buffer, int offset, size_t count) -{ - struct onenand_chip *this = mtd->priv; - void __iomem *bufferram; - - bufferram = this->base + area; - - bufferram += onenand_bufferram_offset(mtd, area); - - if (ONENAND_CHECK_BYTE_ACCESS(count)) { - unsigned short word; - int byte_offset; - - /* Align with word(16-bit) size */ - count--; - - /* Calculate byte access offset */ - byte_offset = offset + count; - - /* Read word and save byte */ - word = this->read_word(bufferram + byte_offset); - word = (word & ~0xff) | buffer[count]; - this->write_word(word, bufferram + byte_offset); - } - - memcpy(bufferram + offset, buffer, count); - - return 0; -} - -/** - * 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 - * @return 1 if there are valid data, otherwise 0 - * - * Check bufferram if there is data we required - */ -static int onenand_check_bufferram(struct mtd_info *mtd, loff_t addr) -{ - struct onenand_chip *this = mtd->priv; - int blockpage, found = 0; - unsigned int i; - - 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; - } - } - - 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 found; -} - -/** - * onenand_update_bufferram - [GENERIC] Update BufferRAM information - * @param mtd MTD data structure - * @param addr address to update - * @param valid valid flag - * - * Update BufferRAM information - */ -static void onenand_update_bufferram(struct mtd_info *mtd, loff_t addr, - int valid) -{ - struct onenand_chip *this = mtd->priv; - int blockpage; - unsigned int i; - - if (ONENAND_IS_2PLANE(this)) - blockpage = onenand_get_2x_blockpage(mtd, addr); - else - blockpage = (int) (addr >> this->page_shift); - - /* 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); - if (valid) - this->bufferram[i].blockpage = blockpage; - else - this->bufferram[i].blockpage = -1; -} - -/** - * 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; - } -} - -/** - * onenand_get_device - [GENERIC] Get chip for selected access - * @param mtd MTD device structure - * @param new_state the state which is requested - * - * Get the device and lock it for exclusive access - */ -static int onenand_get_device(struct mtd_info *mtd, int new_state) -{ - struct onenand_chip *this = mtd->priv; - DECLARE_WAITQUEUE(wait, current); - - /* - * Grab the lock and see if the device is available - */ - while (1) { - spin_lock(&this->chip_lock); - 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) { - spin_unlock(&this->chip_lock); - return (this->state == FL_PM_SUSPENDED) ? 0 : -EAGAIN; - } - set_current_state(TASK_UNINTERRUPTIBLE); - add_wait_queue(&this->wq, &wait); - spin_unlock(&this->chip_lock); - schedule(); - remove_wait_queue(&this->wq, &wait); - } - - return 0; -} - -/** - * onenand_release_device - [GENERIC] release chip - * @param mtd MTD device structure - * - * Deselect, release chip lock and wake up anyone waiting on the device - */ -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; - wake_up(&this->wq); - spin_unlock(&this->chip_lock); -} - -/** - * 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; - int ret; - - this->read_bufferram(mtd, ONENAND_SPARERAM, this->oob_buf, 0, - mtd->oobsize); - ret = mtd_ooblayout_get_databytes(mtd, buf, this->oob_buf, - column, thislen); - if (ret) - return ret; - - 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 ops: oob operation description structure - * - * 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; - 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; - - pr_debug("%s: from = 0x%08x, len = %i\n", __func__, (unsigned int)from, - (int)len); - - oobsize = mtd_oobavail(mtd, ops); - 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; - - while (read < len) { - cond_resched(); - - thislen = min_t(int, writesize, len - read); - - column = from & (writesize - 1); - if (column + thislen > writesize) - thislen = writesize - column; - - if (!onenand_check_bufferram(mtd, from)) { - this->command(mtd, ONENAND_CMD_READ, from, writesize); - - ret = this->wait(mtd, FL_READING); - 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); - - 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; - - from += thislen; - buf += thislen; - } - - /* - * 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); - - oobsize = mtd_oobavail(mtd, ops); - 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 - */ - 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_oob_nolock - [MTD Interface] OneNAND read out-of-band - * @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 - */ -static int onenand_read_oob_nolock(struct mtd_info *mtd, loff_t from, - struct mtd_oob_ops *ops) -{ - struct onenand_chip *this = mtd->priv; - 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; - - pr_debug("%s: from = 0x%08x, len = %i\n", __func__, (unsigned int)from, - (int)len); - - /* Initialize return length value */ - ops->oobretlen = 0; - - if (mode == MTD_OPS_AUTO_OOB) - oobsize = mtd->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; - } - - stats = mtd->ecc_stats; - - readcmd = ONENAND_IS_4KB_PAGE(this) ? ONENAND_CMD_READ : ONENAND_CMD_READOOB; - - while (read < len) { - cond_resched(); - - thislen = oobsize - column; - thislen = min_t(int, thislen, len); - - this->command(mtd, readcmd, from, mtd->oobsize); - - onenand_update_bufferram(mtd, from, 0); - - ret = this->wait(mtd, FL_READING); - if (unlikely(ret)) - ret = onenand_recover_lsb(mtd, from, ret); - - 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; - - buf += thislen; - - /* Read more? */ - if (read < len) { - /* Page size */ - from += mtd->writesize; - column = 0; - } - } - - ops->oobretlen = read; - - if (ret) - return ret; - - if (mtd->ecc_stats.failed - stats.failed) - return -EBADMSG; - - return 0; -} - -/** - * 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) -{ - 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); - - 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 -/** - * onenand_verify_oob - [GENERIC] verify the oob contents after a write - * @param mtd MTD device structure - * @param buf the databuffer to verify - * @param to offset to read from - */ -static int onenand_verify_oob(struct mtd_info *mtd, const u_char *buf, loff_t to) -{ - struct onenand_chip *this = mtd->priv; - 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, 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, 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 - [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(struct mtd_info *mtd, const u_char *buf, loff_t addr, size_t len) -{ - struct onenand_chip *this = mtd->priv; - int ret = 0; - int thislen, column; - - column = addr & (this->writesize - 1); - - while (len != 0) { - thislen = min_t(int, this->writesize - column, len); - - this->command(mtd, ONENAND_CMD_READ, addr, this->writesize); - - onenand_update_bufferram(mtd, addr, 0); - - 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(...) (0) -#define onenand_verify_oob(...) (0) -#endif - -#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_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 - * @param retlen pointer to variable to store the number of written bytes - * @param buf the data to write - * - * Write with ECC - */ -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; - - if (this->state == FL_PM_SUSPENDED) - return -EBUSY; - - /* Wait for any existing operation to clear */ - onenand_panic_wait(mtd); - - 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) || NOTALIGNED(len))) { - printk(KERN_ERR "%s: Attempt to write not page aligned data\n", - __func__); - return -EINVAL; - } - - column = to & (mtd->writesize - 1); - - /* Loop until all data write */ - while (written < len) { - int thislen = min_t(int, mtd->writesize - column, len - written); - u_char *wbuf = (u_char *) buf; - - 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); - this->write_bufferram(mtd, ONENAND_SPARERAM, ffchars, 0, mtd->oobsize); - - this->command(mtd, ONENAND_CMD_PROG, to, mtd->writesize); - - onenand_panic_wait(mtd); - - /* In partial page write we don't update bufferram */ - onenand_update_bufferram(mtd, to, !subpage); - if (ONENAND_IS_2PLANE(this)) { - ONENAND_SET_BUFFERRAM1(this); - onenand_update_bufferram(mtd, to + this->writesize, !subpage); - } - - written += thislen; - - if (written == len) - break; - - column = 0; - to += thislen; - buf += thislen; - } - - *retlen = written; - return 0; -} - -/** - * 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) -{ - return mtd_ooblayout_set_databytes(mtd, buf, oob_buf, column, thislen); -} - -/** - * 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; - oobsize = mtd_oobavail(mtd, ops); - 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_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_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, oobcmd; - u_char *oobbuf; - size_t len = ops->ooblen; - const u_char *buf = ops->oobbuf; - unsigned int mode = ops->mode; - - 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 */ - ops->oobretlen = 0; - - if (mode == MTD_OPS_AUTO_OOB) - oobsize = mtd->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; - } - - /* 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; - } - - 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, oobsize, len - written); - - cond_resched(); - - this->command(mtd, ONENAND_CMD_BUFFERRAM, to, mtd->oobsize); - - /* We send data to spare ram with oobsize - * to prevent byte access */ - 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, 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) { - printk(KERN_ERR "%s: write failed %d\n", __func__, ret); - break; - } - - ret = onenand_verify_oob(mtd, oobbuf, to); - if (ret) { - printk(KERN_ERR "%s: verify failed %d\n", - __func__, ret); - break; - } - - written += thislen; - if (written == len) - break; - - to += mtd->writesize; - buf += thislen; - column = 0; - } - - ops->oobretlen = written; - - return ret; -} - -/** - * onenand_write_oob - [MTD Interface] NAND write data and/or out-of-band - * @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) -{ - 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 ret; -} - -/** - * onenand_block_isbad_nolock - [GENERIC] Check if a block is marked bad - * @param mtd MTD device structure - * @param ofs offset from device start - * @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_isbad_nolock(struct mtd_info *mtd, loff_t ofs, int allowbbt) -{ - struct onenand_chip *this = mtd->priv; - struct bbm_info *bbm = this->bbm; - - /* Return info from the table */ - 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_multiblock_erase - [INTERN] erase block(s) using multiblock erase - * @param mtd MTD device structure - * @param instr erase instruction - * @param region erase region - * - * Erase one or more blocks up to 64 block at a time - */ -static int onenand_multiblock_erase(struct mtd_info *mtd, - struct erase_info *instr, - unsigned int block_size) -{ - struct onenand_chip *this = mtd->priv; - loff_t addr = instr->addr; - int len = instr->len; - int eb_count = 0; - int ret = 0; - int bdry_block = 0; - - instr->state = MTD_ERASING; - - 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; - } - - /* 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; - } - - 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; -} - - -/** - * 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; - - 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_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; - } - - 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 = addr; - 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; -} - -/** - * 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; - - pr_debug("%s: start=0x%012llx, len=%llu\n", __func__, - (unsigned long long)instr->addr, - (unsigned long long)instr->len); - - 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; -} - -/** - * onenand_sync - [MTD Interface] sync - * @param mtd MTD device structure - * - * Sync is actually a wait for chip ready function - */ -static void onenand_sync(struct mtd_info *mtd) -{ - pr_debug("%s: called\n", __func__); - - /* Grab the lock and see if the device is available */ - onenand_get_device(mtd, FL_SYNCING); - - /* Release it and go back */ - onenand_release_device(mtd); -} - -/** - * onenand_block_isbad - [MTD Interface] Check whether the block at the given offset is bad - * @param mtd MTD device structure - * @param ofs offset relative to mtd start - * - * Check whether the block is bad - */ -static int onenand_block_isbad(struct mtd_info *mtd, loff_t ofs) -{ - int ret; - - onenand_get_device(mtd, FL_READING); - ret = onenand_block_isbad_nolock(mtd, ofs, 0); - onenand_release_device(mtd); - return ret; -} - -/** - * onenand_default_block_markbad - [DEFAULT] mark a block bad - * @param mtd MTD device structure - * @param ofs offset from device start - * - * This is the default implementation, which can be overridden by - * a hardware specific driver. - */ -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}; - struct mtd_oob_ops ops = { - .mode = MTD_OPS_PLACE_OOB, - .ooblen = 2, - .oobbuf = buf, - .ooboffs = 0, - }; - int block; - - /* Get block number */ - block = onenand_block(this, ofs); - if (bbm->bbt) - bbm->bbt[block >> 2] |= 0x01 << ((block & 0x03) << 1); - - /* We write two bytes, so we don't have to mess with 16-bit access */ - ofs += mtd->oobsize + (bbm->badblockpos & ~0x01); - /* 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); -} - -/** - * onenand_block_markbad - [MTD Interface] Mark the block at the given offset as bad - * @param mtd MTD device structure - * @param ofs offset relative to mtd start - * - * Mark the block as bad - */ -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); - if (ret) { - /* If it was bad already, return success and do nothing */ - if (ret > 0) - return 0; - return ret; - } - - onenand_get_device(mtd, FL_WRITING); - ret = this->block_markbad(mtd, ofs); - onenand_release_device(mtd); - return ret; -} - -/** - * 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 lock or unlock - * @param cmd lock or unlock command - * - * Lock or unlock one or more blocks - */ -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 = 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_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, 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_LOCKING); - - /* Sanity check */ - while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS) - & ONENAND_CTRL_ONGO) - continue; - - /* Check lock status */ - status = this->read_word(this->base + ONENAND_REG_WP_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 + 1; 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); - /* Write lock command */ - this->command(mtd, cmd, 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; - - /* Check lock status */ - status = this->read_word(this->base + ONENAND_REG_WP_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 - -/** - * 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); - -/** - * do_otp_read - [DEFAULT] Read OTP block area - * @param mtd MTD device structure - * @param from The offset to read - * @param len number of bytes to read - * @param retlen pointer to variable to store the number of readbytes - * @param buf the databuffer to put/get data - * - * Read OTP block area. - */ -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 = 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); - this->wait(mtd, FL_RESETING); - - return ret; -} - -/** - * do_otp_write - [DEFAULT] Write OTP block area - * @param mtd MTD device structure - * @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 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) { - memcpy(this->page_buf, buf, len); - memset(this->page_buf + len, 0xff, mtd->writesize - len); - pbuf = this->page_buf; - len = mtd->writesize; - } - - /* Enter OTP access mode */ - this->command(mtd, ONENAND_CMD_OTP_ACCESS, 0, 0); - this->wait(mtd, FL_OTPING); - - 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); - this->wait(mtd, FL_RESETING); - - return ret; -} - -/** - * do_otp_lock - [DEFAULT] Lock OTP block area - * @param mtd MTD device structure - * @param from The offset to lock - * @param len number of bytes to lock - * @param retlen pointer to variable to store the number of lock bytes - * @param buf the databuffer to put/get data - * - * Lock OTP block area. - */ -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; - - 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; -} - -/** - * onenand_otp_walk - [DEFAULT] Handle OTP operation - * @param mtd MTD device structure - * @param from The offset to read/write - * @param len number of bytes to read/write - * @param retlen pointer to variable to store the number of read bytes - * @param buf the databuffer to put/get data - * @param action do given action - * @param mode specify user and factory - * - * Handle OTP operation. - */ -static int onenand_otp_walk(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf, - otp_op_t action, int mode) -{ - struct onenand_chip *this = mtd->priv; - int otp_pages; - int density; - int ret = 0; - - *retlen = 0; - - density = onenand_get_density(this->device_id); - if (density < ONENAND_DEVICE_DENSITY_512Mb) - otp_pages = 20; - else - otp_pages = 50; - - if (mode == MTD_OTP_FACTORY) { - from += mtd->writesize * otp_pages; - otp_pages = ONENAND_PAGES_PER_BLOCK - otp_pages; - } - - /* Check User/Factory boundary */ - 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) { - ret = -ENOSPC; - break; - } - - otpinfo = (struct otp_info *) buf; - otpinfo->start = from; - otpinfo->length = mtd->writesize; - otpinfo->locked = 0; - - from += mtd->writesize; - buf += sizeof(struct otp_info); - *retlen += sizeof(struct otp_info); - } else { - size_t tmp_retlen; - - ret = action(mtd, from, len, &tmp_retlen, buf); - if (ret) - break; - - buf += tmp_retlen; - len -= tmp_retlen; - *retlen += tmp_retlen; - - } - otp_pages--; - } - onenand_release_device(mtd); - - return ret; -} - -/** - * onenand_get_fact_prot_info - [MTD Interface] Read factory OTP info - * @param mtd MTD device structure - * @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, size_t len, - size_t *retlen, struct otp_info *buf) -{ - return onenand_otp_walk(mtd, 0, len, retlen, (u_char *) buf, NULL, - MTD_OTP_FACTORY); -} - -/** - * onenand_read_fact_prot_reg - [MTD Interface] Read factory OTP area - * @param mtd MTD device structure - * @param from The offset to read - * @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 area. - */ -static int onenand_read_fact_prot_reg(struct mtd_info *mtd, loff_t from, - size_t len, size_t *retlen, u_char *buf) -{ - return onenand_otp_walk(mtd, from, len, retlen, buf, do_otp_read, MTD_OTP_FACTORY); -} - -/** - * onenand_get_user_prot_info - [MTD Interface] Read user OTP info - * @param mtd MTD device structure - * @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, size_t len, - size_t *retlen, struct otp_info *buf) -{ - return onenand_otp_walk(mtd, 0, len, retlen, (u_char *) buf, NULL, - MTD_OTP_USER); -} - -/** - * onenand_read_user_prot_reg - [MTD Interface] Read user OTP area - * @param mtd MTD device structure - * @param from The offset to read - * @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 user OTP area. - */ -static int onenand_read_user_prot_reg(struct mtd_info *mtd, loff_t from, - size_t len, size_t *retlen, u_char *buf) -{ - return onenand_otp_walk(mtd, from, len, retlen, buf, do_otp_read, MTD_OTP_USER); -} - -/** - * onenand_write_user_prot_reg - [MTD Interface] Write user OTP area - * @param mtd MTD device structure - * @param from 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 user OTP area. - */ -static int onenand_write_user_prot_reg(struct mtd_info *mtd, loff_t from, - size_t len, size_t *retlen, u_char *buf) -{ - return onenand_otp_walk(mtd, from, len, retlen, buf, do_otp_write, MTD_OTP_USER); -} - -/** - * onenand_lock_user_prot_reg - [MTD Interface] Lock user OTP area - * @param mtd MTD device structure - * @param from The offset to lock - * @param len number of bytes to unlock - * - * Write lock mark on spare area in page 0 in OTP block - */ -static int onenand_lock_user_prot_reg(struct mtd_info *mtd, loff_t from, - size_t len) -{ - 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(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 = 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; - - /* 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_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 & version ID - */ -static void onenand_print_device_info(int device, int version) -{ - 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 = 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"}, -}; - -/** - * onenand_check_maf - Check manufacturer ID - * @param manuf manufacturer ID - * - * Check manufacturer ID - */ -static int onenand_check_maf(int manuf) -{ - int size = ARRAY_SIZE(onenand_manuf_ids); - char *name; - int i; - - for (i = 0; i < size; i++) - if (manuf == onenand_manuf_ids[i].id) - break; - - if (i < size) - name = onenand_manuf_ids[i].name; - else - name = "Unknown"; - - printk(KERN_DEBUG "OneNAND Manufacturer: %s (0x%0x)\n", name, manuf); - - return (i == size); -} - -/** -* 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 values from command with ones from register - */ -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 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); - - /* Read manufacturer and device IDs from BootRAM */ - 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; - - /* 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); - - /* Check OneNAND device */ - 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, 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; - } - - /* - * For Flex-OneNAND, chipsize represents maximum possible device size. - * mtd->size represents the actual device size. - */ - this->chipsize = (16 << density) << 20; - - /* 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; - /* 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->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; - - /* REVISIT: Multichip handling */ - - if (FLEXONENAND(this)) - flexonenand_get_size(mtd); - else - mtd->size = this->chipsize; - - /* - * 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; -} - -/** - * onenand_suspend - [MTD Interface] Suspend the OneNAND flash - * @param mtd MTD device structure - */ -static int onenand_suspend(struct mtd_info *mtd) -{ - return onenand_get_device(mtd, FL_PM_SUSPENDED); -} - -/** - * onenand_resume - [MTD Interface] Resume the OneNAND flash - * @param mtd MTD device structure - */ -static void onenand_resume(struct mtd_info *mtd) -{ - struct onenand_chip *this = mtd->priv; - - if (this->state == FL_PM_SUSPENDED) - onenand_release_device(mtd); - else - printk(KERN_ERR "%s: resume() called for the chip which is not " - "in suspended state\n", __func__); -} - -/** - * onenand_scan - [OneNAND Interface] Scan for the OneNAND device - * @param mtd MTD device structure - * @param maxchips Number of chips to scan for - * - * This fills out all the not initialized function pointers - * with the defaults. - * The flash ID is read and the mtd/chip structures are - * filled with the appropriate values. - */ -int onenand_scan(struct mtd_info *mtd, int maxchips) -{ - int i, ret; - struct onenand_chip *this = mtd->priv; - - if (!this->read_word) - this->read_word = onenand_readw; - if (!this->write_word) - this->write_word = onenand_writew; - - if (!this->command) - this->command = onenand_command; - if (!this->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; - if (!this->write_bufferram) - this->write_bufferram = onenand_write_bufferram; - - if (!this->block_markbad) - this->block_markbad = onenand_default_block_markbad; - if (!this->scan_bbt) - this->scan_bbt = onenand_default_bbt; - - if (onenand_probe(mtd)) - return -ENXIO; - - /* Set Sync. Burst Read after probing */ - if (this->mmcontrol) { - printk(KERN_INFO "OneNAND Sync. Burst Read support\n"); - this->read_bufferram = onenand_sync_read_bufferram; - } - - /* Allocate buffers, if necessary */ - if (!this->page_buf) { - 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)) { - mtd_set_ooblayout(mtd, &flexonenand_ooblayout_ops); - mtd->subpage_sft = 0; - } else { - mtd_set_ooblayout(mtd, &onenand_oob_128_ooblayout_ops); - mtd->subpage_sft = 2; - } - if (ONENAND_IS_NOP_1(this)) - mtd->subpage_sft = 0; - break; - case 64: - mtd_set_ooblayout(mtd, &onenand_oob_32_64_ooblayout_ops); - mtd->subpage_sft = 2; - break; - - case 32: - mtd_set_ooblayout(mtd, &onenand_oob_32_64_ooblayout_ops); - mtd->subpage_sft = 1; - break; - - default: - printk(KERN_WARNING "%s: No OOB scheme defined for oobsize %d\n", - __func__, mtd->oobsize); - mtd->subpage_sft = 0; - /* To prevent kernel oops */ - mtd_set_ooblayout(mtd, &onenand_oob_32_64_ooblayout_ops); - 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 - */ - ret = mtd_ooblayout_count_freebytes(mtd); - if (ret < 0) - ret = 0; - - mtd->oobavail = ret; - - mtd->ecc_strength = 1; - - /* Fill in remaining MTD driver data */ - mtd->type = ONENAND_IS_MLC(this) ? MTD_MLCNANDFLASH : MTD_NANDFLASH; - mtd->flags = MTD_CAP_NANDFLASH; - mtd->_erase = onenand_erase; - mtd->_point = NULL; - mtd->_unpoint = NULL; - 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; -#endif - 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 */ - if (!(this->options & ONENAND_SKIP_INITIAL_UNLOCKING)) - this->unlock_all(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; -} - -/** - * onenand_release - [OneNAND Interface] Free resources held by the OneNAND device - * @param mtd MTD device structure - */ -void onenand_release(struct mtd_info *mtd) -{ - struct onenand_chip *this = mtd->priv; - - /* Deregister partitions */ - mtd_device_unregister(mtd); - - /* Free bad block table memory, if allocated */ - if (this->bbm) { - struct bbm_info *bbm = this->bbm; - kfree(bbm->bbt); - kfree(this->bbm); - } - /* 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); -EXPORT_SYMBOL_GPL(onenand_release); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Kyungmin Park "); -MODULE_DESCRIPTION("Generic OneNAND flash driver code"); diff --git a/drivers/mtd/onenand/onenand_bbt.c b/drivers/mtd/onenand/onenand_bbt.c deleted file mode 100644 index dde20487937d..000000000000 --- a/drivers/mtd/onenand/onenand_bbt.c +++ /dev/null @@ -1,248 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Bad Block Table support for the OneNAND driver - * - * Copyright(c) 2005 Samsung Electronics - * Kyungmin Park - * - * Derived from nand_bbt.c - * - * TODO: - * Split BBT core and chip specific BBT. - */ - -#include -#include -#include -#include - -/** - * check_short_pattern - [GENERIC] check if a pattern is in the buffer - * @param buf the buffer to search - * @param len the length of buffer to search - * @param paglen the pagelength - * @param 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 and the pattern is expected to start - * at offset 0. - * - */ -static int check_short_pattern(uint8_t *buf, int len, int paglen, struct nand_bbt_descr *td) -{ - int i; - uint8_t *p = buf; - - /* Compare the pattern */ - for (i = 0; i < td->len; i++) { - if (p[i] != td->pattern[i]) - return -1; - } - return 0; -} - -/** - * create_bbt - [GENERIC] Create a bad block table by scanning the device - * @param mtd MTD device structure - * @param buf temporary buffer - * @param bd descriptor for the good/bad block search pattern - * @param 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 - */ -static int create_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *bd, int chip) -{ - struct onenand_chip *this = mtd->priv; - struct bbm_info *bbm = this->bbm; - int i, j, numblocks, len, scanlen; - 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 = 2; - - /* We need only read few bytes from the OOB area */ - scanlen = ooblen = 0; - readlen = bd->len; - - /* chip == -1 case only */ - /* Note that numblocks is 2 * (real numblocks) here; - * see i += 2 below as it makses shifting and masking less painful - */ - 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++) { - /* No need to read pages fully, - * just read required OOB bytes */ - ret = onenand_bbt_read_oob(mtd, - from + j * this->writesize + bd->offs, &ops); - - /* If it is a initial bad block, just ignore it */ - if (ret == ONENAND_BBT_READ_FATAL_ERROR) - return -EIO; - - if (ret || check_short_pattern(&buf[j * scanlen], - scanlen, this->writesize, bd)) { - bbm->bbt[i >> 3] |= 0x03 << (i & 0x6); - printk(KERN_INFO "OneNAND eraseblock %d is an " - "initial bad block\n", i >> 1); - mtd->ecc_stats.badblocks++; - break; - } - } - i += 2; - - if (FLEXONENAND(this)) { - rgn = flexonenand_region(mtd, from); - from += mtd->eraseregions[rgn].erasesize; - } else - from += (1 << bbm->bbt_erase_shift); - } - - return 0; -} - - -/** - * onenand_memory_bbt - [GENERIC] create a memory based bad block table - * @param mtd MTD device structure - * @param 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 - */ -static inline int onenand_memory_bbt (struct mtd_info *mtd, struct nand_bbt_descr *bd) -{ - struct onenand_chip *this = mtd->priv; - - return create_bbt(mtd, this->page_buf, bd, -1); -} - -/** - * onenand_isbad_bbt - [OneNAND Interface] Check if a block is bad - * @param mtd MTD device structure - * @param offs offset in the device - * @param allowbbt allow access to bad block table region - */ -static int onenand_isbad_bbt(struct mtd_info *mtd, loff_t offs, int allowbbt) -{ - struct onenand_chip *this = mtd->priv; - struct bbm_info *bbm = this->bbm; - int block; - uint8_t res; - - /* Get block number * 2 */ - block = (int) (onenand_block(this, offs) << 1); - res = (bbm->bbt[block >> 3] >> (block & 0x06)) & 0x03; - - 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) { - case 0x00: return 0; - case 0x01: return 1; - case 0x02: return allowbbt ? 0 : 1; - } - - return 1; -} - -/** - * onenand_scan_bbt - [OneNAND Interface] scan, find, read and maybe create bad block table(s) - * @param mtd MTD device structure - * @param bd descriptor for the good/bad block search pattern - * - * 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 is freed - * by the onenand_release function. - * - */ -static int onenand_scan_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd) -{ - struct onenand_chip *this = mtd->priv; - struct bbm_info *bbm = this->bbm; - int len, ret = 0; - - 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; - - /* Set the bad block position */ - bbm->badblockpos = ONENAND_BADBLOCK_POS; - - /* Set erase shift */ - bbm->bbt_erase_shift = this->erase_shift; - - if (!bbm->isbad_bbt) - bbm->isbad_bbt = onenand_isbad_bbt; - - /* Scan the device to build a memory based bad block table */ - if ((ret = onenand_memory_bbt(mtd, bd))) { - printk(KERN_ERR "onenand_scan_bbt: Can't scan flash and build the RAM-based BBT\n"); - kfree(bbm->bbt); - bbm->bbt = NULL; - } - - return ret; -} - -/* - * 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 largepage_memorybased = { - .options = 0, - .offs = 0, - .len = 2, - .pattern = scan_ff_pattern, -}; - -/** - * onenand_default_bbt - [OneNAND Interface] Select a default bad block table for the device - * @param mtd MTD device structure - * - * This function selects the default bad block table - * support for the device and calls the onenand_scan_bbt function - */ -int onenand_default_bbt(struct mtd_info *mtd) -{ - struct onenand_chip *this = mtd->priv; - struct bbm_info *bbm; - - this->bbm = kzalloc(sizeof(struct bbm_info), GFP_KERNEL); - if (!this->bbm) - return -ENOMEM; - - bbm = this->bbm; - - /* 1KB page has same configuration as 2KB page */ - if (!bbm->badblock_pattern) - bbm->badblock_pattern = &largepage_memorybased; - - return onenand_scan_bbt(mtd, bbm->badblock_pattern); -} diff --git a/drivers/mtd/onenand/samsung.c b/drivers/mtd/onenand/samsung.c deleted file mode 100644 index 2e9d076e445a..000000000000 --- a/drivers/mtd/onenand/samsung.c +++ /dev/null @@ -1,1012 +0,0 @@ -/* - * Samsung S3C64XX/S5PC1XX OneNAND driver - * - * Copyright © 2008-2010 Samsung Electronics - * Kyungmin Park - * Marek Szyprowski - * - * 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: emulate the pseudo BufferRAM - * S5PC110: use DMA - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "samsung.h" - -enum soc_type { - TYPE_S3C6400, - TYPE_S3C6410, - 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 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 - -/* 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; - void __iomem *ahb_addr; - int bootram_command; - void *page_buf; - void *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; - 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 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 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 = onenand->page_buf; - s = 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 = onenand->page_buf; - if (index == 1) - p += this->writesize; - } else { - p = 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_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 = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); - if (!mtd) - return -ENOMEM; - - onenand = devm_kzalloc(&pdev->dev, sizeof(struct s3c_onenand), - GFP_KERNEL); - if (!onenand) - return -ENOMEM; - - this = (struct onenand_chip *) &mtd[1]; - mtd->priv = this; - mtd->dev.parent = &pdev->dev; - onenand->pdev = pdev; - onenand->type = platform_get_device_id(pdev)->driver_data; - - s3c_onenand_setup(mtd); - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - onenand->base = devm_ioremap_resource(&pdev->dev, r); - if (IS_ERR(onenand->base)) - return PTR_ERR(onenand->base); - - onenand->phys_base = r->start; - - /* 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); - onenand->ahb_addr = devm_ioremap_resource(&pdev->dev, r); - if (IS_ERR(onenand->ahb_addr)) - return PTR_ERR(onenand->ahb_addr); - - /* Allocate 4KiB BufferRAM */ - onenand->page_buf = devm_kzalloc(&pdev->dev, SZ_4K, - GFP_KERNEL); - if (!onenand->page_buf) - return -ENOMEM; - - /* Allocate 128 SpareRAM */ - onenand->oob_buf = devm_kzalloc(&pdev->dev, 128, GFP_KERNEL); - if (!onenand->oob_buf) - return -ENOMEM; - - /* S3C doesn't handle subpage write */ - mtd->subpage_sft = 0; - this->subpagesize = mtd->writesize; - - } else { /* S5PC110 */ - r = platform_get_resource(pdev, IORESOURCE_MEM, 1); - onenand->dma_addr = devm_ioremap_resource(&pdev->dev, r); - if (IS_ERR(onenand->dma_addr)) - return PTR_ERR(onenand->dma_addr); - - 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 = devm_request_irq(&pdev->dev, r->start, - s5pc110_onenand_irq, - IRQF_SHARED, "onenand", - &onenand); - if (err) { - dev_err(&pdev->dev, "failed to get irq\n"); - return err; - } - } - } - - err = onenand_scan(mtd, 1); - if (err) - return err; - - 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); - if (err) { - dev_err(&pdev->dev, "failed to parse partitions and register the MTD device\n"); - onenand_release(mtd); - return err; - } - - platform_set_drvdata(pdev, mtd); - - return 0; -} - -static int s3c_onenand_remove(struct platform_device *pdev) -{ - struct mtd_info *mtd = platform_get_drvdata(pdev); - - onenand_release(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 const struct platform_device_id s3c_onenand_driver_ids[] = { - { - .name = "s3c6400-onenand", - .driver_data = TYPE_S3C6400, - }, { - .name = "s3c6410-onenand", - .driver_data = TYPE_S3C6410, - }, { - .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 "); -MODULE_DESCRIPTION("Samsung OneNAND controller support"); diff --git a/drivers/mtd/onenand/samsung.h b/drivers/mtd/onenand/samsung.h deleted file mode 100644 index 9016dc0136a8..000000000000 --- a/drivers/mtd/onenand/samsung.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2008-2010 Samsung Electronics - * Kyungmin Park - * - * 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 -- cgit v1.2.3 From 9e7eb9c90d769ad724136f47e374b2edfa351f9b Mon Sep 17 00:00:00 2001 From: Cyrille Pitchen Date: Thu, 15 Mar 2018 20:04:38 +0100 Subject: MAINTAINERS: update maintainers for MTD and SPI NOR subsystems remove myself as MTD and SPI NOR maintainer. Signed-off-by: Cyrille Pitchen Signed-off-by: Boris Brezillon --- MAINTAINERS | 2 -- 1 file changed, 2 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 3bdc260e36b7..7892db9a9494 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9037,7 +9037,6 @@ M: Brian Norris M: Boris Brezillon M: Marek Vasut M: Richard Weinberger -M: Cyrille Pitchen L: linux-mtd@lists.infradead.org W: http://www.linux-mtd.infradead.org/ Q: http://patchwork.ozlabs.org/project/linux-mtd/list/ @@ -13006,7 +13005,6 @@ F: arch/arm/boot/dts/spear* F: arch/arm/mach-spear/ SPI NOR SUBSYSTEM -M: Cyrille Pitchen M: Marek Vasut L: linux-mtd@lists.infradead.org W: http://www.linux-mtd.infradead.org/ -- cgit v1.2.3