615 lines
16 KiB
C
615 lines
16 KiB
C
/*
|
|
* GSC3280 SoC NAND Controller Driver
|
|
*
|
|
* Copyright (C) 2013 BLX IC Design Corp.,Ltd.
|
|
* Author: Fei Lu, Lufei@china-cpu.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; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
*/
|
|
|
|
|
|
#include <linux/ioport.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/mtd/mtd.h>
|
|
#include <linux/mtd/nand.h>
|
|
#include <linux/mtd/partitions.h>
|
|
#include <asm/cacheflush.h>
|
|
|
|
/*regs*/
|
|
#define NFC_CONF 0x1300
|
|
#define NFC_CTRL 0x1304
|
|
#define NFC_COMM 0x1308
|
|
#define NFC_ADDR0L 0x130C
|
|
#define NFC_ADDR1L 0x1310
|
|
#define NFC_DATA 0x1314
|
|
#define NFC_PB0 0x1318
|
|
#define NFC_STATE 0x131C
|
|
#define NFC_ECCSTATUS 0x1340
|
|
#define NFC_ADDR0H 0x1344
|
|
#define NFC_ADDR1H 0x1348
|
|
#define NFC_DMAADDR 0x1380
|
|
#define NFC_DMACTRL 0x1384
|
|
#define NFC_DMACNTR 0x1388
|
|
|
|
|
|
/*configuration regs ---TIMER_BITS(timer width):7*/
|
|
#define GSC3280_FLCONF_PSM 0x1<<31
|
|
#define GSC3280_FLCONF_TWB (0x13)<<23
|
|
#define GSC3280_FLCONF_TWHR (0x13)<<17
|
|
#define GSC3280_FLCONF_TWP (0x9)<<11
|
|
#define GSC3280_FLCONF_TRP (0x9)<<5
|
|
#define GSC3280_FLCONF_TWH 0x4
|
|
|
|
/*control regs*/
|
|
#define GSC3280_FLCTRL_WP (1<<23)
|
|
#define GSC3280_FLCTRL_FLCE (1<<15)
|
|
#define GSC3280_FLCTRL_BS (1<<12)
|
|
#define GSC3280_FLCTRL_PS (1<<11)
|
|
#define GSC3280_FLCTRL_SS (1<<10)
|
|
#define GSC3280_FLCTRL_EEN (1<<9)
|
|
#define GSC3280_FLCTRL_INTEN (1<<8)
|
|
#define GSC3280_FLCTRL_AERREN (1<<7)
|
|
#define GSC3280_FLCTRL_PIEN (1<<6)
|
|
#define GSC3280_FLCTRL_RBIEN (1<<5)
|
|
#define GSC3280_FLCTRL_DMAIEN (1<<4)
|
|
#define GSC3280_FLCTRL_DMATR (1<<3)
|
|
#define GSC3280_FLCTRL_TRANSIE (1<<2)
|
|
#define GSC3280_FLCTRL_RBMODE (1<<1)
|
|
#define GSC3280_FLCTRL_ADDRC (1<<0)
|
|
|
|
/*fldmactrl reg*/
|
|
#define GSC3280_FLDMACTRL_SF (1<<24)
|
|
#define GSC3280_FLDMACTRL_DIR(x) ((x)<<16)
|
|
#define GSC3280_FLDMACTRL_SIZE(x) ((x)<<14)
|
|
#define GSC3280_FLDMACTRL_BURST(x) ((x)<<11)
|
|
#define GSC3280_FLDMACTRL_BUSY (1<<2)
|
|
#define GSC3280_FLDMACTRL_ERRFLAG (1<<1)
|
|
|
|
/*
|
|
gsc3280 only supports 512 Bytes HW ECC,give out 13 bytes ecc code;
|
|
soft ecc, 256 bytes block data give out 3 bytes ecc result;
|
|
Different hardware ECC is done automatically.
|
|
*/
|
|
#define GSC3280_ECC_SIZE 256
|
|
#define GSC3280_ECC_BYTES 3
|
|
|
|
|
|
struct gsc3280_nand_platform_data {
|
|
int num_partitions;
|
|
struct mtd_partition *partitions;
|
|
struct nand_ecclayout *ecc_layout;
|
|
};
|
|
|
|
struct gsc3280_nand {
|
|
struct mtd_info mtd;
|
|
struct nand_chip chip;
|
|
void __iomem *base;
|
|
struct gsc3280_nand_platform_data *pdata;
|
|
int addrflag;
|
|
};
|
|
|
|
#if 0
|
|
static struct mtd_partition gsc3280_default_nand_partitions[ ] = {
|
|
[0] = {
|
|
.name = "Bootloader",
|
|
.offset = 0,
|
|
.size = 0x80000,/*512k*/
|
|
},
|
|
[1] = {
|
|
.name = "Parameters",
|
|
.offset = MTDPART_OFS_APPEND,
|
|
.size = 0x40000,/*256k*/
|
|
},
|
|
[2] = {
|
|
.name = "Reserved",
|
|
.offset = MTDPART_OFS_APPEND,
|
|
.size = 0x40000,/*256k*/
|
|
},
|
|
[3] = {
|
|
.name = "Linux Kernel",
|
|
.offset = MTDPART_OFS_APPEND,
|
|
.size = 0x500000,/*5M*/
|
|
},
|
|
[4] = {
|
|
.name = "jffs2",
|
|
.offset = MTDPART_OFS_APPEND,
|
|
.size = 0x500000,/*5M*/
|
|
/* .size = 0xa00000,*//*10M*/
|
|
},
|
|
[5] = {
|
|
.name = "Yaffs2",
|
|
.offset = MTDPART_OFS_APPEND,
|
|
.size = 0x10000000,/*256M*/
|
|
},
|
|
[6] = {
|
|
.name = "Others",
|
|
.offset = MTDPART_OFS_APPEND,
|
|
.size = MTDPART_SIZ_FULL,
|
|
},
|
|
};
|
|
#endif
|
|
#if 1
|
|
static struct mtd_partition gsc3280_default_nand_partitions[ ] = {
|
|
[0] = {
|
|
.name = "Bootloader",
|
|
.offset = 0,
|
|
.size = 0x80000,/*512k*/
|
|
},
|
|
[1] = {
|
|
.name = "Parameters",
|
|
.offset = MTDPART_OFS_APPEND,
|
|
.size = 0x40000,/*256k*/
|
|
},
|
|
[2] = {
|
|
.name = "Reserved",
|
|
.offset = MTDPART_OFS_APPEND,
|
|
.size = 0x40000,/*256k*/
|
|
},
|
|
[3] = {
|
|
.name = "kernel",
|
|
.offset = MTDPART_OFS_APPEND,
|
|
.size = 0x500000,/*5M*/
|
|
},
|
|
[4] = {
|
|
.name = "rootfs",
|
|
.offset = MTDPART_OFS_APPEND,
|
|
.size = 0xa00000,/*10M*/
|
|
},
|
|
[5] = {
|
|
.name = "userdata",
|
|
.offset = MTDPART_OFS_APPEND,
|
|
.size = MTDPART_SIZ_FULL,
|
|
},
|
|
};
|
|
#endif
|
|
static struct gsc3280_nand_platform_data gsc3280_platform_default_nand = {
|
|
.num_partitions = ARRAY_SIZE(gsc3280_default_nand_partitions),
|
|
.partitions = gsc3280_default_nand_partitions,
|
|
};
|
|
|
|
static inline struct gsc3280_nand *mtd_to_gsc3280_nand(struct mtd_info *mtd)
|
|
{
|
|
return container_of(mtd, struct gsc3280_nand, mtd);
|
|
}
|
|
|
|
static void gsc3280_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl)
|
|
{
|
|
struct gsc3280_nand *nand = mtd_to_gsc3280_nand(mtd);
|
|
struct nand_chip *chip = mtd->priv;
|
|
void *IO_ADDR_W=NULL;
|
|
|
|
if(ctrl & NAND_CA){
|
|
switch(cmd){
|
|
case NAND_CMD_READ0:
|
|
/*chip->addrflag=3;*//*dma buffer*/
|
|
case NAND_CMD_READ1:
|
|
case NAND_CMD_READID:
|
|
case NAND_CMD_READOOB:
|
|
case NAND_CMD_RNDOUT:
|
|
nand->addrflag=0;
|
|
break;
|
|
case NAND_CMD_WRITE0:
|
|
case NAND_CMD_SEQIN:
|
|
case NAND_CMD_PAGEPROG:
|
|
nand->addrflag=1;
|
|
break;
|
|
case NAND_CMD_ERASE1:
|
|
nand->addrflag=2;
|
|
break;
|
|
}
|
|
}
|
|
if (ctrl & NAND_CTRL_CHANGE) {
|
|
if (ctrl & NAND_CLE)
|
|
IO_ADDR_W = nand->base + NFC_COMM;
|
|
else if (ctrl & NAND_ALE){
|
|
switch(nand->addrflag){
|
|
case 0:
|
|
IO_ADDR_W = nand->base + NFC_ADDR0L;
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
IO_ADDR_W = nand->base + NFC_ADDR1L;
|
|
break;
|
|
case 3:
|
|
IO_ADDR_W = nand->base;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
IO_ADDR_W = nand->base + NFC_DATA;
|
|
chip->IO_ADDR_W = IO_ADDR_W;
|
|
chip->IO_ADDR_R = IO_ADDR_W;
|
|
/*
|
|
if (ctrl & NAND_NCE)
|
|
writel(readl(nand->base + NFC_CTRL) | GSC3280_FLCTRL_FLCE,
|
|
nand->base + NFC_CTRL);
|
|
else
|
|
writel(readl(nand->base + NFC_CTRL) & ~GSC3280_FLCTRL_FLCE,
|
|
nand->base + NFC_CTRL);
|
|
*/
|
|
}
|
|
if (cmd != NAND_CMD_NONE && !(ctrl & NAND_CA) )
|
|
writel(cmd, chip->IO_ADDR_W);
|
|
}
|
|
|
|
static int gsc3280_dev_ready(struct mtd_info *mtd)
|
|
{
|
|
struct gsc3280_nand *nand = mtd_to_gsc3280_nand(mtd);
|
|
uint8_t state = readb(nand->base + NFC_STATE);
|
|
return (state&0x61)==0x1;
|
|
|
|
}
|
|
#ifdef CONFIG_MTD_GSC3280_NAND_DMA
|
|
int gsc3280_nand_dma_transfer(struct mtd_info *mtd, const uint8_t *buffer,
|
|
int size, int col, int isread)
|
|
{
|
|
uint32_t cntr;
|
|
struct gsc3280_nand *nand = mtd_to_gsc3280_nand(mtd);
|
|
unsigned long memaddr=virt_to_phys((void *)buffer);
|
|
uint32_t state;
|
|
|
|
if(!col)
|
|
cntr=(size>>2)<<16;
|
|
else
|
|
cntr=((mtd->writesize-col)>>2)<<16 | col;
|
|
writel(cntr,nand->base + NFC_DMACNTR);
|
|
writel(memaddr,nand->base + NFC_DMAADDR);
|
|
dma_cache_wback_inv((unsigned long)buffer,(unsigned long)(buffer+mtd->writesize));
|
|
if(isread)
|
|
writel(0x0101d000,nand->base + NFC_DMACTRL);
|
|
else
|
|
writel(0x0100d000,nand->base + NFC_DMACTRL);
|
|
do{
|
|
state=readl(nand->base + NFC_STATE);
|
|
}while(state&0x80);
|
|
|
|
return 0;
|
|
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_MTD_GSC3280_NAND_HWECC
|
|
static int gsc3280_nand_check_hwecc(struct mtd_info *mtd, uint8_t *dat,
|
|
uint8_t *read_ecc, uint8_t *calc_ecc)
|
|
{
|
|
struct gsc3280_nand *nand = mtd_to_gsc3280_nand(mtd);
|
|
uint32_t eccstatus;
|
|
uint8_t eccerror;
|
|
uint8_t ecccorrect;
|
|
|
|
eccstatus=readl(nand->base + NFC_ECCSTATUS);
|
|
if(readl(nand->base + NFC_CTRL)&GSC3280_FLCTRL_PS){
|
|
eccerror=(eccstatus>>16)&&0xf;
|
|
ecccorrect=eccstatus&&0xf;
|
|
}else{
|
|
eccerror=(eccstatus>>16)&&0xff;
|
|
ecccorrect=eccstatus&&0xff;
|
|
}
|
|
|
|
if(!eccerror||!(eccerror^ecccorrect))
|
|
return 0;
|
|
else{
|
|
printk("gsc3280_nand_correct_data: not implemented\n");
|
|
return -1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static int gsc3280_nand_read_page_raw(struct mtd_info *mtd,
|
|
struct nand_chip *chip,uint8_t *buf, int page)
|
|
{
|
|
struct gsc3280_nand *nand = mtd_to_gsc3280_nand(mtd);
|
|
#ifdef CONFIG_MTD_GSC3280_NAND_DMA
|
|
chip->dma_trans(mtd,buf,mtd->writesize,0,1);
|
|
#else
|
|
memcpy((void *)buf,nand->base,mtd->writesize);
|
|
#endif
|
|
return chip->ecc.read_oob(mtd, chip, page, 1);
|
|
}
|
|
|
|
static int gsc3280_nand_read_page_ecc(struct mtd_info *mtd, struct nand_chip *chip,
|
|
uint8_t *buf, int page)
|
|
{
|
|
#ifdef CONFIG_MTD_GSC3280_NAND_HWECC
|
|
int ret;
|
|
ret=chip->ecc.correct(mtd,NULL,NULL,NULL);
|
|
if(ret)
|
|
return -1;
|
|
else{
|
|
chip->ecc.read_page_raw(mtd, chip, buf, page);
|
|
}
|
|
#else
|
|
int i, eccsize = chip->ecc.size;
|
|
int eccbytes = chip->ecc.bytes;
|
|
int eccsteps = chip->ecc.steps;
|
|
uint8_t *p = buf;
|
|
uint8_t *ecc_calc = chip->buffers->ecccalc;
|
|
uint8_t *ecc_code = chip->buffers->ecccode;
|
|
uint32_t *eccpos = chip->ecc.layout->eccpos;
|
|
|
|
chip->ecc.read_page_raw(mtd, chip, buf, page);
|
|
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize)
|
|
chip->ecc.calculate(mtd, p, &ecc_calc[i]);
|
|
|
|
for (i = 0; i < chip->ecc.total; i++)
|
|
ecc_code[i] = chip->oob_poi[eccpos[i]];
|
|
|
|
eccsteps = chip->ecc.steps;
|
|
p = buf;
|
|
for (i = 0 ; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
|
|
int stat;
|
|
stat = chip->ecc.correct(mtd, p, &ecc_code[i], &ecc_calc[i]);
|
|
if (stat < 0)
|
|
mtd->ecc_stats.failed++;
|
|
else
|
|
mtd->ecc_stats.corrected += stat;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static void gsc3280_nand_write_page_ecc(struct mtd_info *mtd, struct nand_chip *chip,
|
|
const uint8_t *buf)
|
|
{
|
|
#ifndef CONFIG_MTD_GSC3280_NAND_HWECC
|
|
int i, eccsize = chip->ecc.size;
|
|
int eccbytes = chip->ecc.bytes;
|
|
int eccsteps = chip->ecc.steps;
|
|
uint8_t *ecc_calc = chip->buffers->ecccalc;
|
|
const uint8_t *p = buf;
|
|
uint32_t *eccpos = chip->ecc.layout->eccpos;
|
|
|
|
/* Software ecc calculation */
|
|
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize)
|
|
chip->ecc.calculate(mtd, p, &ecc_calc[i]);
|
|
for (i = 0; i < chip->ecc.total; i++)
|
|
chip->oob_poi[eccpos[i]] = ecc_calc[i];
|
|
|
|
#endif
|
|
chip->ecc.write_page_raw(mtd, chip, buf);
|
|
}
|
|
|
|
static void gsc3280_nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
|
|
const uint8_t *buf)
|
|
{
|
|
struct gsc3280_nand *nand = mtd_to_gsc3280_nand(mtd);
|
|
#ifdef CONFIG_MTD_GSC3280_NAND_DMA
|
|
chip->dma_trans(mtd,buf,mtd->writesize,0,0);
|
|
#else
|
|
memcpy(nand->base,buf,mtd->writesize);
|
|
#endif
|
|
memset(nand->base+mtd->writesize,0xff,mtd->oobsize);
|
|
}
|
|
|
|
static int gsc3280_nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
|
const uint8_t *buf, int page, int cached, int raw)
|
|
{
|
|
int status;
|
|
|
|
if(unlikely(raw))
|
|
chip->ecc.write_page_raw(mtd, chip, buf);
|
|
else
|
|
chip->ecc.write_page(mtd, chip, buf);
|
|
chip->cmdfunc(mtd, NAND_CMD_WRITE0, -1, page);
|
|
status = chip->waitfunc(mtd, chip);
|
|
/*
|
|
* See if operation failed and additional status checks are
|
|
* available
|
|
*/
|
|
if ((status & NAND_STATUS_FAIL) && (chip->errstat))
|
|
status = chip->errstat(mtd, chip, FL_WRITING, status,
|
|
page);
|
|
if (status & NAND_STATUS_FAIL)
|
|
return -1;
|
|
chip->ecc.write_oob(mtd, chip, page&chip->pagemask);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_MTD_CMDLINE_PARTS
|
|
static const char *part_probes[] = {"cmdline", NULL};
|
|
#endif
|
|
|
|
static int board_nand_init_snd(struct mtd_info *mtd)
|
|
{
|
|
uint32_t ctrl;
|
|
struct gsc3280_nand *nand = mtd_to_gsc3280_nand(mtd);
|
|
|
|
ctrl=readl(nand->base+NFC_CTRL);
|
|
/*
|
|
#ifdef CONFIG_MTD_GSC3280_NAND_DMA
|
|
ctrl |= GSC3280_FLCTRL_DMATR;
|
|
#endif
|
|
*/
|
|
#ifdef CONFIG_MTD_GSC3280_NAND_HWECC
|
|
ctrl |= GSC3280_FLCTRL_EEN;
|
|
/*
|
|
GSC3280_FLCTRL_INTEN|GSC3280_FLCTRL_AERREN|GSC3280_FLCTRL_PIEN| \
|
|
GSC3280_FLCTRL_RBIEN|GSC3280_FLCTRL_DMAIEN|GSC3280_FLCTRL_TRANSIE;
|
|
|
|
*/
|
|
#endif
|
|
if(mtd->writesize == 0x800)
|
|
ctrl |= GSC3280_FLCTRL_PS;/*page=2k*/
|
|
if(mtd->erasesize/mtd->writesize == 128)
|
|
ctrl |= GSC3280_FLCTRL_BS;/*Block=128pages*/
|
|
if(mtd->oobsize != 0x40 && mtd->oobsize != 0x80)
|
|
ctrl |= GSC3280_FLCTRL_SS;/*Extended mode*/
|
|
ctrl |= GSC3280_FLCTRL_ADDRC;/*Default 5 ADDR CYCLES*/
|
|
writel(ctrl, nand->base+NFC_CTRL);
|
|
return 0;
|
|
}
|
|
|
|
static int board_nand_init(struct mtd_info *mtd,struct nand_chip *chip)
|
|
{
|
|
uint32_t cfg;
|
|
struct gsc3280_nand *nand = mtd_to_gsc3280_nand(mtd);
|
|
|
|
cfg = GSC3280_FLCONF_PSM;
|
|
cfg |= GSC3280_FLCONF_TWB;
|
|
cfg |= GSC3280_FLCONF_TWHR;
|
|
cfg |= GSC3280_FLCONF_TWP;
|
|
cfg |= GSC3280_FLCONF_TRP;
|
|
cfg |= GSC3280_FLCONF_TWH;
|
|
writel(cfg,nand->base+NFC_CONF);
|
|
|
|
/* initialize nand_chip data structure */
|
|
chip->IO_ADDR_R = nand->base+NFC_DATA;
|
|
chip->IO_ADDR_W = nand->base+NFC_DATA;
|
|
chip->select_chip = NULL;
|
|
/* cmd control always must be implemented */
|
|
chip->cmd_ctrl = gsc3280_cmd_ctrl;
|
|
chip->dev_ready = gsc3280_dev_ready;
|
|
/*
|
|
nand->set_pb = gsc3280_set_pb;
|
|
*/
|
|
#ifdef CONFIG_MTD_GSC3280_NAND_DMA
|
|
chip->dma_trans=gsc3280_nand_dma_transfer;
|
|
#endif
|
|
chip->write_page = gsc3280_nand_write_page;
|
|
chip->ecc.read_page = gsc3280_nand_read_page_ecc;
|
|
chip->ecc.write_page = gsc3280_nand_write_page_ecc;
|
|
chip->ecc.read_page_raw = gsc3280_nand_read_page_raw;
|
|
chip->ecc.write_page_raw = gsc3280_nand_write_page_raw;
|
|
chip->ecc.size = GSC3280_ECC_SIZE;
|
|
chip->ecc.bytes = GSC3280_ECC_BYTES;
|
|
#ifdef CONFIG_MTD_GSC3280_NAND_HWECC
|
|
chip->ecc.mode = NAND_ECC_HW;
|
|
chip->ecc.correct = gsc3280_nand_check_hwecc;
|
|
#else
|
|
chip->ecc.mode = NAND_ECC_SOFT;
|
|
#endif
|
|
chip->options = NAND_USE_FLASH_BBT;//NAND_SKIP_BBTSCAN;
|
|
return 0;
|
|
}
|
|
|
|
static int __devinit gsc3280_nand_probe(struct platform_device *pdev)
|
|
{
|
|
int ret;
|
|
struct resource *mem, *ioarea;
|
|
struct gsc3280_nand *nand;
|
|
struct nand_chip *chip;
|
|
struct mtd_info *mtd;
|
|
struct gsc3280_nand_platform_data *pdata = pdev->dev.platform_data=&gsc3280_platform_default_nand;
|
|
struct mtd_partition *partition_info=NULL;
|
|
int num_partitions = 0;
|
|
|
|
nand = kzalloc(sizeof(*nand), GFP_KERNEL);
|
|
if (!nand) {
|
|
dev_err(&pdev->dev, "Failed to allocate device structure.\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
mem = platform_get_resource(pdev, IORESOURCE_MEM,0);
|
|
if (!mem) {
|
|
dev_err(&pdev->dev, "Failed to get platform %s memory\n", pdev->name);
|
|
ret = -ENXIO;
|
|
goto err;
|
|
}
|
|
ioarea = request_mem_region(mem->start, resource_size(mem), pdev->name);
|
|
if (!ioarea) {
|
|
dev_err(&pdev->dev, "Failed to request %s memory region\n", pdev->name);
|
|
ret = -EBUSY;
|
|
goto err;
|
|
}
|
|
nand->base = ioremap(mem->start, resource_size(mem));
|
|
if (!nand->base) {
|
|
dev_err(&pdev->dev, "Failed to ioremap %s memory region\n", pdev->name);
|
|
ret = -EBUSY;
|
|
goto err_free;
|
|
}
|
|
|
|
mtd = &nand->mtd;
|
|
chip = &nand->chip;
|
|
mtd->priv = chip;
|
|
mtd->owner = THIS_MODULE;
|
|
mtd->name = pdev->name;
|
|
board_nand_init(mtd,chip);
|
|
|
|
if (pdata)
|
|
chip->ecc.layout = pdata->ecc_layout;
|
|
nand->pdata = pdata;
|
|
platform_set_drvdata(pdev, nand);
|
|
ret = nand_scan_ident(mtd, 1, NULL);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to scan nand\n");
|
|
goto err_nand_scan;
|
|
}
|
|
board_nand_init_snd(mtd);
|
|
ret = nand_scan_tail(mtd);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to scan nand\n");
|
|
goto err_nand_scan;
|
|
}
|
|
#ifdef CONFIG_MTD_CMDLINE_PARTS
|
|
num_partitions = parse_mtd_partitions(mtd, part_probes,
|
|
&partition_info, 0);
|
|
#endif
|
|
if (num_partitions <= 0 && pdata) {
|
|
num_partitions = pdata->num_partitions;
|
|
partition_info = pdata->partitions;
|
|
}
|
|
ret = mtd_device_register(mtd, partition_info, num_partitions);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to add mtd device\n");
|
|
goto err_nand_release;
|
|
}
|
|
dev_info(&pdev->dev, "Successfully registered GSC3280 NAND driver\n");
|
|
return 0;
|
|
err_nand_release:
|
|
nand_release(&nand->mtd);
|
|
err_nand_scan:
|
|
platform_set_drvdata(pdev, NULL);
|
|
err_free:
|
|
release_mem_region(mem->start, resource_size(mem));
|
|
iounmap(nand->base);
|
|
err:
|
|
kfree(nand);
|
|
return ret;
|
|
}
|
|
|
|
static int __devexit gsc3280_nand_remove(struct platform_device *pdev)
|
|
{
|
|
struct gsc3280_nand *nand = platform_get_drvdata(pdev);
|
|
|
|
nand_release(&nand->mtd);
|
|
iounmap(nand->base);
|
|
platform_set_drvdata(pdev, NULL);
|
|
kfree(nand);
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver gsc3280_nand_driver = {
|
|
.probe = gsc3280_nand_probe,
|
|
.remove = __devexit_p(gsc3280_nand_remove),
|
|
.driver = {
|
|
.name = "gsc3280-nand",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
};
|
|
|
|
static int __init gsc3280_nand_init(void)
|
|
{
|
|
return platform_driver_register(&gsc3280_nand_driver);
|
|
}
|
|
|
|
static void __exit gsc3280_nand_exit(void)
|
|
{
|
|
platform_driver_unregister(&gsc3280_nand_driver);
|
|
}
|
|
|
|
module_init(gsc3280_nand_init);
|
|
module_exit(gsc3280_nand_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("lufei <lufei@china-cpu.com>");
|
|
MODULE_DESCRIPTION("NAND controller driver for GSC3280 SoC");
|
|
MODULE_ALIAS("platform:gsc3280-nand");
|