All of lore.kernel.org
 help / color / mirror / Atom feed
From: Todd Poynor <tpoynor@mvista.com>
To: "Vladimir A. Barinov" <vbarinov@ru.mvista.com>
Cc: linux-mtd@lists.infradead.org, linux-mips@linux-mips.org
Subject: Re: [PATCH] PNX8550 NAND flash driver
Date: Thu, 12 Jan 2006 10:24:02 -0800	[thread overview]
Message-ID: <43C69EC2.2070601@mvista.com> (raw)
In-Reply-To: <43A2F819.1040106@ru.mvista.com>

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

WARNING: multiple messages have this Message-ID (diff)
From: Todd Poynor <tpoynor@mvista.com>
To: "Vladimir A. Barinov" <vbarinov@ru.mvista.com>
Cc: linux-mips@linux-mips.org, linux-mtd@lists.infradead.org
Subject: Re: [PATCH] PNX8550 NAND flash driver
Date: Thu, 12 Jan 2006 10:24:02 -0800	[thread overview]
Message-ID: <43C69EC2.2070601@mvista.com> (raw)
In-Reply-To: <43A2F819.1040106@ru.mvista.com>

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

  parent reply	other threads:[~2006-01-15 19:15 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2005-12-16 17:23 [PATCH] PNX8550 NAND flash driver Vladimir A. Barinov
2005-12-16 17:23 ` Vladimir A. Barinov
2005-12-20 14:27 ` Vladimir A. Barinov
2005-12-20 14:28   ` Vladimir A. Barinov
2006-01-12 18:24 ` Todd Poynor [this message]
2006-01-12 18:24   ` Todd Poynor
2006-02-14 12:59   ` Vladimir A. Barinov
2006-02-14 13:00     ` Vladimir A. Barinov
2006-02-22  0:57     ` Todd Poynor
2006-02-22  0:57       ` Todd Poynor
2006-07-10  9:53     ` Thomas Gleixner
2006-07-10  9:53       ` Thomas Gleixner
2006-07-25 18:34       ` Jurgen
2006-07-25 18:34         ` Jurgen
2006-07-26  7:02         ` Thomas Gleixner
2006-07-26  7:02           ` Thomas Gleixner

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=43C69EC2.2070601@mvista.com \
    --to=tpoynor@mvista.com \
    --cc=linux-mips@linux-mips.org \
    --cc=linux-mtd@lists.infradead.org \
    --cc=vbarinov@ru.mvista.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.