From: "Dan Brown" <dan_brown@ieee.org>
To: "Thomas Gleixner" <tglx@linutronix.de>
Cc: linux-mtd@lists.infradead.org
Subject: [PATCH] Towards DiskOnChip support as a NAND driver
Date: Wed, 16 Jun 2004 10:19:00 -0400 [thread overview]
Message-ID: <073c01c453ac$de849e10$0100a8c0@superfortress> (raw)
[-- Attachment #1: Type: text/plain, Size: 2099 bytes --]
For review and testing, here is a patch which brings us a step closer to
proper support for DiskOnChip devices. This is against the 20040615
snapshot.
This patch includes the following:
drivers/mtd/nand/diskonchip.c:
- Fix pipeline problems for DOC2000 and Millenium/2000TSOP (only tested
on 2000TSOP)
- Add bad block table (read-only) support for INFTL-based devices (only
2000TSOP for the moment)
- Add hardware ECC support for DOC2000 and Millenium/2000TSOP (only
tested on 2000TSOP and needs more testing!)
- Add auto-partitioning for INFTL-based devices (should be made
optional?)
drivers/mtd/nand/nand_base.c:
- Add support for DiskOnChip hardware ECC
- Fix a null-pointer ref that occurs if a NAND driver supplies a primary
but not a mirror BBT descriptor
include/linux/mtd/nand.h:
- Add a return value to calculate_ecc. This is used by nand_base to
skip the call to correct_data if we know there are no errors. (This saves a
6-byte comparison-against-zero when using DOC. Is it worth changing the API
for it? You tell me.)
-----------------------------------------------------------
Please realize that this needs a lot more testing. I still haven't gotten
JFFS2 to run properly with this (though I suspect that may reflect my
inexperience with JFFS2). Also, the change to nand.h may break other nand
drivers (though the fix is trivial).
Things that aren't done yet:
- BBT support for NFTL-based devices (coming soon!)
- BBT updating (not important when using (I)NFTL filesystem, but matters
for JFFS2)
- Auto-partitioning for NFTL-based devices?
- DOC Millenium Plus support
- Proper BBT handling for multi-floor INFTL devices (don't try it with
the current code!)
- Modifying (I)NFTL to use the nand-based DiskOnChip driver
- Modifying JFFS2 to use autooob layout? (Not sure about this)
- Modifying flash_eraseall to use autooob layout? (Should work OK on
DOC by coincidence)
- Adding the nand/diskonchip driver into the kernel build system instead
of the old one
- More testing.
Enjoy!
-Dan Brown
[-- Attachment #2: docnand.diff --]
[-- Type: application/octet-stream, Size: 16074 bytes --]
diff -u --recursive mtd-20040615/drivers/mtd/nand/diskonchip.c mtd/drivers/mtd/nand/diskonchip.c
--- mtd-20040615/drivers/mtd/nand/diskonchip.c 2004-03-27 18:00:08.000000000 -0500
+++ mtd/drivers/mtd/nand/diskonchip.c 2004-06-16 07:09:34.000000000 -0400
@@ -20,6 +20,8 @@
#include <linux/mtd/nand.h>
#include <linux/mtd/doc2000.h>
#include <linux/mtd/compatmac.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/inftl.h>
struct doc_priv {
unsigned long virtadr;
@@ -31,6 +33,16 @@
int curchip;
};
+static char inftl_bbt_pattern[] = "MSYS_BBT";
+
+static struct nand_bbt_descr inftl_bbt_descr = {
+ .options =NAND_BBT_LASTBLOCK | NAND_BBT_8BIT,
+ .offs =8,
+ .len = 8,
+ .maxblocks = 4,
+ .pattern = inftl_bbt_pattern
+};
+
#define DoC_is_Millennium(doc) ((doc)->ChipID == DOC_ChipID_DocMil)
#define DoC_is_2000(doc) ((doc)->ChipID == DOC_ChipID_Doc2k)
@@ -110,10 +122,12 @@
unsigned long docptr = doc->virtadr;
ReadDOC(docptr, CDSNSlowIO);
+ DoC_Delay(doc, 2);
u_char ret = ReadDOC(docptr, 2k_CDSN_IO);
if (debug) printk("read_byte returns %02x\n", ret);
return ret;
}
+
static void doc2000_writebuf(struct mtd_info *mtd,
const u_char *buf, int len)
{
@@ -180,7 +194,7 @@
return 0;
}
-static uint16_t doc200x_ident_chip(struct mtd_info *mtd, int nr)
+static uint16_t __init doc200x_ident_chip(struct mtd_info *mtd, int nr)
{
struct nand_chip *this = mtd->priv;
struct doc_priv *doc = (void *)this->priv;
@@ -224,7 +238,7 @@
return ret;
}
-static void doc2000_count_chips(struct mtd_info *mtd)
+static void __init doc2000_count_chips(struct mtd_info *mtd)
{
struct nand_chip *this = mtd->priv;
struct doc_priv *doc = (void *)this->priv;
@@ -276,11 +290,12 @@
struct doc_priv *doc = (void *)this->priv;
unsigned long docptr = doc->virtadr;
- ReadDOC(docptr, CDSNSlowIO);
+ //ReadDOC(docptr, CDSNSlowIO);
/* 11.4.5 -- delay twice to allow extended length cycle */
DoC_Delay(doc, 2);
ReadDOC(docptr, ReadPipeInit);
- return ReadDOC(docptr, Mil_CDSN_IO);
+ //return ReadDOC(docptr, Mil_CDSN_IO);
+ return ReadDOC(docptr, LastDataRead);
}
static void doc2001_writebuf(struct mtd_info *mtd,
@@ -314,6 +329,7 @@
/* Terminate read pipeline */
buf[i] = ReadDOC(docptr, LastDataRead);
}
+
static int doc2001_verifybuf(struct mtd_info *mtd,
const u_char *buf, int len)
{
@@ -425,30 +441,250 @@
return 0;
}
-struct doc_priv mydoc = {
- .physadr = 0xd4000,
+static int doc200x_enable_hwecc(struct mtd_info *mtd, int mode)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = (void *)this->priv;
+ unsigned long docptr = doc->virtadr;
+
+ /* Prime the ECC engine */
+ WriteDOC(DOC_ECC_RESET, docptr, ECCConf);
+ if (mode == NAND_ECC_READ)
+ WriteDOC(DOC_ECC_EN, docptr, ECCConf);
+ else
+ WriteDOC(DOC_ECC_EN | DOC_ECC_RW, docptr, ECCConf);
+ return 0;
+}
+
+static int doc2000_calculate_ecc(struct mtd_info *mtd, const u_char *dat,
+ unsigned char *ecc_code)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = (void *)this->priv;
+ unsigned long docptr = doc->virtadr;
+ volatile char dummy;
+ int stat;
+ int i;
+ // DBB : check for NULL ecc_code here?
+
+ // DBB note: I'm using the presence of dat as a read/write indicator. FIX ME!
+ if (dat) {
+ // DBB: this is assumed to be a read
+ // flush the pipeline:
+ dummy = ReadDOC(docptr, 2k_ECCStatus);
+ dummy = ReadDOC(docptr, 2k_ECCStatus);
+ dummy = ReadDOC(docptr, 2k_ECCStatus);
+ stat = dummy & 0x80;
+ } else {
+ // DBB: this is assumed to be a write
+ WriteDOC(doc->CDSNControl & ~CDSN_CTRL_FLASH_IO, docptr, CDSNControl);
+ WriteDOC(0, docptr, 2k_CDSN_IO);
+ WriteDOC(0, docptr, 2k_CDSN_IO);
+ WriteDOC(0, docptr, 2k_CDSN_IO);
+ WriteDOC(doc->CDSNControl, docptr, CDSNControl);
+ stat = 1;
+ }
+ if (stat) {
+ for (i = 0; i < 6; i++)
+ ecc_code[i] = ReadDOC_(docptr, DoC_ECCSyndrome0 + i);
+ } else {
+ /* On read, if there were no ECC errors, the syndrome is by
+ definition zero. We can skip actually reading it. */
+ memset(ecc_code, 0, 6);
+ }
+ WriteDOC(DOC_ECC_DIS, docptr, ECCConf);
+ return stat;
+}
+
+static int doc2001_calculate_ecc(struct mtd_info *mtd, const u_char *dat,
+ unsigned char *ecc_code)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = (void *)this->priv;
+ unsigned long docptr = doc->virtadr;
+ volatile char dummy;
+ int stat;
+ int i;
+ // DBB : check for NULL ecc_code here?
+
+ // DBB note: I'm using the presence of dat as a read/write indicator. FIX ME!
+ if (dat) {
+ // DBB: this is assumed to be a read
+ // flush the pipeline:
+ dummy = ReadDOC(docptr, ECCConf);
+ dummy = ReadDOC(docptr, ECCConf);
+ dummy = ReadDOC(docptr, ECCConf);
+ stat = dummy & 0x80;
+ } else {
+ // DBB: this is assumed to be a write
+ WriteDOC(0, docptr, NOP);
+ WriteDOC(0, docptr, NOP);
+ WriteDOC(0, docptr, NOP);
+ stat = 1;
+ }
+ if (stat) {
+ for (i = 0; i < 6; i++)
+ ecc_code[i] = ReadDOC_(docptr, DoC_ECCSyndrome0 + i);
+ } else {
+ /* On read, if there were no ECC errors, the syndrome is by
+ definition zero. We can skip actually reading it. */
+ memset(ecc_code, 0, 6);
+ }
+ WriteDOC(DOC_ECC_DIS, docptr, ECCConf);
+ return stat;
+}
+
+static int doc200x_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc)
+{
+ int ret = doc_decode_ecc(dat, calc_ecc);
+ printk(KERN_ERR "doc200x_correct_data corrected %d errors\n", ret);
+ return ret;
+}
+
+static struct doc_priv mydoc = {
+ .physadr = 0xd0000,
.curfloor = -1,
.curchip = -1,
};
-u_char mydatabuf[528];
+//u_char mydatabuf[528];
-struct nand_chip mynand = {
+static struct nand_oobinfo doc200x_oobinfo = {
+ .useecc = MTD_NANDECC_AUTOPLACE,
+ .eccbytes = 6,
+ .eccpos = {0, 1, 2, 3, 4, 5},
+ .oobfree = { {8, 8} }
+};
+
+static struct nand_chip mynand = {
.priv = (void *)&mydoc,
.select_chip = doc200x_select_chip,
.hwcontrol = doc200x_hwcontrol,
.dev_ready = doc200x_dev_ready,
.waitfunc = doc200x_wait,
.block_bad = doc200x_block_bad,
- .eccmode = NAND_ECC_SOFT,
- .data_buf = mydatabuf,
+ .eccmode = NAND_ECC_DISKONCHIP,
+ //.data_buf = mydatabuf,
+ .options = NAND_USE_FLASH_BBT,
+ .autooob = &doc200x_oobinfo,
+ .correct_data = doc200x_correct_data,
+ .enable_hwecc = doc200x_enable_hwecc
};
-struct mtd_info mymtd = {
+static struct mtd_info mymtd = {
.priv = (void *)&mynand,
.owner = THIS_MODULE,
};
+/* This is a stripped-down copy of the code in inftlmount.c */
+static int __init inftl_partscan(struct mtd_info *mtd)
+{
+ u_char buf[SECTORSIZE];
+ struct INFTLMediaHeader *mh = (struct INFTLMediaHeader *) &buf;
+ int offs;
+ for (offs = 0; offs < mtd->size; offs += mtd->erasesize) {
+ int ret, retlen;
+ if ((ret = MTD_READ(mtd, offs, SECTORSIZE, &retlen, buf)))
+ continue;
+ if (retlen < sizeof(struct INFTLMediaHeader)) continue;
+//printk(KERN_ERR "Read %d bytes at %d, string is %s\n", retlen, offs, buf);
+ if (!memcmp(mh->bootRecordID, "BNAND", 6)) break;
+ }
+ if (offs >= mtd->size) {
+ printk(KERN_WARNING "INFTL Media Header not found.\n");
+ return 0;
+ }
+
+ mh->NoOfBootImageBlocks = le32_to_cpu(mh->NoOfBootImageBlocks);
+ mh->NoOfBinaryPartitions = le32_to_cpu(mh->NoOfBinaryPartitions);
+ mh->NoOfBDTLPartitions = le32_to_cpu(mh->NoOfBDTLPartitions);
+ mh->BlockMultiplierBits = le32_to_cpu(mh->BlockMultiplierBits);
+ mh->FormatFlags = le32_to_cpu(mh->FormatFlags);
+ mh->PercentUsed = le32_to_cpu(mh->PercentUsed);
+
+#ifdef CONFIG_MTD_DEBUG_VERBOSE
+ if (CONFIG_MTD_DEBUG_VERBOSE >= 2)
+ printk(KERN_ERR "Found INFTL Media Header at 0x%x:\n"
+ " bootRecordID = %s\n"
+ " NoOfBootImageBlocks = %d\n"
+ " NoOfBinaryPartitions = %d\n"
+ " NoOfBDTLPartitions = %d\n"
+ " BlockMultiplerBits = %d\n"
+ " FormatFlgs = %d\n"
+ " OsakVersion = 0x%x\n"
+ " PercentUsed = %d\n",
+ offs,
+ mh->bootRecordID, mh->NoOfBootImageBlocks,
+ mh->NoOfBinaryPartitions,
+ mh->NoOfBDTLPartitions,
+ mh->BlockMultiplierBits, mh->FormatFlags,
+ mh->OsakVersion, mh->PercentUsed);
+#endif
+
+ if (mh->BlockMultiplierBits != 0) {
+ printk(KERN_ERR "Currently only BlockMultiplierBits=0 is supported.\n");
+ return 0;
+ }
+
+ struct mtd_partition parts[6];
+ memset((char *) parts, 0, sizeof(parts));
+ int numparts = 0;
+ int lastblock = 0;
+
+ /* Scan the partitions */
+ int i;
+ struct INFTLPartition *ip;
+ for (i = 0; (i < 4); i++) {
+ ip = &(mh->Partitions[i]);
+ ip->virtualUnits = le32_to_cpu(ip->virtualUnits);
+ ip->firstUnit = le32_to_cpu(ip->firstUnit);
+ ip->lastUnit = le32_to_cpu(ip->lastUnit);
+ ip->flags = le32_to_cpu(ip->flags);
+ ip->spareUnits = le32_to_cpu(ip->spareUnits);
+ ip->Reserved0 = le32_to_cpu(ip->Reserved0);
+
+#ifdef CONFIG_MTD_DEBUG_VERBOSE
+ if (CONFIG_MTD_DEBUG_VERBOSE >= 2)
+ printk( " PARTITION[%d] ->\n"
+ " virtualUnits = %d\n"
+ " firstUnit = %d\n"
+ " lastUnit = %d\n"
+ " flags = 0x%x\n"
+ " spareUnits = %d\n",
+ i, ip->virtualUnits, ip->firstUnit,
+ ip->lastUnit, ip->flags,
+ ip->spareUnits);
+#endif
+
+ if ((i == 0) && (ip->firstUnit > 0)) {
+ parts[0].name = "DiskOnChip IPL / Media Header partition";
+ parts[0].offset = 0;
+ parts[0].size = mtd->erasesize * ip->firstUnit;
+ numparts = 1;
+ }
+
+ if (ip->flags & INFTL_BINARY)
+ parts[numparts].name = "DiskOnChip BDK partition";
+ else
+ parts[numparts].name = "DiskOnChip BDTL partition";
+ parts[numparts].offset = mtd->erasesize * ip->firstUnit;
+ parts[numparts].size = mtd->erasesize * (1 + ip->lastUnit - ip->firstUnit);
+ numparts++;
+ if (ip->lastUnit > lastblock) lastblock = ip->lastUnit;
+ if (ip->flags & INFTL_LAST) break;
+ }
+ lastblock++;
+ if ((mtd->erasesize * lastblock) < mtd->size) {
+ parts[numparts].name = "DiskOnChip Remainder partition";
+ parts[numparts].offset = mtd->erasesize * lastblock;
+ parts[numparts].size = mtd->size - parts[numparts].offset;
+ numparts++;
+ }
+ add_mtd_partitions(mtd, parts, numparts);
+ return 1;
+}
+
+
int __init init_nanddoc(void)
{
mydoc.virtadr = (unsigned long)ioremap(mydoc.physadr, DOC_IOREMAP_LEN);
@@ -474,6 +710,9 @@
mynand.write_buf = doc2001_writebuf;
mynand.read_buf = doc2001_readbuf;
mynand.verify_buf = doc2001_verifybuf;
+ mynand.bbt_td = &inftl_bbt_descr;
+ mynand.calculate_ecc = doc2001_calculate_ecc;
+ //mynand.scan_bbt = nftl_scan_bbt;
ReadDOC(mydoc.virtadr, ChipID);
ReadDOC(mydoc.virtadr, ChipID);
@@ -499,11 +738,12 @@
mynand.write_buf = doc2000_writebuf;
mynand.read_buf = doc2000_readbuf;
mynand.verify_buf = doc2000_verifybuf;
+ mynand.calculate_ecc = doc2000_calculate_ecc;
doc2000_count_chips(&mymtd);
nrchips = 4 * mydoc.chips_per_floor;
name = "DiskOnChip 2000 (NFTL Model)";
- mydoc.CDSNControl |= CDSN_CTRL_FLASH_IO;
+ mydoc.CDSNControl |= CDSN_CTRL_FLASH_IO|CDSN_CTRL_ECC_IO;
break;
@@ -516,12 +756,14 @@
}
mymtd.name = name;
add_mtd_device(&mymtd);
+ if (DoC_is_Millennium(&mydoc)) inftl_partscan(&mymtd);
return 0;
}
void __exit cleanup_nanddoc(void)
{
+ del_mtd_partitions(&mymtd);
del_mtd_device(&mymtd);
iounmap((void *)mydoc.virtadr);
}
diff -u --recursive mtd-20040615/drivers/mtd/nand/nand_base.c mtd/drivers/mtd/nand/nand_base.c
--- mtd-20040615/drivers/mtd/nand/nand_base.c 2004-06-07 18:00:15.000000000 -0400
+++ mtd/drivers/mtd/nand/nand_base.c 2004-06-16 07:37:48.000000000 -0400
@@ -830,6 +830,7 @@
case NAND_ECC_HW8_512:
eccbytes += 2;
/* Hardware ecc 6 byte / 512 byte data */
+ case NAND_ECC_DISKONCHIP:
case NAND_ECC_HW6_512:
eccbytes += 3;
/* Hardware ecc 3 byte / 256 data */
@@ -1013,6 +1014,7 @@
int *oob_config, datidx;
int blockcheck = (mtd->erasesize >> this->page_shift) - 1;
int eccbytes = 3;
+ int do_correct_data;
DEBUG (MTD_DEBUG_LEVEL3, "nand_read_ecc: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len);
@@ -1053,6 +1055,7 @@
ecc = this->eccsize;
switch (eccmode) {
case NAND_ECC_HW6_512: /* Hardware ECC 6 byte / 512 byte data */
+ case NAND_ECC_DISKONCHIP:
eccbytes = 6;
break;
case NAND_ECC_HW8_512: /* Hardware ECC 8 byte / 512 byte data */
@@ -1096,6 +1099,8 @@
oob_data = &this->data_buf[end];
eccsteps = this->eccsteps;
+ do_correct_data = 1;
+ ecc_status = 0;
switch (eccmode) {
case NAND_ECC_NONE: { /* No ECC, Read in a page */
@@ -1125,13 +1130,29 @@
}
break;
+ case NAND_ECC_DISKONCHIP:
+ /* On DOC, we have to read data + OOB-stored syndrome,
+ then read the hw-computed syndrome, then the rest
+ of the OOB data. */
+ this->enable_hwecc(mtd, NAND_ECC_READ);
+ this->read_buf(mtd, data_poi, ecc);
+ this->read_buf(mtd, oob_data, eccbytes);
+ /* On DOC (at least), we have a ECC error register bit
+ that allows us to skip the tedious process of reading
+ the hw-computed syndrome, checking for and correcting
+ errors, etc. */
+ do_correct_data = this->calculate_ecc(mtd, data_poi, ecc_calc);
+ break;
default:
printk (KERN_WARNING "Invalid NAND_ECC_MODE %d\n", this->eccmode);
BUG();
}
/* read oobdata */
- this->read_buf(mtd, oob_data, mtd->oobsize);
+ if (eccmode == NAND_ECC_DISKONCHIP)
+ this->read_buf(mtd, oob_data + eccbytes, mtd->oobsize - eccbytes);
+ else
+ this->read_buf(mtd, oob_data, mtd->oobsize);
/* Skip ECC, if not active */
if (eccmode == NAND_ECC_NONE)
@@ -1143,7 +1164,8 @@
/* correct data, if neccecary */
for (i = 0, j = 0, datidx = 0; i < this->eccsteps; i++, datidx += ecc) {
- ecc_status = this->correct_data(mtd, &data_poi[datidx], &ecc_code[j], &ecc_calc[j]);
+ if (do_correct_data)
+ ecc_status = this->correct_data(mtd, &data_poi[datidx], &ecc_code[j], &ecc_calc[j]);
/* Get next chunk of ecc bytes */
j += eccbytes;
@@ -2384,6 +2406,7 @@
case NAND_ECC_HW3_512:
case NAND_ECC_HW6_512:
case NAND_ECC_HW8_512:
+ case NAND_ECC_DISKONCHIP:
if (mtd->oobblock == 256) {
printk (KERN_WARNING "512 byte HW ECC not possible on 256 Byte pagesize, fallback to SW ECC \n");
this->eccmode = NAND_ECC_SOFT;
diff -u --recursive mtd-20040615/drivers/mtd/nand/nand_bbt.c mtd/drivers/mtd/nand/nand_bbt.c
--- mtd-20040615/drivers/mtd/nand/nand_bbt.c 2004-06-04 18:00:12.000000000 -0400
+++ mtd/drivers/mtd/nand/nand_bbt.c 2004-06-16 07:40:34.000000000 -0400
@@ -794,7 +794,7 @@
/* Prevent the bbt regions from erasing / writing */
mark_bbt_region (mtd, td);
- mark_bbt_region (mtd, md);
+ if (md) mark_bbt_region (mtd, md);
kfree (buf);
return res;
diff -u --recursive mtd-20040615/include/linux/mtd/nand.h mtd/include/linux/mtd/nand.h
--- mtd-20040615/include/linux/mtd/nand.h 2004-06-04 18:00:13.000000000 -0400
+++ mtd/include/linux/mtd/nand.h 2004-06-16 07:49:07.000000000 -0400
@@ -281,7 +281,7 @@
int (*dev_ready)(struct mtd_info *mtd);
void (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column, int page_addr);
int (*waitfunc)(struct mtd_info *mtd, struct nand_chip *this, int state);
- void (*calculate_ecc)(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code);
+ int (*calculate_ecc)(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code);
int (*correct_data)(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc);
void (*enable_hwecc)(struct mtd_info *mtd, int mode);
void (*erase_cmd)(struct mtd_info *mtd, int page);
next reply other threads:[~2004-06-16 14:07 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2004-06-16 14:19 Dan Brown [this message]
2004-06-16 22:32 ` [PATCH] Towards DiskOnChip support as a NAND driver Thomas Gleixner
2004-06-16 22:46 ` David Woodhouse
2004-06-16 22:50 ` Thomas Gleixner
[not found] <E1BacpZ-0008CJ-KT@canuck.infradead.org>
2004-06-16 16:13 ` Slim
2004-06-16 20:23 ` Dan Brown
2004-06-16 21:47 ` Thomas Gleixner
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='073c01c453ac$de849e10$0100a8c0@superfortress' \
--to=dan_brown@ieee.org \
--cc=linux-mtd@lists.infradead.org \
--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