power management routines for NAND driver

Vitaly Wool vwool at ru.mvista.com
Fri Aug 12 11:57:43 EDT 2005


> > P. S. Is it reasonable to send the use case (i. e. the NAND driver I'm
> > working at that uses the simple framework introduced)?
> 
> Sure

Here it is (no cleanup, work in progress... ;) :

/*
 *  drivers/mtd/nand/pnx4008.c
 *
 *  Copyright (c) 2005 MontaVista <sources at mvista.com> 
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 *  Overview:
 *   This is a device driver for the NAND flash device found on the
 *   PNX4008 board. It supports 16-bit 32MiB Samsung k9f5616 chip.
 *
 */

#include <linux/slab.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/pm.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/partitions.h>
#include <linux/dma-mapping.h>

#include <asm/io.h>
#include <asm/sizes.h>
#include <asm/dma.h>

#include <asm/arch/hardware.h>
#include <asm/arch/dma.h>
#include <asm/arch/gpio_new.h>

#include "pnx4008.h"

#define MAX_DMA_TRANSFER_SIZE 0x7FF

struct pnx4008_mtd_s {
    struct mtd_info 	mtd;
    struct nand_chip 	nand;
    int 		dma;
    struct completion 	done;
    u32			flashclk_reg;
    struct device *	dev;
};

static struct pnx4008_mtd_s *pnx4008_nand_data = NULL;
static int mlc_flash_dma = 1;

static int pnx4008_nand_ready(struct mtd_info *mtd);
static void pnx4008_nand_command ( struct mtd_info *mtd, unsigned command,
		                   int column, int page_addr);
static void pnx4008_nand_hwcontrol( struct mtd_info* mtd, int hwc );

#define MLC_WRITE( nand, offset, what ) __raw_writel( what, (u8*)( ((struct nand_chip*)nand)->IO_ADDR_R) + offset )
#define MLC_READ( nand, offset ) __raw_readl( (u8*)( ((struct nand_chip*)nand)->IO_ADDR_R) + offset )
#define MLC_READ2( nand, offset ) __raw_readb( (u8*)( ((struct nand_chip*)nand)->IO_ADDR_W) + offset ) 
#define MLC_WRITE2( nand, offset, what ) __raw_writeb( what, (u8*)( ((struct nand_chip*)nand)->IO_ADDR_W) + offset )

static inline u8 pnx4008_read_data( struct nand_chip* this )
{
	while( ! (MLC_READ( this, MLC_ISR ) & MLC_ISR_READY ) ) {
		continue;
	}
	return MLC_READ2( this, 0 );
}

static u8 pnx4008_nand_read_byte( struct mtd_info *mtd ) 
{
	return pnx4008_read_data( mtd->priv );
}

static void pnx4008_nand_write_byte( struct mtd_info* mtd, u8 byte )
{
	MLC_WRITE2( mtd->priv, 0, byte );
}

static void pnx4008_nand_dma_handler( int irq, void* context, struct pt_regs* regs)
{
	struct pnx4008_mtd_s* data = context;
	complete( &data->done );
}

static inline int  
pnx4008_nand_config_dma( int channel, u32 dma_buf, int len, int mode )
{
        pnx4008_dma_config_t cfg;
        pnx4008_dma_ch_config_t ch_cfg;
        pnx4008_dma_ch_ctrl_t ch_ctrl;
	int peripheral_id = 12;
	int err;
		
	if( mode == DMA_MODE_WRITE ) {
		ch_cfg.flow_cntrl = FC_MEM2PER_DMA;
                cfg.src_addr = dma_buf;
                ch_cfg.src_per = 0;
                cfg.dest_addr = PNX4008_FLASH_DATA;
                ch_cfg.dest_per = peripheral_id;
                ch_ctrl.di = 0;
                ch_ctrl.si = 1;
	} else if ( mode == DMA_MODE_READ ) {
		ch_cfg.flow_cntrl = FC_PER2MEM_DMA;
		cfg.dest_addr = dma_buf;
		ch_cfg.dest_per = 0;
		cfg.src_addr = PNX4008_FLASH_DATA;
		ch_cfg.src_per = peripheral_id;
		ch_ctrl.si = 0;
		ch_ctrl.di = 1;
	} else {
		return -EFAULT;
	}
	ch_cfg.halt = 0;
	ch_cfg.active = 1;
	ch_cfg.lock = 0;
	ch_cfg.itc = 1;
	ch_cfg.ie = 1;
	
	ch_ctrl.tc_mask = 1;
	ch_ctrl.cacheable = 0;
	ch_ctrl.bufferable = 0;
	ch_ctrl.priv_mode = 1;
	ch_ctrl.dest_ahb1 = 0;
	ch_ctrl.src_ahb1 = 0;
	ch_ctrl.dwidth = ch_ctrl.swidth = WIDTH_WORD;
	ch_ctrl.dbsize = 1;
	ch_ctrl.sbsize = 1;
	ch_ctrl.tr_size = len;

	if( 0 > ( err = pnx4008_dma_pack_config( &ch_cfg, &cfg.ch_cfg ))) {
		goto out;
	}
	if( 0 > (err = pnx4008_dma_pack_control(&ch_ctrl, &cfg.ch_ctrl))) {
		goto out;
	}
	err = pnx4008_config_channel( channel, &cfg ); 
out:	
	return err;
}

static void pnx4008_nand_write_buf( struct mtd_info *mtd, const u8 *buf, int len)
{
	struct nand_chip* this = mtd->priv;
	struct pnx4008_mtd_s* ctx = this->priv;
	dma_addr_t dma_addr;
	void* vaddr;
	int dma_len;

	if( mlc_flash_dma ) {
		do {
			dma_len = min( MAX_DMA_TRANSFER_SIZE, len );
			vaddr = dma_alloc_coherent( ctx->dev, dma_len, &dma_addr, GFP_KERNEL );
			memcpy( vaddr, buf, dma_len );
			pnx4008_nand_config_dma( ctx->dma, dma_addr, dma_len, DMA_MODE_WRITE );
			init_completion( &ctx->done );
			pnx4008_dma_ch_enable( ctx->dma );
			wait_for_completion( &ctx->done );		
			pnx4008_dma_ch_disable( ctx->dma );
			dma_free_coherent( ctx->dev, dma_len, vaddr, dma_addr );
			len -= dma_len; buf += dma_len;
		} while( len > 0 );
	} else {
    		while( len -- ) 
			pnx4008_nand_write_byte( mtd, *buf++ );
		while( !this->dev_ready( mtd ) ) 
			continue;
	}
}

static void pnx4008_nand_read_buf(struct mtd_info *mtd, u8 * buf, int len)
{
	struct nand_chip* this = mtd->priv;
	struct pnx4008_mtd_s* ctx = this->priv;
	dma_addr_t dma_addr;
	void* vaddr;
	int dma_len;

	if( mlc_flash_dma ) {	
		do {
			dma_len = min( MAX_DMA_TRANSFER_SIZE, len );
			vaddr = dma_alloc_coherent( ctx->dev, dma_len, &dma_addr, GFP_KERNEL );
			pnx4008_nand_config_dma( ctx->dma, dma_addr, dma_len, DMA_MODE_READ );
			init_completion( &ctx->done );
			pnx4008_dma_ch_enable( ctx->dma );
			wait_for_completion( &ctx->done );		
			pnx4008_dma_ch_disable( ctx->dma );
			memcpy( buf, vaddr, dma_len );
 			dma_free_coherent( ctx->dev, dma_len, vaddr, dma_addr );
			len -= dma_len; buf += dma_len;
		} while( len > 0 );
	} else {
		while( len -- ) 
			*buf ++ = pnx4008_nand_read_byte( mtd );
	}
}

