* [PATCH] PNX8550 NAND flash driver
@ 2005-12-16 17:23 Vladimir A. Barinov
2005-12-20 14:28 ` Vladimir A. Barinov
2006-01-12 18:24 ` Todd Poynor
0 siblings, 2 replies; 8+ messages in thread
From: Vladimir A. Barinov @ 2005-12-16 17:23 UTC (permalink / raw)
To: linux-mtd, linux-mips
[-- Attachment #1: Type: text/plain, Size: 138 bytes --]
Hi All,
Attached patch is NAND flash driver for PNX8550 based platforms.
Any comments and suggestions are highly appreciated.
Vladimir
[-- Attachment #2: nand.patch --]
[-- Type: text/plain, Size: 23135 bytes --]
Signed-off-by: vbarinov@ru.mvista.com
drivers/mtd/nand/Kconfig | 6
drivers/mtd/nand/Makefile | 1
drivers/mtd/nand/pnx8550.c | 747 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 754 insertions(+)
Index: linux-2.6.15_0/drivers/mtd/nand/Kconfig
===================================================================
--- linux-2.6.15_0.orig/drivers/mtd/nand/Kconfig
+++ linux-2.6.15_0/drivers/mtd/nand/Kconfig
@@ -90,6 +90,12 @@ config MTD_NAND_S3C2410
No board specfic support is done by this driver, each board
must advertise a platform_device for the driver to attach.
+config MTD_NAND_PNX8550
+ tristate "NAND Flash support for PNX8550"
+ depends on PNX8550 && MTD_NAND
+ help
+ This enables the NAND flash controller on the PNX8550.
+
config MTD_NAND_S3C2410_DEBUG
bool "S3C2410 NAND driver debug"
depends on MTD_NAND_S3C2410
Index: linux-2.6.15_0/drivers/mtd/nand/pnx8550.c
===================================================================
--- /dev/null
+++ linux-2.6.15_0/drivers/mtd/nand/pnx8550.c
@@ -0,0 +1,747 @@
+/*
+ * Copyright (C) 2005 Koninklijke Philips Electronics N.V.
+ * All Rights Reserved.
+ *
+ * Based on: drivers/mtd/nand/pnx8550.c by Torbjorn Lundberg
+ * $Id: pnx8550_nand.c,v 1.8 2004/11/12 10:46:58 tobbe Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Overview:
+ * This is a device driver for the NAND flash device found on the
+ * PNX8550 board which utilizes the Samsung K9F5616U0C part. This is
+ * a 32MByte (16M x 16 bits) NAND flash device.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/nand_ecc.h>
+#include <linux/mtd/compatmac.h>
+#include <linux/interrupt.h>
+#include <linux/mtd/partitions.h>
+#include <asm/io.h>
+#include <asm/mach-pnx8550/nand.h>
+
+#define UBTM_NAME "microBTM"
+#define UBTM_BLOCK_START ( 0x00000000)
+#define UBTM_BLOCK_END ( 0x00004000) /* 16K size, first block */
+#define UBTM_SIZE ( UBTM_BLOCK_END - UBTM_BLOCK_START)
+
+#define BOOTLOADER_NAME "bootloader"
+#define BOOTLOADER_BLOCK_START ( UBTM_BLOCK_END)
+#define BOOTLOADER_BLOCK_END ( 0x00040000) /* 256K - 16K = 240K */
+#define BOOTLOADER_SIZE ( BOOTLOADER_BLOCK_END - BOOTLOADER_BLOCK_START)
+
+#define ROMFS_SYS_NAME "ROMFS-Tools"
+#define ROMFS_SYS_BLOCK_START ( BOOTLOADER_BLOCK_END)
+#define ROMFS_SYS_BLOCK_END ( 0x00600000) /* 6M - 256K = 5.75M */
+#define ROMFS_SYS_SIZE ( ROMFS_SYS_BLOCK_END - ROMFS_SYS_BLOCK_START)
+
+#define ROMFS_APP_NAME "ROMFS-User"
+#define ROMFS_APP_BLOCK_START ( ROMFS_SYS_BLOCK_END)
+#define ROMFS_APP_BLOCK_END ( 0x01000000) /* 16M - 6M = 10M */
+#define ROMFS_APP_SIZE ( ROMFS_APP_BLOCK_END - ROMFS_APP_BLOCK_START)
+
+#define USER_NAME "User"
+#define USER_BLOCK_START ( ROMFS_APP_BLOCK_END)
+#define USER_BLOCK_END ( 0x02000000) /* 32M - 16M = 16M */
+#define USER_SIZE ( USER_BLOCK_END - USER_BLOCK_START)
+
+#define NAND_ADDR(_col, _page) ((_col) & (mtd->oobblock - 1)) + ((_page) << this->page_shift)
+
+#define NAND_ADDR_SEND(_addr) pNandAddr[(_addr)/sizeof(u16)] = 0
+
+#define NAND_TRANSFER_TO(_addr, _buffer, _bytes) pnx8550_nand_transfer((_buffer), ((u8*)pNandAddr) + (_addr), (_bytes), 1)
+
+#define NAND_TRANSFER_FROM(_addr, _buffer, _bytes) pnx8550_nand_transfer(((u8*)pNandAddr) + (_addr), (_buffer), (_bytes), 0)
+
+static void pnx8550_nand_register_setup(u_char cmd_no, u_char addr_no,
+ u_char include_data, u_char monitor_ACK,
+ u_char enable64M, int cmd_a, int cmd_b);
+
+static inline void pnx8550_nand_wait_for_dev_ready(void);
+
+static void pnx8550_nand_transfer(void *from, void *to, int bytes, int toxio);
+
+static void pnx8550_nand_transferDMA(void *from, void *to, int bytes,
+ int toxio);
+
+/*
+ * Define partitions for flash device
+ */
+#define NUM_PARTITIONS 5
+const static struct mtd_partition partition_info[NUM_PARTITIONS] = {
+ {
+ .name = UBTM_NAME,
+ .offset = UBTM_BLOCK_START,
+ .size = UBTM_SIZE},
+ {
+ .name = BOOTLOADER_NAME,
+ .offset = BOOTLOADER_BLOCK_START,
+ .size = BOOTLOADER_SIZE},
+ {
+ .name = ROMFS_SYS_NAME,
+ .offset = ROMFS_SYS_BLOCK_START,
+ .size = ROMFS_SYS_SIZE},
+ {
+ .name = ROMFS_APP_NAME,
+ .offset = ROMFS_APP_BLOCK_START,
+ .size = ROMFS_APP_SIZE},
+ {
+ .name = USER_NAME,
+ .offset = USER_BLOCK_START,
+ .size = USER_SIZE}
+};
+
+/* Bad block descriptor for 16Bit nand flash */
+static uint8_t scan_ff_pattern[] = { 0xff, 0xff };
+static struct nand_bbt_descr nand16bit_memorybased = {
+ .options = 0,
+ .offs = 0,
+ .len = 2,
+ .pattern = scan_ff_pattern
+};
+
+/* OOB Placement information that lines up with the boot loader code */
+static struct nand_oobinfo nand16bit_oob_16 = {
+ .useecc = MTD_NANDECC_AUTOPLACE,
+ .eccbytes = 6,
+ .eccpos = {2, 3, 4, 5, 6, 7},
+ .oobfree = {{8, 8}}
+};
+
+/* Pointer into XIO for access to the 16Bit NAND flash device */
+static volatile u16 *pNandAddr;
+
+/* Last command sent to the pnx8550_nand_command function */
+static int last_command = -1;
+/*
+ Next column address to read/write, set by pnx8550_nand_command
+ updated by the read/write functions
+*/
+static int last_col_addr = -1;
+/*
+ Next page address to read/write, set by pnx8550_nand_command
+ updated by the read/write functions
+*/
+static int last_page_addr = -1;
+
+/*
+ 32bit Aligned/DMA buffer
+*/
+static u_char *transferBuffer = NULL;
+
+static struct mtd_info pnx8550_mtd;
+static struct nand_chip pnx8550_nand;
+
+/**
+ * Transfer data to/from the NAND chip.
+ * This function decides whether to use DMA or not depending on
+ * the amount of data to transfer and the alignment of the buffers.
+ *
+ * @from: Address to transfer data from
+ * @to: Address to transfer the data to
+ * @bytes: Number of bytes to transfer
+ * @toxio: Whether the transfer is going to XIO or not.
+ */
+static void pnx8550_nand_transfer(void *from, void *to, int bytes, int toxio)
+{
+ u16 *from16 = (u16 *) from;
+ u16 *to16 = (u16 *) to;
+
+ int i;
+
+ if ((u32) from & 3) {
+ printk
+ ("%s: from buffer not 32bit aligned, will not use fastest transfer mechanism\n",
+ __FUNCTION__);
+ }
+ if ((u32) to & 3) {
+ printk
+ ("%s: to buffer not 32bit aligned, will not use fastest transfer mechanism\n",
+ __FUNCTION__);
+ }
+
+ if (((bytes & 3) || (bytes < 16)) || ((u32) to & 3) || ((u32) from & 3)) {
+ if (((bytes & 1) == 0) &&
+ (((u32) to & 1) == 0) && (((u32) from & 1) == 0)) {
+ int words = bytes / 2;
+
+ local_irq_disable();
+ for (i = 0; i < words; i++) {
+ to16[i] = from16[i];
+ }
+ local_irq_enable();
+ } else {
+ printk
+ ("%s: Transfer failed, byte-aligned transfers no allowed!\n",
+ __FUNCTION__);
+ }
+ } else {
+ pnx8550_nand_transferDMA(from, to, bytes, toxio);
+ }
+}
+
+/**
+ * Transfer data to/from the NAND chip using DMA
+ *
+ * @from: Address to transfer data from
+ * @to: Address to transfer the data to
+ * @bytes: Number of bytes to transfer
+ * @toxio: Whether the transfer is going to XIO or not.
+ */
+static void pnx8550_nand_transferDMA(void *from, void *to, int bytes, int toxio)
+{
+ int cmd = 0;
+ u32 internal;
+ u32 external;
+
+ if (toxio) {
+ cmd = PNX8550_DMA_CTRL_PCI_CMD_WRITE;
+ dma_cache_wback(from, bytes);
+ internal = (u32) virt_to_phys(from);
+ external = (u32) to - KSEG1;
+ } else {
+ cmd = PNX8550_DMA_CTRL_PCI_CMD_READ;
+ internal = (u32) virt_to_phys(to);
+ external = (u32) from - KSEG1;
+ }
+
+ local_irq_disable();
+ PNX8550_DMA_TRANS_SIZE = bytes >> 2; /* Length in words */
+ PNX8550_DMA_EXT_ADDR = external;
+ PNX8550_DMA_INT_ADDR = internal;
+ PNX8550_DMA_INT_CLEAR = 0xffff;
+ PNX8550_DMA_CTRL = PNX8550_DMA_CTRL_BURST_512 |
+ PNX8550_DMA_CTRL_SND2XIO | PNX8550_DMA_CTRL_INIT_DMA | cmd;
+
+ while ((PNX8550_DMA_INT_STATUS & PNX8550_DMA_INT_COMPL) == 0) ;
+
+ if (!toxio) {
+ dma_cache_inv(to, bytes);
+ }
+ local_irq_enable();
+}
+
+/**
+ * pnx8550_nand_read_byte - read one byte endianess aware from the chip
+ * @mtd: MTD device structure
+ *
+ */
+static u_char pnx8550_nand_read_byte(struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+ u16 data = 0;
+ int addr = NAND_ADDR(last_col_addr, last_page_addr);
+ /*
+ Read ID is a special case as we have to read BOTH bytes at the same
+ time otherwise it doesn't work, once we have both bytes we work out
+ which one we want.
+ */
+ if (last_command == NAND_CMD_READID) {
+ u32 *pNandAddr32 = (u32 *) pNandAddr;
+ u32 data32;
+ data32 = cpu_to_le32(pNandAddr32[0]);
+ if (last_col_addr) {
+ data = (u16) (data32 >> 16);
+ } else {
+ data = (u16) data32;
+ }
+ } else {
+ data = cpu_to_le16(pNandAddr[(addr / sizeof(u16))]);
+ if ((addr & 0x1) == 1) {
+ data = (data & 0xff00) >> 16;
+ }
+ }
+ /*
+ Status is a special case, we don't need to increment the address
+ because the address isn't used by the chip
+ */
+ if (last_command != NAND_CMD_STATUS) {
+ last_col_addr++;
+ }
+ return data & 0xff;
+}
+
+/**
+ * pnx8550_nand_read_word - read one word from the chip
+ * @mtd: MTD device structure
+ *
+ * Read function for 16bit buswith without
+ * endianess conversion
+ */
+static u16 pnx8550_nand_read_word(struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+ int addr = NAND_ADDR(last_col_addr, last_page_addr);
+ u16 data = pNandAddr[(addr / sizeof(u16))];
+ return data;
+}
+
+/**
+ * pnx8550_nand_write_byte - write one byte endianess aware to the chip
+ * @mtd: MTD device structure
+ * @byte: pointer to data byte to write
+ *
+ * Write function for 16bit buswith with
+ * endianess conversion
+ */
+static void pnx8550_nand_write_byte(struct mtd_info *mtd, u_char byte)
+{
+ struct nand_chip *this = mtd->priv;
+ int addr = NAND_ADDR(last_col_addr, last_page_addr);
+ pNandAddr[(addr / sizeof(u16))] = le16_to_cpu((u16) byte);
+}
+
+/**
+ * pnx8550_nand_write_word - write one word to the chip
+ * @mtd: MTD device structure
+ * @word: data word to write
+ *
+ * Write function for 16bit buswith without
+ * endianess conversion
+ */
+static void pnx8550_nand_write_word(struct mtd_info *mtd, u16 word)
+{
+ struct nand_chip *this = mtd->priv;
+ int addr = NAND_ADDR(last_col_addr, last_page_addr);
+ pNandAddr[(addr / sizeof(u16))] = word;
+}
+
+/**
+ * pnx8550_nand_write_buf - write buffer to chip
+ * @mtd: MTD device structure
+ * @buf: data buffer
+ * @len: number of bytes to write
+ *
+ */
+static void pnx8550_nand_write_buf(struct mtd_info *mtd, const u_char * buf,
+ int len)
+{
+ struct nand_chip *this = mtd->priv;
+ int addr = NAND_ADDR(last_col_addr, last_page_addr);
+ int pageLen;
+ int oobLen = 0;
+ u_char *transBuf = (u_char *) buf;
+
+ /* some sanity checking, word access only please */
+ if (len & 1) {
+ printk("%s: non-word aligned length requested!\n",
+ __FUNCTION__);
+ }
+
+ memcpy(transferBuffer, buf, len);
+ transBuf = transferBuffer;
+
+ /*
+ Work out whether we are going to write to the OOB area
+ after a standard page write.
+ This is not the case when the command function is called
+ with a column address > page size. Then we write as though
+ it is to the page rather than the OOB as the command function
+ has already selected the OOB area.
+ */
+ if ((last_col_addr + len) > mtd->oobblock)
+ oobLen = (last_col_addr + len) - mtd->oobblock;
+ pageLen = len - oobLen;
+
+ /* Clear the done flag */
+ PNX8550_GPXIO_CTRL |= PNX8550_GPXIO_CLR_DONE;
+ if (pageLen > 0) {
+ NAND_TRANSFER_TO(addr, transBuf, pageLen);
+ }
+ if (oobLen > 0) {
+ pnx8550_nand_wait_for_dev_ready();
+
+ pnx8550_nand_register_setup(1, 0, 0, 1, 0, NAND_CMD_READOOB, 0);
+ /* Work out where in the OOB we are going to start to write */
+ addr = NAND_ADDR(last_col_addr - mtd->oobblock, last_page_addr);
+ NAND_ADDR_SEND(addr);
+ pnx8550_nand_register_setup(2, 3, 1, 1, 0, NAND_CMD_SEQIN,
+ NAND_CMD_PAGEPROG);
+
+ /* Clear the done flag */
+ PNX8550_GPXIO_CTRL |= PNX8550_GPXIO_CLR_DONE;
+ NAND_TRANSFER_TO(addr, transBuf + pageLen, oobLen);
+ }
+
+ /*
+ Increment the address so on the next write we write in the
+ correct place.
+ */
+ last_col_addr += len;
+ if (last_col_addr >= mtd->oobblock + mtd->oobsize) {
+ last_col_addr -= mtd->oobblock + mtd->oobsize;
+ last_page_addr++;
+ }
+}
+
+/**
+ * pnx8550_nand_read_buf - read chip data into buffer
+ * @mtd: MTD device structure
+ * @buf: buffer to store date
+ * @len: number of bytes to read
+ *
+ */
+static void pnx8550_nand_read_buf(struct mtd_info *mtd, u_char * buf, int len)
+{
+ struct nand_chip *this = mtd->priv;
+ int addr = NAND_ADDR(last_col_addr, last_page_addr);
+ int pageLen;
+ int oobLen = 0;
+ u_char *transBuf = buf;
+
+ /* some sanity checking, word access only please */
+ if (len & 1) {
+ printk("%s: non-word aligned length\n", __FUNCTION__);
+ }
+
+ transBuf = transferBuffer;
+
+ /*
+ Work out whether we are going to read the OOB area
+ after a standard page read.
+ This is not the case when the command function is called
+ with a column address > page size. Then we read as though
+ it is from the page rather than the OOB as the command
+ function has already selected the OOB area.
+ */
+ if ((last_col_addr + len) > mtd->oobblock)
+ oobLen = (last_col_addr + len) - mtd->oobblock;
+ pageLen = len - oobLen;
+
+ if (pageLen) {
+ NAND_TRANSFER_FROM(addr, transBuf, pageLen);
+ }
+ if (oobLen > 0) {
+ pnx8550_nand_register_setup(1, 3, 1, 1, 0, NAND_CMD_READOOB, 0);
+ addr = NAND_ADDR(last_col_addr - mtd->oobblock, last_page_addr);
+ NAND_TRANSFER_FROM(addr, transBuf + pageLen, oobLen);
+ }
+ if (transBuf != buf) {
+ memcpy(buf, transBuf, len);
+ }
+
+ /*
+ Increment the address so on the next read we read from the
+ correct place.
+ */
+ last_col_addr += len;
+ if (last_col_addr > mtd->oobblock + mtd->oobsize) {
+ last_col_addr -= mtd->oobblock + mtd->oobsize;
+ last_page_addr++;
+ }
+ return;
+}
+
+/**
+ * pnx8550_nand_verify_buf - Verify chip data against buffer
+ * @mtd: MTD device structure
+ * @buf: buffer containing the data to compare
+ * @len: number of bytes to compare
+ *
+ */
+static int pnx8550_nand_verify_buf(struct mtd_info *mtd, const u_char * buf,
+ int len)
+{
+ int result = 0;
+
+ /* some sanity checking, word access only please */
+ if (len & 1) {
+ printk("%s: non-word aligned length\n", __FUNCTION__);
+ }
+
+ pnx8550_nand_read_buf(mtd, transferBuffer, len);
+ if (memcmp(buf, transferBuffer, len)) {
+ result = -EFAULT;
+ }
+
+ return result;
+
+}
+
+/**
+ * pnx8550_nand_command - Send command to NAND device
+ * @mtd: MTD device structure
+ * @command: the command to be sent
+ * @column: the column address for this command, -1 if none
+ * @page_addr: the page address for this command, -1 if none
+ *
+ * Send command to NAND device.
+ */
+static void pnx8550_nand_command(struct mtd_info *mtd, unsigned command,
+ int column, int page_addr)
+{
+ register struct nand_chip *this = mtd->priv;
+ u_char addr_no = 0;
+ u_char spare = 0;
+ int addr;
+ /*
+ If we are starting a write work out whether it is to the
+ OOB or the main page and position the pointer correctly.
+ */
+ if (command == NAND_CMD_SEQIN) {
+ int readcmd;
+ int col = column;
+ if (column >= mtd->oobblock) {
+ /* OOB area */
+ col -= mtd->oobblock;
+ readcmd = NAND_CMD_READOOB;
+ spare = 1;
+ } else {
+ readcmd = NAND_CMD_READ0;
+ }
+ pnx8550_nand_register_setup(1, 0, 0, 1, 0, readcmd, 0);
+ addr = NAND_ADDR(col, page_addr);
+ NAND_ADDR_SEND(addr);
+ }
+
+ /* Check the number of address bytes */
+ if ((column == -1) && (page_addr == -1)) {
+ addr_no = 0;
+ column = 0;
+ page_addr = 0;
+ } else if ((column == -1) && (page_addr != -1)) {
+ addr_no = 2;
+ column = 0;
+ } else if ((column != -1) && (page_addr == -1)) {
+ addr_no = 1;
+ page_addr = 0;
+ } else {
+ addr_no = 3;
+ }
+
+ last_command = command;
+ last_col_addr = column;
+ last_page_addr = page_addr;
+
+ switch (command) {
+
+ case NAND_CMD_PAGEPROG:
+ // Nothing to do, we've already done it!
+ return;
+
+ case NAND_CMD_SEQIN:
+ if (addr_no != 3)
+ printk
+ ("NAND: Error. Command %02x needs 3 byte address, but addr_no = %d\n",
+ command, addr_no);
+ pnx8550_nand_register_setup(2, 3, 1, 1, spare, NAND_CMD_SEQIN,
+ NAND_CMD_PAGEPROG);
+ return;
+
+ case NAND_CMD_ERASE1:
+ if (addr_no != 2)
+ printk
+ ("NAND: Error. Command %02x needs 2 byte address, but addr_no = %d\n",
+ command, addr_no);
+
+ PNX8550_GPXIO_CTRL |= PNX8550_GPXIO_CLR_DONE;
+
+ pnx8550_nand_register_setup(2, 2, 0, 1, 0, NAND_CMD_ERASE1,
+ NAND_CMD_ERASE2);
+ addr = NAND_ADDR(column, page_addr);
+ NAND_ADDR_SEND(addr);
+ return;
+
+ case NAND_CMD_ERASE2:
+ // Nothing to do, we've already done it!
+ return;
+
+ case NAND_CMD_STATUS:
+ if (addr_no != 0)
+ printk
+ ("NAND: Error. Command %02x needs 0 byte address, but addr_no = %d\n",
+ command, addr_no);
+ pnx8550_nand_register_setup(1, 0, 1, 0, 0, NAND_CMD_STATUS, 0);
+ return;
+
+ case NAND_CMD_RESET:
+ if (addr_no != 0)
+ printk
+ ("NAND: Error. Command %02x needs 0 byte address, but addr_no = %d\n",
+ command, addr_no);
+ pnx8550_nand_register_setup(1, 0, 0, 0, 0, NAND_CMD_RESET, 0);
+ addr = NAND_ADDR(column, page_addr);
+ NAND_ADDR_SEND(addr);
+ return;
+
+ case NAND_CMD_READ0:
+ if (addr_no != 3)
+ printk
+ ("NAND: Error. Command %02x needs 3 byte address, but addr_no = %d\n",
+ command, addr_no);
+
+ pnx8550_nand_register_setup(1, 3, 1, 1, 0, NAND_CMD_READ0, 0);
+ return;
+
+ case NAND_CMD_READ1:
+ printk("Wrong command: %02x\n", command);
+ return;
+
+ case NAND_CMD_READOOB:
+ if (addr_no != 3)
+ printk
+ ("NAND: Error. Command %02x needs 3 byte address, but addr_no = %d\n",
+ command, addr_no);
+ pnx8550_nand_register_setup(1, 3, 1, 1, 0, NAND_CMD_READOOB, 0);
+ return;
+
+ case NAND_CMD_READID:
+ if (addr_no != 1)
+ printk
+ ("NAND: Error. Command %02x needs 1 byte address, but addr_no = %d\n",
+ command, addr_no);
+ pnx8550_nand_register_setup(1, 1, 1, 0, 0, NAND_CMD_READID, 0);
+ return;
+ }
+}
+
+/*
+ * Setup the registers in PCIXIO
+ */
+static void pnx8550_nand_register_setup(u_char cmd_no,
+ u_char addr_no,
+ u_char include_data,
+ u_char monitor_ACK,
+ u_char enable64M, int cmd_a, int cmd_b)
+{
+ unsigned int reg_nand = 0;
+ reg_nand |= enable64M ? PNX8550_XIO_FLASH_64MB : 0;
+ reg_nand |= include_data ? PNX8550_XIO_FLASH_INC_DATA : 0;
+ reg_nand |= PNX8550_XIO_FLASH_CMD_PH(cmd_no);
+ reg_nand |= PNX8550_XIO_FLASH_ADR_PH(addr_no);
+ reg_nand |= PNX8550_XIO_FLASH_CMD_A(cmd_a);
+ reg_nand |= PNX8550_XIO_FLASH_CMD_B(cmd_b);
+ PNX8550_XIO_FLASH_CTRL = reg_nand;
+ barrier();
+}
+
+/*
+ * Wait for the device to be ready for the next command
+ */
+static inline void pnx8550_nand_wait_for_dev_ready(void)
+{
+ while ((PNX8550_XIO_CTRL & PNX8550_XIO_CTRL_XIO_ACK) == 0) ;
+}
+
+/*
+ * Return true if the device is ready, false otherwise
+ */
+static int pnx8550_nand_dev_ready(struct mtd_info *mtd)
+{
+ return ((PNX8550_XIO_CTRL & PNX8550_XIO_CTRL_XIO_ACK) != 0);
+}
+
+/*
+ * hardware specific access to control-lines
+ */
+static void pnx8550_nand_hwcontrol(struct mtd_info *mtd, int cmd)
+{
+ // Nothing to do here, its all done by the XIO block
+}
+
+/*
+ * Main initialization routine
+ */
+int __init pnx8550_nand_init(void)
+{
+ struct nand_chip *this;
+
+ /* Get pointer to private data */
+ this = &pnx8550_nand;
+
+ /* Initialize structures */
+ memset((char *)&pnx8550_mtd, 0, sizeof(struct mtd_info));
+ memset((char *)this, 0, sizeof(struct nand_chip));
+
+ /* Work out address of Nand Flash */
+ pNandAddr = (u16 *) (KSEG1 | (PNX8550_BASE18_ADDR & (~0x7)));
+
+ pNandAddr = (u16 *) (((u32) pNandAddr) +
+ ((PNX8550_XIO_SEL0 & PNX8550_XIO_SEL0_OFFSET_MASK)
+ >> PNX8550_XIO_SEL0_OFFSET_SHIFT) * 8 * 1024 *
+ 1024);
+
+ /* Link the private data with the MTD structure */
+ pnx8550_mtd.priv = this;
+ this->chip_delay = 15;
+ this->options = NAND_BUSWIDTH_16;
+ this->cmdfunc = pnx8550_nand_command;
+ this->read_byte = pnx8550_nand_read_byte;
+ this->read_word = pnx8550_nand_read_word;
+ this->read_buf = pnx8550_nand_read_buf;
+ this->write_byte = pnx8550_nand_write_byte;
+ this->write_word = pnx8550_nand_write_word;
+ this->write_buf = pnx8550_nand_write_buf;
+ this->verify_buf = pnx8550_nand_verify_buf;
+ this->dev_ready = pnx8550_nand_dev_ready;
+ this->hwcontrol = pnx8550_nand_hwcontrol;
+ this->eccmode = NAND_ECC_SOFT;
+ this->badblock_pattern = &nand16bit_memorybased;
+ this->autooob = &nand16bit_oob_16;
+
+ transferBuffer =
+ kmalloc(pnx8550_mtd.oobblock + pnx8550_mtd.oobsize,
+ GFP_DMA | GFP_KERNEL);
+ if (!transferBuffer) {
+ printk(KERN_ERR
+ "Unable to allocate NAND data buffer for PNX8550.\n");
+ return -ENOMEM;
+ }
+
+ /* Scan to find existence of the device */
+ if (nand_scan(&pnx8550_mtd, 1)) {
+ printk("%s: Exiting No Devices\n", __FUNCTION__);
+ return -ENXIO;
+ }
+
+ /* Register the partitions */
+ add_mtd_partitions(&pnx8550_mtd, partition_info, NUM_PARTITIONS);
+
+ /* Return happy */
+ return 0;
+}
+
+module_init(pnx8550_nand_init);
+
+/*
+ * Clean up routine
+ */
+#ifdef MODULE
+static void __exit pnx8550_nand_cleanup(void)
+{
+ /* Unregister the device */
+ del_mtd_device(&pnx8550_mtd);
+ if (transferBuffer) {
+ kfree(transferBuffer);
+ }
+}
+
+module_exit(pnx8550_nand_cleanup);
+#endif
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Adam Charrett");
+MODULE_DESCRIPTION("Driver for 16Bit NAND Flash on the XIO bus for PNX8550");
Index: linux-2.6.15_0/drivers/mtd/nand/Makefile
===================================================================
--- linux-2.6.15_0.orig/drivers/mtd/nand/Makefile
+++ linux-2.6.15_0/drivers/mtd/nand/Makefile
@@ -18,5 +18,6 @@ obj-$(CONFIG_MTD_NAND_H1900) += h1910.o
obj-$(CONFIG_MTD_NAND_RTC_FROM4) += rtc_from4.o
obj-$(CONFIG_MTD_NAND_SHARPSL) += sharpsl.o
obj-$(CONFIG_MTD_NAND_NANDSIM) += nandsim.o
+obj-$(CONFIG_MTD_NAND_PNX8550) += pnx8550.o
nand-objs = nand_base.o nand_bbt.o
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH] PNX8550 NAND flash driver 2005-12-16 17:23 [PATCH] PNX8550 NAND flash driver Vladimir A. Barinov @ 2005-12-20 14:28 ` Vladimir A. Barinov 2006-01-12 18:24 ` Todd Poynor 1 sibling, 0 replies; 8+ messages in thread From: Vladimir A. Barinov @ 2005-12-20 14:28 UTC (permalink / raw) To: linux-mtd, linux-mips [-- Attachment #1: Type: text/plain, Size: 399 bytes --] Hi, Vladimir A. Barinov wrote: > Hi All, > > Attached patch is NAND flash driver for PNX8550 based platforms. > Any comments and suggestions are highly appreciated. Sorry, but this patch is not complete. It's needed to add include/../nand.h file. Attachment is the recasted variant (removing "volatile u16 *" usage, "prink" usage changes and some cosmetic changes) with nand.h part. Vladimir [-- Attachment #2: nand.patch --] [-- Type: text/plain, Size: 25627 bytes --] Signed-off-by: vbarinov@ru.mvista.com drivers/mtd/nand/Kconfig | 6 drivers/mtd/nand/Makefile | 1 drivers/mtd/nand/pnx8550.c | 694 +++++++++++++++++++++++++++++++++++ include/asm-mips/mach-pnx8550/nand.h | 22 + 4 files changed, 721 insertions(+), 2 deletions(-) Index: linux-2.6.15_0/drivers/mtd/nand/Kconfig =================================================================== --- linux-2.6.15_0.orig/drivers/mtd/nand/Kconfig +++ linux-2.6.15_0/drivers/mtd/nand/Kconfig @@ -90,6 +90,12 @@ config MTD_NAND_S3C2410 No board specfic support is done by this driver, each board must advertise a platform_device for the driver to attach. +config MTD_NAND_PNX8550 + tristate "NAND Flash support for PNX8550" + depends on PNX8550 && MTD_NAND + help + This enables the NAND flash controller on the PNX8550. + config MTD_NAND_S3C2410_DEBUG bool "S3C2410 NAND driver debug" depends on MTD_NAND_S3C2410 Index: linux-2.6.15_0/drivers/mtd/nand/pnx8550.c =================================================================== --- /dev/null +++ linux-2.6.15_0/drivers/mtd/nand/pnx8550.c @@ -0,0 +1,694 @@ +/* + * Copyright (C) 2005 Koninklijke Philips Electronics N.V. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Overview: + * This is a device driver for the NAND flash device found on the + * PNX8550 board which utilizes the Samsung K9F5616U0C part. This is + * a 32MByte (16M x 16 bits) NAND flash device. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/nand_ecc.h> +#include <linux/mtd/compatmac.h> +#include <linux/interrupt.h> +#include <linux/mtd/partitions.h> +#include <asm/io.h> +#include <asm/mach-pnx8550/nand.h> + +#define UBTM_NAME "microBTM" +#define UBTM_BLOCK_START ( 0x00000000) +#define UBTM_BLOCK_END ( 0x00004000) /* 16K size, first block */ +#define UBTM_SIZE ( UBTM_BLOCK_END - UBTM_BLOCK_START) + +#define BOOTLOADER_NAME "bootloader" +#define BOOTLOADER_BLOCK_START ( UBTM_BLOCK_END) +#define BOOTLOADER_BLOCK_END ( 0x00040000) /* 256K - 16K = 240K */ +#define BOOTLOADER_SIZE ( BOOTLOADER_BLOCK_END - BOOTLOADER_BLOCK_START) + +#define ROMFS_SYS_NAME "ROMFS-Tools" +#define ROMFS_SYS_BLOCK_START ( BOOTLOADER_BLOCK_END) +#define ROMFS_SYS_BLOCK_END ( 0x00600000) /* 6M - 256K = 5.75M */ +#define ROMFS_SYS_SIZE ( ROMFS_SYS_BLOCK_END - ROMFS_SYS_BLOCK_START) + +#define ROMFS_APP_NAME "ROMFS-User" +#define ROMFS_APP_BLOCK_START ( ROMFS_SYS_BLOCK_END) +#define ROMFS_APP_BLOCK_END ( 0x01000000) /* 16M - 6M = 10M */ +#define ROMFS_APP_SIZE ( ROMFS_APP_BLOCK_END - ROMFS_APP_BLOCK_START) + +#define USER_NAME "User" +#define USER_BLOCK_START ( ROMFS_APP_BLOCK_END) +#define USER_BLOCK_END ( 0x02000000) /* 32M - 16M = 16M */ +#define USER_SIZE ( USER_BLOCK_END - USER_BLOCK_START) + +#define NAND_ADDR(_col, _page) ((_col) & (mtd->oobblock - 1)) + ((_page) << this->page_shift) + +#define NAND_ADDR_SEND(_addr) writew(0, pNandAddr + _addr) + +#define NAND_TRANSFER_TO(_addr, _buffer, _bytes) pnx8550_nand_transfer(_buffer, pNandAddr + _addr, _bytes, 1) +#define NAND_TRANSFER_FROM(_addr, _buffer, _bytes) pnx8550_nand_transfer(pNandAddr + _addr, _buffer, _bytes, 0) + +/* + * Define partitions for flash device + */ +#define NUM_PARTITIONS 5 +const static struct mtd_partition partition_info[NUM_PARTITIONS] = { + { + .name = UBTM_NAME, + .offset = UBTM_BLOCK_START, + .size = UBTM_SIZE}, + { + .name = BOOTLOADER_NAME, + .offset = BOOTLOADER_BLOCK_START, + .size = BOOTLOADER_SIZE}, + { + .name = ROMFS_SYS_NAME, + .offset = ROMFS_SYS_BLOCK_START, + .size = ROMFS_SYS_SIZE}, + { + .name = ROMFS_APP_NAME, + .offset = ROMFS_APP_BLOCK_START, + .size = ROMFS_APP_SIZE}, + { + .name = USER_NAME, + .offset = USER_BLOCK_START, + .size = USER_SIZE} +}; + +/* Bad block descriptor for 16Bit nand flash */ +static uint8_t scan_ff_pattern[] = { 0xff, 0xff }; +static struct nand_bbt_descr nand16bit_memorybased = { + .options = 0, + .offs = 0, + .len = 2, + .pattern = scan_ff_pattern +}; + +/* OOB Placement information that lines up with the boot loader code */ +static struct nand_oobinfo nand16bit_oob_16 = { + .useecc = MTD_NANDECC_AUTOPLACE, + .eccbytes = 6, + .eccpos = {2, 3, 4, 5, 6, 7}, + .oobfree = {{8, 8}} +}; + +/* Pointer into XIO for access to the 16Bit NAND flash device */ +static u8 *pNandAddr; + +/* Last command sent to the pnx8550_nand_command function */ +static int last_command = -1; +/* + * Next column address to read/write, set by pnx8550_nand_command + * updated by the read/write functions + */ +static int last_col_addr = -1; +/* + * Next page address to read/write, set by pnx8550_nand_command + * updated by the read/write functions + */ +static int last_page_addr = -1; + +/* 32bit Aligned/DMA buffer */ +static u_char *transferBuffer; + +static struct mtd_info pnx8550_mtd; +static struct nand_chip pnx8550_nand; + +/** + * Setup the registers in PCIXIO + */ +static void pnx8550_nand_register_setup(u_char cmd_no, + u_char addr_no, + u_char include_data, + u_char monitor_ACK, + u_char enable64M, int cmd_a, int cmd_b) +{ + unsigned int reg_nand = 0; + reg_nand |= enable64M ? PNX8550_XIO_FLASH_64MB : 0; + reg_nand |= include_data ? PNX8550_XIO_FLASH_INC_DATA : 0; + reg_nand |= PNX8550_XIO_FLASH_CMD_PH(cmd_no); + reg_nand |= PNX8550_XIO_FLASH_ADR_PH(addr_no); + reg_nand |= PNX8550_XIO_FLASH_CMD_A(cmd_a); + reg_nand |= PNX8550_XIO_FLASH_CMD_B(cmd_b); + PNX8550_XIO_FLASH_CTRL = reg_nand; + barrier(); +} + +/** + * Wait for the device to be ready for the next command + */ +static inline void pnx8550_nand_wait_for_dev_ready(void) +{ + while ((PNX8550_XIO_CTRL & PNX8550_XIO_CTRL_XIO_ACK) == 0) ; +} + +/** + * Transfer data to/from the NAND chip using DMA + * + * @from: Address to transfer data from + * @to: Address to transfer the data to + * @bytes: Number of bytes to transfer + * @toxio: Whether the transfer is going to XIO or not. + */ +static void pnx8550_nand_transferDMA(void *from, void *to, int bytes, int toxio) +{ + int cmd = 0; + u32 internal; + u32 external; + + if (toxio) { + cmd = PNX8550_DMA_CTRL_PCI_CMD_WRITE; + dma_cache_wback((unsigned long)from, bytes); + internal = (u32) virt_to_phys(from); + external = (u32) to - KSEG1; + } else { + cmd = PNX8550_DMA_CTRL_PCI_CMD_READ; + internal = (u32) virt_to_phys(to); + external = (u32) from - KSEG1; + } + + local_irq_disable(); + PNX8550_DMA_TRANS_SIZE = bytes >> 2; /* Length in words */ + PNX8550_DMA_EXT_ADDR = external; + PNX8550_DMA_INT_ADDR = internal; + PNX8550_DMA_INT_CLEAR = 0xffff; + PNX8550_DMA_CTRL = PNX8550_DMA_CTRL_BURST_512 | + PNX8550_DMA_CTRL_SND2XIO | PNX8550_DMA_CTRL_INIT_DMA | cmd; + + while ((PNX8550_DMA_INT_STATUS & PNX8550_DMA_INT_COMPL) == 0) ; + + if (!toxio) + dma_cache_inv((unsigned long)to, bytes); + + local_irq_enable(); +} + +/** + * Transfer data to/from the NAND chip. + * This function decides whether to use DMA or not depending on + * the amount of data to transfer and the alignment of the buffers. + * + * @from: Address to transfer data from + * @to: Address to transfer the data to + * @bytes: Number of bytes to transfer + * @toxio: Whether the transfer is going to XIO or not. + */ +static void pnx8550_nand_transfer(void *from, void *to, int bytes, int toxio) +{ + u16 *from16 = (u16 *) from; + u16 *to16 = (u16 *) to; + + int i; + + if ((u32) from & 3) + printk(KERN_INFO "%s: from buffer not 32bit aligned, will not " + "use fastest transfer mechanism\n", __FUNCTION__); + if ((u32) to & 3) + printk(KERN_INFO "%s: to buffer not 32bit aligned, will not " + "use fastest transfer mechanism\n", __FUNCTION__); + + if (((bytes & 3) || (bytes < 16)) || ((u32) to & 3) || ((u32) from & 3)) { + if (((bytes & 1) == 0) && + (((u32) to & 1) == 0) && (((u32) from & 1) == 0)) { + int words = bytes / 2; + + local_irq_disable(); + for (i = 0; i < words; i++) + to16[i] = from16[i]; + local_irq_enable(); + } + else + printk(KERN_ERR "%s: Transfer failed, " + "byte-aligned transfers no allowed!\n", + __FUNCTION__); + } else { + pnx8550_nand_transferDMA(from, to, bytes, toxio); + } +} + +/** + * pnx8550_nand_read_byte - read one byte endianess aware from the chip + * @mtd: MTD device structure + */ +static u_char pnx8550_nand_read_byte(struct mtd_info *mtd) +{ + struct nand_chip *this = mtd->priv; + u16 data = 0; + int addr = NAND_ADDR(last_col_addr, last_page_addr); + /* + * Read ID is a special case as we have to read BOTH bytes at the same + * time otherwise it doesn't work, once we have both bytes we work out + * which one we want. + */ + if (last_command == NAND_CMD_READID) { + u32 data32; + data32 = cpu_to_le32(readl(pNandAddr + 0)); + if (last_col_addr) + data = (u16) (data32 >> 16); + else + data = (u16) data32; + } else { + data = readw(pNandAddr + addr); + if ((addr & 0x1) == 1) + data = (data & 0xff00) >> 16; + } + /* + * Status is a special case, we don't need to increment the address + * because the address isn't used by the chip + */ + if (last_command != NAND_CMD_STATUS) + last_col_addr++; + + return data & 0xff; +} + +/** + * pnx8550_nand_read_word - read one word from the chip + * @mtd: MTD device structure + * + * Read function for 16bit buswith without + * endianess conversion + */ +static u16 pnx8550_nand_read_word(struct mtd_info *mtd) +{ + struct nand_chip *this = mtd->priv; + int addr = NAND_ADDR(last_col_addr, last_page_addr); + u16 data = readw(pNandAddr + addr); + return data; +} + +/** + * pnx8550_nand_write_byte - write one byte endianess aware to the chip + * @mtd: MTD device structure + * @byte: pointer to data byte to write + * + * Write function for 16bit buswith with + * endianess conversion + */ +static void pnx8550_nand_write_byte(struct mtd_info *mtd, u_char byte) +{ + struct nand_chip *this = mtd->priv; + int addr = NAND_ADDR(last_col_addr, last_page_addr); + writew(le16_to_cpu((u16) byte), pNandAddr + addr); +} + +/** + * pnx8550_nand_write_word - write one word to the chip + * @mtd: MTD device structure + * @word: data word to write + * + * Write function for 16bit buswith without + * endianess conversion + */ +static void pnx8550_nand_write_word(struct mtd_info *mtd, u16 word) +{ + struct nand_chip *this = mtd->priv; + int addr = NAND_ADDR(last_col_addr, last_page_addr); + writew(word, pNandAddr + addr); +} + +/** + * pnx8550_nand_write_buf - write buffer to chip + * @mtd: MTD device structure + * @buf: data buffer + * @len: number of bytes to write + */ +static void pnx8550_nand_write_buf(struct mtd_info *mtd, const u_char * buf, + int len) +{ + struct nand_chip *this = mtd->priv; + int addr = NAND_ADDR(last_col_addr, last_page_addr); + int pageLen; + int oobLen = 0; + u_char *transBuf = (u_char *) buf; + + /* some sanity checking, word access only please */ + if (len & 1) + pr_debug("%s: non-word aligned length requested!\n", + __FUNCTION__); + + memcpy(transferBuffer, buf, len); + transBuf = transferBuffer; + + /* + * Work out whether we are going to write to the OOB area + * after a standard page write. + * This is not the case when the command function is called + * with a column address > page size. Then we write as though + * it is to the page rather than the OOB as the command function + * has already selected the OOB area. + */ + if ((last_col_addr + len) > mtd->oobblock) + oobLen = (last_col_addr + len) - mtd->oobblock; + pageLen = len - oobLen; + + /* Clear the done flag */ + PNX8550_GPXIO_CTRL |= PNX8550_GPXIO_CLR_DONE; + if (pageLen > 0) + NAND_TRANSFER_TO(addr, transBuf, pageLen); + + if (oobLen > 0) { + pnx8550_nand_wait_for_dev_ready(); + + pnx8550_nand_register_setup(1, 0, 0, 1, 0, NAND_CMD_READOOB, 0); + /* Work out where in the OOB we are going to start to write */ + addr = NAND_ADDR(last_col_addr - mtd->oobblock, last_page_addr); + NAND_ADDR_SEND(addr); + pnx8550_nand_register_setup(2, 3, 1, 1, 0, NAND_CMD_SEQIN, + NAND_CMD_PAGEPROG); + + /* Clear the done flag */ + PNX8550_GPXIO_CTRL |= PNX8550_GPXIO_CLR_DONE; + NAND_TRANSFER_TO(addr, transBuf + pageLen, oobLen); + } + + /* + * Increment the address so on the next write we write in the + * correct place. + */ + last_col_addr += len; + if (last_col_addr >= mtd->oobblock + mtd->oobsize) { + last_col_addr -= mtd->oobblock + mtd->oobsize; + last_page_addr++; + } +} + +/** + * pnx8550_nand_read_buf - read chip data into buffer + * @mtd: MTD device structure + * @buf: buffer to store date + * @len: number of bytes to read + */ +static void pnx8550_nand_read_buf(struct mtd_info *mtd, u_char * buf, int len) +{ + struct nand_chip *this = mtd->priv; + int addr = NAND_ADDR(last_col_addr, last_page_addr); + int pageLen; + int oobLen = 0; + u_char *transBuf = buf; + + /* some sanity checking, word access only please */ + if (len & 1) + pr_debug("%s: non-word aligned length\n", __FUNCTION__); + + transBuf = transferBuffer; + + /* + * Work out whether we are going to read the OOB area + * after a standard page read. + * This is not the case when the command function is called + * with a column address > page size. Then we read as though + * it is from the page rather than the OOB as the command + * function has already selected the OOB area. + */ + if ((last_col_addr + len) > mtd->oobblock) + oobLen = (last_col_addr + len) - mtd->oobblock; + + pageLen = len - oobLen; + + if (pageLen) + NAND_TRANSFER_FROM(addr, transBuf, pageLen); + + if (oobLen > 0) { + pnx8550_nand_register_setup(1, 3, 1, 1, 0, NAND_CMD_READOOB, 0); + addr = NAND_ADDR(last_col_addr - mtd->oobblock, last_page_addr); + NAND_TRANSFER_FROM(addr, transBuf + pageLen, oobLen); + } + + if (transBuf != buf) + memcpy(buf, transBuf, len); + + /* + * Increment the address so on the next read we read from the + * correct place. + */ + last_col_addr += len; + if (last_col_addr > mtd->oobblock + mtd->oobsize) { + last_col_addr -= mtd->oobblock + mtd->oobsize; + last_page_addr++; + } + return; +} + +/** + * pnx8550_nand_verify_buf - Verify chip data against buffer + * @mtd: MTD device structure + * @buf: buffer containing the data to compare + * @len: number of bytes to compare + * + */ +static int pnx8550_nand_verify_buf(struct mtd_info *mtd, const u_char * buf, + int len) +{ + int result = 0; + + /* some sanity checking, word access only please */ + if (len & 1) + pr_debug("%s: non-word aligned length\n", __FUNCTION__); + + pnx8550_nand_read_buf(mtd, transferBuffer, len); + if (memcmp(buf, transferBuffer, len)) + result = -EFAULT; + + return result; + +} + +/** + * pnx8550_nand_command - Send command to NAND device + * @mtd: MTD device structure + * @command: the command to be sent + * @column: the column address for this command, -1 if none + * @page_addr: the page address for this command, -1 if none + * + * Send command to NAND device. + */ +static void pnx8550_nand_command(struct mtd_info *mtd, unsigned command, + int column, int page_addr) +{ + register struct nand_chip *this = mtd->priv; + u_char addr_no = 0; + u_char spare = 0; + int addr; + /* + If we are starting a write work out whether it is to the + OOB or the main page and position the pointer correctly. + */ + if (command == NAND_CMD_SEQIN) { + int readcmd; + int col = column; + if (column >= mtd->oobblock) { + /* OOB area */ + col -= mtd->oobblock; + readcmd = NAND_CMD_READOOB; + spare = 1; + } else { + readcmd = NAND_CMD_READ0; + } + pnx8550_nand_register_setup(1, 0, 0, 1, 0, readcmd, 0); + addr = NAND_ADDR(col, page_addr); + NAND_ADDR_SEND(addr); + } + + /* Check the number of address bytes */ + if ((column == -1) && (page_addr == -1)) { + addr_no = 0; + column = 0; + page_addr = 0; + } else if ((column == -1) && (page_addr != -1)) { + addr_no = 2; + column = 0; + } else if ((column != -1) && (page_addr == -1)) { + addr_no = 1; + page_addr = 0; + } else { + addr_no = 3; + } + + last_command = command; + last_col_addr = column; + last_page_addr = page_addr; + + switch (command) { + case NAND_CMD_PAGEPROG: + /* Nothing to do, we've already done it! */ + return; + + case NAND_CMD_SEQIN: + if (addr_no != 3) + printk(KERN_ERR + "NAND: Command %02x needs 3 byte address, " + "but addr_no = %d\n", command, addr_no); + pnx8550_nand_register_setup(2, 3, 1, 1, spare, NAND_CMD_SEQIN, + NAND_CMD_PAGEPROG); + return; + + case NAND_CMD_ERASE1: + if (addr_no != 2) + pr_debug("NAND: Command %02x needs 2 byte" "address, " + "but addr_no = %d\n", command, addr_no); + PNX8550_GPXIO_CTRL |= PNX8550_GPXIO_CLR_DONE; + pnx8550_nand_register_setup(2, 2, 0, 1, 0, NAND_CMD_ERASE1, + NAND_CMD_ERASE2); + addr = NAND_ADDR(column, page_addr); + NAND_ADDR_SEND(addr); + return; + + case NAND_CMD_ERASE2: + /* Nothing to do, we've already done it! */ + return; + + case NAND_CMD_STATUS: + if (addr_no != 0) + pr_debug("NAND: Command %02x needs 0 byte address, " + "but addr_no = %d\n", command, addr_no); + pnx8550_nand_register_setup(1, 0, 1, 0, 0, NAND_CMD_STATUS, 0); + return; + + case NAND_CMD_RESET: + if (addr_no != 0) + pr_debug("NAND: Command %02x needs 0 byte address, " + "but addr_no = %d\n", command, addr_no); + pnx8550_nand_register_setup(1, 0, 0, 0, 0, NAND_CMD_RESET, 0); + addr = NAND_ADDR(column, page_addr); + NAND_ADDR_SEND(addr); + return; + + case NAND_CMD_READ0: + if (addr_no != 3) + pr_debug("NAND: Command %02x needs 3 byte address, " + "but addr_no = %d\n", command, addr_no); + pnx8550_nand_register_setup(1, 3, 1, 1, 0, NAND_CMD_READ0, 0); + return; + + case NAND_CMD_READ1: + printk(KERN_ERR "Wrong command: %02x\n", command); + return; + + case NAND_CMD_READOOB: + if (addr_no != 3) + pr_debug("NAND: Command %02x needs 3 byte address, " + "but addr_no = %d\n", command, addr_no); + pnx8550_nand_register_setup(1, 3, 1, 1, 0, NAND_CMD_READOOB, 0); + return; + + case NAND_CMD_READID: + if (addr_no != 1) + pr_debug("NAND: Command %02x needs 1 byte address, " + "but addr_no = %d\n", command, addr_no); + pnx8550_nand_register_setup(1, 1, 1, 0, 0, NAND_CMD_READID, 0); + return; + } +} + +/** + * Return true if the device is ready, false otherwise + */ +static int pnx8550_nand_dev_ready(struct mtd_info *mtd) +{ + return ((PNX8550_XIO_CTRL & PNX8550_XIO_CTRL_XIO_ACK) != 0); +} + +/** + * hardware specific access to control-lines + */ +static void pnx8550_nand_hwcontrol(struct mtd_info *mtd, int cmd) +{ + /* Nothing to do here, its all done by the XIO block */ +} + +/** + * Main initialization routine + */ +int __init pnx8550_nand_init(void) +{ + struct nand_chip *this; + + /* Get pointer to private data */ + this = &pnx8550_nand; + + /* Work out address of Nand Flash */ + pNandAddr = (u8 *) (KSEG1 | (PNX8550_BASE18_ADDR & (~0x7))); + + pNandAddr = (u8 *) (((u32) pNandAddr) + + ((PNX8550_XIO_SEL0 & PNX8550_XIO_SEL0_OFFSET_MASK) + >> PNX8550_XIO_SEL0_OFFSET_SHIFT) * 8 * 1024 * + 1024); + + /* Link the private data with the MTD structure */ + pnx8550_mtd.priv = this; + this->chip_delay = 15; + this->options = NAND_BUSWIDTH_16; + this->cmdfunc = pnx8550_nand_command; + this->read_byte = pnx8550_nand_read_byte; + this->read_word = pnx8550_nand_read_word; + this->read_buf = pnx8550_nand_read_buf; + this->write_byte = pnx8550_nand_write_byte; + this->write_word = pnx8550_nand_write_word; + this->write_buf = pnx8550_nand_write_buf; + this->verify_buf = pnx8550_nand_verify_buf; + this->dev_ready = pnx8550_nand_dev_ready; + this->hwcontrol = pnx8550_nand_hwcontrol; + this->eccmode = NAND_ECC_SOFT; + this->badblock_pattern = &nand16bit_memorybased; + this->autooob = &nand16bit_oob_16; + + transferBuffer = + kmalloc(pnx8550_mtd.oobblock + pnx8550_mtd.oobsize, + GFP_DMA | GFP_KERNEL); + if (!transferBuffer) { + printk(KERN_ERR + "Unable to allocate NAND data buffer for PNX8550\n"); + return -ENOMEM; + } + + /* Scan to find existence of the device */ + if (nand_scan(&pnx8550_mtd, 1)) { + printk(KERN_ERR "No NAND devices\n"); + return -ENXIO; + } + + add_mtd_partitions(&pnx8550_mtd, partition_info, NUM_PARTITIONS); + + return 0; +} + +module_init(pnx8550_nand_init); + +#ifdef MODULE +static void __exit pnx8550_nand_cleanup(void) +{ + nand_release(&pnx8550_mtd); + kfree(transferBuffer); +} + +module_exit(pnx8550_nand_cleanup); +#endif + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Adam Charrett"); +MODULE_DESCRIPTION("Driver for 16Bit NAND Flash on the XIO bus for PNX8550"); Index: linux-2.6.15_0/drivers/mtd/nand/Makefile =================================================================== --- linux-2.6.15_0.orig/drivers/mtd/nand/Makefile +++ linux-2.6.15_0/drivers/mtd/nand/Makefile @@ -18,5 +18,6 @@ obj-$(CONFIG_MTD_NAND_H1900) += h1910.o obj-$(CONFIG_MTD_NAND_RTC_FROM4) += rtc_from4.o obj-$(CONFIG_MTD_NAND_SHARPSL) += sharpsl.o obj-$(CONFIG_MTD_NAND_NANDSIM) += nandsim.o +obj-$(CONFIG_MTD_NAND_PNX8550) += pnx8550.o nand-objs = nand_base.o nand_bbt.o Index: linux-2.6.15_0/include/asm-mips/mach-pnx8550/nand.h =================================================================== --- linux-2.6.15_0.orig/include/asm-mips/mach-pnx8550/nand.h +++ linux-2.6.15_0/include/asm-mips/mach-pnx8550/nand.h @@ -4,10 +4,14 @@ #define PNX8550_NAND_BASE_ADDR 0x10000000 #define PNX8550_PCIXIO_BASE 0xBBE40000 +#define PNX8550_BASE10_ADDR *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x050) +#define PNX8550_BASE14_ADDR *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x054) +#define PNX8550_BASE18_ADDR *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x058) #define PNX8550_DMA_EXT_ADDR *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x800) #define PNX8550_DMA_INT_ADDR *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x804) #define PNX8550_DMA_TRANS_SIZE *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x808) #define PNX8550_DMA_CTRL *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x80c) +#define PNX8550_XIO_CTRL *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x810) #define PNX8550_XIO_SEL0 *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x814) #define PNX8550_GPXIO_ADDR *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x820) #define PNX8550_GPXIO_WR *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x824) @@ -21,6 +25,8 @@ #define PNX8550_DMA_INT_ENABLE *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0xfd4) #define PNX8550_DMA_INT_CLEAR *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0xfd8) +#define PNX8550_XIO_CTRL_XIO_ACK 0x00000002 + #define PNX8550_XIO_SEL0_EN_16BIT 0x00800000 #define PNX8550_XIO_SEL0_USE_ACK 0x00400000 #define PNX8550_XIO_SEL0_REN_HIGH 0x00100000 @@ -39,6 +45,9 @@ #define PNX8550_XIO_SEL0_SIZE_64MB 0x00000006 #define PNX8550_XIO_SEL0_ENAB 0x00000001 +#define PNX8550_XIO_SEL0_OFFSET_SHIFT 5 +#define PNX8550_XIO_SEL0_OFFSET_MASK (0xf << PNX8550_XIO_SEL0_OFFSET_SHIFT) + #define PNX8550_SEL0_DEFAULT ((PNX8550_XIO_SEL0_EN_16BIT) | \ (PNX8550_XIO_SEL0_REN_HIGH*0)| \ (PNX8550_XIO_SEL0_REN_LOW*2) | \ @@ -59,11 +68,15 @@ #define PNX8550_XIO_FLASH_64MB 0x00200000 #define PNX8550_XIO_FLASH_INC_DATA 0x00100000 -#define PNX8550_XIO_FLASH_CMD_PH 0x000C0000 +#define PNX8550_XIO_FLASH_CMD_PH_SHIFT 18 +#define PNX8550_XIO_FLASH_CMD_PH_MASK (3 << PNX8550_XIO_FLASH_CMD_PH_SHIFT) +#define PNX8550_XIO_FLASH_CMD_PH(_x) (((_x) << PNX8550_XIO_FLASH_CMD_PH_SHIFT) & PNX8550_XIO_FLASH_CMD_PH_MASK) +#define PNX8550_XIO_FLASH_ADR_PH_SHIFT 16 +#define PNX8550_XIO_FLASH_ADR_PH_MASK (3 << PNX8550_XIO_FLASH_ADR_PH_SHIFT) +#define PNX8550_XIO_FLASH_ADR_PH(_x) (((_x) << PNX8550_XIO_FLASH_ADR_PH_SHIFT) & PNX8550_XIO_FLASH_ADR_PH_MASK) #define PNX8550_XIO_FLASH_CMD_PH2 0x00080000 #define PNX8550_XIO_FLASH_CMD_PH1 0x00040000 #define PNX8550_XIO_FLASH_CMD_PH0 0x00000000 -#define PNX8550_XIO_FLASH_ADR_PH 0x00030000 #define PNX8550_XIO_FLASH_ADR_PH3 0x00030000 #define PNX8550_XIO_FLASH_ADR_PH2 0x00020000 #define PNX8550_XIO_FLASH_ADR_PH1 0x00010000 @@ -118,4 +131,9 @@ #define PNX8550_DMA_INT_CLR_M_ABORT (1<<2) #define PNX8550_DMA_INT_CLR_T_ABORT (1<<1) +#define PNX8550_DMA_INT_ACK 0x00004000 +#define PNX8550_DMA_INT_COMPL 0x00001000 +#define PNX8550_DMA_INT_NONSUP 0x00000200 +#define PNX8550_DMA_INT_ABORT 0x00000004 + #endif ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] PNX8550 NAND flash driver 2005-12-16 17:23 [PATCH] PNX8550 NAND flash driver Vladimir A. Barinov 2005-12-20 14:28 ` Vladimir A. Barinov @ 2006-01-12 18:24 ` Todd Poynor 2006-02-14 13:00 ` Vladimir A. Barinov 1 sibling, 1 reply; 8+ messages in thread From: Todd Poynor @ 2006-01-12 18:24 UTC (permalink / raw) To: Vladimir A. Barinov; +Cc: linux-mips, linux-mtd Vladimir A. Barinov wrote: > Hi All, > > Attached patch is NAND flash driver for PNX8550 based platforms. > Any comments and suggestions are highly appreciated. > > Vladimir > > > ------------------------------------------------------------------------ > > Signed-off-by: vbarinov@ru.mvista.com > > drivers/mtd/nand/Kconfig | 6 > drivers/mtd/nand/Makefile | 1 > drivers/mtd/nand/pnx8550.c | 747 +++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 754 insertions(+) > > Index: linux-2.6.15_0/drivers/mtd/nand/Kconfig > =================================================================== > --- linux-2.6.15_0.orig/drivers/mtd/nand/Kconfig > +++ linux-2.6.15_0/drivers/mtd/nand/Kconfig > @@ -90,6 +90,12 @@ config MTD_NAND_S3C2410 > No board specfic support is done by this driver, each board > must advertise a platform_device for the driver to attach. > > +config MTD_NAND_PNX8550 > + tristate "NAND Flash support for PNX8550" > + depends on PNX8550 && MTD_NAND > + help > + This enables the NAND flash controller on the PNX8550. > + > config MTD_NAND_S3C2410_DEBUG > bool "S3C2410 NAND driver debug" > depends on MTD_NAND_S3C2410 > Index: linux-2.6.15_0/drivers/mtd/nand/pnx8550.c > =================================================================== > --- /dev/null > +++ linux-2.6.15_0/drivers/mtd/nand/pnx8550.c > @@ -0,0 +1,747 @@ > +/* > + * Copyright (C) 2005 Koninklijke Philips Electronics N.V. > + * All Rights Reserved. > + * > + * Based on: drivers/mtd/nand/pnx8550.c by Torbjorn Lundberg > + * $Id: pnx8550_nand.c,v 1.8 2004/11/12 10:46:58 tobbe Exp $ > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. > + * > + * Overview: > + * This is a device driver for the NAND flash device found on the > + * PNX8550 board which utilizes the Samsung K9F5616U0C part. This is > + * a 32MByte (16M x 16 bits) NAND flash device. > + */ > + > +#include <linux/kernel.h> > +#include <linux/init.h> > +#include <linux/slab.h> > +#include <linux/module.h> > +#include <linux/delay.h> > +#include <linux/errno.h> > +#include <linux/sched.h> > +#include <linux/string.h> > +#include <linux/types.h> > +#include <linux/mtd/mtd.h> > +#include <linux/mtd/nand.h> > +#include <linux/mtd/nand_ecc.h> > +#include <linux/mtd/compatmac.h> > +#include <linux/interrupt.h> > +#include <linux/mtd/partitions.h> > +#include <asm/io.h> > +#include <asm/mach-pnx8550/nand.h> > + > +#define UBTM_NAME "microBTM" > +#define UBTM_BLOCK_START ( 0x00000000) > +#define UBTM_BLOCK_END ( 0x00004000) /* 16K size, first block */ > +#define UBTM_SIZE ( UBTM_BLOCK_END - UBTM_BLOCK_START) > + > +#define BOOTLOADER_NAME "bootloader" > +#define BOOTLOADER_BLOCK_START ( UBTM_BLOCK_END) > +#define BOOTLOADER_BLOCK_END ( 0x00040000) /* 256K - 16K = 240K */ > +#define BOOTLOADER_SIZE ( BOOTLOADER_BLOCK_END - BOOTLOADER_BLOCK_START) > + > +#define ROMFS_SYS_NAME "ROMFS-Tools" > +#define ROMFS_SYS_BLOCK_START ( BOOTLOADER_BLOCK_END) > +#define ROMFS_SYS_BLOCK_END ( 0x00600000) /* 6M - 256K = 5.75M */ > +#define ROMFS_SYS_SIZE ( ROMFS_SYS_BLOCK_END - ROMFS_SYS_BLOCK_START) > + > +#define ROMFS_APP_NAME "ROMFS-User" > +#define ROMFS_APP_BLOCK_START ( ROMFS_SYS_BLOCK_END) > +#define ROMFS_APP_BLOCK_END ( 0x01000000) /* 16M - 6M = 10M */ > +#define ROMFS_APP_SIZE ( ROMFS_APP_BLOCK_END - ROMFS_APP_BLOCK_START) > + > +#define USER_NAME "User" > +#define USER_BLOCK_START ( ROMFS_APP_BLOCK_END) > +#define USER_BLOCK_END ( 0x02000000) /* 32M - 16M = 16M */ > +#define USER_SIZE ( USER_BLOCK_END - USER_BLOCK_START) > + > +#define NAND_ADDR(_col, _page) ((_col) & (mtd->oobblock - 1)) + ((_page) << this->page_shift) > + > +#define NAND_ADDR_SEND(_addr) pNandAddr[(_addr)/sizeof(u16)] = 0 > + > +#define NAND_TRANSFER_TO(_addr, _buffer, _bytes) pnx8550_nand_transfer((_buffer), ((u8*)pNandAddr) + (_addr), (_bytes), 1) > + > +#define NAND_TRANSFER_FROM(_addr, _buffer, _bytes) pnx8550_nand_transfer(((u8*)pNandAddr) + (_addr), (_buffer), (_bytes), 0) > + > +static void pnx8550_nand_register_setup(u_char cmd_no, u_char addr_no, > + u_char include_data, u_char monitor_ACK, > + u_char enable64M, int cmd_a, int cmd_b); > + > +static inline void pnx8550_nand_wait_for_dev_ready(void); > + > +static void pnx8550_nand_transfer(void *from, void *to, int bytes, int toxio); > + > +static void pnx8550_nand_transferDMA(void *from, void *to, int bytes, > + int toxio); > + > +/* > + * Define partitions for flash device > + */ > +#define NUM_PARTITIONS 5 > +const static struct mtd_partition partition_info[NUM_PARTITIONS] = { > + { > + .name = UBTM_NAME, > + .offset = UBTM_BLOCK_START, > + .size = UBTM_SIZE}, > + { > + .name = BOOTLOADER_NAME, > + .offset = BOOTLOADER_BLOCK_START, > + .size = BOOTLOADER_SIZE}, > + { > + .name = ROMFS_SYS_NAME, > + .offset = ROMFS_SYS_BLOCK_START, > + .size = ROMFS_SYS_SIZE}, > + { > + .name = ROMFS_APP_NAME, > + .offset = ROMFS_APP_BLOCK_START, > + .size = ROMFS_APP_SIZE}, > + { > + .name = USER_NAME, > + .offset = USER_BLOCK_START, > + .size = USER_SIZE} > +}; > + > +/* Bad block descriptor for 16Bit nand flash */ > +static uint8_t scan_ff_pattern[] = { 0xff, 0xff }; > +static struct nand_bbt_descr nand16bit_memorybased = { > + .options = 0, > + .offs = 0, > + .len = 2, > + .pattern = scan_ff_pattern > +}; > + > +/* OOB Placement information that lines up with the boot loader code */ > +static struct nand_oobinfo nand16bit_oob_16 = { > + .useecc = MTD_NANDECC_AUTOPLACE, > + .eccbytes = 6, > + .eccpos = {2, 3, 4, 5, 6, 7}, > + .oobfree = {{8, 8}} > +}; > + > +/* Pointer into XIO for access to the 16Bit NAND flash device */ > +static volatile u16 *pNandAddr; > + > +/* Last command sent to the pnx8550_nand_command function */ > +static int last_command = -1; > +/* > + Next column address to read/write, set by pnx8550_nand_command > + updated by the read/write functions > +*/ > +static int last_col_addr = -1; > +/* > + Next page address to read/write, set by pnx8550_nand_command > + updated by the read/write functions > +*/ > +static int last_page_addr = -1; > + > +/* > + 32bit Aligned/DMA buffer > +*/ > +static u_char *transferBuffer = NULL; > + > +static struct mtd_info pnx8550_mtd; > +static struct nand_chip pnx8550_nand; > + > +/** > + * Transfer data to/from the NAND chip. > + * This function decides whether to use DMA or not depending on > + * the amount of data to transfer and the alignment of the buffers. > + * > + * @from: Address to transfer data from > + * @to: Address to transfer the data to > + * @bytes: Number of bytes to transfer > + * @toxio: Whether the transfer is going to XIO or not. > + */ > +static void pnx8550_nand_transfer(void *from, void *to, int bytes, int toxio) > +{ > + u16 *from16 = (u16 *) from; > + u16 *to16 = (u16 *) to; > + > + int i; > + > + if ((u32) from & 3) { > + printk > + ("%s: from buffer not 32bit aligned, will not use fastest transfer mechanism\n", > + __FUNCTION__); > + } > + if ((u32) to & 3) { > + printk > + ("%s: to buffer not 32bit aligned, will not use fastest transfer mechanism\n", > + __FUNCTION__); Those printks could get old pretty fast. Debugging info, not needed for normal operation. > + } > + > + if (((bytes & 3) || (bytes < 16)) || ((u32) to & 3) || ((u32) from & 3)) { > + if (((bytes & 1) == 0) && > + (((u32) to & 1) == 0) && (((u32) from & 1) == 0)) { > + int words = bytes / 2; > + > + local_irq_disable(); > + for (i = 0; i < words; i++) { > + to16[i] = from16[i]; > + } > + local_irq_enable(); Really necessary to disable all irqs around this transfer? How long can interrupts be off during that time? > + } else { > + printk > + ("%s: Transfer failed, byte-aligned transfers no allowed!\n", "non-word-aligned"? > + __FUNCTION__); > + } > + } else { > + pnx8550_nand_transferDMA(from, to, bytes, toxio); > + } > +} > + > +/** > + * Transfer data to/from the NAND chip using DMA > + * > + * @from: Address to transfer data from > + * @to: Address to transfer the data to > + * @bytes: Number of bytes to transfer > + * @toxio: Whether the transfer is going to XIO or not. > + */ > +static void pnx8550_nand_transferDMA(void *from, void *to, int bytes, int toxio) > +{ > + int cmd = 0; > + u32 internal; > + u32 external; > + > + if (toxio) { > + cmd = PNX8550_DMA_CTRL_PCI_CMD_WRITE; > + dma_cache_wback(from, bytes); > + internal = (u32) virt_to_phys(from); > + external = (u32) to - KSEG1; > + } else { > + cmd = PNX8550_DMA_CTRL_PCI_CMD_READ; > + internal = (u32) virt_to_phys(to); > + external = (u32) from - KSEG1; > + } > + > + local_irq_disable(); > + PNX8550_DMA_TRANS_SIZE = bytes >> 2; /* Length in words */ > + PNX8550_DMA_EXT_ADDR = external; > + PNX8550_DMA_INT_ADDR = internal; > + PNX8550_DMA_INT_CLEAR = 0xffff; > + PNX8550_DMA_CTRL = PNX8550_DMA_CTRL_BURST_512 | > + PNX8550_DMA_CTRL_SND2XIO | PNX8550_DMA_CTRL_INIT_DMA | cmd; > + > + while ((PNX8550_DMA_INT_STATUS & PNX8550_DMA_INT_COMPL) == 0) ; > + > + if (!toxio) { > + dma_cache_inv(to, bytes); > + } > + local_irq_enable(); Again, necessary to prevent interrupts? > +} > + > +/** > + * pnx8550_nand_read_byte - read one byte endianess aware from the chip > + * @mtd: MTD device structure > + * > + */ > +static u_char pnx8550_nand_read_byte(struct mtd_info *mtd) > +{ > + struct nand_chip *this = mtd->priv; > + u16 data = 0; > + int addr = NAND_ADDR(last_col_addr, last_page_addr); > + /* > + Read ID is a special case as we have to read BOTH bytes at the same > + time otherwise it doesn't work, once we have both bytes we work out > + which one we want. > + */ > + if (last_command == NAND_CMD_READID) { > + u32 *pNandAddr32 = (u32 *) pNandAddr; > + u32 data32; > + data32 = cpu_to_le32(pNandAddr32[0]); > + if (last_col_addr) { > + data = (u16) (data32 >> 16); > + } else { > + data = (u16) data32; > + } > + } else { > + data = cpu_to_le16(pNandAddr[(addr / sizeof(u16))]); > + if ((addr & 0x1) == 1) { > + data = (data & 0xff00) >> 16; > + } > + } > + /* > + Status is a special case, we don't need to increment the address > + because the address isn't used by the chip > + */ > + if (last_command != NAND_CMD_STATUS) { > + last_col_addr++; > + } > + return data & 0xff; > +} > + > +/** > + * pnx8550_nand_read_word - read one word from the chip > + * @mtd: MTD device structure > + * > + * Read function for 16bit buswith without > + * endianess conversion > + */ > +static u16 pnx8550_nand_read_word(struct mtd_info *mtd) > +{ > + struct nand_chip *this = mtd->priv; > + int addr = NAND_ADDR(last_col_addr, last_page_addr); > + u16 data = pNandAddr[(addr / sizeof(u16))]; > + return data; > +} > + > +/** > + * pnx8550_nand_write_byte - write one byte endianess aware to the chip > + * @mtd: MTD device structure > + * @byte: pointer to data byte to write > + * > + * Write function for 16bit buswith with > + * endianess conversion > + */ > +static void pnx8550_nand_write_byte(struct mtd_info *mtd, u_char byte) > +{ > + struct nand_chip *this = mtd->priv; > + int addr = NAND_ADDR(last_col_addr, last_page_addr); > + pNandAddr[(addr / sizeof(u16))] = le16_to_cpu((u16) byte); > +} > + > +/** > + * pnx8550_nand_write_word - write one word to the chip > + * @mtd: MTD device structure > + * @word: data word to write > + * > + * Write function for 16bit buswith without > + * endianess conversion > + */ > +static void pnx8550_nand_write_word(struct mtd_info *mtd, u16 word) > +{ > + struct nand_chip *this = mtd->priv; > + int addr = NAND_ADDR(last_col_addr, last_page_addr); > + pNandAddr[(addr / sizeof(u16))] = word; > +} > + > +/** > + * pnx8550_nand_write_buf - write buffer to chip > + * @mtd: MTD device structure > + * @buf: data buffer > + * @len: number of bytes to write > + * > + */ > +static void pnx8550_nand_write_buf(struct mtd_info *mtd, const u_char * buf, > + int len) > +{ > + struct nand_chip *this = mtd->priv; > + int addr = NAND_ADDR(last_col_addr, last_page_addr); > + int pageLen; > + int oobLen = 0; > + u_char *transBuf = (u_char *) buf; > + > + /* some sanity checking, word access only please */ > + if (len & 1) { > + printk("%s: non-word aligned length requested!\n", > + __FUNCTION__); > + } > + > + memcpy(transferBuffer, buf, len); > + transBuf = transferBuffer; > + > + /* > + Work out whether we are going to write to the OOB area > + after a standard page write. > + This is not the case when the command function is called > + with a column address > page size. Then we write as though > + it is to the page rather than the OOB as the command function > + has already selected the OOB area. > + */ > + if ((last_col_addr + len) > mtd->oobblock) > + oobLen = (last_col_addr + len) - mtd->oobblock; > + pageLen = len - oobLen; > + > + /* Clear the done flag */ > + PNX8550_GPXIO_CTRL |= PNX8550_GPXIO_CLR_DONE; > + if (pageLen > 0) { > + NAND_TRANSFER_TO(addr, transBuf, pageLen); > + } > + if (oobLen > 0) { > + pnx8550_nand_wait_for_dev_ready(); > + > + pnx8550_nand_register_setup(1, 0, 0, 1, 0, NAND_CMD_READOOB, 0); > + /* Work out where in the OOB we are going to start to write */ > + addr = NAND_ADDR(last_col_addr - mtd->oobblock, last_page_addr); > + NAND_ADDR_SEND(addr); > + pnx8550_nand_register_setup(2, 3, 1, 1, 0, NAND_CMD_SEQIN, > + NAND_CMD_PAGEPROG); > + > + /* Clear the done flag */ > + PNX8550_GPXIO_CTRL |= PNX8550_GPXIO_CLR_DONE; > + NAND_TRANSFER_TO(addr, transBuf + pageLen, oobLen); > + } > + > + /* > + Increment the address so on the next write we write in the > + correct place. > + */ > + last_col_addr += len; > + if (last_col_addr >= mtd->oobblock + mtd->oobsize) { > + last_col_addr -= mtd->oobblock + mtd->oobsize; > + last_page_addr++; > + } > +} > + > +/** > + * pnx8550_nand_read_buf - read chip data into buffer > + * @mtd: MTD device structure > + * @buf: buffer to store date > + * @len: number of bytes to read > + * > + */ > +static void pnx8550_nand_read_buf(struct mtd_info *mtd, u_char * buf, int len) > +{ > + struct nand_chip *this = mtd->priv; > + int addr = NAND_ADDR(last_col_addr, last_page_addr); > + int pageLen; > + int oobLen = 0; > + u_char *transBuf = buf; > + > + /* some sanity checking, word access only please */ > + if (len & 1) { > + printk("%s: non-word aligned length\n", __FUNCTION__); > + } > + > + transBuf = transferBuffer; > + > + /* > + Work out whether we are going to read the OOB area > + after a standard page read. > + This is not the case when the command function is called > + with a column address > page size. Then we read as though > + it is from the page rather than the OOB as the command > + function has already selected the OOB area. > + */ > + if ((last_col_addr + len) > mtd->oobblock) > + oobLen = (last_col_addr + len) - mtd->oobblock; > + pageLen = len - oobLen; > + > + if (pageLen) { > + NAND_TRANSFER_FROM(addr, transBuf, pageLen); > + } > + if (oobLen > 0) { > + pnx8550_nand_register_setup(1, 3, 1, 1, 0, NAND_CMD_READOOB, 0); > + addr = NAND_ADDR(last_col_addr - mtd->oobblock, last_page_addr); > + NAND_TRANSFER_FROM(addr, transBuf + pageLen, oobLen); > + } > + if (transBuf != buf) { > + memcpy(buf, transBuf, len); > + } > + > + /* > + Increment the address so on the next read we read from the > + correct place. > + */ > + last_col_addr += len; > + if (last_col_addr > mtd->oobblock + mtd->oobsize) { > + last_col_addr -= mtd->oobblock + mtd->oobsize; > + last_page_addr++; > + } > + return; > +} > + > +/** > + * pnx8550_nand_verify_buf - Verify chip data against buffer > + * @mtd: MTD device structure > + * @buf: buffer containing the data to compare > + * @len: number of bytes to compare > + * > + */ > +static int pnx8550_nand_verify_buf(struct mtd_info *mtd, const u_char * buf, > + int len) > +{ > + int result = 0; > + > + /* some sanity checking, word access only please */ > + if (len & 1) { > + printk("%s: non-word aligned length\n", __FUNCTION__); > + } > + > + pnx8550_nand_read_buf(mtd, transferBuffer, len); > + if (memcmp(buf, transferBuffer, len)) { > + result = -EFAULT; > + } > + > + return result; > + > +} > + > +/** > + * pnx8550_nand_command - Send command to NAND device > + * @mtd: MTD device structure > + * @command: the command to be sent > + * @column: the column address for this command, -1 if none > + * @page_addr: the page address for this command, -1 if none > + * > + * Send command to NAND device. > + */ > +static void pnx8550_nand_command(struct mtd_info *mtd, unsigned command, > + int column, int page_addr) > +{ > + register struct nand_chip *this = mtd->priv; > + u_char addr_no = 0; > + u_char spare = 0; > + int addr; > + /* > + If we are starting a write work out whether it is to the > + OOB or the main page and position the pointer correctly. > + */ > + if (command == NAND_CMD_SEQIN) { > + int readcmd; > + int col = column; > + if (column >= mtd->oobblock) { > + /* OOB area */ > + col -= mtd->oobblock; > + readcmd = NAND_CMD_READOOB; > + spare = 1; > + } else { > + readcmd = NAND_CMD_READ0; > + } > + pnx8550_nand_register_setup(1, 0, 0, 1, 0, readcmd, 0); > + addr = NAND_ADDR(col, page_addr); > + NAND_ADDR_SEND(addr); > + } > + > + /* Check the number of address bytes */ > + if ((column == -1) && (page_addr == -1)) { > + addr_no = 0; > + column = 0; > + page_addr = 0; > + } else if ((column == -1) && (page_addr != -1)) { > + addr_no = 2; > + column = 0; > + } else if ((column != -1) && (page_addr == -1)) { > + addr_no = 1; > + page_addr = 0; > + } else { > + addr_no = 3; > + } > + > + last_command = command; > + last_col_addr = column; > + last_page_addr = page_addr; > + > + switch (command) { > + > + case NAND_CMD_PAGEPROG: > + // Nothing to do, we've already done it! > + return; > + > + case NAND_CMD_SEQIN: > + if (addr_no != 3) > + printk > + ("NAND: Error. Command %02x needs 3 byte address, but addr_no = %d\n", > + command, addr_no); > + pnx8550_nand_register_setup(2, 3, 1, 1, spare, NAND_CMD_SEQIN, > + NAND_CMD_PAGEPROG); > + return; > + > + case NAND_CMD_ERASE1: > + if (addr_no != 2) > + printk > + ("NAND: Error. Command %02x needs 2 byte address, but addr_no = %d\n", > + command, addr_no); > + > + PNX8550_GPXIO_CTRL |= PNX8550_GPXIO_CLR_DONE; > + > + pnx8550_nand_register_setup(2, 2, 0, 1, 0, NAND_CMD_ERASE1, > + NAND_CMD_ERASE2); > + addr = NAND_ADDR(column, page_addr); > + NAND_ADDR_SEND(addr); > + return; > + > + case NAND_CMD_ERASE2: > + // Nothing to do, we've already done it! > + return; > + > + case NAND_CMD_STATUS: > + if (addr_no != 0) > + printk > + ("NAND: Error. Command %02x needs 0 byte address, but addr_no = %d\n", > + command, addr_no); > + pnx8550_nand_register_setup(1, 0, 1, 0, 0, NAND_CMD_STATUS, 0); > + return; > + > + case NAND_CMD_RESET: > + if (addr_no != 0) > + printk > + ("NAND: Error. Command %02x needs 0 byte address, but addr_no = %d\n", > + command, addr_no); > + pnx8550_nand_register_setup(1, 0, 0, 0, 0, NAND_CMD_RESET, 0); > + addr = NAND_ADDR(column, page_addr); > + NAND_ADDR_SEND(addr); > + return; > + > + case NAND_CMD_READ0: > + if (addr_no != 3) > + printk > + ("NAND: Error. Command %02x needs 3 byte address, but addr_no = %d\n", > + command, addr_no); > + > + pnx8550_nand_register_setup(1, 3, 1, 1, 0, NAND_CMD_READ0, 0); > + return; > + > + case NAND_CMD_READ1: > + printk("Wrong command: %02x\n", command); > + return; > + > + case NAND_CMD_READOOB: > + if (addr_no != 3) > + printk > + ("NAND: Error. Command %02x needs 3 byte address, but addr_no = %d\n", > + command, addr_no); > + pnx8550_nand_register_setup(1, 3, 1, 1, 0, NAND_CMD_READOOB, 0); > + return; > + > + case NAND_CMD_READID: > + if (addr_no != 1) > + printk > + ("NAND: Error. Command %02x needs 1 byte address, but addr_no = %d\n", > + command, addr_no); > + pnx8550_nand_register_setup(1, 1, 1, 0, 0, NAND_CMD_READID, 0); > + return; > + } > +} > + > +/* > + * Setup the registers in PCIXIO > + */ > +static void pnx8550_nand_register_setup(u_char cmd_no, > + u_char addr_no, > + u_char include_data, > + u_char monitor_ACK, > + u_char enable64M, int cmd_a, int cmd_b) > +{ > + unsigned int reg_nand = 0; > + reg_nand |= enable64M ? PNX8550_XIO_FLASH_64MB : 0; > + reg_nand |= include_data ? PNX8550_XIO_FLASH_INC_DATA : 0; > + reg_nand |= PNX8550_XIO_FLASH_CMD_PH(cmd_no); > + reg_nand |= PNX8550_XIO_FLASH_ADR_PH(addr_no); > + reg_nand |= PNX8550_XIO_FLASH_CMD_A(cmd_a); > + reg_nand |= PNX8550_XIO_FLASH_CMD_B(cmd_b); > + PNX8550_XIO_FLASH_CTRL = reg_nand; > + barrier(); > +} > + > +/* > + * Wait for the device to be ready for the next command > + */ > +static inline void pnx8550_nand_wait_for_dev_ready(void) > +{ > + while ((PNX8550_XIO_CTRL & PNX8550_XIO_CTRL_XIO_ACK) == 0) ; > +} > + > +/* > + * Return true if the device is ready, false otherwise > + */ > +static int pnx8550_nand_dev_ready(struct mtd_info *mtd) > +{ > + return ((PNX8550_XIO_CTRL & PNX8550_XIO_CTRL_XIO_ACK) != 0); > +} > + > +/* > + * hardware specific access to control-lines > + */ > +static void pnx8550_nand_hwcontrol(struct mtd_info *mtd, int cmd) > +{ > + // Nothing to do here, its all done by the XIO block > +} > + > +/* > + * Main initialization routine > + */ > +int __init pnx8550_nand_init(void) > +{ > + struct nand_chip *this; > + > + /* Get pointer to private data */ > + this = &pnx8550_nand; > + > + /* Initialize structures */ > + memset((char *)&pnx8550_mtd, 0, sizeof(struct mtd_info)); > + memset((char *)this, 0, sizeof(struct nand_chip)); > + > + /* Work out address of Nand Flash */ > + pNandAddr = (u16 *) (KSEG1 | (PNX8550_BASE18_ADDR & (~0x7))); > + > + pNandAddr = (u16 *) (((u32) pNandAddr) + > + ((PNX8550_XIO_SEL0 & PNX8550_XIO_SEL0_OFFSET_MASK) > + >> PNX8550_XIO_SEL0_OFFSET_SHIFT) * 8 * 1024 * > + 1024); > + > + /* Link the private data with the MTD structure */ > + pnx8550_mtd.priv = this; > + this->chip_delay = 15; > + this->options = NAND_BUSWIDTH_16; > + this->cmdfunc = pnx8550_nand_command; > + this->read_byte = pnx8550_nand_read_byte; > + this->read_word = pnx8550_nand_read_word; > + this->read_buf = pnx8550_nand_read_buf; > + this->write_byte = pnx8550_nand_write_byte; > + this->write_word = pnx8550_nand_write_word; > + this->write_buf = pnx8550_nand_write_buf; > + this->verify_buf = pnx8550_nand_verify_buf; > + this->dev_ready = pnx8550_nand_dev_ready; > + this->hwcontrol = pnx8550_nand_hwcontrol; > + this->eccmode = NAND_ECC_SOFT; > + this->badblock_pattern = &nand16bit_memorybased; > + this->autooob = &nand16bit_oob_16; > + > + transferBuffer = > + kmalloc(pnx8550_mtd.oobblock + pnx8550_mtd.oobsize, > + GFP_DMA | GFP_KERNEL); > + if (!transferBuffer) { > + printk(KERN_ERR > + "Unable to allocate NAND data buffer for PNX8550.\n"); > + return -ENOMEM; > + } > + > + /* Scan to find existence of the device */ > + if (nand_scan(&pnx8550_mtd, 1)) { > + printk("%s: Exiting No Devices\n", __FUNCTION__); > + return -ENXIO; Need kfree(transferBuffer) > + } > + > + /* Register the partitions */ > + add_mtd_partitions(&pnx8550_mtd, partition_info, NUM_PARTITIONS); Need Kconfig to select MTD_PARTITIONS if required. > + > + /* Return happy */ > + return 0; > +} > + > +module_init(pnx8550_nand_init); > + > +/* > + * Clean up routine > + */ > +#ifdef MODULE > +static void __exit pnx8550_nand_cleanup(void) > +{ > + /* Unregister the device */ > + del_mtd_device(&pnx8550_mtd); Need del_mtd_partitions I think? > + if (transferBuffer) { > + kfree(transferBuffer); "if (transferBuffer)" not needed and is discouraged . > + } > +} > + > +module_exit(pnx8550_nand_cleanup); > +#endif > + > +MODULE_LICENSE("GPL"); > +MODULE_AUTHOR("Adam Charrett"); > +MODULE_DESCRIPTION("Driver for 16Bit NAND Flash on the XIO bus for PNX8550"); > Index: linux-2.6.15_0/drivers/mtd/nand/Makefile > =================================================================== > --- linux-2.6.15_0.orig/drivers/mtd/nand/Makefile > +++ linux-2.6.15_0/drivers/mtd/nand/Makefile > @@ -18,5 +18,6 @@ obj-$(CONFIG_MTD_NAND_H1900) += h1910.o > obj-$(CONFIG_MTD_NAND_RTC_FROM4) += rtc_from4.o > obj-$(CONFIG_MTD_NAND_SHARPSL) += sharpsl.o > obj-$(CONFIG_MTD_NAND_NANDSIM) += nandsim.o > +obj-$(CONFIG_MTD_NAND_PNX8550) += pnx8550.o > > nand-objs = nand_base.o nand_bbt.o > > > ------------------------------------------------------------------------ > > ______________________________________________________ > Linux MTD discussion mailing list > http://lists.infradead.org/mailman/listinfo/linux-mtd/ -- Todd ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] PNX8550 NAND flash driver 2006-01-12 18:24 ` Todd Poynor @ 2006-02-14 13:00 ` Vladimir A. Barinov 2006-02-22 0:57 ` Todd Poynor 2006-07-10 9:53 ` Thomas Gleixner 0 siblings, 2 replies; 8+ messages in thread From: Vladimir A. Barinov @ 2006-02-14 13:00 UTC (permalink / raw) To: Todd Poynor, linux-mtd, linux-mips [-- Attachment #1: Type: text/plain, Size: 5221 bytes --] Hi Todd, Thank you for review. I attached recasted patch with the additional fix for wrong write in the OOB area. The driver is tested with bonnie++ test tool: This patches fixes the issue of the bug. This is tested by bonnie++ test on NAND flash formatted with jffs2 fs: root@192.168.0.8:~# flash_eraseall -j /dev/mtd4 Erasing 16 Kibyte @ 90c000 -- 56 % complete. Cleanmarker written at 90c000. Skipping bad block at 0x00910000 Erasing 16 Kibyte @ ffc000 -- 99 % complete. Cleanmarker written at ffc000. root@192.168.0.8:~# mount -t jffs2 /dev/mtdblock4 /mnt root@192.168.0.8:~# mount rootfs on / type rootfs (rw) /dev/root on / type nfs (rw,v2,rsize=4096,wsize=4096,hard,udp,nolock,addr=192.168.0.1) proc on /proc type proc (rw,nodiratime) sysfs on /sys type sysfs (rw) tmpfs on /tmp type tmpfs (rw) tmpfs on /dev/shm type tmpfs (rw) /dev/mtdblock4 on /mnt type jffs2 (rw,noatime) root@192.168.0.8:~# cat /proc/mtd dev: size erasesize name mtd0: 00004000 00004000 "microBTM" mtd1: 0003c000 00004000 "bootloader" mtd2: 005c0000 00004000 "ROMFS-Tools" mtd3: 00a00000 00004000 "ROMFS-User" mtd4: 01000000 00004000 "User" root@192.168.0.8:~# root@192.168.0.8:~# bonnie++ -u root -d /mnt -s 16 -r 0 Using uid:0, gid:0. Writing with putc()...done Writing intelligently...done Rewriting...done Reading with getc()...mtd->read(0x78 bytes from 0x64154) returned ECC error done Reading intelligently...done start 'em...done...done...done... Create files in sequential order...done. Stat files in sequential order...done. Delete files in sequential order...done. Create files in random order...done. Stat files in random order...done. Delete files in random order...done. Version 1.03 ------Sequential Output------ --Sequential Input- --Random- -Per Chr- --Block-- -Rewrite- -Per Chr- --Block-- --Seeks-- Machine Size K/sec %CP K/sec %CP K/sec %CP K/sec %CP K/sec %CP /sec %CP 192.168.0.8 16M 286 99 2379 99 2253 98 357 99 +++++ +++ 1961 98 ------Sequential Create------ --------Random Create-------- -Create-- --Read--- -Delete-- -Create-- --Read--- -Delete-- files /sec %CP /sec %CP /sec %CP /sec %CP /sec %CP /sec %CP 16 285 99 3013 99 292 97 275 95 3199 100 295 99 192.168.0.8,16M,286,99,2379,99,2253,98,357,99,+++++,+++,1960.8,98,16,285,99,3013,99,292,97,275,95,3199,100,295,99 root@192.168.0.8:~# Vladimir Todd Poynor wrote: > Vladimir A. Barinov wrote: > >> Hi All, >> >> Attached patch is NAND flash driver for PNX8550 based platforms. >> Any comments and suggestions are highly appreciated. >> >> Vladimir >> >> >> + if ((u32) from & 3) { >> + printk >> + ("%s: from buffer not 32bit aligned, will not use >> fastest transfer mechanism\n", >> + __FUNCTION__); >> + } >> + if ((u32) to & 3) { >> + printk >> + ("%s: to buffer not 32bit aligned, will not use fastest >> transfer mechanism\n", >> + __FUNCTION__); > > > Those printks could get old pretty fast. Debugging info, not needed > for normal operation. Thanks. Removed in the attached patch. > >> + } >> + >> + if (((bytes & 3) || (bytes < 16)) || ((u32) to & 3) || ((u32) >> from & 3)) { >> + if (((bytes & 1) == 0) && >> + (((u32) to & 1) == 0) && (((u32) from & 1) == 0)) { >> + int words = bytes / 2; >> + >> + local_irq_disable(); >> + for (i = 0; i < words; i++) { >> + to16[i] = from16[i]; >> + } >> + local_irq_enable(); > > > Really necessary to disable all irqs around this transfer? How long > can interrupts be off during that time? That is needed since the NAND device is binded to the external XIO bus that could be used by another devices. > > >> + >> + local_irq_disable(); >> + PNX8550_DMA_TRANS_SIZE = bytes >> 2; /* Length in words */ >> + PNX8550_DMA_EXT_ADDR = external; >> + PNX8550_DMA_INT_ADDR = internal; >> + PNX8550_DMA_INT_CLEAR = 0xffff; >> + PNX8550_DMA_CTRL = PNX8550_DMA_CTRL_BURST_512 | >> + PNX8550_DMA_CTRL_SND2XIO | PNX8550_DMA_CTRL_INIT_DMA | cmd; >> + >> + while ((PNX8550_DMA_INT_STATUS & PNX8550_DMA_INT_COMPL) == 0) ; >> + >> + if (!toxio) { >> + dma_cache_inv(to, bytes); >> + } >> + local_irq_enable(); > > > Again, necessary to prevent interrupts? > >> +} + /* Scan to find existence of the device */ >> + if (nand_scan(&pnx8550_mtd, 1)) { >> + printk("%s: Exiting No Devices\n", __FUNCTION__); >> + return -ENXIO; > > > Need kfree(transferBuffer) > >> + } >> + >> + /* Register the partitions */ >> + add_mtd_partitions(&pnx8550_mtd, partition_info, NUM_PARTITIONS); > > > Need Kconfig to select MTD_PARTITIONS if required. Added "ifdef". Thanks. > >> +static void __exit pnx8550_nand_cleanup(void) >> +{ >> + /* Unregister the device */ >> + del_mtd_device(&pnx8550_mtd); > > > Need del_mtd_partitions I think? No. That is in the del_mtd_device(). > >> + if (transferBuffer) { >> + kfree(transferBuffer); > > > "if (transferBuffer)" not needed and is discouraged Already removed. [-- Attachment #2: pnx8550_nand.patch --] [-- Type: text/plain, Size: 25759 bytes --] Signed-off-by: vbarinov@ru.mvista.com drivers/mtd/nand/Kconfig | 6 drivers/mtd/nand/Makefile | 1 drivers/mtd/nand/pnx8550.c | 707 +++++++++++++++++++++++++++++++++++ include/asm-mips/mach-pnx8550/nand.h | 22 - 4 files changed, 734 insertions(+), 2 deletions(-) Index: linux.git/drivers/mtd/nand/Kconfig =================================================================== --- linux.git.orig/drivers/mtd/nand/Kconfig +++ linux.git/drivers/mtd/nand/Kconfig @@ -90,6 +90,12 @@ config MTD_NAND_S3C2410 No board specfic support is done by this driver, each board must advertise a platform_device for the driver to attach. +config MTD_NAND_PNX8550 + tristate "NAND Flash support for PNX8550" + depends on PNX8550 && MTD_NAND + help + This enables the NAND flash controller on the PNX8550. + config MTD_NAND_S3C2410_DEBUG bool "S3C2410 NAND driver debug" depends on MTD_NAND_S3C2410 Index: linux.git/drivers/mtd/nand/Makefile =================================================================== --- linux.git.orig/drivers/mtd/nand/Makefile +++ linux.git/drivers/mtd/nand/Makefile @@ -18,5 +18,6 @@ obj-$(CONFIG_MTD_NAND_H1900) += h1910.o obj-$(CONFIG_MTD_NAND_RTC_FROM4) += rtc_from4.o obj-$(CONFIG_MTD_NAND_SHARPSL) += sharpsl.o obj-$(CONFIG_MTD_NAND_NANDSIM) += nandsim.o +obj-$(CONFIG_MTD_NAND_PNX8550) += pnx8550.o nand-objs = nand_base.o nand_bbt.o Index: linux.git/drivers/mtd/nand/pnx8550.c =================================================================== --- /dev/null +++ linux.git/drivers/mtd/nand/pnx8550.c @@ -0,0 +1,707 @@ +/* + * Copyright (C) 2005 Koninklijke Philips Electronics N.V. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Overview: + * This is a device driver for the NAND flash device found on the + * PNX8550 board which utilizes the Samsung K9F5616U0C part. This is + * a 32MByte (16M x 16 bits) NAND flash device. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/nand_ecc.h> +#include <linux/mtd/compatmac.h> +#include <linux/interrupt.h> +#include <linux/mtd/partitions.h> +#include <asm/io.h> +#include <asm/mach-pnx8550/nand.h> + +#define UBTM_NAME "microBTM" +#define UBTM_BLOCK_START ( 0x00000000) +#define UBTM_BLOCK_END ( 0x00004000) /* 16K size, first block */ +#define UBTM_SIZE ( UBTM_BLOCK_END - UBTM_BLOCK_START) + +#define BOOTLOADER_NAME "bootloader" +#define BOOTLOADER_BLOCK_START ( UBTM_BLOCK_END) +#define BOOTLOADER_BLOCK_END ( 0x00040000) /* 256K - 16K = 240K */ +#define BOOTLOADER_SIZE ( BOOTLOADER_BLOCK_END - BOOTLOADER_BLOCK_START) + +#define ROMFS_SYS_NAME "ROMFS-Tools" +#define ROMFS_SYS_BLOCK_START ( BOOTLOADER_BLOCK_END) +#define ROMFS_SYS_BLOCK_END ( 0x00600000) /* 6M - 256K = 5.75M */ +#define ROMFS_SYS_SIZE ( ROMFS_SYS_BLOCK_END - ROMFS_SYS_BLOCK_START) + +#define ROMFS_APP_NAME "ROMFS-User" +#define ROMFS_APP_BLOCK_START ( ROMFS_SYS_BLOCK_END) +#define ROMFS_APP_BLOCK_END ( 0x01000000) /* 16M - 6M = 10M */ +#define ROMFS_APP_SIZE ( ROMFS_APP_BLOCK_END - ROMFS_APP_BLOCK_START) + +#define USER_NAME "User" +#define USER_BLOCK_START ( ROMFS_APP_BLOCK_END) +#define USER_BLOCK_END ( 0x02000000) /* 32M - 16M = 16M */ +#define USER_SIZE ( USER_BLOCK_END - USER_BLOCK_START) + +#define NAND_ADDR(_col, _page) ((_col) & (mtd->oobblock - 1)) + ((_page) << this->page_shift) + +#define NAND_ADDR_SEND(_addr) writew(0, pNandAddr + _addr) + +#define NAND_TRANSFER_TO(_addr, _buffer, _bytes) pnx8550_nand_transfer(_buffer, pNandAddr + _addr, _bytes, 1) +#define NAND_TRANSFER_FROM(_addr, _buffer, _bytes) pnx8550_nand_transfer(pNandAddr + _addr, _buffer, _bytes, 0) + +/* + * Define partitions for flash device + */ +#define NUM_PARTITIONS 5 +const static struct mtd_partition partition_info[NUM_PARTITIONS] = { + { + .name = UBTM_NAME, + .offset = UBTM_BLOCK_START, + .size = UBTM_SIZE}, + { + .name = BOOTLOADER_NAME, + .offset = BOOTLOADER_BLOCK_START, + .size = BOOTLOADER_SIZE}, + { + .name = ROMFS_SYS_NAME, + .offset = ROMFS_SYS_BLOCK_START, + .size = ROMFS_SYS_SIZE}, + { + .name = ROMFS_APP_NAME, + .offset = ROMFS_APP_BLOCK_START, + .size = ROMFS_APP_SIZE}, + { + .name = USER_NAME, + .offset = USER_BLOCK_START, + .size = USER_SIZE} +}; + +/* Bad block descriptor for 16Bit nand flash */ +static uint8_t scan_ff_pattern[] = { 0xff, 0xff }; +static struct nand_bbt_descr nand16bit_memorybased = { + .options = 0, + .offs = 0, + .len = 2, + .pattern = scan_ff_pattern +}; + +/* OOB Placement information that lines up with the boot loader code */ +static struct nand_oobinfo nand16bit_oob_16 = { + .useecc = MTD_NANDECC_AUTOPLACE, + .eccbytes = 6, + .eccpos = {2, 3, 4, 5, 6, 7}, + .oobfree = {{8, 8}} +}; + +/* Pointer into XIO for access to the 16Bit NAND flash device */ +static u8 *pNandAddr; + +/* Last command sent to the pnx8550_nand_command function */ +static int last_command = -1; +/* + * Next column address to read/write, set by pnx8550_nand_command + * updated by the read/write functions + */ +static int last_col_addr = -1; +/* + * Next page address to read/write, set by pnx8550_nand_command + * updated by the read/write functions + */ +static int last_page_addr = -1; + +/* 32bit Aligned/DMA buffer */ +static u_char *transferBuffer; + +static struct mtd_info pnx8550_mtd; +static struct nand_chip pnx8550_nand; + +/** + * Allocate the buffer used to tranfser data between flash and memory. + * We use a transfer buffer so we know that the buffer is 32bit aligned. + */ +static void pnx8550_nand_alloc_transfer_buffer(void) +{ + if (transferBuffer == NULL) { + transferBuffer = + kmalloc(pnx8550_mtd.oobblock + pnx8550_mtd.oobsize, + GFP_DMA | GFP_KERNEL); + } +} + +/** + * Setup the registers in PCIXIO + */ +static void pnx8550_nand_register_setup(u_char cmd_no, + u_char addr_no, + u_char include_data, + u_char monitor_ACK, + u_char enable64M, int cmd_a, int cmd_b) +{ + unsigned int reg_nand = 0; + reg_nand |= enable64M ? PNX8550_XIO_FLASH_64MB : 0; + reg_nand |= include_data ? PNX8550_XIO_FLASH_INC_DATA : 0; + reg_nand |= PNX8550_XIO_FLASH_CMD_PH(cmd_no); + reg_nand |= PNX8550_XIO_FLASH_ADR_PH(addr_no); + reg_nand |= PNX8550_XIO_FLASH_CMD_A(cmd_a); + reg_nand |= PNX8550_XIO_FLASH_CMD_B(cmd_b); + PNX8550_XIO_FLASH_CTRL = reg_nand; + barrier(); +} + +/** + * Wait for the device to be ready for the next command + */ +static inline void pnx8550_nand_wait_for_dev_ready(void) +{ + while ((PNX8550_XIO_CTRL & PNX8550_XIO_CTRL_XIO_ACK) == 0) ; +} + +/** + * Transfer data to/from the NAND chip using DMA + * + * @from: Address to transfer data from + * @to: Address to transfer the data to + * @bytes: Number of bytes to transfer + * @toxio: Whether the transfer is going to XIO or not. + */ +static void pnx8550_nand_transferDMA(void *from, void *to, int bytes, int toxio) +{ + int cmd = 0; + u32 internal; + u32 external; + + if (toxio) { + cmd = PNX8550_DMA_CTRL_PCI_CMD_WRITE; + dma_cache_wback((unsigned long)from, bytes); + internal = (u32) virt_to_phys(from); + external = (u32) to - KSEG1; + } else { + cmd = PNX8550_DMA_CTRL_PCI_CMD_READ; + internal = (u32) virt_to_phys(to); + external = (u32) from - KSEG1; + } + + local_irq_disable(); + PNX8550_DMA_TRANS_SIZE = bytes >> 2; /* Length in words */ + PNX8550_DMA_EXT_ADDR = external; + PNX8550_DMA_INT_ADDR = internal; + PNX8550_DMA_INT_CLEAR = 0xffff; + PNX8550_DMA_CTRL = PNX8550_DMA_CTRL_BURST_512 | + PNX8550_DMA_CTRL_SND2XIO | PNX8550_DMA_CTRL_INIT_DMA | cmd; + + while ((PNX8550_DMA_INT_STATUS & PNX8550_DMA_INT_COMPL) == 0) ; + + if (!toxio) + dma_cache_inv((unsigned long)to, bytes); + + local_irq_enable(); +} + +/** + * Transfer data to/from the NAND chip. + * This function decides whether to use DMA or not depending on + * the amount of data to transfer and the alignment of the buffers. + * + * @from: Address to transfer data from + * @to: Address to transfer the data to + * @bytes: Number of bytes to transfer + * @toxio: Whether the transfer is going to XIO or not. + */ +static void pnx8550_nand_transfer(void *from, void *to, int bytes, int toxio) +{ + u16 *from16 = (u16 *) from; + u16 *to16 = (u16 *) to; + int i; + + if (((bytes & 3) || (bytes < 16)) || ((u32) to & 3) || ((u32) from & 3)) { + if (((bytes & 1) == 0) && + (((u32) to & 1) == 0) && (((u32) from & 1) == 0)) { + int words = bytes / 2; + + local_irq_disable(); + for (i = 0; i < words; i++) + to16[i] = from16[i]; + local_irq_enable(); + } + else + printk(KERN_ERR "%s: Transfer failed, " + "byte-aligned transfers no allowed!\n", + __FUNCTION__); + } else { + pnx8550_nand_transferDMA(from, to, bytes, toxio); + } +} + +/** + * pnx8550_nand_read_byte - read one byte endianess aware from the chip + * @mtd: MTD device structure + */ +static u_char pnx8550_nand_read_byte(struct mtd_info *mtd) +{ + struct nand_chip *this = mtd->priv; + u16 data = 0; + int addr = NAND_ADDR(last_col_addr, last_page_addr); + /* + * Read ID is a special case as we have to read BOTH bytes at the same + * time otherwise it doesn't work, once we have both bytes we work out + * which one we want. + */ + if (last_command == NAND_CMD_READID) { + u32 data32; + data32 = cpu_to_le32(readl(pNandAddr + 0)); + if (last_col_addr) + data = (u16) (data32 >> 16); + else + data = (u16) data32; + } else { + data = readw(pNandAddr + addr); + if ((addr & 0x1) == 1) + data = (data & 0xff00) >> 16; + } + /* + * Status is a special case, we don't need to increment the address + * because the address isn't used by the chip + */ + if (last_command != NAND_CMD_STATUS) + last_col_addr++; + + return data & 0xff; +} + +/** + * pnx8550_nand_read_word - read one word from the chip + * @mtd: MTD device structure + * + * Read function for 16bit buswith without + * endianess conversion + */ +static u16 pnx8550_nand_read_word(struct mtd_info *mtd) +{ + struct nand_chip *this = mtd->priv; + int addr = NAND_ADDR(last_col_addr, last_page_addr); + u16 data = readw(pNandAddr + addr); + return data; +} + +/** + * pnx8550_nand_write_byte - write one byte endianess aware to the chip + * @mtd: MTD device structure + * @byte: pointer to data byte to write + * + * Write function for 16bit buswith with + * endianess conversion + */ +static void pnx8550_nand_write_byte(struct mtd_info *mtd, u_char byte) +{ + struct nand_chip *this = mtd->priv; + int addr = NAND_ADDR(last_col_addr, last_page_addr); + writew(le16_to_cpu((u16) byte), pNandAddr + addr); +} + +/** + * pnx8550_nand_write_word - write one word to the chip + * @mtd: MTD device structure + * @word: data word to write + * + * Write function for 16bit buswith without + * endianess conversion + */ +static void pnx8550_nand_write_word(struct mtd_info *mtd, u16 word) +{ + struct nand_chip *this = mtd->priv; + int addr = NAND_ADDR(last_col_addr, last_page_addr); + writew(word, pNandAddr + addr); +} + +/** + * pnx8550_nand_write_buf - write buffer to chip + * @mtd: MTD device structure + * @buf: data buffer + * @len: number of bytes to write + */ +static void pnx8550_nand_write_buf(struct mtd_info *mtd, const u_char * buf, + int len) +{ + struct nand_chip *this = mtd->priv; + int addr = NAND_ADDR(last_col_addr, last_page_addr); + int pageLen; + int oobLen = 0; + u_char *transBuf = (u_char *) buf; + + /* some sanity checking, word access only please */ + if (len & 1) + pr_debug("%s: non-word aligned length requested!\n", + __FUNCTION__); + + pnx8550_nand_alloc_transfer_buffer(); + + memcpy(transferBuffer, buf, len); + transBuf = transferBuffer; + + /* + * Work out whether we are going to write to the OOB area + * after a standard page write. + * This is not the case when the command function is called + * with a column address > page size. Then we write as though + * it is to the page rather than the OOB as the command function + * has already selected the OOB area. + */ + if ((last_col_addr + len) > mtd->oobblock) { + if (last_col_addr >= mtd->oobblock) + oobLen = len; + else + oobLen = (last_col_addr + len) - mtd->oobblock; + } + pageLen = len - oobLen; + + /* Clear the done flag */ + PNX8550_GPXIO_CTRL |= PNX8550_GPXIO_CLR_DONE; + if (pageLen > 0) + NAND_TRANSFER_TO(addr, transBuf, pageLen); + + if (oobLen > 0) { + pnx8550_nand_wait_for_dev_ready(); + + pnx8550_nand_register_setup(1, 0, 0, 1, 0, NAND_CMD_READOOB, 0); + /* Work out where in the OOB we are going to start to write */ + addr = NAND_ADDR(last_col_addr - mtd->oobblock, last_page_addr); + NAND_ADDR_SEND(addr); + pnx8550_nand_register_setup(2, 3, 1, 1, 0, NAND_CMD_SEQIN, + NAND_CMD_PAGEPROG); + + /* Clear the done flag */ + PNX8550_GPXIO_CTRL |= PNX8550_GPXIO_CLR_DONE; + NAND_TRANSFER_TO(addr, transBuf + pageLen, oobLen); + } + + /* + * Increment the address so on the next write we write in the + * correct place. + */ + last_col_addr += len; + if (last_col_addr >= mtd->oobblock + mtd->oobsize) { + last_col_addr -= mtd->oobblock + mtd->oobsize; + last_page_addr++; + } +} + +/** + * pnx8550_nand_read_buf - read chip data into buffer + * @mtd: MTD device structure + * @buf: buffer to store date + * @len: number of bytes to read + */ +static void pnx8550_nand_read_buf(struct mtd_info *mtd, u_char * buf, int len) +{ + struct nand_chip *this = mtd->priv; + int addr = NAND_ADDR(last_col_addr, last_page_addr); + int pageLen; + int oobLen = 0; + u_char *transBuf = buf; + + /* some sanity checking, word access only please */ + if (len & 1) + pr_debug("%s: non-word aligned length\n", __FUNCTION__); + + pnx8550_nand_alloc_transfer_buffer(); + + transBuf = transferBuffer; + + /* + * Work out whether we are going to read the OOB area + * after a standard page read. + * This is not the case when the command function is called + * with a column address > page size. Then we read as though + * it is from the page rather than the OOB as the command + * function has already selected the OOB area. + */ + if ((last_col_addr + len) > mtd->oobblock) { + if (last_col_addr >= mtd->oobblock) + oobLen = len; + else + oobLen = (last_col_addr + len) - mtd->oobblock; + } + pageLen = len - oobLen; + + if (pageLen) + NAND_TRANSFER_FROM(addr, transBuf, pageLen); + + if (oobLen > 0) { + pnx8550_nand_register_setup(1, 3, 1, 1, 0, NAND_CMD_READOOB, 0); + addr = NAND_ADDR(last_col_addr - mtd->oobblock, last_page_addr); + NAND_TRANSFER_FROM(addr, transBuf + pageLen, oobLen); + } + + memcpy(buf, transBuf, len); + + /* + * Increment the address so on the next read we read from the + * correct place. + */ + last_col_addr += len; + if (last_col_addr > mtd->oobblock + mtd->oobsize) { + last_col_addr -= mtd->oobblock + mtd->oobsize; + last_page_addr++; + } + return; +} + +/** + * pnx8550_nand_verify_buf - Verify chip data against buffer + * @mtd: MTD device structure + * @buf: buffer containing the data to compare + * @len: number of bytes to compare + * + */ +static int pnx8550_nand_verify_buf(struct mtd_info *mtd, const u_char * buf, + int len) +{ + int result = 0; + + /* some sanity checking, word access only please */ + if (len & 1) + pr_debug("%s: non-word aligned length\n", __FUNCTION__); + + pnx8550_nand_read_buf(mtd, transferBuffer, len); + if (memcmp(buf, transferBuffer, len)) + result = -EFAULT; + + return result; + +} + +/** + * pnx8550_nand_command - Send command to NAND device + * @mtd: MTD device structure + * @command: the command to be sent + * @column: the column address for this command, -1 if none + * @page_addr: the page address for this command, -1 if none + * + * Send command to NAND device. + */ +static void pnx8550_nand_command(struct mtd_info *mtd, unsigned command, + int column, int page_addr) +{ + register struct nand_chip *this = mtd->priv; + u_char addr_no = 0; + u_char spare = 0; + int addr; + /* + If we are starting a write work out whether it is to the + OOB or the main page and position the pointer correctly. + */ + if (command == NAND_CMD_SEQIN) { + int readcmd; + int col = column; + if (column >= mtd->oobblock) { + /* OOB area */ + col -= mtd->oobblock; + readcmd = NAND_CMD_READOOB; + spare = 1; + } else { + readcmd = NAND_CMD_READ0; + } + pnx8550_nand_register_setup(1, 0, 0, 1, 0, readcmd, 0); + addr = NAND_ADDR(col, page_addr); + NAND_ADDR_SEND(addr); + } + + /* Check the number of address bytes */ + if ((column == -1) && (page_addr == -1)) { + addr_no = 0; + column = 0; + page_addr = 0; + } else if ((column == -1) && (page_addr != -1)) { + addr_no = 2; + column = 0; + } else if ((column != -1) && (page_addr == -1)) { + addr_no = 1; + page_addr = 0; + } else { + addr_no = 3; + } + + last_command = command; + last_col_addr = column; + last_page_addr = page_addr; + + switch (command) { + case NAND_CMD_PAGEPROG: + /* Nothing to do, we've already done it! */ + return; + + case NAND_CMD_SEQIN: + if (addr_no != 3) + printk(KERN_ERR + "NAND: Command %02x needs 3 byte address," + "but addr_no = %d\n", command, addr_no); + pnx8550_nand_register_setup(2, 3, 1, 1, spare, NAND_CMD_SEQIN, + NAND_CMD_PAGEPROG); + return; + + case NAND_CMD_ERASE1: + if (addr_no != 2) + pr_debug("NAND: Command %02x needs 2 byte" "address," + "but addr_no = %d\n", command, addr_no); + PNX8550_GPXIO_CTRL |= PNX8550_GPXIO_CLR_DONE; + pnx8550_nand_register_setup(2, 2, 0, 1, 0, NAND_CMD_ERASE1, + NAND_CMD_ERASE2); + addr = NAND_ADDR(column, page_addr); + NAND_ADDR_SEND(addr); + return; + + case NAND_CMD_ERASE2: + /* Nothing to do, we've already done it! */ + return; + + case NAND_CMD_STATUS: + if (addr_no != 0) + pr_debug("NAND: Command %02x needs 0 byte address," + "but addr_no = %d\n", command, addr_no); + pnx8550_nand_register_setup(1, 0, 1, 0, 0, NAND_CMD_STATUS, 0); + return; + + case NAND_CMD_RESET: + if (addr_no != 0) + pr_debug("NAND: Command %02x needs 0 byte address," + "but addr_no = %d\n", command, addr_no); + pnx8550_nand_register_setup(1, 0, 0, 0, 0, NAND_CMD_RESET, 0); + addr = NAND_ADDR(column, page_addr); + NAND_ADDR_SEND(addr); + return; + + case NAND_CMD_READ0: + if (addr_no != 3) + pr_debug("NAND: Command %02x needs 3 byte address," + "but addr_no = %d\n", command, addr_no); + pnx8550_nand_register_setup(1, 3, 1, 1, 0, NAND_CMD_READ0, 0); + return; + + case NAND_CMD_READ1: + printk(KERN_ERR "Wrong command: %02x\n", command); + return; + + case NAND_CMD_READOOB: + if (addr_no != 3) + pr_debug("NAND: Command %02x needs 3 byte address," + "but addr_no = %d\n", command, addr_no); + pnx8550_nand_register_setup(1, 3, 1, 1, 0, NAND_CMD_READOOB, 0); + return; + + case NAND_CMD_READID: + if (addr_no != 1) + pr_debug("NAND: Command %02x needs 1 byte address," + "but addr_no = %d\n", command, addr_no); + pnx8550_nand_register_setup(1, 1, 1, 0, 0, NAND_CMD_READID, 0); + return; + } +} + +/** + * Return true if the device is ready, false otherwise + */ +static int pnx8550_nand_dev_ready(struct mtd_info *mtd) +{ + return ((PNX8550_XIO_CTRL & PNX8550_XIO_CTRL_XIO_ACK) != 0); +} + +/** + * hardware specific access to control-lines + */ +static void pnx8550_nand_hwcontrol(struct mtd_info *mtd, int cmd) +{ + /* Nothing to do here, its all done by the XIO block */ +} + +/** + * Main initialization routine + */ +int __init pnx8550_nand_init(void) +{ + struct nand_chip *this; + + /* Get pointer to private data */ + this = &pnx8550_nand; + + /* Work out address of Nand Flash */ + pNandAddr = (u8 *) (KSEG1 | (PNX8550_BASE18_ADDR & (~0x7))); + + pNandAddr = (u8 *) (((u32) pNandAddr) + + ((PNX8550_XIO_SEL0 & PNX8550_XIO_SEL0_OFFSET_MASK) + >> PNX8550_XIO_SEL0_OFFSET_SHIFT) * 8 * 1024 * + 1024); + + /* Link the private data with the MTD structure */ + pnx8550_mtd.priv = this; + this->chip_delay = 15; + this->options = NAND_BUSWIDTH_16; + this->cmdfunc = pnx8550_nand_command; + this->read_byte = pnx8550_nand_read_byte; + this->read_word = pnx8550_nand_read_word; + this->read_buf = pnx8550_nand_read_buf; + this->write_byte = pnx8550_nand_write_byte; + this->write_word = pnx8550_nand_write_word; + this->write_buf = pnx8550_nand_write_buf; + this->verify_buf = pnx8550_nand_verify_buf; + this->dev_ready = pnx8550_nand_dev_ready; + this->hwcontrol = pnx8550_nand_hwcontrol; + this->eccmode = NAND_ECC_SOFT; + this->badblock_pattern = &nand16bit_memorybased; + this->autooob = &nand16bit_oob_16; + + /* Scan to find existence of the device */ + if (nand_scan(&pnx8550_mtd, 1)) { + printk(KERN_ERR "No NAND devices\n"); + return -ENXIO; + } + + if (!transferBuffer) { + printk(KERN_ERR + "Unable to allocate NAND data buffer for PNX8550\n"); + return -ENOMEM; + } +#ifdef CONFIG_MTD_PARTITIONS + add_mtd_partitions(&pnx8550_mtd, partition_info, NUM_PARTITIONS); +#endif + + return 0; +} + +module_init(pnx8550_nand_init); + +#ifdef MODULE +static void __exit pnx8550_nand_cleanup(void) +{ + nand_release(&pnx8550_mtd); + kfree(transferBuffer); +} + +module_exit(pnx8550_nand_cleanup); +#endif + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Adam Charrett"); +MODULE_DESCRIPTION("Driver for 16Bit NAND Flash on the XIO bus for PNX8550"); Index: linux.git/include/asm-mips/mach-pnx8550/nand.h =================================================================== --- linux.git.orig/include/asm-mips/mach-pnx8550/nand.h +++ linux.git/include/asm-mips/mach-pnx8550/nand.h @@ -4,10 +4,14 @@ #define PNX8550_NAND_BASE_ADDR 0x10000000 #define PNX8550_PCIXIO_BASE 0xBBE40000 +#define PNX8550_BASE10_ADDR *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x050) +#define PNX8550_BASE14_ADDR *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x054) +#define PNX8550_BASE18_ADDR *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x058) #define PNX8550_DMA_EXT_ADDR *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x800) #define PNX8550_DMA_INT_ADDR *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x804) #define PNX8550_DMA_TRANS_SIZE *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x808) #define PNX8550_DMA_CTRL *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x80c) +#define PNX8550_XIO_CTRL *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x810) #define PNX8550_XIO_SEL0 *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x814) #define PNX8550_GPXIO_ADDR *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x820) #define PNX8550_GPXIO_WR *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0x824) @@ -21,6 +25,8 @@ #define PNX8550_DMA_INT_ENABLE *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0xfd4) #define PNX8550_DMA_INT_CLEAR *(volatile unsigned long *)(PNX8550_PCIXIO_BASE + 0xfd8) +#define PNX8550_XIO_CTRL_XIO_ACK 0x00000002 + #define PNX8550_XIO_SEL0_EN_16BIT 0x00800000 #define PNX8550_XIO_SEL0_USE_ACK 0x00400000 #define PNX8550_XIO_SEL0_REN_HIGH 0x00100000 @@ -39,6 +45,9 @@ #define PNX8550_XIO_SEL0_SIZE_64MB 0x00000006 #define PNX8550_XIO_SEL0_ENAB 0x00000001 +#define PNX8550_XIO_SEL0_OFFSET_SHIFT 5 +#define PNX8550_XIO_SEL0_OFFSET_MASK (0xf << PNX8550_XIO_SEL0_OFFSET_SHIFT) + #define PNX8550_SEL0_DEFAULT ((PNX8550_XIO_SEL0_EN_16BIT) | \ (PNX8550_XIO_SEL0_REN_HIGH*0)| \ (PNX8550_XIO_SEL0_REN_LOW*2) | \ @@ -59,11 +68,15 @@ #define PNX8550_XIO_FLASH_64MB 0x00200000 #define PNX8550_XIO_FLASH_INC_DATA 0x00100000 -#define PNX8550_XIO_FLASH_CMD_PH 0x000C0000 +#define PNX8550_XIO_FLASH_CMD_PH_SHIFT 18 +#define PNX8550_XIO_FLASH_CMD_PH_MASK (3 << PNX8550_XIO_FLASH_CMD_PH_SHIFT) +#define PNX8550_XIO_FLASH_CMD_PH(_x) (((_x) << PNX8550_XIO_FLASH_CMD_PH_SHIFT) & PNX8550_XIO_FLASH_CMD_PH_MASK) +#define PNX8550_XIO_FLASH_ADR_PH_SHIFT 16 +#define PNX8550_XIO_FLASH_ADR_PH_MASK (3 << PNX8550_XIO_FLASH_ADR_PH_SHIFT) +#define PNX8550_XIO_FLASH_ADR_PH(_x) (((_x) << PNX8550_XIO_FLASH_ADR_PH_SHIFT) & PNX8550_XIO_FLASH_ADR_PH_MASK) #define PNX8550_XIO_FLASH_CMD_PH2 0x00080000 #define PNX8550_XIO_FLASH_CMD_PH1 0x00040000 #define PNX8550_XIO_FLASH_CMD_PH0 0x00000000 -#define PNX8550_XIO_FLASH_ADR_PH 0x00030000 #define PNX8550_XIO_FLASH_ADR_PH3 0x00030000 #define PNX8550_XIO_FLASH_ADR_PH2 0x00020000 #define PNX8550_XIO_FLASH_ADR_PH1 0x00010000 @@ -118,4 +131,9 @@ #define PNX8550_DMA_INT_CLR_M_ABORT (1<<2) #define PNX8550_DMA_INT_CLR_T_ABORT (1<<1) +#define PNX8550_DMA_INT_ACK 0x00004000 +#define PNX8550_DMA_INT_COMPL 0x00001000 +#define PNX8550_DMA_INT_NONSUP 0x00000200 +#define PNX8550_DMA_INT_ABORT 0x00000004 + #endif ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] PNX8550 NAND flash driver 2006-02-14 13:00 ` Vladimir A. Barinov @ 2006-02-22 0:57 ` Todd Poynor 2006-07-10 9:53 ` Thomas Gleixner 1 sibling, 0 replies; 8+ messages in thread From: Todd Poynor @ 2006-02-22 0:57 UTC (permalink / raw) To: Vladimir A. Barinov; +Cc: linux-mips, linux-mtd Hi Vladimir -- a couple comments. > + PNX8550_XIO_FLASH_CTRL = reg_nand; > + barrier(); > +} barrier() at the end of a function shouldn't be needed, function exit is an implicit optimizer flush? > + pnx8550_nand_alloc_transfer_buffer(); > + > + memcpy(transferBuffer, buf, len); Something should check for NULL return from kmalloc in both places this is called. > + /* Scan to find existence of the device */ > + if (nand_scan(&pnx8550_mtd, 1)) { > + printk(KERN_ERR "No NAND devices\n"); > + return -ENXIO; > + } > + > + if (!transferBuffer) { > + printk(KERN_ERR > + "Unable to allocate NAND data buffer for PNX8550\n"); > + return -ENOMEM; > + } Not sure why transferBuffer was expected to be allocated at this point (only when first read/write_buf called, scan does read_byte)? -- Todd ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] PNX8550 NAND flash driver 2006-02-14 13:00 ` Vladimir A. Barinov 2006-02-22 0:57 ` Todd Poynor @ 2006-07-10 9:53 ` Thomas Gleixner 2006-07-25 18:34 ` Jurgen 1 sibling, 1 reply; 8+ messages in thread From: Thomas Gleixner @ 2006-07-10 9:53 UTC (permalink / raw) To: Vladimir A. Barinov; +Cc: linux-mtd, Todd Poynor, linux-mips On Tue, 2006-02-14 at 15:59 +0300, Vladimir A. Barinov wrote: > >> + } > >> + > >> + if (((bytes & 3) || (bytes < 16)) || ((u32) to & 3) || ((u32) > >> from & 3)) { > >> + if (((bytes & 1) == 0) && > >> + (((u32) to & 1) == 0) && (((u32) from & 1) == 0)) { > >> + int words = bytes / 2; > >> + > >> + local_irq_disable(); > >> + for (i = 0; i < words; i++) { > >> + to16[i] = from16[i]; > >> + } > >> + local_irq_enable(); > > > > > > Really necessary to disable all irqs around this transfer? How long > > can interrupts be off during that time? > > That is needed since the NAND device is binded to the external XIO bus > that could be used by another devices. Err, you have to protect the whole access sequence then. What protects the chip against access between the command write and data read ? If this really is a bus conflict problem, then you need some more protection than this. Can you please describe in detail why you think this is necessary. tglx ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] PNX8550 NAND flash driver 2006-07-10 9:53 ` Thomas Gleixner @ 2006-07-25 18:34 ` Jurgen 2006-07-26 7:02 ` Thomas Gleixner 0 siblings, 1 reply; 8+ messages in thread From: Jurgen @ 2006-07-25 18:34 UTC (permalink / raw) To: Thomas Gleixner; +Cc: linux-mtd, Vladimir A. Barinov, Todd Poynor, linux-mips Thomas Gleixner schreef: > On Tue, 2006-02-14 at 15:59 +0300, Vladimir A. Barinov wrote: > >>>> + } >>>> + >>>> + if (((bytes & 3) || (bytes < 16)) || ((u32) to & 3) || ((u32) >>>> from & 3)) { >>>> + if (((bytes & 1) == 0) && >>>> + (((u32) to & 1) == 0) && (((u32) from & 1) == 0)) { >>>> + int words = bytes / 2; >>>> + >>>> + local_irq_disable(); >>>> + for (i = 0; i < words; i++) { >>>> + to16[i] = from16[i]; >>>> + } >>>> + local_irq_enable(); >>>> >>> Really necessary to disable all irqs around this transfer? How long >>> can interrupts be off during that time? >>> >> That is needed since the NAND device is binded to the external XIO bus >> that could be used by another devices. >> > > Err, you have to protect the whole access sequence then. What protects > the chip against access between the command write and data read ? > > If this really is a bus conflict problem, then you need some more > protection than this. > > Can you please describe in detail why you think this is necessary. > > tglx > > > > > > > > Root cause of the problem lies within the early implementation of the low-level NAND commands. There was a severe risk that the PCI accesses were stalled because of a Read Status command for the NAND Flash. This Read Status was launched immediately after program/erase command. The hardware itself will wait for the Ready/Busy to be high and only then launch the Read Status command. This behavior caused timeout on the internal bus because PCI was unable to use the pins during this wait. If this problem was coinciding with an ISR that tried to perform a PCI status register, then this PCI access could possibly timeout (because the PCI pins were already claimed for the XIO access that is depending on the RBY signal). Since the problem only showed during the PCI device ISR, the quick'n'dirty hack was to disable interrupts during XIO accesses. A better fix that should be available somewhere, is to improve the low-level NAND driver that will first check the status of the Ready/Busy line and only THEN launch the Read NAND Status command... Jurgen ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] PNX8550 NAND flash driver 2006-07-25 18:34 ` Jurgen @ 2006-07-26 7:02 ` Thomas Gleixner 0 siblings, 0 replies; 8+ messages in thread From: Thomas Gleixner @ 2006-07-26 7:02 UTC (permalink / raw) To: Jurgen; +Cc: Todd Poynor, Vladimir A. Barinov, linux-mtd, linux-mips On Tue, 2006-07-25 at 20:34 +0200, Jurgen wrote: > Root cause of the problem lies within the early implementation of the > low-level NAND commands. There was a severe risk that the PCI accesses > were stalled because of a Read Status command for the NAND Flash. This > Read Status was launched immediately after program/erase command. The > hardware itself will wait for the Ready/Busy to be high and only then > launch the Read Status command. This behavior caused timeout on the > internal bus because PCI was unable to use the pins during this wait. The hardware design is broken. Status Read can be requested while R/B is low. See NAND datasheets. > If this problem was coinciding with an ISR that tried to perform a PCI > status register, then this PCI access could possibly timeout (because > the PCI pins were already claimed for the XIO access that is depending > on the RBY signal). > > Since the problem only showed during the PCI device ISR, the > quick'n'dirty hack was to disable interrupts during XIO accesses. > > A better fix that should be available somewhere, is to improve the > low-level NAND driver that will first check the status of the Ready/Busy > line and only THEN launch the Read NAND Status command... Thats not an improvement. Thats a hack for your broken hardware. You'd burden the R/B check on every sane hardware out there. You can add the R/B check to the chip->cmd_ctrl() function of your board driver. tglx ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2006-07-26 6:59 UTC | newest] Thread overview: 8+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2005-12-16 17:23 [PATCH] PNX8550 NAND flash driver Vladimir A. Barinov 2005-12-20 14:28 ` Vladimir A. Barinov 2006-01-12 18:24 ` Todd Poynor 2006-02-14 13:00 ` Vladimir A. Barinov 2006-02-22 0:57 ` Todd Poynor 2006-07-10 9:53 ` Thomas Gleixner 2006-07-25 18:34 ` Jurgen 2006-07-26 7:02 ` Thomas Gleixner
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox