nuc900 nand flash mtd 驱动

nuc900 nand flash mtd 驱动,请参考!

/*
 * Copyright © 2009 Nuvoton technology corporation.
 *
 * Wan ZongShun <mcuos.com@gmail.com>
 *
 * 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;version 2 of the License.
 *
 */

#include <linux/slab.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/blkdev.h>

#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/partitions.h>
#include <linux/dma-mapping.h>


#include <mach/map.h>
#include <mach/regs-clock.h>
#include <mach/regs-fmi.h>
#include <mach/gnand/GNAND.h>
#include <mach/nuc900_nand.h>
#define REG_MFSEL    (W90X900_VA_GCR + 0xC)
/*
#define REG_FMICSR       0x00
#define REG_SMCSR        0xa0
#define REG_SMISR        0xac
#define REG_SMCMD        0xb0
#define REG_SMADDR       0xb4
#define REG_SMDATA       0xb8
*/
#define RESET_FMI    0x01
#define NAND_EN        0x08
#define READYBUSY    (0x01 << 18)

#define SWRST        0x01
#define PSIZE        (0x01 << 3)
#define DMARWEN        (0x03 << 1)
#define BUSWID        (0x01 << 4)
#define ECC4EN        (0x01 << 5)
#define WP        (0x01 << 24)
#define NANDCS        (0x03 << 25)
#define ENDADDR        (0x01 << 31)

#define NANDCS1        (0x01 << 25)
#define ECCBYTE512 3
#define ECCBYTE2K  3

/* DMAC Control and Status Register (DMACCSR) */
#define DMACCSR_DMACEN        (1)
#define DMACCSR_SW_RST        (1<<1)
#define DMACCSR_SG_EN1        (1<<2)
#define DMACCSR_SG_EN2        (1<<3)
#define DMACCSR_ATA_BUSY    (1<<8)
#define DMACCSR_FMI_BUSY    (1<<9)

/******************************************/
#define FMI_ERR_ID    0xFFFF0100

#define FMI_TIMEOUT                (FMI_ERR_ID|0x01)
/* NAND error */
#define FMI_SM_INIT_ERROR        (FMI_ERR_ID|0x20)
#define FMI_SM_RB_ERR            (FMI_ERR_ID|0x21)
#define FMI_SM_STATE_ERROR        (FMI_ERR_ID|0x22)
#define FMI_SM_ECC_ERROR        (FMI_ERR_ID|0x23)
#define FMI_SM_STATUS_ERR        (FMI_ERR_ID|0x24)
#define FMI_SM_ID_ERR            (FMI_ERR_ID|0x25)
#define FMI_SM_INVALID_BLOCK    (FMI_ERR_ID|0x26)


bool volatile  _fmi_bIsSMDataReady=0;
//#define DEBUG_NAND
//#define nuc900_nand_debug(fmt,args...) printk(fmt,##args)
#define nuc900_nand_debug(fmt,args...) 


#define read_data_reg(dev)        
    __raw_readl(REG_SMDATA)

#define write_data_reg(dev, val)    
    __raw_writel((val), REG_SMDATA)

#define write_cmd_reg(dev, val)        
    __raw_writel((val), REG_SMCMD)

#define write_addr_reg(dev, val)    
    __raw_writel((val), REG_SMADDR)
#define nuc900_nand_read(reg)        __raw_readl(reg)

struct nuc900_nand {
    struct mtd_info mtd;
    struct nand_chip chip;
    void __iomem *reg;
    struct clk *clk;
    spinlock_t lock;
};
extern struct semaphore  fmi_sem;
extern struct semaphore dmac_sem;


#define NUM_PARTITIONS 4

/*请保证分区的总和数和nandflash的实际大小一致,目前开发板上的nandflash是128M*/
#define UBOOT_SIZE     SZ_1M*1
#define KERNEL_SIZE     SZ_1M*5
#define ROOT_SIZE        SZ_1M*44
#define USER_SIZE     SZ_1M*78

/*vitual addr and phy addr for dma */
static unsigned char * nand_vaddr = NULL;
static unsigned char * nand_phyaddr = NULL;

static const struct mtd_partition partitions[] = {
    { .name = "U-boot",
      .offset = 0,
      .size = UBOOT_SIZE
     },
    { .name = "linux 2.6.35 kernel",
      .offset = UBOOT_SIZE,
      .size = KERNEL_SIZE
    },
    { .name = "root",
       .offset = UBOOT_SIZE+KERNEL_SIZE,
      .size = ROOT_SIZE
    },
    { .name = "user",
      .offset = UBOOT_SIZE+KERNEL_SIZE+ROOT_SIZE,
      .size = USER_SIZE
    }
};

static unsigned char nuc900_nand_read_byte(struct mtd_info *mtd)
{
    unsigned char ret;
    struct nuc900_nand *nand;

    nand = container_of(mtd, struct nuc900_nand, mtd);
  __raw_writel( NAND_EN,  REG_FMICSR);
    ret = (unsigned char)read_data_reg(nand);

    return ret;
}

static void nuc900_nand_read_buf(struct mtd_info *mtd,
                 unsigned char *buf, int len)
{
    int i;
    struct nand_chip *chip = mtd->priv;
    //nuc900_nand_debug("nuc900_nand_read_buf: len=%d
",len);
    __raw_writel( NAND_EN,  REG_FMICSR);
    if(len==mtd->oobsize){// read oob data
        chip->cmdfunc(mtd, NAND_CMD_RNDOUT, mtd->writesize, -1);
      if(down_interruptible(&fmi_sem)) //jhe+ 2010.12.21
      {
          printk("nuc900 mtd nand driver read buf sem error
");
          return;
      }
      for (i = 0; i < len; i++)
           buf[i] = __raw_readl(REG_SMDATA)& 0xff;
           
#ifdef DEBUG_NAND
      nuc900_nand_debug("oob read
");
      for (i = 0; i < len; i++)
           nuc900_nand_debug(" 0x%02x |",buf[i]);
      nuc900_nand_debug("
");    
#endif      
      
        goto readout1;
    } 
    if(down_interruptible(&fmi_sem)) //jhe+ 2010.12.21
  {
          printk("nuc900 mtd nand driver read buf sem error
");
          return;
    }
    
    //normal page read use dma
    if (down_interruptible(&dmac_sem))
      return ;
    while (__raw_readl(REG_DMACCSR)&DMACCSR_FMI_BUSY); //Wait IP finished... for safe
    __raw_writel((unsigned long)nand_phyaddr,REG_DMACSAR2);
    _fmi_bIsSMDataReady = 0;
    __raw_writel(__raw_readl(REG_SMCSR) | 0x02,REG_SMCSR); //enable DMA read
    while (!_fmi_bIsSMDataReady);
  up(&dmac_sem);

    memcpy(buf,nand_vaddr,len);
    
readout1:
    up(&fmi_sem);    
}

static void nuc900_nand_write_buf(struct mtd_info *mtd,
                  const unsigned char *buf, int len)
{
    int i;
  struct nand_chip *chip = mtd->priv;
    int length = mtd->oobsize;
    int dmanum = 0;

    //nuc900_nand_debug("nuc900nand write buf:len=%d
",len);
  __raw_writel( NAND_EN,  REG_FMICSR);
    if(len==length){//  write for oob 
        chip->cmdfunc(mtd, NAND_CMD_RNDIN, mtd->writesize, -1);
        if(down_interruptible(&fmi_sem)) //jhe+ 2010.12.21
      {
           printk("nuc900 mtd nand driver write buf sem error
");
           return;
      }
        #ifdef DEBUG_NAND
        nuc900_nand_debug("oobdata:len=%d
",len);
      for (i = 0; i < len; i++)
           nuc900_nand_debug(" 0x%02x |",buf[i]);
      nuc900_nand_debug("
");    
      #endif
      
        i=0;
        while(i<len){
            __raw_writel(buf[i],REG_SMDATA);
            i=i+1;
        }
        
    goto write_out;
    }
    
      if(down_interruptible(&fmi_sem)) //jhe+ 2010.12.21
      {
           printk("nuc900 mtd nand driver write buf sem error
");
           return;
      }
      //normal page write use dma
        while(dmanum < len)//give the first 512 to the dma space
        {
            nand_vaddr[dmanum] = buf[dmanum];
            dmanum++;
         }    
             
         while(dmanum < 2112)
         {
              nand_vaddr[dmanum] = 0xff;
              dmanum++;
         }
         #if 0
         nuc900_nand_debug("
");
       nuc900_nand_debug(" 0x%02x |",__raw_readl(REG_SMRA_0));
      nuc900_nand_debug(" 0x%02x |",__raw_readl(REG_SMRA_1));
      nuc900_nand_debug(" 0x%02x |",__raw_readl(REG_SMRA_2));
      nuc900_nand_debug(" 0x%02x |",__raw_readl(REG_SMRA_3));
      nuc900_nand_debug(" 0x%02x |",__raw_readl(REG_SMRA_4));
      nuc900_nand_debug(" 0x%02x |",__raw_readl(REG_SMRA_5));
      nuc900_nand_debug(" 0x%02x |",__raw_readl(REG_SMRA_6));
      nuc900_nand_debug(" 0x%02x |",__raw_readl(REG_SMRA_7));
      nuc900_nand_debug(" 0x%02x |",__raw_readl(REG_SMRA_8));
      nuc900_nand_debug(" 0x%02x |",__raw_readl(REG_SMRA_9));
      nuc900_nand_debug(" 0x%02x |",__raw_readl(REG_SMRA_10));
      nuc900_nand_debug(" 0x%02x |",__raw_readl(REG_SMRA_11));
      nuc900_nand_debug(" 0x%02x |",__raw_readl(REG_SMRA_12));
      nuc900_nand_debug(" 0x%02x |",__raw_readl(REG_SMRA_13));
      nuc900_nand_debug(" 0x%02x |",__raw_readl(REG_SMRA_14));
      nuc900_nand_debug(" 0x%02x |",__raw_readl(REG_SMRA_15));
         nuc900_nand_debug("
");
         #endif 
         if (down_interruptible(&dmac_sem))
            return ;
        //normal page write use dma
      while (__raw_readl(REG_DMACCSR)&DMACCSR_FMI_BUSY); //Wait IP finished... for safe
      __raw_writel((unsigned long)nand_phyaddr,REG_DMACSAR2);
      _fmi_bIsSMDataReady = 0;
        __raw_writel(__raw_readl(REG_SMCSR) | 0x04,REG_SMCSR); //enable DMA write
        while (!_fmi_bIsSMDataReady);//wait for dma finished
      //__raw_writel(__raw_readl(REG_SMISR)|0x01,REG_SMISR);  //clear DMA finished flag
     // __raw_writel(0x11223344,REG_SMRA_15);
     up(&dmac_sem);
write_out:
     up(&fmi_sem);
     return;    
}

/* select chip */
static void nuc900_nand_select_chip(struct mtd_info *mtd, int chipnr)
{
  struct nuc900_nand *nand;
    nand = container_of(mtd, struct nuc900_nand, mtd);
        
    if(down_interruptible(&fmi_sem)) //jhe+ 2010.12.21
    {
        printk("nuc900 mtd nand driver select_chip sem error
");
        return ;
    }
  __raw_writel( NAND_EN,  REG_FMICSR);
    switch (chipnr) {
    case -1://no chip selected 
        __raw_writel(__raw_readl(REG_SMCSR) | NANDCS,
                   REG_SMCSR);
        break;
    case 0://select nand chip 0
         __raw_writel(__raw_readl(REG_SMCSR) & ~NANDCS,
                   REG_SMCSR);
        break;
    case 1://select nand chip 1
         __raw_writel((__raw_readl(REG_SMCSR) & (~NANDCS))| NANDCS1,
                   REG_SMCSR);
        break;
    default:
        BUG();
    }
  
  up(&fmi_sem);

}
static int nuc900_check_rb(struct nuc900_nand *nand)
{
    unsigned int val;
    spin_lock(&nand->lock);
    __raw_writel( NAND_EN,  REG_FMICSR);
    val = __raw_readl(REG_SMISR );
    val &= READYBUSY;
    spin_unlock(&nand->lock);

    return val;
}

static int nuc900_nand_devready(struct mtd_info *mtd)
{
    struct nuc900_nand *nand;
    int ready;

    nand = container_of(mtd, struct nuc900_nand, mtd);
  __raw_writel( NAND_EN,  REG_FMICSR);
    ready = (nuc900_check_rb(nand)) ? 1 : 0;
    return ready;
}
/* functions */
int fmiSMCheckRB(void)
{
        __raw_writel( NAND_EN,  REG_FMICSR);
        while (1) {
                if (__raw_readl(REG_SMISR) & 0x400) {
                        __raw_writel(0x400,REG_SMISR);
                        return 1;
                }
        }
        return 0;
}
static void nuc900_nand_command_lp(struct mtd_info *mtd, unsigned int command,
                   int column, int page_addr)
{
    register struct nand_chip *chip = mtd->priv;
    struct nuc900_nand *nand;
  //nuc900_nand_debug("command=0x%x,column=0x%x,page_addr=0x%x
",command,column,page_addr);
    nand = container_of(mtd, struct nuc900_nand, mtd);
    // enable SM
  __raw_writel( NAND_EN,  REG_FMICSR);

    if (command == NAND_CMD_READOOB) {
        column += mtd->writesize;
        command = NAND_CMD_READ0;
    }
    if((command == NAND_CMD_READ0)||(command ==NAND_CMD_SEQIN)){
        //nuc900_nand_debug("clear R/B flag before cmd
");
                /* clear R/B flag */
    while (!(__raw_readl(REG_SMISR) & 0x40000));
    __raw_writel(0x400, REG_SMISR);
    }

    write_cmd_reg(nand, command & 0xff);

    if (column != -1 || page_addr != -1) {

        if (column != -1) {
            if (chip->options & NAND_BUSWIDTH_16)
                column >>= 1;
            write_addr_reg(nand, column);
            write_addr_reg(nand, column >> 8 | ENDADDR);
        }
        if (page_addr != -1) {
            write_addr_reg(nand, page_addr);

            if (chip->chipsize > (128 << 20)) {
                write_addr_reg(nand, page_addr >> 8);
                write_addr_reg(nand, page_addr >> 16 | ENDADDR);
            } else {
                write_addr_reg(nand, page_addr >> 8 | ENDADDR);
            }
        }
    }

    switch (command) {
    case NAND_CMD_CACHEDPROG:
    case NAND_CMD_ERASE1:
    case NAND_CMD_ERASE2:
    case NAND_CMD_SEQIN:
    case NAND_CMD_RNDIN:
    
    case NAND_CMD_DEPLETE1:
        //nuc900_nand_debug("command=0x%x
",command);
        return;
  case NAND_CMD_PAGEPROG:
      fmiSMCheckRB();
      return;
  case NAND_CMD_STATUS:
    if (__raw_readl(REG_SMDATA) & 0x01) {    // 1:fail; 0:pass
       // printk("Nand Status  error!!
");
        return;
    }      
    return;
    case NAND_CMD_STATUS_ERROR:
    case NAND_CMD_STATUS_ERROR0:
    case NAND_CMD_STATUS_ERROR1:
    case NAND_CMD_STATUS_ERROR2:
    case NAND_CMD_STATUS_ERROR3:
        udelay(chip->chip_delay);
        return;

    case NAND_CMD_RESET:
        if (chip->dev_ready)
            break;
        udelay(chip->chip_delay);

        write_cmd_reg(nand, NAND_CMD_STATUS);
        write_cmd_reg(nand, command);

        while (!nuc900_check_rb(nand))
            ;

        return;

    case NAND_CMD_RNDOUT:
        write_cmd_reg(nand, NAND_CMD_RNDOUTSTART);
        
        return;

    case NAND_CMD_READ0:

        write_cmd_reg(nand, NAND_CMD_READSTART);
        fmiSMCheckRB();
        break;
        
    default:

        if (!chip->dev_ready) {
            udelay(chip->chip_delay);
            return;
        }
    }

    /* Apply this short delay always to ensure that we do wait tWB in
     * any case on any machine. */
    //ndelay(100);

    while (!chip->dev_ready(mtd))
        ;
}

// SM functions
int fmiSM_Reset(void)
{

        u32 volatile i;
        __raw_writel( NAND_EN,  REG_FMICSR);
        __raw_writel(0xff,REG_SMCMD);
        for (i=100; i>0; i--);

        if (!fmiSMCheckRB())
                return -1;
        return 0;
}

static void nuc900_nand_enable(struct nuc900_nand *nand)
{
        
    unsigned int val;

    if(down_interruptible(&fmi_sem)) //jhe+ 2010.12.21
    {
        printk("nuc900 mtd nand driver nand_enable sem error
");
        return;
    }
    spin_lock(&nand->lock);
  

    // enable SM
    __raw_writel( 0x3050b, REG_SMTCR );//set timer control
    __raw_writel( NAND_EN,  REG_FMICSR);
    
    /* init SM interface */
    __raw_writel((__raw_readl(REG_SMCSR)&0xf8ffffc0), REG_SMCSR);    // disable ecc4
    fmiSM_Reset();
    __raw_writel((__raw_readl(REG_SMCSR)&0xfffffff0)|0x01000008, REG_SMCSR);    // psize:2048; wp# set 1

    //
    __raw_writel(0x01, REG_SMIER);//enable dma interrupter
    spin_unlock(&nand->lock);
    up(&fmi_sem);
}

static irqreturn_t fmi_interrupt(int irq, void *devid)
{
        unsigned int volatile isr;
        // SM interrupt status
        isr = __raw_readl(REG_SMISR);
        ///printk("fmi_interrupt
");
        //DMA read/write transfer is done
        if (isr & 0x01) {
                _fmi_bIsSMDataReady = 1;
                __raw_writel(0x01,REG_SMISR);
                return IRQ_HANDLED;
        } else {
                return IRQ_NONE;
        }

}


static int __devinit nuc900_nand_probe(struct platform_device *pdev)
{
    struct nuc900_nand *nuc900_nand;
    struct mtd_info *mtd;
    struct nand_chip *chip;
    int retval;
    struct resource *res;
  nuc900_nand_debug("nuc900_nand_probe in
");
  
  nand_vaddr = (unsigned char *) dma_alloc_coherent(NULL,2112, (dma_addr_t *) &nand_phyaddr, GFP_KERNEL);
  if(nand_vaddr == NULL){
      printk(KERN_ERR "NUC900_nand: failed to allocate ram for nand data.
");
        return -ENOMEM;
  } 
  
    retval = 0;
  /* Allocate memory for the device structure (and zero it) */
    nuc900_nand = kzalloc(sizeof(struct nuc900_nand), GFP_KERNEL);
    if (!nuc900_nand){
        printk(KERN_ERR "NUC900_nand: failed to allocate device structure.
");
        return -ENOMEM;
    }
    mtd=&nuc900_nand->mtd;
    chip = &(nuc900_nand->chip);
    
    chip->priv = nuc900_nand;        /* link the private data structures */
    mtd->priv    = chip;
    mtd->owner    = THIS_MODULE;
    spin_lock_init(&nuc900_nand->lock);
    
    
    __raw_writel(__raw_readl(REG_CLKEN) | 0x30,REG_CLKEN);
    __raw_writel(((__raw_readl(REG_MFSEL) & 0xFFFFFFF3) | 0x00000004),REG_MFSEL); /* select NAND function pins */
    
    if (request_irq(20, fmi_interrupt, IRQF_SHARED, "900_NAND", &pdev->dev)) {
                printk("NAND: Request IRQ error
");
                retval = -ENOENT;
                    goto fail1;
  }
  if (down_interruptible(&dmac_sem)){
      retval = -ENOENT;
          goto fail1;
    }
    while (__raw_readl(REG_DMACCSR)&DMACCSR_FMI_BUSY); //Wait IP finished... for safe
        // DMAC Initial
  __raw_writel(0x00000003, REG_DMACCSR);
        //Enable DMAC
  __raw_writel(0x00000001, REG_DMACCSR);
        // Enable target abort interrupt generation during DMA transfer.
  __raw_writel(0x00000001, REG_DMACIER);
  up(&dmac_sem);

            /* init SM interface */
  __raw_writel((__raw_readl(REG_SMCSR)&0xf8ffffc0),REG_SMCSR);    // disable ecc4
  if (down_interruptible(&dmac_sem)){
      retval = -ENOENT;
          goto fail1;
    }
  while (__raw_readl(REG_DMACCSR)&DMACCSR_FMI_BUSY); //Wait IP finished... for safe

   // enable all
   __raw_writel(__raw_readl(REG_DMACCSR) | DMACCSR_DMACEN, REG_DMACCSR); //enable DMAC for FMI

   /* enable all interrupt */
   __raw_writel(DMACIER_TABORT_IE, REG_DMACIER); //Enable target abort interrupt generation during DMA transfer
   __raw_writel(FMIIER_DTA_IE, REG_FMIIER); //Enable DMAC READ/WRITE target abort interrupt generation
   up(&dmac_sem);
    
    chip->cmdfunc        = nuc900_nand_command_lp;
    chip->dev_ready        = nuc900_nand_devready;
    chip->read_byte        = nuc900_nand_read_byte;
    chip->write_buf        = nuc900_nand_write_buf;
    chip->read_buf        = nuc900_nand_read_buf;
    chip->select_chip  = nuc900_nand_select_chip;
    chip->chip_delay    = 50;
    chip->options        = 0;
    chip->ecc.mode        = NAND_ECC_SOFT;
    chip->ecc.size = 256;
    chip->ecc.bytes = 3;

  platform_set_drvdata(pdev, nuc900_nand);
  
    nuc900_nand_enable(nuc900_nand);
    nuc900_nand_debug("REG_SMCSR=0x%x,REG_SMIER=0x%x,SMTCR=0x%x
",__raw_readl(REG_SMCSR),__raw_readl(REG_SMIER),__raw_readl(REG_SMTCR));

    
    /* first scan to find the device and get the page size */
    if (nand_scan_ident(mtd, 1, NULL)) {
        retval = -ENXIO;
        goto err_scan_ident;
    }
    
    
    //chip->ecc.bytes = CONFIG_SYS_NAND_ECCBYTES;
        /* ECC is calculated for the whole page (1 step) */
      //chip->ecc.size = mtd->writesize;
            /* set ECC page size */
        switch (mtd->writesize) {
        case 512:
            __raw_writel( __raw_readl(REG_SMCSR) & ( ~PSIZE ), REG_SMCSR );    // psize:512; wp# set 1
            //chip->ecc.bytes =ECCBYTE512;
            break;
        case 2048:
            __raw_writel( __raw_readl(REG_SMCSR)|PSIZE , REG_SMCSR );    // psize:2048; wp# set 1
            //chip->ecc.bytes =ECCBYTE2K;
            break;
        default:
            /* page size not handled by HW ECC */
            /* switching back to soft ECC */
            chip->ecc.mode = NAND_ECC_SOFT;
            chip->ecc.calculate = NULL;
            chip->ecc.correct = NULL;
            chip->ecc.hwctl = NULL;
            chip->ecc.read_page = NULL;
            chip->ecc.postpad = 0;
            chip->ecc.prepad = 0;
            chip->ecc.bytes = 0;
            break;
        }
    
    /* second phase scan */
    if (nand_scan_tail(mtd)) {
        
        dma_free_coherent(NULL, 2112, nand_vaddr, (dma_addr_t )nand_phyaddr);
        retval = -ENXIO;
        goto fail3;
    }

    add_mtd_partitions(mtd, partitions,
                        ARRAY_SIZE(partitions));

    return retval;

fail3:    
    nuc900_nand_debug("nuc900_nand_probe fail3
");
    //iounmap(nuc900_nand->reg);
err_scan_ident:    
    platform_set_drvdata(pdev, NULL);
fail2:
    //nuc900_nand_debug("nuc900_nand_probe fail2
");    
    //release_mem_region(res->start, resource_size(res));
fail1:
    nuc900_nand_debug("nuc900_nand_probe fail1
");
    kfree(nuc900_nand);
    return retval;
}

static int __devexit nuc900_nand_remove(struct platform_device *pdev)
{
    struct nuc900_nand *nuc900_nand = platform_get_drvdata(pdev);
    //struct resource *res;
    struct mtd_info *mtd = &nuc900_nand->mtd;

    nand_release(mtd);
    //iounmap(nuc900_nand->reg);
    //res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    //release_mem_region(res->start, resource_size(res));

    //clk_disable(nuc900_nand->clk);
    //clk_put(nuc900_nand->clk);
  free_irq(IRQ_FMI, NULL);
    kfree(nuc900_nand);
    /* Free dma space */
    dma_free_coherent(NULL, 2112, nand_vaddr, (dma_addr_t )nand_phyaddr);

    platform_set_drvdata(pdev, NULL);

    return 0;
}

static struct platform_driver nuc900_nand_driver = {
    .probe        = nuc900_nand_probe,
    .remove        = __devexit_p(nuc900_nand_remove),
    .driver        = {
        .name    = "nuc900-nand",
        .owner    = THIS_MODULE,
    },
};

static int __init nuc900_nand_init(void)
{
    return platform_driver_register(&nuc900_nand_driver);
}

static void __exit nuc900_nand_exit(void)
{
    platform_driver_unregister(&nuc900_nand_driver);
}

module_init(nuc900_nand_init);
module_exit(nuc900_nand_exit);

MODULE_AUTHOR("Tanshi Li <dolphin96011@gmail.com>");
MODULE_DESCRIPTION("w90p910/NUC9xx nand driver!");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:nuc900-nand");
原文地址:https://www.cnblogs.com/chuncky/p/4312767.html