From: Kyungmin Park <kyungmin.park@samsung.com>
To: linux-mtd@lists.infradead.org, linux-omap-open-source@linux.omap.com
Cc: 'Dongjun Shin' <d.j.shin@samsung.com>,
"'Zhang, Jian'" <jzhang@ti.com>,
"'Johnson, Steve-OMAP'" <stevej@ti.com>,
"'Woodruff, Richard'" <r-woodruff2@ti.com>,
'Thomas Gleixner' <tglx@linutronix.de>
Subject: [PATCH] OneNAND MTD support
Date: Mon, 04 Jul 2005 10:19:14 +0900 [thread overview]
Message-ID: <0IJ200CBRX00K2@mmp2.samsung.com> (raw)
In-Reply-To: <940F0204B50E3E4886CC4E7D562F6D3448C212@dlee04.ent.ti.com>
[-- Attachment #1: Type: text/plain, Size: 1248 bytes --]
Hi
We are happy to release OneNAND MTD to open source community.
Brief desciption of OneNAND is following
Samsung OneNANDT is a highly reliable embedded memory targeted for both
consumer electronic, and next generation mobile phone market. With Samsung's
accumulated NAND flash technologies over the last decade, Samsung designs an
ideal single memory chip based on NAND architecture integrating SRAM buffers
and logic interface.
Samsung OneNANDT takes both advantages from high-speed data read function of
NOR flash and the advanced data storage function of NAND flash.
(from
http://www.samsung.com/Products/Semiconductor/Flash/OneNAND_TM/index.htm)
This patch is based on latest linux 2.6 kernel from linux-omap-2.6.git
It's simple implementation.
So there's no optimization and performance tuning.
It is useful to test OneNAND chip with new platform and verify hardware
configurations.
Now it's tested with CRAMFS and JFFS2 on OMAP OSK
Note: The bad block handling is not yet implemented.
since we want to use NAND BBT routine and NAND BBT routine has NAND-specific
code, then
At first we want to split BBT code into BBT core and NAND-specific code then
we will merge with OneNAND
Any comments are welcome.
Thank you.
Kyungmin Park
[-- Attachment #2: onenand-mtd.patch --]
[-- Type: application/octet-stream, Size: 56464 bytes --]
Index: drivers/mtd/onenand/Kconfig
===================================================================
--- /dev/null (tree:4b825dc642cb6eb9a060e54bf8d69288fbee4904)
+++ uncommitted/drivers/mtd/onenand/Kconfig (mode:100644)
@@ -0,0 +1,36 @@
+#
+# linux/drivers/mtd/onenand/Kconfig
+#
+
+menu "OneNAND Flash Device Drivers (EXPERIMENTAL)"
+ depends on MTD != n && EXPERIMENTAL
+
+config MTD_ONENAND
+ tristate "OneNAND Device Support"
+ depends on MTD
+ select MTD_ONENAND_INFO
+ help
+ This enables support for accessing all type of OneNAND flash
+ devices. For further information see
+ <http://www.samsung.com/Products/Semiconductor/Flash/OneNAND_TM/index.htm>.
+
+config MTD_ONENAND_VERIFY_WRITE
+ bool "Verify OneNAND page writes"
+ depends on MTD_ONENAND
+ help
+ This adds an extra check when data is written to the flash. The
+ OneNAND flash device internally checks only bits transitioning
+ from 1 to 0. There is a rare possibility that even though the
+ device thinks the write was successful, a bit could have been
+ flipped accidentaly due to device wear or something else.
+
+config MTD_ONENAND_INFO
+ tristate
+
+config MTD_ONENAND_OMAP
+ tristate "OneNAND Flash device on OMAP board"
+ depends on ARM && ARCH_OMAP && MTD_ONENAND
+ help
+ Support for OneNAND flash on TI OMAP board.
+
+endmenu
Index: drivers/mtd/onenand/Makefile
===================================================================
--- /dev/null (tree:4b825dc642cb6eb9a060e54bf8d69288fbee4904)
+++ uncommitted/drivers/mtd/onenand/Makefile (mode:100644)
@@ -0,0 +1,8 @@
+#
+# linux/drivers/mtd/onenand/Makefile
+#
+
+obj-$(CONFIG_MTD_ONENAND) += onenand_base.o
+obj-$(CONFIG_MTD_ONENAND_INFO) += onenand_info.o
+
+obj-$(CONFIG_MTD_ONENAND_OMAP) += omap-onenand.o
Index: drivers/mtd/onenand/omap-onenand.c
===================================================================
--- /dev/null (tree:4b825dc642cb6eb9a060e54bf8d69288fbee4904)
+++ uncommitted/drivers/mtd/onenand/omap-onenand.c (mode:100644)
@@ -0,0 +1,196 @@
+/*
+ * linux/drivers/mtd/onenand/omap-onenand.c
+ *
+ * Copyright (c) 2005 Samsung Electronics
+ * Kyungmin Park <kyungmin.park@samsung.com>
+ *
+ * Derived from linux/drivers/mtd/nand/omap-nand-flash.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Overview:
+ * This is a device driver for the OneNAND flash device for TI OMAP boards.
+ */
+
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/module.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/onenand.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/io.h>
+#include <asm/arch/hardware.h>
+#include <asm/arch/tc.h>
+#include <asm/sizes.h>
+
+#define OMAP_ONENAND_FLASH_START1 OMAP_CS2A_PHYS
+#define OMAP_ONENAND_FLASH_START2 OMAP_CS1B_PHYS
+/*
+ * MTD structure for OMAP board
+ */
+static struct mtd_info *omap_onenand_mtd = NULL;
+
+static void __iomem *omap_onenand_flash_base;
+
+/*
+ * Define partitions for flash devices
+ */
+
+#ifdef CONFIG_MTD_PARTITIONS
+static struct mtd_partition static_partition[] = {
+ {
+ .name = "X-Loader + U-Boot",
+ .offset = 0,
+ .size = SZ_128K,
+ .mask_flags = MTD_WRITEABLE /* force read-only */
+ },
+ {
+ .name = "U-Boot Environment",
+ .offset = MTDPART_OFS_APPEND,
+ .size = SZ_128K,
+ .mask_flags = MTD_WRITEABLE /* force read-only */
+ },
+ {
+ .name = "kernel",
+ .offset = MTDPART_OFS_APPEND,
+ .size = 2 * SZ_1M
+ },
+ {
+ .name = "filesystem0",
+ .offset = MTDPART_OFS_APPEND,
+ .size = SZ_16M,
+ },
+ {
+ .name = "filesystem1",
+ .offset = MTDPART_OFS_APPEND,
+ .size = MTDPART_SIZ_FULL,
+ },
+};
+
+const char *part_probes[] = { "cmdlinepart", NULL, };
+
+#endif
+
+/* Scan to find existance of the device at omap_onenand_flash_base.
+ This also allocates oob and data internal buffers */
+static int __init probe_onenand_chip(void)
+{
+ struct onenand_chip *this;
+
+ this = (struct onenand_chip *) (&omap_onenand_mtd[1]);
+
+ /* Initialize structures */
+ memset((char *) this, 0, sizeof(struct onenand_chip));
+
+ this->base = omap_onenand_flash_base;
+
+ if (onenand_scan(omap_onenand_mtd, 1))
+ return -ENXIO;
+
+ return 0;
+}
+
+static char onenand_name[] = "onenand";
+
+/*
+ * Main initialization routine
+ */
+static int __init omap_onenand_init (void)
+{
+ struct onenand_chip *this;
+ struct mtd_partition *dynamic_partition = 0;
+ int err = 0;
+
+ /* Allocate memory for MTD device structure and private data */
+ omap_onenand_mtd = kmalloc (sizeof(struct mtd_info) + sizeof (struct onenand_chip),
+ GFP_KERNEL);
+ if (!omap_onenand_mtd) {
+ printk (KERN_WARNING "Unable to allocate OneNAND MTD device structure.\n");
+ err = -ENOMEM;
+ goto out;
+ }
+
+ /* Get pointer to private data */
+ this = (struct onenand_chip *) (&omap_onenand_mtd[1]);
+
+ /* Initialize structures */
+ memset((char *) omap_onenand_mtd, 0, sizeof(struct mtd_info) + sizeof(struct onenand_chip));
+
+ /* Link the private data with the MTD structure */
+ omap_onenand_mtd->priv = this;
+
+ /* try the first address */
+ omap_onenand_flash_base = ioremap(OMAP_ONENAND_FLASH_START1, SZ_128K);
+ omap_onenand_mtd->name = onenand_name;
+ if (probe_onenand_chip()){
+ /* try the second address */
+ iounmap(omap_onenand_flash_base);
+ omap_onenand_flash_base = ioremap(OMAP_ONENAND_FLASH_START2, SZ_128K);
+ if (probe_onenand_chip()){
+ iounmap(omap_onenand_flash_base);
+ err = -ENXIO;
+ goto out_mtd;
+ }
+ }
+
+ /* Register the partitions */
+ switch (omap_onenand_mtd->size) {
+ case SZ_128M:
+ case SZ_64M:
+ case SZ_32M:
+#ifdef CONFIG_MTD_PARTITIONS
+ err = parse_mtd_partitions(omap_onenand_mtd, part_probes,
+ &dynamic_partition, 0);
+ if (err > 0)
+ err = add_mtd_partitions(omap_onenand_mtd,
+ dynamic_partition, err);
+ else if (1)
+ err = add_mtd_partitions(omap_onenand_mtd,
+ static_partition,
+ ARRAY_SIZE(static_partition));
+ else
+#endif
+ err = add_mtd_device(omap_onenand_mtd);
+ if (err)
+ goto out_buf;
+ break;
+
+ default:
+ printk(KERN_WARNING "Unsupported OneNAND device\n");
+ err = -ENXIO;
+ goto out_buf;
+ }
+
+ goto out;
+
+out_buf:
+ onenand_release(omap_onenand_mtd);
+ iounmap(omap_onenand_flash_base);
+out_mtd:
+ kfree(omap_onenand_mtd);
+out:
+ return err;
+}
+
+/*
+ * Clean up routine
+ */
+static void __exit omap_onenand_cleanup (void)
+{
+ /* onenand_release frees MTD partitions, MTD structure
+ and onenand internal buffers */
+ onenand_release(omap_onenand_mtd);
+ iounmap(omap_onenand_flash_base);
+ kfree(omap_onenand_mtd);
+}
+
+module_init(omap_onenand_init);
+module_exit(omap_onenand_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>");
+MODULE_DESCRIPTION("Glue layer for OneNAND flash on OMAP boards");
Index: drivers/mtd/onenand/onenand_base.c
===================================================================
--- /dev/null (tree:4b825dc642cb6eb9a060e54bf8d69288fbee4904)
+++ uncommitted/drivers/mtd/onenand/onenand_base.c (mode:100644)
@@ -0,0 +1,1310 @@
+/*
+ * linux/drivers/mtd/onenand/onenand_base.c
+ *
+ * Copyright (C) 2005 Samsung Electronics
+ * Kyungmin Park <kyungmin.park@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/onenand.h>
+
+#include <asm/io.h>
+
+/**
+ * onenand_oob_64 - oob info for large (2KB) page
+ */
+static struct nand_oobinfo onenand_oob_64 = {
+ .useecc = MTD_NANDECC_AUTOPLACE,
+ .eccbytes = 20,
+ .eccpos = {
+ 8, 9, 10, 11, 12,
+ 24, 25, 26, 27, 28,
+ 40, 41, 42, 43, 44,
+ 56, 57, 58, 59, 60,
+ },
+ .oobfree = {
+ {2, 3}, {14, 2}, {18, 3}, {30, 2},
+ {24, 3}, {46, 2}, {40, 3}, {62, 2} }
+};
+
+/**
+ * onenand_oob_32 - oob info for middle (1KB) page
+ */
+static struct nand_oobinfo onenand_oob_32 = {
+ .useecc = MTD_NANDECC_AUTOPLACE,
+ .eccbytes = 10,
+ .eccpos = {
+ 8, 9, 10, 11, 12,
+ 24, 25, 26, 27, 28,
+ },
+ .oobfree = { {2, 3}, {14, 2}, {18, 3}, {30, 2} }
+};
+
+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 */
+};
+
+/**
+ * onenand_block_address - [DEFAULT] Get block address
+ * @param device the device id
+ * @param block the block
+ * @return translated block address if DDP, otherwise same
+ *
+ * Setup Start Address 1 Register (F100h)
+ */
+static int onenand_block_address(int device, int block)
+{
+ if (device & ONENAND_DEVICE_IS_DDP) {
+ /* Device Flash Core select, NAND Flash Block Address */
+ int dfs = 0, density, mask;
+
+ density = device >> ONENAND_DEVICE_DENSITY_SHIFT;
+ mask = (1 << (density + 6));
+
+ if (block & mask)
+ dfs = 1;
+
+ return (dfs << ONENAND_DDP_SHIFT) | (block & (mask - 1));
+ }
+
+ return block;
+}
+
+/**
+ * onenand_bufferram_address - [DEFAULT] Get bufferram address
+ * @param device the device id
+ * @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(int device, int block)
+{
+ if (device & ONENAND_DEVICE_IS_DDP) {
+ /* Device BufferRAM Select */
+ int dbs = 0, density, mask;
+
+ density = device >> ONENAND_DEVICE_DENSITY_SHIFT;
+ mask = (1 << (density + 6));
+
+ if (block & mask)
+ dbs = 1;
+
+ return (dbs << ONENAND_DDP_SHIFT);
+ }
+
+ return 0;
+}
+
+/**
+ * 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);
+}
+
+/**
+ * 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, readcmd = 0;
+ int block, page;
+ /* Now we use page size operation */
+ int sectors = 4, count = 4;
+
+ /* Address translation */
+ switch (cmd) {
+ case ONENAND_CMD_UNLOCK:
+ case ONENAND_CMD_LOCK:
+ case ONENAND_CMD_LOCK_TIGHT:
+ block = -1;
+ page = -1;
+ break;
+
+ case ONENAND_CMD_ERASE:
+ case ONENAND_CMD_BUFFERRAM:
+ block = (int) (addr >> this->erase_shift);
+ page = -1;
+ break;
+
+ default:
+ block = (int) (addr >> this->erase_shift);
+ page = (int) (addr >> this->page_shift);
+ 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->device_id, block);
+ onenand_writew(value, this->base + ONENAND_REG_START_ADDRESS2);
+
+ /* 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->device_id, block);
+ onenand_writew(value, this->base + ONENAND_REG_START_ADDRESS1);
+ }
+
+ if (page != -1) {
+ int dataram;
+
+ switch (cmd) {
+ case ONENAND_CMD_READ:
+ case ONENAND_CMD_READOOB:
+ dataram = ONENAND_SET_NEXT_BUFFERRAM(this);
+ readcmd = 1;
+ break;
+
+ default:
+ dataram = ONENAND_CURRENT_BUFFERRAM(this);
+ break;
+ }
+
+ /* Write 'FPA, FSA' of Flash */
+ value = onenand_page_address(page, sectors);
+ onenand_writew(value, this->base + ONENAND_REG_START_ADDRESS8);
+
+ /* Write 'BSA, BSC' of DataRAM */
+ value = onenand_buffer_address(dataram, sectors, count);
+ onenand_writew(value, this->base + ONENAND_REG_START_BUFFER);
+
+ if (readcmd) {
+ /* Select DataRAM for DDP */
+ value = onenand_bufferram_address(this->device_id, block);
+ onenand_writew(value, this->base + ONENAND_REG_START_ADDRESS2);
+ }
+ }
+
+ /* Interrupt clear */
+ onenand_writew(ONENAND_INT_CLEAR, this->base + ONENAND_REG_INTERRUPT);
+
+ /* Write command */
+ onenand_writew(cmd, this->base + ONENAND_REG_COMMAND);
+
+ return 0;
+}
+
+/**
+ * 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, ecc;
+
+ /* The 10 msec is enough */
+ timeout = jiffies + msecs_to_jiffies(10);
+ while (time_before(jiffies, timeout)) {
+ interrupt = onenand_readw(this->base + ONENAND_REG_INTERRUPT);
+
+ if (interrupt & flags)
+ break;
+ yield();
+ }
+ /* To get correct interrupt status in timeout case */
+ interrupt = onenand_readw(this->base + ONENAND_REG_INTERRUPT);
+
+ if (!interrupt)
+ BUG();
+
+ ctrl = onenand_readw(this->base + ONENAND_REG_CTRL_STATUS);
+
+ if (ctrl & ONENAND_CTRL_ERROR) {
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: controller error = 0x%04x", ctrl);
+ return -EIO;
+ }
+
+ if (ctrl & ONENAND_CTRL_LOCK) {
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: it's locked error = 0x%04x", ctrl);
+ return -EIO;
+ }
+
+ if (interrupt & ONENAND_INT_READ) {
+ ecc = onenand_readw(this->base + ONENAND_REG_ECC_STATUS);
+ if (ecc & ONENAND_ECC_2BIT_ALL) {
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: ECC error = 0x%04x", ecc);
+ return -EBADMSG;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * onenand_dataram - [OneNAND Interface] Read/write the dataram area
+ * @param mtd MTD data stru
+ * @param write write operation if given
+ * @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/write the dataram area
+ */
+static int onenand_dataram(struct mtd_info *mtd, int write,
+ unsigned char *buffer, int offset, int count)
+{
+ struct onenand_chip *this = mtd->priv;
+ void __iomem *src, *dest;
+ void __iomem *dataram;
+
+ dataram = this->base + ONENAND_DATARAM;
+
+ if (ONENAND_CURRENT_BUFFERRAM(this))
+ dataram += mtd->oobblock;
+
+ if (write) {
+ src = buffer;
+ dest = dataram + offset;
+ } else {
+ src = dataram + offset;
+ dest = buffer;
+ }
+
+ memcpy(dest, src, count);
+
+ return 0;
+}
+
+/**
+ * onenand_spareram - [OneNAND Interface] Read/write the spareram area
+ * @param mtd MTD data stru
+ * @param write write operation if given
+ * @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/write the spareram area
+ */
+static int onenand_spareram(struct mtd_info *mtd, int write,
+ unsigned char *buffer, int offset, int count)
+{
+ struct onenand_chip *this = mtd->priv;
+ void __iomem *src, *dest;
+ void __iomem *spareram;
+
+ spareram = this->base + ONENAND_SPARERAM;
+
+ if (ONENAND_CURRENT_BUFFERRAM(this))
+ spareram += mtd->oobsize;
+
+ if (write) {
+ src = buffer;
+ dest = spareram + offset;
+ } else {
+ src = spareram + offset;
+ dest = buffer;
+ }
+
+ memcpy(dest, src, count);
+
+ return 0;
+}
+
+
+/**
+ * onenand_get_device - [GENERIC] Get chip for selected access
+ * @param this the onenand chip descriptor
+ * @param new_state the state which is requested
+ *
+ * Get the device and lock it for exclusive access
+ */
+static void onenand_get_device(struct onenand_chip *this, int new_state)
+{
+ struct onenand_chip *active = this;
+ DECLARE_WAITQUEUE(wait, current);
+
+ /*
+ * Grab the lock and see if the device is available
+ */
+retry:
+ if (active == this) {
+ spin_lock(&this->chip_lock);
+ if (this->state == FL_READY) {
+ this->state = new_state;
+ spin_unlock(&this->chip_lock);
+ return;
+ }
+ }
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&active->wq, &wait);
+ spin_unlock(&active->chip_lock);
+ schedule();
+ remove_wait_queue(&active->wq, &wait);
+ goto retry;
+}
+
+/**
+ * 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;
+
+ /* Release the chip */
+ spin_lock(&this->chip_lock);
+ this->state = FL_READY;
+ wake_up(&this->wq);
+ spin_unlock(&this->chip_lock);
+}
+
+/**
+ * onenand_read_ecc - [MTD Interface] Read data with ECC
+ * @param mtd MTD device structure
+ * @param from offset to read from
+ * @param len number of bytes to read
+ * @param retlen pointer to variable to store the number of read bytes
+ * @param buf the databuffer to put data
+ * @param oob_buf filesystem supplied oob data buffer
+ * @param oobsel oob selection structure
+ *
+ * OneNAND read with ECC
+ */
+static int onenand_read_ecc(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf,
+ u_char *oob_buf, struct nand_oobinfo *oobsel)
+{
+ struct onenand_chip *this = mtd->priv;
+ int read = 0, column;
+ int thislen;
+ int ret = 0;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "onenand_read_ecc: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len);
+
+ /* Do not allow reads past end of device */
+ if ((from + len) > mtd->size) {
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_read_ecc: Attempt read beyond end of device\n");
+ *retlen = 0;
+ return -EINVAL;
+ }
+
+ /* Grab the lock and see if the device is available */
+ onenand_get_device(this, FL_READING);
+
+ /* TODO handling oob */
+
+ while (read < len) {
+ thislen = min_t(int, mtd->oobblock, len - read);
+
+ column = from & (mtd->oobblock - 1);
+ if (column + thislen > mtd->oobblock)
+ thislen = mtd->oobblock - column;
+
+ this->command(mtd, ONENAND_CMD_READ, from, mtd->oobblock);
+
+ ret = this->wait(mtd, FL_READING);
+ /* First copy data and check return value for ECC handling */
+
+ this->dataram(mtd, ONENAND_READ, buf, column, thislen);
+
+ read += thislen;
+
+ if (ret) {
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_read_ecc: read failed = %d\n", ret);
+ goto out;
+ }
+
+ if (read == len)
+ break;
+
+ from += thislen;
+ buf += thislen;
+ }
+
+out:
+ /* Deselect and wake up anyone waiting on the device */
+ onenand_release_device(mtd);
+
+ /*
+ * Return success, if no ECC failures, else -EBADMSG
+ * fs driver will take care of that, because
+ * retlen == desired len and result == -EBADMSG
+ */
+ *retlen = read;
+ return ret;
+}
+
+/**
+ * onenand_read - [MTD Interface] MTD compability function for onenand_read_ecc
+ * @param mtd MTD device structure
+ * @param from offset to read from
+ * @param len number of bytes to read
+ * @param retlen pointer to variable to store the number of read bytes
+ * @param buf the databuffer to put data
+ *
+ * This function simply calls onenand_read_ecc with oob buffer and oobsel = NULL
+*/
+static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ return onenand_read_ecc(mtd, from, len, retlen, buf, NULL, NULL);
+}
+
+/**
+ * onenand_read_oob - [MTD Interface] OneNAND read out-of-band
+ * @param mtd MTD device structure
+ * @param from offset to read from
+ * @param len number of bytes to read
+ * @param retlen pointer to variable to store the number of read bytes
+ * @param buf the databuffer to put data
+ *
+ * OneNAND read out-of-band data from the spare area
+ */
+static int onenand_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct onenand_chip *this = mtd->priv;
+ int read = 0, thislen, column;
+ int ret = 0;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "onenand_read_oob: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len);
+
+ /* Initialize return length value */
+ *retlen = 0;
+
+ /* Do not allow reads past end of device */
+ if (unlikely((from + len) > mtd->size)) {
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_read_oob: Attempt read beyond end of device\n");
+ return -EINVAL;
+ }
+
+ /* Grab the lock and see if the device is available */
+ onenand_get_device(this, FL_READING);
+
+ column = from & (mtd->oobblock - 1);
+
+ while (read < len) {
+ thislen = mtd->oobsize - column;
+ thislen = min_t(int, thislen, len);
+
+ this->command(mtd, ONENAND_CMD_READOOB, from, mtd->oobsize);
+
+ ret = this->wait(mtd, FL_READING);
+ /* First copy data and check return value for ECC handling */
+
+ this->spareram(mtd, ONENAND_READ, buf, column, thislen);
+
+ read += thislen;
+
+ if (ret) {
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_read_oob: read failed = %d\n", ret);
+ goto out;
+ }
+
+ buf += thislen;
+
+ /* Read more? */
+ if (read < len) {
+ /* Page size */
+ from += mtd->oobblock;
+ column = 0;
+ }
+ }
+
+out:
+ /* Deselect and wake up anyone waiting on the device */
+ onenand_release_device(mtd);
+
+ *retlen = read;
+ return ret;
+}
+
+#ifdef CONFIG_MTD_ONENAND_VERIFY_WRITE
+/**
+ * onenand_verify_page - [GENERIC] verify the chip contents after a write
+ * @param mtd MTD device structure
+ * @param buf the databuffer to verify
+ * @param block block address
+ * @param page page address
+ *
+ * Check DataRAM area directly
+ */
+static int onenand_verify_page(struct mtd_info *mtd, u_char *buf,
+ loff_t addr, int block, int page)
+{
+ struct onenand_chip *this = mtd->priv;
+ void __iomem *dataram0, *dataram1;
+ int ret = 0;
+
+ this->command(mtd, ONENAND_CMD_READ, addr, mtd->oobblock);
+
+ ret = this->wait(mtd, FL_READING);
+ if (ret)
+ return ret;
+
+ /* Check, if the two dataram areas are same */
+ dataram0 = this->base + ONENAND_DATARAM;
+ dataram1 = dataram0 + mtd->oobblock;
+
+ if (memcmp(dataram0, dataram1, mtd->oobblock))
+ return -EBADMSG;
+
+ return 0;
+}
+#else
+#define onenand_verify_page(...) (0)
+#endif
+
+#define NOTALIGNED(x) ((x & (mtd->oobblock - 1)) != 0)
+
+/**
+ * onenand_write_ecc - [MTD Interface] OneNAND write with ECC
+ * @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 eccbuf filesystem supplied oob data buffer
+ * @param oobsel oob selection structure
+ *
+ * OneNAND write with ECC
+ */
+static int onenand_write_ecc(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf,
+ u_char *eccbuf, struct nand_oobinfo *oobsel)
+{
+ struct onenand_chip *this = mtd->priv;
+ int written = 0;
+ int ret = 0;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "onenand_write_ecc: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len);
+
+ /* Initialize retlen, in case of early exit */
+ *retlen = 0;
+
+ /* Do not allow writes past end of device */
+ if (unlikely((to + len) > mtd->size)) {
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_ecc: Attempt write to past end of device\n");
+ return -EINVAL;
+ }
+
+ /* Reject writes, which are not page aligned */
+ if (unlikely(NOTALIGNED(to)) || unlikely(NOTALIGNED(len))) {
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_ecc: Attempt to write not page aligned data\n");
+ return -EINVAL;
+ }
+
+ /* Grab the lock and see if the device is available */
+ onenand_get_device(this, FL_WRITING);
+
+ /* Loop until all data write */
+ while (written < len) {
+ int thislen = min_t(int, mtd->oobblock, len - written);
+
+ this->command(mtd, ONENAND_CMD_BUFFERRAM, to, mtd->oobblock);
+
+ this->dataram(mtd, ONENAND_WRITE, (u_char *) buf, 0, thislen);
+ this->spareram(mtd, ONENAND_WRITE, (u_char *) ffchars, 0, mtd->oobsize);
+
+ this->command(mtd, ONENAND_CMD_PROG, to, mtd->oobblock);
+
+ ret = this->wait(mtd, FL_WRITING);
+ if (ret) {
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_ecc: write filaed %d\n", ret);
+ goto out;
+ }
+
+ /* Only check verify write turn on */
+ ret = onenand_verify_page(mtd, (u_char *) buf, to, block, page);
+ if (ret) {
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_ecc: verify failed %d\n", ret);
+ goto out;
+ }
+
+ written += thislen;
+
+ if (written == len)
+ break;
+
+ to += thislen;
+ buf += thislen;
+ }
+
+out:
+ /* Deselect and wake up anyone waiting on the device */
+ onenand_release_device(mtd);
+
+ *retlen = written;
+
+ return ret;
+}
+
+/**
+ * onenand_write - [MTD Interface] compability function for onenand_write_ecc
+ * @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
+ *
+ * This function simply calls onenand_write_ecc
+ * with oob buffer and oobsel = NULL
+ */
+static int onenand_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ return onenand_write_ecc(mtd, to, len, retlen, buf, NULL, NULL);
+}
+
+/**
+ * onenand_write_oob - [MTD Interface] 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
+ *
+ * OneNAND write out-of-band
+ */
+static int onenand_write_oob(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, status;
+ int written = 0;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "onenand_write_oob: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len);
+
+ /* Initialize retlen, in case of early exit */
+ *retlen = 0;
+
+ /* Do not allow writes past end of device */
+ if (unlikely((to + len) > mtd->size)) {
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_oob: Attempt write to past end of device\n");
+ return -EINVAL;
+ }
+
+ /* Grab the lock and see if the device is available */
+ onenand_get_device(this, FL_WRITING);
+
+ /* Loop until all data write */
+ while (written < len) {
+ int thislen = min_t(int, mtd->oobsize, len - written);
+
+ column = to & (mtd->oobsize - 1);
+
+ this->command(mtd, ONENAND_CMD_BUFFERRAM, to, mtd->oobsize);
+
+ this->spareram(mtd, ONENAND_WRITE, (u_char *) ffchars, 0, mtd->oobsize);
+ this->spareram(mtd, ONENAND_WRITE, (u_char *) buf, column, thislen);
+
+ this->command(mtd, ONENAND_CMD_PROGOOB, to, mtd->oobsize);
+
+ status = this->wait(mtd, FL_WRITING);
+ if (status)
+ goto out;
+
+ written += thislen;
+
+ if (written == len)
+ break;
+
+ to += thislen;
+ buf += thislen;
+ }
+
+out:
+ /* Deselect and wake up anyone waiting on the device */
+ onenand_release_device(mtd);
+
+ *retlen = written;
+
+ return 0;
+}
+
+/**
+ * onenand_writev_ecc - [MTD Interface] write with iovec with ecc
+ * @param mtd MTD device structure
+ * @param vecs the iovectors to write
+ * @param count number of vectors
+ * @param to offset to write to
+ * @param retlen pointer to variable to store the number of written bytes
+ * @param eccbuf filesystem supplied oob data buffer
+ * @param oobsel oob selection structure
+ *
+ * OneNAND write with iovec with ecc
+ */
+static int onenand_writev_ecc(struct mtd_info *mtd, const struct kvec *vecs,
+ unsigned long count, loff_t to, size_t *retlen,
+ u_char *eccbuf, struct nand_oobinfo *oobsel)
+{
+ struct onenand_chip *this = mtd->priv;
+ unsigned char buffer[mtd->oobblock], *pbuf;
+ size_t total_len, len;
+ int i, written = 0;
+ int ret = 0;
+
+ /* Preset written len for early exit */
+ *retlen = 0;
+
+ /* Calculate total length of data */
+ total_len = 0;
+ for (i = 0; i < count; i++)
+ total_len += vecs[i].iov_len;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "onenand_writev_ecc: to = 0x%08x, len = %i, count = %ld\n", (unsigned int) to, (unsigned int) total_len, count);
+
+ /* Do not allow write past end of the device */
+ if (unlikely((to + total_len) > mtd->size)) {
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_writev_ecc: Attempted write past end of device\n");
+ return -EINVAL;
+ }
+
+ /* Reject writes, which are not page aligned */
+ if (unlikely(NOTALIGNED(to)) || unlikely(NOTALIGNED(total_len))) {
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_writev_ecc: Attempt to write not page aligned data\n");
+ return -EINVAL;
+ }
+
+ /* Grab the lock and see if the device is available */
+ onenand_get_device(this, FL_WRITING);
+
+ /* TODO handling oob */
+
+ /* Loop until all keve's data has been written */
+ len = 0;
+ while (count) {
+ pbuf = buffer;
+ /*
+ * If the given tuple is >= pagesize then
+ * write it out from the iov
+ */
+ if ((vecs->iov_len - len) >= mtd->oobblock) {
+ pbuf = vecs->iov_base + len;
+
+ len += mtd->oobblock;
+
+ /* Check, if we have to switch to the next tuple */
+ if (len >= (int) vecs->iov_len) {
+ vecs++;
+ len = 0;
+ count--;
+ }
+ } else {
+ int cnt = 0, thislen;
+ while (cnt < mtd->oobblock) {
+ thislen = min_t(int, mtd->oobblock - cnt, vecs->iov_len - len);
+ memcpy(buffer + cnt, vecs->iov_base + len, thislen);
+ cnt += thislen;
+ len += thislen;
+
+ /* Check, if we have to switch to the next tuple */
+ if (len >= (int) vecs->iov_len) {
+ vecs++;
+ len = 0;
+ count--;
+ }
+ }
+ }
+
+ this->command(mtd, ONENAND_CMD_BUFFERRAM, to, mtd->oobblock);
+
+ this->dataram(mtd, ONENAND_WRITE, pbuf, 0, mtd->oobblock);
+ this->spareram(mtd, ONENAND_WRITE, (u_char *) ffchars, 0, mtd->oobsize);
+
+ this->command(mtd, ONENAND_CMD_PROG, to, mtd->oobblock);
+
+ ret = this->wait(mtd, FL_WRITING);
+ if (ret) {
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_writev_ecc: write failed %d\n", ret);
+ goto out;
+ }
+
+ /* TODO */
+ ret = onenand_verify_page(mtd, (u_char *) pbuf, to, block, page);
+ if (ret) {
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_writev_ecc: verify failed %d\n", ret);
+ goto out;
+ }
+
+ written += mtd->oobblock;
+
+ to += mtd->oobblock;
+ }
+
+out:
+ /* Deselect and wakt up anyone waiting on the device */
+ onenand_release_device(mtd);
+
+ *retlen = written;
+
+ return 0;
+}
+
+/**
+ * onenand_writev - [MTD Interface] compabilty function for onenand_writev_ecc
+ * @param mtd MTD device structure
+ * @param vecs the iovectors to write
+ * @param count number of vectors
+ * @param to offset to write to
+ * @param retlen pointer to variable to store the number of written bytes
+ *
+ * OneNAND write with kvec. This just calls the ecc function
+ */
+static int onenand_writev(struct mtd_info *mtd, const struct kvec *vecs,
+ unsigned long count, loff_t to, size_t *retlen)
+{
+ return onenand_writev_ecc(mtd, vecs, count, to, retlen, NULL, NULL);
+}
+
+/**
+ * onenand_erase - [MTD Interface] erase block(s)
+ * @param mtd MTD device structure
+ * @param instr erase instruction
+ *
+ * Erase one ore 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;
+ int len;
+ int ret = 0;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "onenand_erase: start = 0x%08x, len = %i\n", (unsigned int) instr->addr, (unsigned int) instr->len);
+
+ block_size = (1 << this->erase_shift);
+
+ /* Start address must align on block boundary */
+ if (unlikely(instr->addr & (block_size - 1))) {
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_erase: Unaligned address\n");
+ return -EINVAL;
+ }
+
+ /* Length must align on block boundary */
+ if (unlikely(instr->len & (block_size - 1))) {
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_erase: Length not block aligned\n");
+ return -EINVAL;
+ }
+
+ /* Do not allow erase past end of device */
+ if (unlikely((instr->len + instr->addr) > mtd->size)) {
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_erase: Erase past end of device\n");
+ return -EINVAL;
+ }
+
+ instr->fail_addr = 0xffffffff;
+
+ /* Grab the lock and see if the device is available */
+ onenand_get_device(this, FL_ERASING);
+
+ /* Loop throught the pages */
+ len = instr->len;
+ addr = instr->addr;
+
+ instr->state = MTD_ERASING;
+
+ while (len) {
+
+ /* TODO Check badblock */
+
+ this->command(mtd, ONENAND_CMD_ERASE, addr, block_size);
+
+ ret = this->wait(mtd, FL_ERASING);
+ /* Check, if it is write protected */
+ if (ret) {
+ if (ret == -EPERM)
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_erase: Device is write protected!!!\n");
+ else
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_erase: Failed erase, block %d\n", block);
+ instr->state = MTD_ERASE_FAILED;
+ instr->fail_addr = addr;
+ goto erase_exit;
+ }
+
+ len -= block_size;
+ addr += block_size;
+ }
+
+ instr->state = MTD_ERASE_DONE;
+
+erase_exit:
+
+ ret = instr->state == MTD_ERASE_DONE ? 0 : -EIO;
+ /* Do call back function */
+ if (!ret)
+ mtd_erase_callback(instr);
+
+ /* Deselect and wake up anyone waiting on the device */
+ onenand_release_device(mtd);
+
+ 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)
+{
+ struct onenand_chip *this = mtd->priv;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "onenand_sync: called\n");
+
+ /* Grab the lock and see if the device is available */
+ onenand_get_device(this, 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
+ */
+static int onenand_block_isbad(struct mtd_info *mtd, loff_t ofs)
+{
+ /*
+ * TODO
+ * 1. Bad block table (BBT)
+ * -> using NAND BBT to support JFFS2
+ * 2. Bad block management (BBM)
+ * -> bad block replace scheme
+ *
+ * Currently we do nothing
+ */
+ return 0;
+}
+
+/**
+ * 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
+ */
+static int onenand_block_markbad(struct mtd_info *mtd, loff_t ofs)
+{
+ /* see above */
+ return 0;
+}
+
+/**
+ * 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, size_t len)
+{
+ struct onenand_chip *this = mtd->priv;
+ int start, end, block, value, status;
+
+ start = ofs >> this->erase_shift;
+ end = len >> this->erase_shift;
+
+ /* Continuous lock scheme */
+ if (this->options & ONENAND_CONT_LOCK) {
+ /* Set start block address */
+ onenand_writew(start, this->base + ONENAND_REG_START_BLOCK_ADDRESS);
+ /* Set end block address */
+ onenand_writew(end - 1, this->base + ONENAND_REG_END_BLOCK_ADDRESS);
+ /* Write unlock command */
+ this->command(mtd, ONENAND_CMD_UNLOCK, 0, 0);
+
+ /* There's no return value */
+ this->wait(mtd, FL_UNLOCKING);
+
+ /* Sanity check */
+ while (onenand_readw(this->base + ONENAND_REG_CTRL_STATUS)
+ & ONENAND_CTRL_ONGO)
+ continue;
+
+ /* Check lock status */
+ status = onenand_readw(this->base + ONENAND_REG_WP_STATUS);
+ if (!(status & ONENAND_WP_US))
+ printk(KERN_ERR "wp status = 0x%x\n", status);
+
+ return 0;
+ }
+
+ /* Block lock scheme */
+ for (block = start; block < end; block++) {
+ /* Set start block address */
+ onenand_writew(block, this->base + ONENAND_REG_START_BLOCK_ADDRESS);
+ /* Write unlock command */
+ this->command(mtd, ONENAND_CMD_UNLOCK, 0, 0);
+
+ /* There's no return value */
+ this->wait(mtd, FL_UNLOCKING);
+
+ /* Sanity check */
+ while (onenand_readw(this->base + ONENAND_REG_CTRL_STATUS)
+ & ONENAND_CTRL_ONGO)
+ continue;
+
+ /* Set block address for read block status */
+ value = onenand_block_address(this->device_id, block);
+ onenand_writew(value, this->base + ONENAND_REG_START_ADDRESS1);
+
+ /* Check lock status */
+ status = onenand_readw(this->base + ONENAND_REG_WP_STATUS);
+ if (!(status & ONENAND_WP_US))
+ printk(KERN_ERR "block = %d, wp status = 0x%x\n", block, status);
+ }
+
+ return 0;
+}
+
+
+/**
+ * onenand_scan - [OneNAND Interface] Probe the OneNAND device
+ * @param mtd MTD device structure
+ *
+ * OneNAND detection method:
+ * Compare the the values from command with ones from register
+ */
+static int onenand_probe(struct mtd_info *mtd)
+{
+ struct onenand_chip *this = mtd->priv;
+ int bram_maf_id, bram_dev_id, maf_id, dev_id;
+ int density;
+
+ /* Send the command for reading device ID from BootRAM */
+ onenand_writew(ONENAND_CMD_READID, this->base + ONENAND_BOOTRAM);
+
+ /* Read manufacturer and device IDs from BootRAM */
+ bram_maf_id = onenand_readw(this->base + ONENAND_BOOTRAM + 0x0);
+ bram_dev_id = onenand_readw(this->base + ONENAND_BOOTRAM + 0x2);
+
+ /* Chech empty address */
+ if (bram_maf_id == bram_dev_id)
+ return -ENXIO;
+
+ /* Reset OneNAND to read default register values */
+ onenand_writew(ONENAND_CMD_RESET, this->base + ONENAND_BOOTRAM);
+
+ /* Read manufacturer and device IDs from Register */
+ maf_id = onenand_readw(this->base + ONENAND_REG_MANUFACTURER_ID);
+ dev_id = onenand_readw(this->base + ONENAND_REG_DEVICE_ID);
+
+ /* Check OneNAND device */
+ if (maf_id != bram_maf_id || dev_id != bram_dev_id)
+ return -ENXIO;
+
+ /* Manufacturer ID */
+ onenand_print_maf_info(maf_id);
+ /* Flash device information */
+ onenand_print_device_info(dev_id);
+ this->device_id = dev_id;
+
+ density = dev_id >> ONENAND_DEVICE_DENSITY_SHIFT;
+ this->chipsize = (16 << density) << 20;
+
+ /* OneNAND page size & block size */
+ /* The data buffer size is equal to page size */
+ mtd->oobblock = onenand_readw(this->base + ONENAND_REG_DATA_BUFFER_SIZE);
+ mtd->oobsize = mtd->oobblock >> 5;
+ /* Pagers per block is always 64 in OneNAND */
+ mtd->erasesize = mtd->oobblock << 6;
+
+ this->erase_shift = ffs(mtd->erasesize) - 1;
+ this->page_shift = ffs(mtd->oobblock) - 1;
+ this->ppb_shift = (this->erase_shift - this->page_shift);
+ this->page_mask = (mtd->erasesize / mtd->oobblock) - 1;
+
+ /* TODO: Multichip handling */
+
+ mtd->size = this->chipsize;
+
+ /* Version ID */
+ this->version_id = onenand_readw(this->base + ONENAND_REG_VERSION_ID);
+ printk(KERN_DEBUG "OneNAND version = 0x%04x\n", this->version_id);
+
+ /* Lock scheme */
+ if (density <= ONENAND_DEVICE_DENSITY_512Mb &&
+ !(this->version_id >> ONENAND_VERSION_PROCESS_SHIFT)) {
+ printk(KERN_INFO "Lock scheme is Continues Lock\n");
+ this->options |= ONENAND_CONT_LOCK;
+ }
+
+ return 0;
+}
+
+
+/**
+ * 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)
+{
+ struct onenand_chip *this = mtd->priv;
+
+ if (!this->command)
+ this->command = onenand_command;
+ if (!this->wait)
+ this->wait = onenand_wait;
+
+ if (!this->dataram)
+ this->dataram = onenand_dataram;
+ if (!this->spareram)
+ this->spareram = onenand_spareram;
+
+ if (onenand_probe(mtd))
+ return -ENXIO;
+
+ this->state = FL_READY;
+ init_waitqueue_head(&this->wq);
+ spin_lock_init(&this->chip_lock);
+
+ switch (mtd->oobsize) {
+ case 64:
+ this->autooob = &onenand_oob_64;
+ break;
+
+ case 32:
+ this->autooob = &onenand_oob_32;
+ break;
+
+ default:
+ printk(KERN_WARNING "No OOB scheme defined for oobsize %d\n",
+ mtd->oobsize);
+ break;
+ }
+
+ memcpy(&mtd->oobinfo, this->autooob, sizeof(mtd->oobinfo));
+
+ /* Fill in remaining MTD driver data */
+ mtd->type = MTD_NANDFLASH;
+ mtd->flags = MTD_CAP_NANDFLASH;
+ mtd->ecctype = MTD_ECC_SW;
+ mtd->erase = onenand_erase;
+ mtd->point = NULL;
+ mtd->unpoint = NULL;
+ mtd->read = onenand_read;
+ mtd->write = onenand_write;
+ mtd->read_ecc = onenand_read_ecc;
+ mtd->write_ecc = onenand_write_ecc;
+ mtd->read_oob = onenand_read_oob;
+ mtd->write_oob = onenand_write_oob;
+ mtd->readv = NULL;
+ mtd->writev = onenand_writev;
+ mtd->writev_ecc = onenand_writev_ecc;
+ mtd->sync = onenand_sync;
+ mtd->lock = NULL;
+ mtd->unlock = onenand_unlock;
+ mtd->suspend = NULL;
+ mtd->resume = NULL;
+ mtd->block_isbad = onenand_block_isbad;
+ mtd->block_markbad = onenand_block_markbad;
+ mtd->owner = THIS_MODULE;
+
+ /* Unlock whole block */
+ mtd->unlock(mtd, 0x0, this->chipsize);
+
+ 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)
+{
+#ifdef CONFIG_MTD_PARTITIONS
+ /* Deregister partitions */
+ del_mtd_partitions (mtd);
+#endif
+ /* Deregister the device */
+ del_mtd_device (mtd);
+}
+
+EXPORT_SYMBOL(onenand_scan);
+EXPORT_SYMBOL(onenand_release);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>");
+MODULE_DESCRIPTION("Generic OneNAND flash driver code");
Index: drivers/mtd/onenand/onenand_info.c
===================================================================
--- /dev/null (tree:4b825dc642cb6eb9a060e54bf8d69288fbee4904)
+++ uncommitted/drivers/mtd/onenand/onenand_info.c (mode:100644)
@@ -0,0 +1,53 @@
+/*
+ * linux/drivers/mtd/onenand/onenand_info.c
+ *
+ * Copyright (C) 2005 Samsung Electronics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/mtd/onenand.h>
+
+void onenand_print_device_info(int device)
+{
+ int vcc, demuxed, ddp, density;
+
+ vcc = device & ONENAND_DEVICE_VCC_MASK;
+ demuxed = device & ONENAND_DEVICE_IS_DEMUX;
+ ddp = device & ONENAND_DEVICE_IS_DDP;
+ density = device >> ONENAND_DEVICE_DENSITY_SHIFT;
+ printk(KERN_INFO "%sOneNAND%s %dMB %sV 16-bit (0x%02x)\n",
+ demuxed ? "" : "Muxed ",
+ ddp ? "(DDP)" : "",
+ (16 << density),
+ vcc ? "2.65/3.3" : "1.8",
+ device);
+}
+
+static const struct onenand_manufacturers onenand_manuf_ids[] = {
+ {ONENAND_MFR_SAMSUNG, "Samsung"},
+ {ONENAND_MFR_UNKNOWN, "Unknown"}
+};
+
+void onenand_print_maf_info(int manuf)
+{
+ int i;
+
+ for (i = 0; onenand_manuf_ids[i].id; i++) {
+ if (manuf == onenand_manuf_ids[i].id)
+ break;
+ }
+
+ printk(KERN_DEBUG "OneNAND Manufacturer: %s\n",
+ onenand_manuf_ids[i].name);
+}
+
+EXPORT_SYMBOL_GPL(onenand_print_device_info);
+EXPORT_SYMBOL_GPL(onenand_print_maf_info);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>");
+MODULE_DESCRIPTION("Display OneNAND device and manufacturer ID's");
Index: include/linux/mtd/onenand.h
===================================================================
--- /dev/null (tree:4b825dc642cb6eb9a060e54bf8d69288fbee4904)
+++ uncommitted/include/linux/mtd/onenand.h (mode:100644)
@@ -0,0 +1,132 @@
+/*
+ * linux/include/linux/mtd/onenand.h
+ *
+ * Copyright (C) 2005 Samsung Electronics
+ * Kyungmin Park <kyungmin.park@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __LINUX_MTD_ONENAND_H
+#define __LINUX_MTD_ONENAND_H
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/onenand_regs.h>
+
+/* Scan and identify a OneNAND device */
+extern int onenand_scan(struct mtd_info *mtd, int max_chips);
+/* Free resources held by the OneNAND device */
+extern void onenand_release(struct mtd_info *mtd);
+
+/* OneNADA read / write value */
+enum {
+ ONENAND_READ = 0,
+ ONENAND_WRITE,
+};
+
+/**
+ * onenand_state_t - chip states
+ * Enumeration for OneNAND flash chip state
+ */
+typedef enum {
+ FL_READY,
+ FL_READING,
+ FL_WRITING,
+ FL_ERASING,
+ FL_SYNCING,
+ FL_UNLOCKING,
+} onenand_state_t;
+
+/**
+ * struct onenand_chip - OneNAND Private Flash Chip Data
+ * @param base [BOARDSPECIFIC] address to access OneNAND
+ * @param chipsize [INTERN] the size of one chip for multichip arrays
+ * @param device_id [INTERN] device ID
+ * @param verstion_id [INTERN] version ID
+ * @param options [BOARDSPECIFIC] various chip options. They can partly be set to inform onenand_scan about
+ * @param erase_shift [INTERN] number of address bits in a block
+ * @param page_shift [INTERN] number of address bits in a page
+ * @param ppb_shift [INTERN] number of address bits in a pages per block
+ * @param page_mask [INTERN] a page per block mask
+ * @param bufferam [INTERN] BufferRAM index
+ * @param command [REPLACEABLE] hardwarespecific function for writing commands to the chip
+ * @param wait [REPLACEABLE] hardwarespecific function for wait on ready
+ * @param dataram [REPLACEABLE] hardwarespecific function for Data Area
+ * @param spareram [REPLACEABLE] hardwarespecific function for Spare Area
+ * @param chip_lock [INTERN] spinlock used to protect access to this structure and the chip
+ * @param wq [INTERN] wait queue to sleep on if a OneNAND operation is in progress
+ * @param state [INTERN] the current state of the OneNAND device
+ * @param autooob [REPLACEABLE] the default (auto)placement scheme
+ * @param priv [OPTIONAL] pointer to private chip date
+ */
+struct onenand_chip {
+ void __iomem *base;
+ unsigned int chipsize;
+ unsigned int device_id;
+ unsigned int version_id;
+ unsigned int options;
+
+ unsigned int erase_shift;
+ unsigned int page_shift;
+ unsigned int ppb_shift; /* Pages per block shift */
+ unsigned int page_mask;
+
+ unsigned int bufferram;
+
+ int (*command)(struct mtd_info *mtd, int cmd, loff_t address, size_t len);
+ int (*wait)(struct mtd_info *mtd, int state);
+ int (*dataram)(struct mtd_info *mtd, int write, unsigned char *buffer,
+ int offset, int count);
+ int (*spareram)(struct mtd_info *mtd, int write, unsigned char *buffer,
+ int offset, int count);
+
+ spinlock_t chip_lock;
+ wait_queue_head_t wq;
+ onenand_state_t state;
+
+ struct nand_oobinfo *autooob;
+
+ void *priv;
+};
+
+#define ONENAND_CURRENT_BUFFERRAM(this) (this->bufferram)
+#define ONENAND_NEXT_BUFFERRAM(this) (this->bufferram ^ 1)
+#define ONENAND_SET_NEXT_BUFFERRAM(this) (this->bufferram ^= 1)
+
+/*
+ * Functions to access the OneNAND IO region
+ */
+#define onenand_readw(a) readw(a)
+#define onenand_writew(v,a) writew(v, a)
+
+/*
+ * Options bits
+ */
+#define ONENAND_CONT_LOCK (0x0001)
+
+
+/*
+ * OneNAND Flash Manufacturer ID Codes
+ */
+#define ONENAND_MFR_SAMSUNG 0xec
+#define ONENAND_MFR_UNKNOWN 0x00
+
+/**
+ * struct nand_manufacturers - NAND Flash Manufacturer ID Structure
+ * @param name: Manufacturer name
+ * @param id: manufacturer ID code of device.
+*/
+struct onenand_manufacturers {
+ int id;
+ char *name;
+};
+
+extern void onenand_print_device_info(int device_id);
+extern void onenand_print_maf_info(int manuf_id);
+
+#endif /* __LINUX_MTD_ONENAND_H */
Index: include/linux/mtd/onenand_regs.h
===================================================================
--- /dev/null (tree:4b825dc642cb6eb9a060e54bf8d69288fbee4904)
+++ uncommitted/include/linux/mtd/onenand_regs.h (mode:100644)
@@ -0,0 +1,167 @@
+/*
+ * linux/include/linux/mtd/onenand_regs.h
+ *
+ * OneNAND Register header file
+ *
+ * Copyright (C) 2005 Samsung Electronics
+ *
+ * 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 __ONENAND_REG_H
+#define __ONENAND_REG_H
+
+/* Memory Address Map Translation (Word order) */
+#define ONENAND_MEMORY_MAP(x) ((x) << 1)
+
+/*
+ * External BufferRAM area
+ */
+#define ONENAND_BOOTRAM ONENAND_MEMORY_MAP(0x0000)
+#define ONENAND_DATARAM ONENAND_MEMORY_MAP(0x0200)
+#define ONENAND_SPARERAM ONENAND_MEMORY_MAP(0x8010)
+
+/*
+ * OneNAND Registers
+ */
+#define ONENAND_REG_MANUFACTURER_ID ONENAND_MEMORY_MAP(0xF000)
+#define ONENAND_REG_DEVICE_ID ONENAND_MEMORY_MAP(0xF001)
+#define ONENAND_REG_VERSION_ID ONENAND_MEMORY_MAP(0xF002)
+#define ONENAND_REG_DATA_BUFFER_SIZE ONENAND_MEMORY_MAP(0xF003)
+#define ONENAND_REG_BOOT_BUFFER_SIZE ONENAND_MEMORY_MAP(0xF004)
+#define ONENAND_REG_NUM_BUFFERS ONENAND_MEMORY_MAP(0xF005)
+#define ONENAND_REG_TECHNOLOGY ONENAND_MEMORY_MAP(0xF006)
+
+#define ONENAND_REG_START_ADDRESS1 ONENAND_MEMORY_MAP(0xF100)
+#define ONENAND_REG_START_ADDRESS2 ONENAND_MEMORY_MAP(0xF101)
+#define ONENAND_REG_START_ADDRESS3 ONENAND_MEMORY_MAP(0xF102)
+#define ONENAND_REG_START_ADDRESS4 ONENAND_MEMORY_MAP(0xF103)
+#define ONENAND_REG_START_ADDRESS5 ONENAND_MEMORY_MAP(0xF104)
+#define ONENAND_REG_START_ADDRESS6 ONENAND_MEMORY_MAP(0xF105)
+#define ONENAND_REG_START_ADDRESS7 ONENAND_MEMORY_MAP(0xF106)
+#define ONENAND_REG_START_ADDRESS8 ONENAND_MEMORY_MAP(0xF107)
+
+#define ONENAND_REG_START_BUFFER ONENAND_MEMORY_MAP(0xF200)
+#define ONENAND_REG_COMMAND ONENAND_MEMORY_MAP(0xF220)
+#define ONENAND_REG_SYS_CFG1 ONENAND_MEMORY_MAP(0xF221)
+#define ONENAND_REG_SYS_CFG2 ONENAND_MEMORY_MAP(0xF222)
+#define ONENAND_REG_CTRL_STATUS ONENAND_MEMORY_MAP(0xF240)
+#define ONENAND_REG_INTERRUPT ONENAND_MEMORY_MAP(0xF241)
+#define ONENAND_REG_START_BLOCK_ADDRESS ONENAND_MEMORY_MAP(0xF24C)
+#define ONENAND_REG_END_BLOCK_ADDRESS ONENAND_MEMORY_MAP(0xF24D)
+#define ONENAND_REG_WP_STATUS ONENAND_MEMORY_MAP(0xF24E)
+
+#define ONENAND_REG_ECC_STATUS ONENAND_MEMORY_MAP(0xFF00)
+#define ONENAND_REG_ECC_M0 ONENAND_MEMORY_MAP(0xFF01)
+#define ONENAND_REG_ECC_S0 ONENAND_MEMORY_MAP(0xFF02)
+#define ONENAND_REG_ECC_M1 ONENAND_MEMORY_MAP(0xFF03)
+#define ONENAND_REG_ECC_S1 ONENAND_MEMORY_MAP(0xFF04)
+#define ONENAND_REG_ECC_M2 ONENAND_MEMORY_MAP(0xFF05)
+#define ONENAND_REG_ECC_S2 ONENAND_MEMORY_MAP(0xFF06)
+#define ONENAND_REG_ECC_M3 ONENAND_MEMORY_MAP(0xFF07)
+#define ONENAND_REG_ECC_S3 ONENAND_MEMORY_MAP(0xFF08)
+
+/*
+ * Device ID Register F001h (R)
+ */
+#define ONENAND_DEVICE_DENSITY_SHIFT (4)
+#define ONENAND_DEVICE_IS_DDP (1 << 3)
+#define ONENAND_DEVICE_IS_DEMUX (1 << 2)
+#define ONENAND_DEVICE_VCC_MASK (0x3)
+
+#define ONENAND_DEVICE_DENSITY_512Mb (0x002)
+
+/*
+ * Version ID Register F002h (R)
+ */
+#define ONENAND_VERSION_PROCESS_SHIFT (8)
+
+/*
+ * Start Address 1 F100h (R/W)
+ */
+#define ONENAND_DDP_SHIFT (15)
+
+/*
+ * Start Address 8 F107h (R/W)
+ */
+#define ONENAND_FPA_MASK (0x3f)
+#define ONENAND_FPA_SHIFT (2)
+#define ONENAND_FSA_MASK (0x03)
+
+/*
+ * Start Buffer Register F200h (R/W)
+ */
+#define ONENAND_BSA_MASK (0x03)
+#define ONENAND_BSA_SHIFT (8)
+#define ONENAND_BSA_BOOTRAM (0 << 2)
+#define ONENAND_BSA_DATARAM0 (2 << 2)
+#define ONENAND_BSA_DATARAM1 (3 << 2)
+#define ONENAND_BSC_MASK (0x03)
+
+/*
+ * Command Register F220h (R/W)
+ */
+#define ONENAND_CMD_READ (0x00)
+#define ONENAND_CMD_READOOB (0x13)
+#define ONENAND_CMD_PROG (0x80)
+#define ONENAND_CMD_PROGOOB (0x1A)
+#define ONENAND_CMD_UNLOCK (0x23)
+#define ONENAND_CMD_LOCK (0x2A)
+#define ONENAND_CMD_LOCK_TIGHT (0x2C)
+#define ONENAND_CMD_ERASE (0x94)
+#define ONENAND_CMD_RESET (0xF0)
+#define ONENAND_CMD_READID (0x90)
+
+/* NOTE: Those are not *REAL* commands */
+#define ONENAND_CMD_BUFFERRAM (0x1978)
+
+/*
+ * System Configuration 1 Register F221h (R, R/W)
+ */
+#define ONENAND_SYS_CFG1_SYNC_READ (1 << 15)
+#define ONENAND_SYS_CFG1_BRL (1 << 12)
+#define ONENAND_SYS_CFG1_BL (1 << 9)
+#define ONENAND_SYS_CFG1_NO_ECC (1 << 8)
+#define ONENAND_SYS_CFG1_RDY (1 << 7)
+#define ONENAND_SYS_CFG1_INT (1 << 6)
+#define ONENAND_SYS_CFG1_IOBE (1 << 5)
+#define ONENAND_SYS_CFG1_RDY_CONF (1 << 4)
+
+/*
+ * Controller Status Register F240h (R)
+ */
+#define ONENAND_CTRL_ONGO (1 << 15)
+#define ONENAND_CTRL_LOCK (1 << 14)
+#define ONENAND_CTRL_LOAD (1 << 13)
+#define ONENAND_CTRL_PROGRAM (1 << 12)
+#define ONENAND_CTRL_ERASE (1 << 11)
+#define ONENAND_CTRL_ERROR (1 << 10)
+#define ONENAND_CTRL_RSTB (1 << 7)
+
+/*
+ * Interrupt Status Register F241h (R)
+ */
+#define ONENAND_INT_MASTER (1 << 15)
+#define ONENAND_INT_READ (1 << 7)
+#define ONENAND_INT_WRITE (1 << 6)
+#define ONENAND_INT_ERASE (1 << 5)
+#define ONENAND_INT_RESET (1 << 4)
+#define ONENAND_INT_CLEAR (0 << 0)
+
+/*
+ * NAND Flash Write Protection Status Register F24Eh (R)
+ */
+#define ONENAND_WP_US (1 << 2)
+#define ONENAND_WP_LS (1 << 1)
+#define ONENAND_WP_LTS (1 << 0)
+
+/*
+ * ECC Status Reigser FF00h (R)
+ */
+#define ONENAND_ECC_1BIT (1 << 0)
+#define ONENAND_ECC_2BIT (1 << 1)
+#define ONENAND_ECC_2BIT_ALL (0xAAAA)
+
+#endif /* __ONENAND_REG_H */
next parent reply other threads:[~2005-07-04 1:29 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <940F0204B50E3E4886CC4E7D562F6D3448C212@dlee04.ent.ti.com>
2005-07-04 1:19 ` Kyungmin Park [this message]
2005-07-04 10:05 ` [PATCH] OneNAND MTD support Thomas Gleixner
2005-07-09 2:13 ` Kyungmin Park
2005-07-09 9:10 ` Thomas Gleixner
2005-07-11 1:53 ` Kyungmin Park
2005-07-11 7:47 ` Thomas Gleixner
2005-07-11 8:30 ` Kyungmin Park
2005-07-11 8:47 ` Thomas Gleixner
2005-07-11 9:23 ` Kyungmin Park
2005-07-11 10:46 ` Thomas Gleixner
2005-07-04 13:30 ` Artem B. Bityuckiy
2005-07-04 13:41 ` Thomas Gleixner
[not found] <430D873B.1050108@in2soft.de>
2005-08-25 23:37 ` Kyungmin Park
2005-08-26 7:41 ` Bernhard Priewasser
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=0IJ200CBRX00K2@mmp2.samsung.com \
--to=kyungmin.park@samsung.com \
--cc=d.j.shin@samsung.com \
--cc=jzhang@ti.com \
--cc=linux-mtd@lists.infradead.org \
--cc=linux-omap-open-source@linux.omap.com \
--cc=r-woodruff2@ti.com \
--cc=stevej@ti.com \
--cc=tglx@linutronix.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox