drivers: mtd: nand : add rockchip nandc v6 driver

Change-Id: Id4c0c9aee52e6cd797cc4272b04597ea69b37d61
Signed-off-by: Yifeng Zhao <zyf@rock-chips.com>
This commit is contained in:
Yifeng Zhao
2019-02-27 10:21:46 +08:00
committed by Tao Huang
parent 77404a6a28
commit 2e260a2b72
3 changed files with 794 additions and 1 deletions

View File

@@ -550,4 +550,10 @@ config MTD_NAND_HISI504
help
Enables support for NAND controller on Hisilicon SoC Hip04.
config MTD_NAND_ROCKCHIP_V6
tristate "Support for NAND controller V6 on Rockchip SoC"
depends on ARCH_ROCKCHIP
help
Enables support for NAND controller V6 on Rockchip SoC.
endif # MTD_NAND

View File

@@ -55,5 +55,5 @@ obj-$(CONFIG_MTD_NAND_BCM47XXNFLASH) += bcm47xxnflash/
obj-$(CONFIG_MTD_NAND_SUNXI) += sunxi_nand.o
obj-$(CONFIG_MTD_NAND_HISI504) += hisi504_nand.o
obj-$(CONFIG_MTD_NAND_BRCMNAND) += brcmnand/
obj-$(CONFIG_MTD_NAND_ROCKCHIP_V6) += rockchip_nand_v6.o
nand-objs := nand_base.o nand_bbt.o nand_timings.o

View File

@@ -0,0 +1,787 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2016-2019 RockChip, Inc.
* Author: yifeng.zhao@rock-chips.com
*/
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/partitions.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/dmaengine.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/iopoll.h>
#define NANDC_V6_NUM_BANKS 4
#define NANDC_V6_DEF_TIMEOUT 20000
#define NANDC_V6_READ 0
#define NANDC_V6_WRITE 1
#define NANDC_REG_V6_FMCTL 0x00
#define NANDC_REG_V6_FMWAIT 0x04
#define NANDC_REG_V6_FLCTL 0x08
#define NANDC_REG_V6_BCHCTL 0x0c
#define NANDC_REG_V6_DMA_CFG 0x10
#define NANDC_REG_V6_DMA_BUF0 0x14
#define NANDC_REG_V6_DMA_BUF1 0x18
#define NANDC_REG_V6_DMA_ST 0x1C
#define NANDC_REG_V6_BCHST 0x20
#define NANDC_REG_V6_RANDMZ 0x150
#define NANDC_REG_V6_VER 0x160
#define NANDC_REG_V6_INTEN 0x16C
#define NANDC_REG_V6_INTCLR 0x170
#define NANDC_REG_V6_INTST 0x174
#define NANDC_REG_V6_SPARE0 0x200
#define NANDC_REG_V6_SPARE1 0x230
#define NANDC_REG_V6_BANK0 0x800
#define NANDC_REG_V6_BANK1 0x900
#define NANDC_REG_V6_BANK2 0xa00
#define NANDC_REG_V6_BANK3 0xb00
#define NANDC_REG_V6_SRAM0 0x1000
#define NANDC_REG_V6_SRAM1 0x1400
#define NANDC_REG_V6_DATA 0x00
#define NANDC_REG_V6_ADDR 0x04
#define NANDC_REG_V6_CMD 0x08
/* FMCTL */
#define NANDC_V6_WP BIT(8)
#define NANDC_V6_CE_SEL_MSK 0xFF
#define NANDC_V6_CE_SEL(x) (1 << (x))
#define NANDC_V6_RDY BIT(9)
/* FLCTL */
#define NANDC_V6_FL_RST BIT(0)
#define NANDC_V6_FL_DIR_S 0x1
#define NANDC_V6_FL_XFER_START BIT(2)
#define NANDC_V6_FL_XFER_EN BIT(3)
#define NANDC_V6_FL_ST_BUF_S 0x4
#define NANDC_V6_FL_XFER_COUNT BIT(5)
#define NANDC_V6_FL_ACORRECT BIT(10)
#define NANDC_V6_FL_XFER_READY BIT(20)
/* BCHCTL */
#define NAND_V6_BCH_REGION_S 0x5
#define NAND_V6_BCH_REGION_M 0x7
/* BCHST */
#define NANDC_V6_BCH0_ST_ERR BIT(2)
#define NANDC_V6_BCH1_ST_ERR BIT(15)
#define NANDC_V6_ECC_ERR_CNT0(x) (((((x) & (0x1F << 3)) >> 3) \
| (((x) & (1 << 27)) >> 22)) & 0x3F)
#define NANDC_V6_ECC_ERR_CNT1(x) (((((x) & (0x1F << 16)) >> 16) \
| (((x) & (1 << 29)) >> 24)) & 0x3F)
#define NANDC_V6_INT_DMA BIT(0)
/*
* NAND Controller structure: stores rk nand controller information
*
* @chip: nand chip info
* @mtd mtd info
* @dev: parent device (used to print error messages)
* @regs: NAND controller registers
* @hclk: NAND Controller ahb clock
* @clk: NAND Controller interface clock
* @gclk: NAND Controller clock gate
* @ecc_mode: NAND Controller current ecc mode
* @selected_bank: NAND Controller current selected bank
* @clk_rate: NAND controller current clock rate
* @oob_buf: temp buffer for oob read and write
* @page_buf: temp buffer for page read and write
* @complete: a completion object used to wait for NAND
* controller events
*/
struct rk_nandc {
struct nand_chip chip;
struct mtd_info mtd;
struct device *dev;
void __iomem *regs;
struct clk *hclk;
struct clk *clk;
struct clk *gclk;
int ecc_mode;
int max_ecc_strength;
unsigned char banks[NANDC_V6_NUM_BANKS];
int selected_bank;
unsigned long clk_rate;
u32 *oob_buf;
u32 *page_buf;
struct completion complete;
};
static inline struct rk_nandc *mtd_to_rk_nandc(struct mtd_info *mtd)
{
return container_of(mtd, struct rk_nandc, mtd);
}
static void rk_nandc_init(struct rk_nandc *nandc)
{
writel(0, nandc->regs + NANDC_REG_V6_RANDMZ);
writel(0, nandc->regs + NANDC_REG_V6_DMA_CFG);
writel(NANDC_V6_WP, nandc->regs + NANDC_REG_V6_FMCTL);
writel(NANDC_V6_FL_RST, nandc->regs + NANDC_REG_V6_FLCTL);
writel(0x1081, nandc->regs + NANDC_REG_V6_FMWAIT);
}
static irqreturn_t rk_nandc_interrupt(int irq, void *dev_id)
{
struct rk_nandc *nandc = dev_id;
u32 st = readl(nandc->regs + NANDC_REG_V6_INTST);
u32 ien = readl(nandc->regs + NANDC_REG_V6_INTEN);
if (!(ien & st))
return IRQ_NONE;
if ((ien & st) == ien)
complete(&nandc->complete);
writel(st, nandc->regs + NANDC_REG_V6_INTCLR);
writel(~st & ien, nandc->regs + NANDC_REG_V6_INTEN);
return IRQ_HANDLED;
}
static void rk_nandc_select_chip(struct mtd_info *mtd, int chipnr)
{
struct rk_nandc *nandc = mtd_to_rk_nandc(mtd);
struct nand_chip *chip = mtd->priv;
u32 reg;
int banknr;
void __iomem *bank_base;
reg = readl(nandc->regs + NANDC_REG_V6_FMCTL);
reg &= ~NANDC_V6_CE_SEL_MSK;
if (chipnr == -1) {
banknr = -1;
} else {
banknr = nandc->banks[chipnr];
bank_base = nandc->regs + NANDC_REG_V6_BANK0 + banknr * 0x100;
chip->IO_ADDR_R = bank_base;
chip->IO_ADDR_W = bank_base;
reg |= NANDC_V6_CE_SEL(banknr);
}
writel(reg, nandc->regs + NANDC_REG_V6_FMCTL);
nandc->selected_bank = banknr;
}
static void rk_nandc_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
{
struct rk_nandc *nandc = mtd_to_rk_nandc(mtd);
struct nand_chip *chip = mtd->priv;
u32 reg;
void __iomem *bank_base = nandc->regs + NANDC_REG_V6_BANK0
+ nandc->selected_bank * 0x100;
WARN_ON(nandc->selected_bank < 0);
if (ctrl & NAND_CTRL_CHANGE) {
WARN_ON((ctrl & NAND_ALE) && (ctrl & NAND_CLE));
if (ctrl & NAND_ALE)
bank_base += NANDC_REG_V6_ADDR;
else if (ctrl & NAND_CLE)
bank_base += NANDC_REG_V6_CMD;
chip->IO_ADDR_W = bank_base;
reg = readl(nandc->regs + NANDC_REG_V6_FMCTL);
reg &= ~NANDC_V6_CE_SEL_MSK;
if (ctrl & NAND_NCE)
reg |= NANDC_V6_CE_SEL(nandc->selected_bank);
writel(reg, nandc->regs + NANDC_REG_V6_FMCTL);
}
if (dat != NAND_CMD_NONE)
writeb(dat & 0xFF, chip->IO_ADDR_W);
}
static void rk_nandc_xfer_start(struct rk_nandc *nandc, u8 dir, u8 n_KB,
dma_addr_t dma_data, dma_addr_t dma_oob)
{
u32 reg;
reg = readl(nandc->regs + NANDC_REG_V6_BCHCTL);
reg = (reg & (~(0x7 << 5))) | (nandc->selected_bank << 5);
writel(reg, nandc->regs + NANDC_REG_V6_BCHCTL);
reg = (1 << 0) | ((!dir) << 1) | (1 << 2) | (2 << 3) | (7 << 6) |
(16 << 9);
writel(reg, nandc->regs + NANDC_REG_V6_DMA_CFG);
writel(dma_data, nandc->regs + NANDC_REG_V6_DMA_BUF0);
writel(dma_oob, nandc->regs + NANDC_REG_V6_DMA_BUF1);
reg = (dir << 1) | (1 << 3) | (1 << 5) | (1 << 10) | (n_KB << 22) |
(1 << 29);
writel(reg, nandc->regs + NANDC_REG_V6_FLCTL);
reg |= (1 << 2);
writel(reg, nandc->regs + NANDC_REG_V6_FLCTL);
}
static int rk_nand_wait_for_xfer_done(struct rk_nandc *nandc)
{
u32 reg;
int ret;
void __iomem *ptr = nandc->regs + NANDC_REG_V6_FLCTL;
ret = readl_poll_timeout_atomic(ptr, reg,
reg & NANDC_V6_FL_XFER_READY,
1, 10000);
if (ret)
pr_err("timeout reg=%x\n", reg);
return ret;
}
static unsigned long rk_nand_dma_map_single(void *ptr, int size, int dir)
{
#ifdef CONFIG_ARM64
__dma_map_area((void *)ptr, size, dir);
return ((unsigned long)virt_to_phys((void *)ptr));
#else
return dma_map_single(NULL, (void *)ptr, size, dir);
#endif
}
static void rk_nand_dma_unmap_single(unsigned long ptr, int size, int dir)
{
#ifdef CONFIG_ARM64
__dma_unmap_area(phys_to_virt(ptr), size, dir);
#else
dma_unmap_single(NULL, (dma_addr_t)ptr, size, dir);
#endif
}
static int rk_nandc_hw_syndrome_ecc_read_page(struct mtd_info *mtd,
struct nand_chip *chip,
u8 *buf,
int oob_required, int page)
{
struct rk_nandc *nandc = mtd_to_rk_nandc(mtd);
struct nand_ecc_ctrl *ecc = &chip->ecc;
int max_bitflips = 0;
dma_addr_t dma_data, dma_oob;
int ret, i;
int bch_st;
int dma_oob_size = ecc->steps * 128;
dma_data = rk_nand_dma_map_single(nandc->page_buf, mtd->writesize,
DMA_FROM_DEVICE);
dma_oob = rk_nand_dma_map_single(nandc->oob_buf, dma_oob_size,
DMA_FROM_DEVICE);
init_completion(&nandc->complete);
writel(NANDC_V6_INT_DMA, nandc->regs + NANDC_REG_V6_INTEN);
rk_nandc_xfer_start(nandc, 0, ecc->steps, dma_data, dma_oob);
wait_for_completion_timeout(&nandc->complete, msecs_to_jiffies(5));
rk_nand_wait_for_xfer_done(nandc);
rk_nand_dma_unmap_single(dma_data, mtd->writesize, DMA_FROM_DEVICE);
rk_nand_dma_unmap_single(dma_oob, dma_oob_size, DMA_FROM_DEVICE);
if (oob_required) {
u8 oob_step = (nandc->ecc_mode <= 24) ? 64 : 128;
u8 *oob;
u32 tmp;
for (i = 0; i < ecc->steps; i++) {
oob = chip->oob_poi + i * (ecc->bytes + 4);
tmp = nandc->oob_buf[i * oob_step / 4];
*oob++ = (u8)tmp;
*oob++ = (u8)(tmp >> 8);
*oob++ = (u8)(tmp >> 16);
*oob++ = (u8)(tmp >> 24);
}
}
for (i = 0; i < ecc->steps / 2; i++) {
bch_st = readl(nandc->regs + NANDC_REG_V6_BCHST + i * 4);
if (bch_st & NANDC_V6_BCH0_ST_ERR ||
bch_st & NANDC_V6_BCH1_ST_ERR) {
mtd->ecc_stats.failed++;
max_bitflips = -1;
} else {
ret = NANDC_V6_ECC_ERR_CNT0(bch_st);
mtd->ecc_stats.corrected += ret;
max_bitflips = max_t(unsigned int, max_bitflips, ret);
ret = NANDC_V6_ECC_ERR_CNT1(bch_st);
mtd->ecc_stats.corrected += ret;
max_bitflips = max_t(unsigned int, max_bitflips, ret);
}
}
memcpy(buf, nandc->page_buf, mtd->writesize);
if (max_bitflips == -1) {
dev_err(nandc->dev, "read_page %x %x %x %x %x %p %x\n",
page, max_bitflips, bch_st, ((u32 *)buf)[0],
((u32 *)buf)[1], buf, (u32)dma_data);
}
return max_bitflips;
}
static int rk_nandc_hw_syndrome_ecc_write_page(struct mtd_info *mtd,
struct nand_chip *chip,
const u8 *buf,
int oob_required, int page)
{
struct rk_nandc *nandc = mtd_to_rk_nandc(mtd);
struct nand_ecc_ctrl *ecc = &chip->ecc;
dma_addr_t dma_data, dma_oob;
int i;
int dma_oob_size = ecc->steps * 64;
for (i = 0; i < ecc->steps; i++) {
u32 tmp;
if (oob_required) {
u8 *oob;
oob = chip->oob_poi + i * (ecc->bytes + 4);
tmp = oob[0] | (oob[1] << 8) | (oob[1] << 16) |
(oob[1] << 24);
} else {
tmp = 0xFFFFFFFF;
}
nandc->oob_buf[i] = tmp;
}
memcpy(nandc->page_buf, buf, mtd->writesize);
dma_data = rk_nand_dma_map_single((void *)nandc->page_buf,
mtd->writesize, DMA_TO_DEVICE);
dma_oob = rk_nand_dma_map_single(nandc->oob_buf, dma_oob_size,
DMA_TO_DEVICE);
init_completion(&nandc->complete);
writel(NANDC_V6_INT_DMA, nandc->regs + NANDC_REG_V6_INTEN);
rk_nandc_xfer_start(nandc, 1, ecc->steps, dma_data, dma_oob);
wait_for_completion_timeout(&nandc->complete, msecs_to_jiffies(10));
rk_nand_wait_for_xfer_done(nandc);
rk_nand_dma_unmap_single(dma_data, mtd->writesize, DMA_TO_DEVICE);
rk_nand_dma_unmap_single(dma_oob, dma_oob_size, DMA_TO_DEVICE);
return 0;
}
static int rk_nandc_hw_ecc_read_oob(struct mtd_info *mtd,
struct nand_chip *chip,
int page)
{
chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
chip->pagebuf = -1;
return chip->ecc.read_page(mtd, chip, chip->buffers->databuf, 1, page);
}
static int rk_nandc_hw_ecc_write_oob(struct mtd_info *mtd,
struct nand_chip *chip,
int page)
{
int ret, status;
chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0, page);
chip->pagebuf = -1;
memset(chip->buffers->databuf, 0xff, mtd->writesize);
ret = chip->ecc.write_page(mtd, chip, chip->buffers->databuf, 1, page);
if (ret)
return ret;
chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
status = chip->waitfunc(mtd, chip);
return status & NAND_STATUS_FAIL ? -EIO : 0;
}
static void rk_nandc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
{
struct rk_nandc *nandc = mtd_to_rk_nandc(mtd);
int offs = 0;
void __iomem *bank_base = nandc->regs + NANDC_REG_V6_BANK0
+ nandc->selected_bank * 0x100;
for (offs = 0; offs < len; offs++)
buf[offs] = readl(bank_base);
}
static void rk_nandc_write_buf(struct mtd_info *mtd, const uint8_t *buf,
int len)
{
struct rk_nandc *nandc = mtd_to_rk_nandc(mtd);
int offs = 0;
void __iomem *bank_base = nandc->regs + NANDC_REG_V6_BANK0
+ nandc->selected_bank * 0x100;
for (offs = 0; offs < len; offs++)
writeb(buf[offs], bank_base);
}
static u8 rk_nandc_read_byte(struct mtd_info *mtd)
{
u8 ret;
rk_nandc_read_buf(mtd, &ret, 1);
return ret;
}
static struct nand_ecclayout nand_oob_fix = {
.eccbytes = 28,
.eccpos = {
4, 5, 6, 7, 8, 9, 10},
.oobfree = {
{.offset = 0,
.length = 4} }
};
static int rk_nandc_hw_ecc_setup(struct mtd_info *mtd,
struct nand_ecc_ctrl *ecc,
uint32_t strength)
{
struct rk_nandc *nandc = mtd_to_rk_nandc(mtd);
u32 reg;
ecc->strength = strength;
ecc->bytes = DIV_ROUND_UP(ecc->strength * 14, 8);
/* HW ECC always work with even numbers of ECC bytes */
ecc->bytes = ALIGN(ecc->bytes, 2);
switch (ecc->strength) {
case 60:
reg = 0x00040010;
break;
case 40:
reg = 0x00040000;
break;
case 24:
reg = 0x00000010;
break;
case 16:
reg = 0x00000000;
break;
default:
return -EINVAL;
}
writel(reg, nandc->regs + NANDC_REG_V6_BCHCTL);
return 0;
}
static int rk_nandc_hw_ecc_ctrl_init(struct mtd_info *mtd,
struct nand_ecc_ctrl *ecc)
{
static const u8 strengths[] = {60, 40, 24, 16};
struct rk_nandc *nandc = mtd_to_rk_nandc(mtd);
int max_strength;
u32 i, ver;
ecc->size = 1024;
ecc->prepad = 4;
ecc->steps = mtd->writesize / ecc->size;
max_strength = ((mtd->oobsize / ecc->steps) - 4) * 8 / 14;
nandc->max_ecc_strength = 60;
ver = readl(nandc->regs + NANDC_REG_V6_VER);
if (ver == 0x801)
nandc->max_ecc_strength = 16;
else if (ver == 0x56363232 || ver == 0x56383030 || ver == 0x56363030)
nandc->max_ecc_strength = 60;
else
dev_err(nandc->dev, "unsupported nandc version %x\n", ver);
if (max_strength > nandc->max_ecc_strength)
max_strength = nandc->max_ecc_strength;
nandc->page_buf = kmalloc(mtd->writesize, GFP_KERNEL | GFP_DMA);
if (!nandc->page_buf)
return -ENOMEM;
nandc->oob_buf = kmalloc(ecc->steps * 128, GFP_KERNEL | GFP_DMA);
if (!nandc->oob_buf) {
kfree(nandc->page_buf);
return -ENOMEM;
}
for (i = 0; i < ARRAY_SIZE(strengths); i++) {
if (max_strength >= strengths[i])
break;
}
if (i >= ARRAY_SIZE(strengths)) {
dev_err(nandc->dev, "unsupported strength\n");
return -ENOTSUPP;
}
nandc->ecc_mode = strengths[i];
rk_nandc_hw_ecc_setup(mtd, ecc, nandc->ecc_mode);
nand_oob_fix.eccbytes = ecc->bytes * ecc->steps;
for (i = 0; i < (u32)ecc->bytes; i++)
nand_oob_fix.eccpos[i] = i + 4;
ecc->layout = &nand_oob_fix;
if (mtd->oobsize < (u32)(ecc->bytes + 4) * ecc->steps)
return -EINVAL;
return 0;
}
static int rk_nandc_ecc_init(struct mtd_info *mtd, struct nand_ecc_ctrl *ecc)
{
int ret;
switch (ecc->mode) {
case NAND_ECC_HW_SYNDROME:
ret = rk_nandc_hw_ecc_ctrl_init(mtd, ecc);
if (ret)
return ret;
ecc->read_page = rk_nandc_hw_syndrome_ecc_read_page;
ecc->write_page = rk_nandc_hw_syndrome_ecc_write_page;
ecc->read_oob = rk_nandc_hw_ecc_read_oob;
ecc->write_oob = rk_nandc_hw_ecc_write_oob;
break;
case NAND_ECC_HW:
case NAND_ECC_NONE:
case NAND_ECC_SOFT:
break;
default:
return -EINVAL;
}
return 0;
}
static int rk_nandc_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip)
{
int page, res = 0, i;
struct nand_chip *chip = (struct nand_chip *)mtd->priv;
u16 bad = 0xff;
u8 *data_buf = chip->buffers->databuf;
int chipnr = (int)(ofs >> chip->chip_shift);
page = (int)(ofs >> chip->page_shift) & chip->pagemask;
chip->select_chip(mtd, chipnr);
chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
if (rk_nandc_hw_syndrome_ecc_read_page(mtd, chip, data_buf, 0,
page) == -1) {
chip->cmdfunc(mtd, NAND_CMD_READOOB, chip->badblockpos, page);
for (i = 0; i < 8; i++) {
bad = chip->read_byte(mtd);
if (bad)
break;
}
if (i >= 8)
res = 1;
}
chip->select_chip(mtd, -1);
return res;
}
static int rk_nandc_dev_ready(struct mtd_info *mtd)
{
struct rk_nandc *nandc = mtd_to_rk_nandc(mtd);
u32 reg;
reg = readl(nandc->regs + NANDC_REG_V6_FMCTL);
return (reg & NANDC_V6_RDY);
}
static int rk_nandc_chips_init(struct device *dev, struct rk_nandc *nandc)
{
struct nand_chip *chip;
struct mtd_info *mtd;
size_t chipnr, bank_idx;
int nand_maf_id, nand_dev_id;
int ret;
mtd = &nandc->mtd;
chip = &nandc->chip;
mtd->priv = chip;
mtd->owner = THIS_MODULE;
mtd->name = "rk-nand";
chip->ecc.mode = NAND_ECC_HW_SYNDROME;
chip->chip_delay = 25;
chip->select_chip = rk_nandc_select_chip;
chip->cmd_ctrl = rk_nandc_cmd_ctrl;
chip->read_buf = rk_nandc_read_buf;
chip->write_buf = rk_nandc_write_buf;
chip->read_byte = rk_nandc_read_byte;
chip->block_bad = rk_nandc_block_bad;
chip->dev_ready = rk_nandc_dev_ready;
chip->bbt_options = NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB;
chip->options = NAND_NO_SUBPAGE_WRITE;
chipnr = 0;
for (bank_idx = 0; bank_idx < NANDC_V6_NUM_BANKS; bank_idx++) {
nandc->banks[chipnr] = bank_idx;
if (chipnr == 0) {
ret = nand_scan_ident(mtd, 1, NULL);
if (ret)
continue;
chip->select_chip(mtd, 0);
chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
nand_maf_id = chip->read_byte(mtd);
nand_dev_id = chip->read_byte(mtd);
chip->select_chip(mtd, -1);
} else {
chip->select_chip(mtd, chipnr);
chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
if (nand_maf_id != chip->read_byte(mtd) ||
nand_dev_id != chip->read_byte(mtd)) {
chip->select_chip(mtd, -1);
continue;
}
chip->select_chip(mtd, -1);
chip->numchips++;
mtd->size += chip->chipsize;
}
chipnr++;
}
if (chipnr == 0) {
dev_err(dev, "No NAND chips found\n");
return -ENODEV;
}
rk_nandc_ecc_init(mtd, &chip->ecc);
ret = nand_scan_tail(mtd);
if (ret) {
dev_err(dev, "Failed to scan NAND: %d\n", ret);
return ret;
}
ret = mtd_device_parse_register(mtd, NULL, NULL, NULL, 0);
if (ret) {
dev_err(dev, "failed to register mtd device: %d\n", ret);
nand_release(mtd);
return ret;
}
return 0;
}
static int rk_nandc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *r;
struct rk_nandc *nandc;
int irq;
int ret;
int clock_frequency;
nandc = devm_kzalloc(dev, sizeof(*nandc), GFP_KERNEL);
if (!nandc)
return -ENOMEM;
nandc->dev = dev;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
nandc->regs = devm_ioremap_resource(dev, r);
if (IS_ERR(nandc->regs))
return PTR_ERR(nandc->regs);
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(dev, "failed to retrieve irq\n");
return irq;
}
nandc->hclk = devm_clk_get(dev, "hclk_nandc");
if (IS_ERR(nandc->hclk)) {
dev_err(dev, "failed to retrieve hclk_nandc %p\n", nandc->hclk);
return PTR_ERR(nandc->hclk);
}
ret = clk_prepare_enable(nandc->hclk);
if (ret)
return ret;
nandc->clk = devm_clk_get(dev, "clk_nandc");
if (IS_ERR(nandc->clk)) {
dev_err(dev, "failed to retrieve nandc clk\n");
ret = PTR_ERR(nandc->clk);
goto out_ahb_clk_unprepare;
}
if (of_property_read_u32(dev->of_node, "clock-frequency",
&clock_frequency))
clock_frequency = 150 * 1000 * 1000;
clk_set_rate(nandc->clk, clock_frequency);
ret = clk_prepare_enable(nandc->clk);
if (ret)
goto out_ahb_clk_unprepare;
nandc->clk_rate = clk_get_rate(nandc->clk);
nandc->gclk = devm_clk_get(&pdev->dev, "g_clk_nandc");
if (!(IS_ERR(nandc->gclk)))
clk_prepare_enable(nandc->gclk);
rk_nandc_init(nandc);
writel(0, nandc->regs + NANDC_REG_V6_INTEN);
ret = devm_request_irq(dev, irq, rk_nandc_interrupt,
0, "rk-nand", nandc);
if (ret)
goto out_nandc_clk_unprepare;
platform_set_drvdata(pdev, nandc);
ret = rk_nandc_chips_init(dev, nandc);
if (ret) {
dev_err(dev, "failed to init nand chips\n");
goto out_nandc_clk_unprepare;
}
return 0;
out_nandc_clk_unprepare:
clk_disable_unprepare(nandc->clk);
out_ahb_clk_unprepare:
clk_disable_unprepare(nandc->hclk);
return ret;
}
static int rk_nandc_remove(struct platform_device *pdev)
{
struct rk_nandc *nandc = platform_get_drvdata(pdev);
kfree(nandc->page_buf);
kfree(nandc->oob_buf);
clk_disable_unprepare(nandc->clk);
clk_disable_unprepare(nandc->hclk);
return 0;
}
static const struct of_device_id rk_nandc_ids[] = {
{.compatible = "rockchip,rk-nandc"},
{.compatible = "rockchip,rk-nandc-v6"},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rk_nandc_ids);
static struct platform_driver rk_nandc_driver = {
.driver = {
.name = "rk-nand",
.of_match_table = rk_nandc_ids,
},
.probe = rk_nandc_probe,
.remove = rk_nandc_remove,
};
module_platform_driver(rk_nandc_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yifeng Zhao <zyf@rock-chips.com>");
MODULE_DESCRIPTION("MTD NAND driver for Rockchip SoC");