static int pnx4008_nand_verify_buf( struct mtd_info *mtd, const u8 *buf, int len)
{
	int err = 0;

	while( len -- ) {
		if( *buf ++ != pnx4008_nand_read_byte( mtd ) ) {
			err = -EFAULT;
			break;
		}
	}
	return err;
}

static int pnx4008_nand_suspend(struct device *dev, u32 state, u32 level)
{
	int retval = 0;

#ifdef CONFIG_PM
	struct mtd_info *mtd = dev_get_drvdata(dev);
	if (mtd && mtd->suspend && level == SUSPEND_SAVE_STATE) {
		retval = mtd->suspend(mtd);
		if (retval == 0) {
			pnx4008_nand_data->flashclk_reg = __raw_readl(IO_ADDRESS(PNX4008_FLASHCLK_CTRL_BASE));
			__raw_writel(0, IO_ADDRESS(PNX4008_FLASHCLK_CTRL_BASE));
			udelay(1);
		}
	}
#endif
	return retval;
}

static int pnx4008_nand_resume(struct device *dev, u32 level)
{
#ifdef CONFIG_PM
	struct mtd_info *mtd = dev_get_drvdata(dev);

	if (mtd && mtd->resume && level == RESUME_RESTORE_STATE) {
		mtd->resume(mtd);
		__raw_writel(pnx4008_nand_data->flashclk_reg, IO_ADDRESS(PNX4008_FLASHCLK_CTRL_BASE));	
		udelay(1);
	}
#endif
	return 0;
}

#ifdef CONFIG_MTD_PARTITIONS
static struct mtd_partition static_partition[] = {
	{ .name = "Booting Image",
	  .offset =	0,
	  .size = 64 * 1024,
	  .mask_flags =	MTD_WRITEABLE  /* force read-only */
 	},
	{ .name = "U-Boot",
	  .offset =	MTDPART_OFS_APPEND,
	  .size = 256 * 1024,
          .mask_flags = MTD_WRITEABLE  /* force read-only */
 	},
	{ .name = "U-Boot Environment",
	  .offset =	MTDPART_OFS_APPEND,
	  .size = 192 * 1024
	},
	{ .name = "Kernel",
	  .offset =	MTDPART_OFS_APPEND,
	  .size = 2 * SZ_1M
	},
	{ .name = "File System",
	  .size = MTDPART_SIZ_FULL,
	  .offset =	MTDPART_OFS_APPEND,
	},
};

const char *part_probes[] = { 
	"cmdlinepart", 
	NULL,  
};

#endif

