Scott Wood wrote:
[EMAIL PROTECTED] wrote:
+unsigned char cs;
+volatile unsigned long gpmc_cs_base_add;
Make these static. gpmc_cs_base_add should be a pointer, not "unsigned
long". Volatile isn't needed since you use I/O accessors, and
definitely isn't needed on the address itself.
Changed in attachment.
+/*
+ * omap_nand_hwcontrol - Set the address pointers corretly for the
+ * following address/data/command operation
+ */
+static void omap_nand_hwcontrol(struct mtd_info *mtd, int cmd,
+ unsigned int ctrl)
+{
+ register struct nand_chip *this = mtd->priv;
+
+ /* Point the IO_ADDR to DATA and ADDRESS registers instead
+ of chip address */
+ switch (ctrl) {
+ case NAND_CTRL_CHANGE | NAND_CTRL_CLE:
+ this->IO_ADDR_W = (void *) gpmc_cs_base_add + GPMC_NAND_CMD;
+ this->IO_ADDR_R = (void *) gpmc_cs_base_add + GPMC_NAND_DAT;
+ break;
+ case NAND_CTRL_CHANGE | NAND_CTRL_ALE:
+ this->IO_ADDR_W = (void *) gpmc_cs_base_add + GPMC_NAND_ADR;
+ this->IO_ADDR_R = (void *) gpmc_cs_base_add + GPMC_NAND_DAT;
+ break;
+ case NAND_CTRL_CHANGE | NAND_NCE:
+ this->IO_ADDR_W = (void *) gpmc_cs_base_add + GPMC_NAND_DAT;
+ this->IO_ADDR_R = (void *) gpmc_cs_base_add + GPMC_NAND_DAT;
+ break;
+ }
IO_ADDR_R never seems to change; you can leave it out of here and
omap_nand_wait.
Yes, thanks. Removed in attachment.
+/*
+ * omap_nand_wait - called primarily after a program/erase operation
+ * so that we access NAND again only after the device
+ * is ready again.
+ * @mtd: MTD device structure
+ * @chip: nand_chip structure
+ */
+static int omap_nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
+{
+ register struct nand_chip *this = mtd->priv;
+ int status = 0;
+
+ this->IO_ADDR_W = (void *) gpmc_cs_base_add + GPMC_NAND_CMD;
+ this->IO_ADDR_R = (void *) gpmc_cs_base_add + GPMC_NAND_DAT;
+ /* Send the status command and loop until the device is free */
+ while (!(status & 0x40)) {
+ writeb(NAND_CMD_STATUS & 0xFF, this->IO_ADDR_W);
+ status = readb(this->IO_ADDR_R);
+ }
Maybe should just do this, to avoid changing client-visible state:
writeb(NAND_CMD_STATUS, &gpmc_cs_base_add[GPMC_NAND_CMD]);
No need for the "& 0xFF".
Modified.
+ /* Init ECC Control Register */
+ /* Clear all ECC | Enable Reg1 */
+ val = ((0x00000001 << 8) | 0x00000001);
+ writel(val, GPMC_BASE + GPMC_ECC_CONTROL);
+ writel(0x3fcff000, GPMC_BASE + GPMC_ECC_SIZE_CONFIG);
Symbolic constants for the bit values would be nice.
Added. Hope you like them ;)
+/*
+ * omap_calculate_ecc - Generate non-inverted ECC bytes.
+ *
+ * Using noninverted ECC can be considered ugly since writing a blank
+ * page ie. padding will clear the ECC bytes. This is no problem as
+ * long nobody is trying to write data on the seemingly unused page.
+ * Reading an erased page will produce an ECC mismatch between
+ * generated and read ECC bytes that has to be dealt with separately.
Where is it dealt with separately?
We already talked about this and extended the comment. To my
understanding this special handling can't be done in
omap_calculate_ecc() as it is called from generic NAND code and
doesn't know if ECC it calculates is correct or not?
Do you have any proposals where and how to handle this?
Any propsals from OMAP experts?
Easiest short term solution would be to add a "FIXME" to comment?
+ unsigned long val = 0x0;
Unnecessary initialization.
Removed.
+ unsigned long reg;
+
+ /* Start Reading from HW ECC1_Result = 0x200 */
+ reg = (unsigned long) (GPMC_BASE + GPMC_ECC1_RESULT);
+ val = readl(reg);
readl() takes a pointer. ARM gets away without a warning here because
it uses macros rather than inline functions, but it's bad practice.
Helper variable removed.
+ /* Stop reading anymore ECC vals and clear old results
+ * enable will be called if more reads are required */
+ reg = (unsigned long) (GPMC_BASE + GPMC_ECC_CONFIG);
+ writel(0x000, reg);
Likewise.
Ditto.
+void omap_nand_switch_ecc(int hardware)
+{
+ struct nand_chip *nand;
+
+ if (nand_curr_device < 0 ||
+ nand_curr_device >= CONFIG_SYS_MAX_NAND_DEVICE ||
+ !nand_info[nand_curr_device].name) {
+ printf("Error: Can't switch ecc, no devices available\n");
+ return;
+ }
+
+ nand = (&nand_info[nand_curr_device])->priv;
+
+ if (!hardware) {
+ nand->ecc.mode = NAND_ECC_SOFT;
+ nand->ecc.layout = &sw_nand_oob_64;
+ nand->ecc.size = 256; /* set default eccsize */
+ nand->ecc.bytes = 3;
+ nand->ecc.steps = 8;
+ nand->ecc.hwctl = 0;
+ nand->ecc.calculate = nand_calculate_ecc;
+ nand->ecc.correct = nand_correct_data;
+ } else {
+ nand->ecc.mode = NAND_ECC_HW;
+ nand->ecc.layout = &hw_nand_oob_64;
+ nand->ecc.size = 512;
+ nand->ecc.bytes = 3;
+ nand->ecc.steps = 4;
+ nand->ecc.hwctl = omap_enable_hwecc;
+ nand->ecc.correct = omap_correct_data;
+ nand->ecc.calculate = omap_calculate_ecc;
+ omap_hwecc_init(nand);
+ }
Do you need to do anything similar to omap_hwecc_init() when switching
to SW ECC to tell the hardware to stop doing ECC?
I don't think so, but maybe the experts can help here.
Comment in omap_calculate_ecc() tells us:
/* Stop reading anymore ECC vals and clear old result, enable will be
called if more reads are required */
Sounds like an "auto stop and re-init if needed" (in omap_enable_hwecc()).
To summarize: If you agree with changes in attachment, last open point
is the "ECC mismatch" issue. Do you agree?
Thanks for re-review!
Dirk
Subject: [PATCH 08/13 v4] ARM: OMAP3: Add NAND support
From: Dirk Behme <[EMAIL PROTECTED]>
Add NAND support
Signed-off-by: Dirk Behme <[EMAIL PROTECTED]>
---
Changes in version v4:
- Incorporate further review results from Scott Wood.
Changes in version v3:
- Fix/update NAND driver and seperate it into an own patch as proposed by Scott
Wood
drivers/mtd/nand/Makefile | 1
drivers/mtd/nand/omap3.c | 376 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 377 insertions(+)
Index: u-boot-arm/drivers/mtd/nand/Makefile
===================================================================
--- u-boot-arm.orig/drivers/mtd/nand/Makefile
+++ u-boot-arm/drivers/mtd/nand/Makefile
@@ -38,6 +38,7 @@ endif
COBJS-$(CONFIG_NAND_FSL_ELBC) += fsl_elbc_nand.o
COBJS-$(CONFIG_NAND_FSL_UPM) += fsl_upm.o
COBJS-$(CONFIG_NAND_S3C64XX) += s3c64xx.o
+COBJS-$(CONFIG_NAND_OMAP3) += omap3.o
endif
COBJS := $(COBJS-y)
Index: u-boot-arm/drivers/mtd/nand/omap3.c
===================================================================
--- /dev/null
+++ u-boot-arm/drivers/mtd/nand/omap3.c
@@ -0,0 +1,376 @@
+/*
+ * (C) Copyright 2004-2008 Texas Instruments, <www.ti.com>
+ * Rohit Choraria <[EMAIL PROTECTED]>
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <common.h>
+#include <asm/io.h>
+#include <asm/arch/mem.h>
+#include <linux/mtd/nand_ecc.h>
+#include <nand.h>
+
+static unsigned char cs;
+static void __iomem *gpmc_cs_base_add;
+
+#define GPMC_BUF_EMPTY 0
+#define GPMC_BUF_FULL 1
+
+#define ECCCLEAR (0x1 << 8)
+#define ECCRESULTREG1 (0x1 << 0)
+#define ECCSIZE512BYTE 0xFF
+#define ECCSIZE1 (ECCSIZE512BYTE << 22)
+#define ECCSIZE0 (ECCSIZE512BYTE << 12)
+#define ECCSIZE0SEL (0x000 << 0)
+
+/*
+ * omap_nand_hwcontrol - Set the address pointers corretly for the
+ * following address/data/command operation
+ */
+static void omap_nand_hwcontrol(struct mtd_info *mtd, int cmd,
+ unsigned int ctrl)
+{
+ register struct nand_chip *this = mtd->priv;
+
+ /* Point the IO_ADDR to DATA and ADDRESS registers instead
+ of chip address */
+ switch (ctrl) {
+ case NAND_CTRL_CHANGE | NAND_CTRL_CLE:
+ this->IO_ADDR_W = gpmc_cs_base_add + GPMC_NAND_CMD;
+ break;
+ case NAND_CTRL_CHANGE | NAND_CTRL_ALE:
+ this->IO_ADDR_W = gpmc_cs_base_add + GPMC_NAND_ADR;
+ break;
+ case NAND_CTRL_CHANGE | NAND_NCE:
+ this->IO_ADDR_W = gpmc_cs_base_add + GPMC_NAND_DAT;
+ break;
+ }
+
+ if (cmd != NAND_CMD_NONE)
+ writeb(cmd, this->IO_ADDR_W);
+}
+
+/*
+ * omap_nand_wait - called primarily after a program/erase operation
+ * so that we access NAND again only after the device
+ * is ready again.
+ * @mtd: MTD device structure
+ * @chip: nand_chip structure
+ */
+static int omap_nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
+{
+ register struct nand_chip *this = mtd->priv;
+ int status = 0;
+
+ /* Send the status command and loop until the device is free */
+ while (!(status & 0x40)) {
+ writeb(NAND_CMD_STATUS, gpmc_cs_base_add + GPMC_NAND_CMD);
+ status = readb(this->IO_ADDR_R);
+ }
+ return status;
+}
+
+/*
+ * omap_nand_write_buf16 - [DEFAULT] write buffer to chip
+ * @mtd: MTD device structure
+ * @buf: data buffer
+ * @len: number of bytes to write
+ *
+ * Default write function for 16bit buswith
+ */
+static void omap_nand_write_buf16(struct mtd_info *mtd, const u_char *buf,
+ int len)
+{
+ int i;
+ struct nand_chip *this = mtd->priv;
+ u16 *p = (u16 *) buf;
+ len >>= 1;
+
+ for (i = 0; i < len; i++) {
+ writew(p[i], this->IO_ADDR_W);
+ while (GPMC_BUF_EMPTY == (readl(GPMC_STATUS) & GPMC_BUF_FULL));
+ }
+}
+
+/*
+ * omap_hwecc_init - Initialize the Hardware ECC for NAND flash in
+ * GPMC controller
+ * @mtd: MTD device structure
+ *
+ */
+static void omap_hwecc_init(struct nand_chip *chip)
+{
+ /* Init ECC Control Register */
+ /* Clear all ECC | Enable Reg1 */
+ writel(ECCCLEAR | ECCRESULTREG1, GPMC_BASE + GPMC_ECC_CONTROL);
+ writel(ECCSIZE1 | ECCSIZE0 | ECCSIZE0SEL,
+ GPMC_BASE + GPMC_ECC_SIZE_CONFIG);
+}
+
+/*
+ * gen_true_ecc - This function will generate true ECC value, which
+ * can be used when correcting data read from NAND flash memory core
+ *
+ * @ecc_buf: buffer to store ecc code
+ *
+ * @return: re-formatted ECC value
+ */
+static unsigned int gen_true_ecc(u8 *ecc_buf)
+{
+ return ecc_buf[0] | (ecc_buf[1] << 16) | ((ecc_buf[2] & 0xF0) << 20) |
+ ((ecc_buf[2] & 0x0F) << 8);
+}
+
+/*
+ * omap_correct_data - Compares the ecc read from nand spare area with ECC
+ * registers values and corrects one bit error if it has occured
+ * Further details can be had from OMAP TRM and the following selected links:
+ * http://en.wikipedia.org/wiki/Hamming_code
+ *
http://www.cs.utexas.edu/users/plaxton/c/337/05f/slides/ErrorCorrection-4.pdf
+ *
+ * @mtd: MTD device structure
+ * @dat: page data
+ * @read_ecc: ecc read from nand flash
+ * @calc_ecc: ecc read from ECC registers
+ *
+ * @return 0 if data is OK or corrected, else returns -1
+ */
+static int omap_correct_data(struct mtd_info *mtd, u_char *dat,
+ u_char *read_ecc, u_char *calc_ecc)
+{
+ unsigned int orig_ecc, new_ecc, res, hm;
+ unsigned short parity_bits, byte;
+ unsigned char bit;
+
+ /* Regenerate the orginal ECC */
+ orig_ecc = gen_true_ecc(read_ecc);
+ new_ecc = gen_true_ecc(calc_ecc);
+ /* Get the XOR of real ecc */
+ res = orig_ecc ^ new_ecc;
+ if (res) {
+ /* Get the hamming width */
+ hm = hweight32(res);
+ /* Single bit errors can be corrected! */
+ if (hm == 12) {
+ /* Correctable data! */
+ parity_bits = res >> 16;
+ bit = (parity_bits & 0x7);
+ byte = (parity_bits >> 3) & 0x1FF;
+ /* Flip the bit to correct */
+ dat[byte] ^= (0x1 << bit);
+ } else if (hm == 1) {
+ printf("Error: Ecc is wrong\n");
+ /* ECC itself is corrupted */
+ return 2;
+ } else {
+ printf("Error: Bad compare! failed\n");
+ /* detected 2 bit error */
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * omap_calculate_ecc - Generate non-inverted ECC bytes.
+ *
+ * Using noninverted ECC can be considered ugly since writing a blank
+ * page ie. padding will clear the ECC bytes. This is no problem as
+ * long nobody is trying to write data on the seemingly unused page.
+ * Reading an erased page will produce an ECC mismatch between
+ * generated and read ECC bytes that has to be dealt with separately.
+ * E.g. if page is 0xFF (fresh erased), and if HW ECC engine within GPMC
+ * is used, the result of read will be 0x0 while the ECC offsets of the
+ * spare area will be 0xFF which will result in an ECC mismatch.
+ * @mtd: MTD structure
+ * @dat: unused
+ * @ecc_code: ecc_code buffer
+ */
+static int omap_calculate_ecc(struct mtd_info *mtd, const u_char *dat,
+ u_char *ecc_code)
+{
+ unsigned long val;
+
+ /* Start Reading from HW ECC1_Result = 0x200 */
+ val = readl(GPMC_BASE + GPMC_ECC1_RESULT);
+
+ ecc_code[0] = val & 0xFF;
+ ecc_code[1] = (val >> 16) & 0xFF;
+ ecc_code[2] = ((val >> 8) & 0x0F) | ((val >> 20) & 0xF0);
+
+ /* Stop reading anymore ECC vals and clear old results
+ * enable will be called if more reads are required */
+ writel(0x000, GPMC_BASE + GPMC_ECC_CONFIG);
+
+ return 0;
+}
+
+/*
+ * omap_enable_ecc - This function enables the hardware ecc functionality
+ * @mtd: MTD device structure
+ * @mode: Read/Write mode
+ */
+static void omap_enable_hwecc(struct mtd_info *mtd, int mode)
+{
+ struct nand_chip *chip = mtd->priv;
+ unsigned int val, dev_width = (chip->options & NAND_BUSWIDTH_16) >> 1;
+
+ switch (mode) {
+ case NAND_ECC_READ:
+ case NAND_ECC_WRITE:
+ /* Clear the ecc result registers, select ecc reg as 1 */
+ writel(ECCCLEAR | ECCRESULTREG1, GPMC_BASE + GPMC_ECC_CONTROL);
+ /* Size 0 = 0xFF, Size1 is 0xFF - both are 512 bytes
+ * tell all regs to generate size0 sized regs
+ * we just have a single ECC engine for all CS
+ */
+ writel(ECCSIZE1 | ECCSIZE0 | ECCSIZE0SEL,
+ GPMC_BASE + GPMC_ECC_SIZE_CONFIG);
+ val = (dev_width << 7) | (cs << 1) | (0x1);
+ writel(val, GPMC_BASE + GPMC_ECC_CONFIG);
+ break;
+ default:
+ printf("Error: Unrecognized Mode[%d]!\n", mode);
+ break;
+ }
+}
+
+static struct nand_ecclayout hw_nand_oob_64 = {
+ .eccbytes = 12,
+ .eccpos = {
+ 2, 3, 4, 5,
+ 6, 7, 8, 9,
+ 10, 11, 12, 13},
+ .oobfree = { {14, 50} }
+};
+
+static struct nand_ecclayout sw_nand_oob_64 = {
+ .eccbytes = 24,
+ .eccpos = {
+ 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55,
+ 56, 57, 58, 59, 60, 61, 62, 63},
+ .oobfree = { {2, 38} }
+};
+
+void omap_nand_switch_ecc(int hardware)
+{
+ struct nand_chip *nand;
+
+ if (nand_curr_device < 0 ||
+ nand_curr_device >= CONFIG_SYS_MAX_NAND_DEVICE ||
+ !nand_info[nand_curr_device].name) {
+ printf("Error: Can't switch ecc, no devices available\n");
+ return;
+ }
+
+ nand = (&nand_info[nand_curr_device])->priv;
+
+ if (!hardware) {
+ nand->ecc.mode = NAND_ECC_SOFT;
+ nand->ecc.layout = &sw_nand_oob_64;
+ nand->ecc.size = 256; /* set default eccsize */
+ nand->ecc.bytes = 3;
+ nand->ecc.steps = 8;
+ nand->ecc.hwctl = 0;
+ nand->ecc.calculate = nand_calculate_ecc;
+ nand->ecc.correct = nand_correct_data;
+ } else {
+ nand->ecc.mode = NAND_ECC_HW;
+ nand->ecc.layout = &hw_nand_oob_64;
+ nand->ecc.size = 512;
+ nand->ecc.bytes = 3;
+ nand->ecc.steps = 4;
+ nand->ecc.hwctl = omap_enable_hwecc;
+ nand->ecc.correct = omap_correct_data;
+ nand->ecc.calculate = omap_calculate_ecc;
+ omap_hwecc_init(nand);
+ }
+}
+
+/*
+ * Board-specific NAND initialization. The following members of the
+ * argument are board-specific:
+ * - IO_ADDR_R: address to read the 8 I/O lines of the flash device
+ * - IO_ADDR_W: address to write the 8 I/O lines of the flash device
+ * - cmd_ctrl: hardwarespecific function for accesing control-lines
+ * - waitfunc: hardwarespecific function for accesing device ready/busy line
+ * - ecc.hwctl: function to enable (reset) hardware ecc generator
+ * - ecc.mode: mode of ecc, see defines
+ * - chip_delay: chip dependent delay for transfering data from array to
+ * read regs (tR)
+ * - options: various chip options. They can partly be set to inform
+ * nand_scan about special functionality. See the defines for further
+ * explanation
+ */
+int board_nand_init(struct nand_chip *nand)
+{
+ int gpmc_config = 0;
+ cs = 0;
+ while (cs <= GPMC_MAX_CS) {
+ /* Each GPMC set for a single CS is at offset 0x30 */
+ /* already remapped for us */
+ gpmc_cs_base_add = (GPMC_CONFIG_CS0 + (cs * 0x30));
+ /* xloader/Uboot would have written the NAND type for us
+ * NOTE: This is a temporary measure and cannot handle ONENAND.
+ * The proper way of doing this is to pass the setup of
+ * u-boot up to kernel using kernel params - something on
+ * the lines of machineID
+ */
+ /* Check if NAND type is set */
+ if ((readl(gpmc_cs_base_add + GPMC_CONFIG1) & 0xC00) ==
+ 0x800) {
+ /* Found it!! */
+ break;
+ }
+ cs++;
+ }
+ if (cs > GPMC_MAX_CS) {
+ printf("NAND: Unable to find NAND settings in " \
+ "GPMC Configuration - quitting\n");
+ }
+
+ gpmc_config = readl(GPMC_CONFIG);
+ /* Disable Write protect */
+ gpmc_config |= 0x10;
+ writel(gpmc_config, GPMC_CONFIG);
+
+ nand->IO_ADDR_R = gpmc_cs_base_add + GPMC_NAND_DAT;
+ nand->IO_ADDR_W = gpmc_cs_base_add + GPMC_NAND_CMD;
+
+ nand->cmd_ctrl = omap_nand_hwcontrol;
+ nand->options = NAND_NO_PADDING | NAND_CACHEPRG | NAND_NO_AUTOINCR |
+ NAND_BUSWIDTH_16 | NAND_NO_AUTOINCR;
+
+ /* Use custom write buf due to additional delay. Read uses default. */
+ nand->write_buf = omap_nand_write_buf16;
+ nand->ecc.mode = NAND_ECC_SOFT;
+ /* if RDY/BSY line is connected to OMAP then use the omap ready
+ * function and the generic nand_wait function which reads the
+ * status register after monitoring the RDY/BSY line. Otherwise
+ * use a standard chip delay which is slightly more than tR
+ * (AC Timing) of the NAND device and read the status register
+ * until you get a failure or success
+ */
+ nand->waitfunc = omap_nand_wait;
+ nand->chip_delay = 50;
+
+ return 0;
+}
_______________________________________________
U-Boot mailing list
U-Boot@lists.denx.de
http://lists.denx.de/mailman/listinfo/u-boot