static int __init pnx4008_nand_probe (struct device *dev)
{
	struct nand_chip *this;
	struct mtd_info *mtd;
#ifdef CONFIG_MTD_PARTITIONS
	struct mtd_partition *dynamic_partition = 0;
#endif
	int err = 0;
	u32 flashclk_init = FLASHCLK_MLC_CLOCKS | FLASHCLK_MLC_SELECT;

	pnx4008_nand_data = kmalloc( sizeof( struct pnx4008_mtd_s ), GFP_KERNEL );
	if (!pnx4008_nand_data) {
		printk (KERN_ERR "%s: failed to allocate mtd_info\n", __FUNCTION__ );
		err = -ENOMEM;
		goto out;
	}
	memset( pnx4008_nand_data, 0, sizeof( struct pnx4008_mtd_s ) );
	pnx4008_nand_data->dma = -1;

	pnx4008_nand_data->dev = dev;

	/* structures must be linked */
	this = &pnx4008_nand_data->nand;
	mtd = &pnx4008_nand_data->mtd;
	mtd->priv = this;
	this->priv = pnx4008_nand_data;

	this->chip_delay = 30;
	this->IO_ADDR_R = (void __iomem*)IO_ADDRESS( PNX4008_MLC_FLASH_BASE );
        this->IO_ADDR_W = (void __iomem*)IO_ADDRESS( PNX4008_FLASH_DATA );
        this->options = NAND_SAMSUNG_LP_OPTIONS;
        this->hwcontrol = pnx4008_nand_hwcontrol;
	this->dev_ready = pnx4008_nand_ready;
        this->eccmode = NAND_ECC_SOFT;
	this->cmdfunc = pnx4008_nand_command;
	this->read_byte = pnx4008_nand_read_byte;
	this->write_byte = pnx4008_nand_write_byte;
	this->write_buf = pnx4008_nand_write_buf;
	this->read_buf = pnx4008_nand_read_buf;
	this->verify_buf = pnx4008_nand_verify_buf;
	
	if( mlc_flash_dma ) {
		pnx4008_nand_data->dma = pnx4008_request_channel( 
				"MLC Flash", -1, 
				pnx4008_nand_dma_handler, pnx4008_nand_data );
		if( pnx4008_nand_data->dma <= 0 ) {
			printk( KERN_ERR"%s: cannot request DMA channel (%d), falling back to non-DMA mode.\n", __FUNCTION__, pnx4008_nand_data->dma );
			mlc_flash_dma = 0;
		} else {
			flashclk_init |= FLASHCLK_NAND_RnB_REQ_E;
		}
	}	
	__raw_writel( flashclk_init, IO_ADDRESS( PNX4008_FLASHCLK_CTRL_BASE ) );
	pnx4008_nand_data->flashclk_reg = flashclk_init;	
	
	MLC_WRITE( this, MLC_LOCK_PR, MLC_UNLOCK_MAGIC );
	/* Setting Buswidth for controller */
	MLC_WRITE( this, MLC_ICR, 
	  	MLC_ICR_WIDTH8 | MLC_ICR_AWC3 | 
	  	MLC_ICR_SMALLBLK | MLC_ICR_WP_DISABLED ); 

    	/* remove the write protection (/WP), by setting GPO_01 to high */
    	__raw_writel( GPIO_FLASH_WP, 
			IO_ADDRESS( PNX4008_PIO_BASE ) + GPIO_OUTP_SET);

	MLC_WRITE( this, MLC_LOCK_PR, MLC_UNLOCK_MAGIC );
	MLC_WRITE( this, MLC_TIME, 
			FLASH_MLC_TIME_tWP( 4 )  |
			FLASH_MLC_TIME_tWH( 7 )  |
			FLASH_MLC_TIME_tRP( 6 )  |
			FLASH_MLC_TIME_tREH( 2 ) |
			FLASH_MLC_TIME_tRHZ( 3 ) |
			FLASH_MLC_TIME_tRWB( 3 ) |
			FLASH_MLC_TIME_tCEA( 0 ) );  /* 104 Hz */
	/* wait 6 uSeconds for device to become ready */
	udelay(6);

	if( nand_scan( mtd, 1 ) ) {
		goto out_1;
	}

#ifdef CONFIG_MTD_PARTITIONS
        err = parse_mtd_partitions( mtd, part_probes,
                                    &dynamic_partition, 0);
        if (err > 0)
             err = add_mtd_partitions( mtd,
				       dynamic_partition, err);
        else 
             err = add_mtd_partitions( mtd,
				       static_partition, 
				       ARRAY_SIZE( static_partition ) );
	if( err < 0 ) {
		goto out_2;
	}

#endif
	dev_set_drvdata(dev, mtd);
	return 0;

#ifdef CONFIG_MTD_PARTITIONS
out_2:
	nand_release( mtd );
#endif

out_1:	
	kfree ( pnx4008_nand_data );
out:
	return err;
}

static void __exit pnx4008_nand_remove (struct device *dev)
{
	dev_set_drvdata(dev, NULL);

	nand_release ( &pnx4008_nand_data->mtd);
	if( pnx4008_nand_data->dma >=0 ) {
		pnx4008_free_channel( pnx4008_nand_data->dma );
	}
	kfree ( pnx4008_nand_data );
	
	__raw_writel( 0, IO_ADDRESS( PNX4008_FLASHCLK_CTRL_BASE ) );
}


static struct device_driver pnx4008_nand_driver = {
	.name		= "pnx4008-flash",
	.bus		= &platform_bus_type,
	.probe		= pnx4008_nand_probe,
	.remove		= __exit_p(pnx4008_nand_remove),
	.suspend	= pnx4008_nand_suspend,
	.resume		= pnx4008_nand_resume,
};

static int __init pnx4008_nand_init(void)
{
	return driver_register(&pnx4008_nand_driver);
}

static void __exit pnx4008_nand_exit(void)
{
	driver_unregister(&pnx4008_nand_driver);
}

module_exit(pnx4008_nand_exit);
module_init(pnx4008_nand_init);

static int pnx4008_nand_ready(struct mtd_info *mtd)
{	
    const u32 mask = MLC_ISR_READY | MLC_ISR_IFREADY | MLC_ISR_ECCREADY;
    return ( ( MLC_READ( mtd->priv, MLC_ISR ) & mask ) == mask ) ? 1 : 0;
}

static void pnx4008_nand_hwcontrol( struct mtd_info* mtd, int hwc )
{
}

static void pnx4008_nand_command ( struct mtd_info *mtd, unsigned command,
		                   int column, int page_addr)
{
    struct nand_chip *this = mtd->priv;

    /* Emulate NAND_CMD_READOOB */
    if (command == NAND_CMD_READOOB) {
        column += mtd->oobblock;
        command = NAND_CMD_READ0;
    }
    
    /* Write out the command to the device. */
    MLC_WRITE( this, MLC_CMD, command );

    if (column != -1 || page_addr != -1) {
        /* Serially input address */
        if (column != -1) {
            /* Adjust columns for 16 bit buswidth */
            if (this->options & NAND_BUSWIDTH_16)
                column >>= 1;
            MLC_WRITE( this, MLC_ADDR, column & 0xff);
            MLC_WRITE( this, MLC_ADDR, (column >> 8) & 0xff);
        }
        if (page_addr != -1) {
            MLC_WRITE( this, MLC_ADDR, page_addr & 0xff );
            MLC_WRITE( this, MLC_ADDR, (page_addr >> 8) & 0xff);

            /* One more address cycle for devices > 128MiB */
            if (this->chipsize > (128 << 20)) {
		MLC_WRITE( this, MLC_ADDR, (page_addr >> 16) & 0xff);
	    }
        }
    }
    /*
     * program and erase have their own busy handlers
     * status and sequential in needs no delay
    */
    switch (command) 
    {
    case NAND_CMD_CACHEDPROG:
    case NAND_CMD_PAGEPROG:
    case NAND_CMD_ERASE1:
    case NAND_CMD_ERASE2:
    case NAND_CMD_SEQIN:
    case NAND_CMD_STATUS:
        return;
    case NAND_CMD_RESET:
        if (this->dev_ready)
            break;
        udelay(this->chip_delay);
        MLC_WRITE( this, MLC_CMD, NAND_CMD_STATUS );
	while(( pnx4008_read_data( this ) & 0x40 ) == 0 ) 
	    continue;
        return;
    case NAND_CMD_READ0:
        /* Write out the start read command */
        MLC_WRITE( this, MLC_CMD, NAND_CMD_READSTART);
	/* fall down */
    default:
        /*
         * If we don't have access to the busy pin, we apply the given
         * command delay
        */
        if (!this->dev_ready) {
            udelay (this->chip_delay);
            return;
        }
    }
    /* Apply this short delay always to ensure that we do wait tWB in
     * any case on any machine. */
    ndelay (100);
    /* wait until command is processed */
    while (!this->dev_ready(mtd)) {
	continue;
    }
}

MODULE_LICENSE("GPL");
MODULE_AUTHOR("dmitry pervushin <dpervushin at ru.mvista.com>");
MODULE_PARM( mlc_flash_dma, "i" );




More information about the linux-mtd mailing list