diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig index 5fc9a1bde4ac..de67426f0e74 100644 --- a/drivers/mtd/nand/raw/Kconfig +++ b/drivers/mtd/nand/raw/Kconfig @@ -561,4 +561,10 @@ config MTD_NAND_TEGRA is supported. Extra OOB bytes when using HW ECC are currently not supported. +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 diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile index d5a5f9832b88..4cec7de328ba 100644 --- a/drivers/mtd/nand/raw/Makefile +++ b/drivers/mtd/nand/raw/Makefile @@ -57,6 +57,7 @@ obj-$(CONFIG_MTD_NAND_BRCMNAND) += brcmnand/ obj-$(CONFIG_MTD_NAND_QCOM) += qcom_nandc.o obj-$(CONFIG_MTD_NAND_MTK) += mtk_ecc.o mtk_nand.o obj-$(CONFIG_MTD_NAND_TEGRA) += tegra_nand.o +obj-$(CONFIG_MTD_NAND_ROCKCHIP_V6) += rockchip_nand_v6.o nand-objs := nand_base.o nand_bbt.o nand_timings.o nand_ids.o nand-objs += nand_amd.o diff --git a/drivers/mtd/nand/raw/rockchip_nand_v6.c b/drivers/mtd/nand/raw/rockchip_nand_v6.c new file mode 100644 index 000000000000..216557244039 --- /dev/null +++ b/drivers/mtd/nand/raw/rockchip_nand_v6.c @@ -0,0 +1,920 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2016-2019 RockChip, Inc. + * Author: yifeng.zhao@rock-chips.com + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NANDC_V6_NUM_CHIPS 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 chip structure: stores NAND chip device related information + * + * @node: used to store NAND chips into a list + * @nand: base NAND chip structure + * @clk_rate: clk_rate required for this NAND chip + * @timing_cfg TIMING_CFG register value for this NAND chip + * @selected: current active CS + * @nsels: number of CS lines required by the NAND chip + * @sels: array of CS lines descriptions + */ +struct rk_nand_chip { + struct list_head node; + struct nand_chip nand; + unsigned long clk_rate; + u32 timing_cfg; + int selected; + int nsels; + int sels[NANDC_V6_NUM_CHIPS]; +}; + +static inline struct rk_nand_chip *to_rk_nand_chip(struct nand_chip *nand) +{ + return container_of(nand, struct rk_nand_chip, nand); +} + +/* + * NAND Controller structure: stores rk nand controller information + * + * @controller: base controller structure + * @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 + * @assigned_cs: bitmask describing already assigned CS lines + * @clk_rate: NAND controller current clock rate + * @complete: a completion object used to wait for NAND + * controller events + * @chips: a list containing all the NAND chips attached to + * this NAND controller + * @ecc_mode: NAND Controller current ecc mode + * @oob_buf: temp buffer for oob read and write + * @page_buf: temp buffer for page read and write + */ +struct rk_nfc { + struct nand_controller controller; + struct device *dev; + void __iomem *regs; + struct clk *hclk; + struct clk *clk; + struct clk *gclk; + unsigned long assigned_cs; + unsigned long clk_rate; + struct completion complete; + struct list_head chips; + int selected_cs; + int ecc_mode; + int max_ecc_strength; + u32 *oob_buf; + u32 *page_buf; +}; + +static inline struct rk_nfc *to_rk_nfc(struct nand_controller *ctrl) +{ + return container_of(ctrl, struct rk_nfc, controller); +} + +static void rk_nfc_init(struct rk_nfc *nfc) +{ + writel(0, nfc->regs + NANDC_REG_V6_RANDMZ); + writel(0, nfc->regs + NANDC_REG_V6_DMA_CFG); + writel(NANDC_V6_WP, nfc->regs + NANDC_REG_V6_FMCTL); + writel(NANDC_V6_FL_RST, nfc->regs + NANDC_REG_V6_FLCTL); + writel(0x1081, nfc->regs + NANDC_REG_V6_FMWAIT); +} + +static irqreturn_t rk_nfc_interrupt(int irq, void *dev_id) +{ + struct rk_nfc *nfc = dev_id; + u32 st = readl(nfc->regs + NANDC_REG_V6_INTST); + u32 ien = readl(nfc->regs + NANDC_REG_V6_INTEN); + + if (!(ien & st)) + return IRQ_NONE; + + if ((ien & st) == ien) + complete(&nfc->complete); + + writel(st, nfc->regs + NANDC_REG_V6_INTCLR); + writel(~st & ien, nfc->regs + NANDC_REG_V6_INTEN); + + return IRQ_HANDLED; +} + +static void rk_nfc_select_chip(struct mtd_info *mtd, int chipnr) +{ + struct nand_chip *nand = mtd_to_nand(mtd); + struct rk_nand_chip *chip = to_rk_nand_chip(nand); + struct rk_nfc *nfc = nand_get_controller_data(nand); + u32 reg; + int banknr; + void __iomem *bank_base; + + if (chipnr > 0 && chipnr >= chip->nsels) + return; + + if (chipnr == chip->selected) + return; + + reg = readl(nfc->regs + NANDC_REG_V6_FMCTL); + reg &= ~NANDC_V6_CE_SEL_MSK; + + if (chipnr >= 0) { + banknr = chip->sels[chipnr]; + bank_base = nfc->regs + NANDC_REG_V6_BANK0 + banknr * 0x100; + + nand->IO_ADDR_R = bank_base; + nand->IO_ADDR_W = bank_base; + + reg |= NANDC_V6_CE_SEL(banknr); + } else { + banknr = -1; + } + writel(reg, nfc->regs + NANDC_REG_V6_FMCTL); + + chip->selected = chipnr; + nfc->selected_cs = banknr; +} + +static void rk_nfc_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl) +{ + struct nand_chip *nand = mtd_to_nand(mtd); + struct rk_nfc *nfc = nand_get_controller_data(nand); + u32 reg; + void __iomem *bank_base = nfc->regs + NANDC_REG_V6_BANK0 + + nfc->selected_cs * 0x100; + + 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; + nand->IO_ADDR_W = bank_base; + + reg = readl(nfc->regs + NANDC_REG_V6_FMCTL); + reg &= ~NANDC_V6_CE_SEL_MSK; + if (ctrl & NAND_NCE) + reg |= NANDC_V6_CE_SEL(nfc->selected_cs); + writel(reg, nfc->regs + NANDC_REG_V6_FMCTL); + } + + if (dat != NAND_CMD_NONE) + writeb(dat & 0xFF, nand->IO_ADDR_W); +} + +static void rk_nfc_xfer_start(struct rk_nfc *nfc, u8 dir, u8 n_KB, + dma_addr_t dma_data, dma_addr_t dma_oob) +{ + u32 reg; + + reg = readl(nfc->regs + NANDC_REG_V6_BCHCTL); + reg = (reg & (~(0x7 << 5))) | (nfc->selected_cs << 5); + writel(reg, nfc->regs + NANDC_REG_V6_BCHCTL); + + reg = (1 << 0) | ((!dir) << 1) | (1 << 2) | (2 << 3) | (7 << 6) | + (16 << 9); + writel(reg, nfc->regs + NANDC_REG_V6_DMA_CFG); + writel(dma_data, nfc->regs + NANDC_REG_V6_DMA_BUF0); + writel(dma_oob, nfc->regs + NANDC_REG_V6_DMA_BUF1); + + reg = (dir << 1) | (1 << 3) | (1 << 5) | (1 << 10) | (n_KB << 22) | + (1 << 29); + writel(reg, nfc->regs + NANDC_REG_V6_FLCTL); + reg |= (1 << 2); + writel(reg, nfc->regs + NANDC_REG_V6_FLCTL); +} + +static int rk_nand_wait_for_xfer_done(struct rk_nfc *nfc) +{ + u32 reg; + int ret; + void __iomem *ptr = nfc->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_nfc_hw_syndrome_ecc_read_page(struct mtd_info *mtd, + struct nand_chip *nand, + u8 *buf, + int oob_required, int page) +{ + struct rk_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd)); + struct nand_ecc_ctrl *ecc = &nand->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; + + nand_read_page_op(nand, page, 0, NULL, 0); + + dma_data = rk_nand_dma_map_single(nfc->page_buf, mtd->writesize, + DMA_FROM_DEVICE); + dma_oob = rk_nand_dma_map_single(nfc->oob_buf, dma_oob_size, + DMA_FROM_DEVICE); + + init_completion(&nfc->complete); + writel(NANDC_V6_INT_DMA, nfc->regs + NANDC_REG_V6_INTEN); + rk_nfc_xfer_start(nfc, 0, ecc->steps, dma_data, dma_oob); + wait_for_completion_timeout(&nfc->complete, msecs_to_jiffies(5)); + rk_nand_wait_for_xfer_done(nfc); + 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 = (nfc->ecc_mode <= 24) ? 64 : 128; + u8 *oob; + u32 tmp; + + for (i = 0; i < ecc->steps; i++) { + oob = nand->oob_poi + i * (ecc->bytes + 4); + tmp = nfc->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(nfc->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, nfc->page_buf, mtd->writesize); + + if (max_bitflips == -1) { + dev_err(nfc->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_nfc_hw_syndrome_ecc_write_page(struct mtd_info *mtd, + struct nand_chip *nand, + const u8 *buf, + int oob_required, int page) +{ + struct rk_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd)); + struct nand_ecc_ctrl *ecc = &nand->ecc; + dma_addr_t dma_data, dma_oob; + int i; + int dma_oob_size = ecc->steps * 64; + + nand_prog_page_begin_op(nand, page, 0, NULL, 0); + + for (i = 0; i < ecc->steps; i++) { + u32 tmp; + + if (oob_required) { + u8 *oob; + + oob = nand->oob_poi + i * (ecc->bytes + 4); + tmp = oob[0] | (oob[1] << 8) | (oob[1] << 16) | + (oob[1] << 24); + } else { + tmp = 0xFFFFFFFF; + } + nfc->oob_buf[i] = tmp; + } + + memcpy(nfc->page_buf, buf, mtd->writesize); + dma_data = rk_nand_dma_map_single((void *)nfc->page_buf, + mtd->writesize, DMA_TO_DEVICE); + dma_oob = rk_nand_dma_map_single(nfc->oob_buf, dma_oob_size, + DMA_TO_DEVICE); + init_completion(&nfc->complete); + writel(NANDC_V6_INT_DMA, nfc->regs + NANDC_REG_V6_INTEN); + rk_nfc_xfer_start(nfc, 1, ecc->steps, dma_data, dma_oob); + wait_for_completion_timeout(&nfc->complete, msecs_to_jiffies(10)); + rk_nand_wait_for_xfer_done(nfc); + 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 nand_prog_page_end_op(nand); +} + +static int rk_nfc_hw_ecc_read_oob(struct mtd_info *mtd, + struct nand_chip *nand, + int page) +{ + nand->cmdfunc(mtd, NAND_CMD_READ0, 0, page); + + nand->pagebuf = -1; + + return nand->ecc.read_page(mtd, nand, nand->data_buf, 1, page); +} + +static int rk_nfc_hw_ecc_write_oob(struct mtd_info *mtd, + struct nand_chip *nand, + int page) +{ + int ret, status; + + nand->cmdfunc(mtd, NAND_CMD_SEQIN, 0, page); + + nand->pagebuf = -1; + + memset(nand->data_buf, 0xff, mtd->writesize); + ret = nand->ecc.write_page(mtd, nand, nand->data_buf, 1, page); + if (ret) + return ret; + + nand->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + + status = nand->waitfunc(mtd, nand); + + return status & NAND_STATUS_FAIL ? -EIO : 0; +} + +static void rk_nfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + struct rk_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd)); + int offs = 0; + void __iomem *bank_base = nfc->regs + NANDC_REG_V6_BANK0 + + nfc->selected_cs * 0x100; + + for (offs = 0; offs < len; offs++) + buf[offs] = readl(bank_base); +} + +static void rk_nfc_write_buf(struct mtd_info *mtd, const uint8_t *buf, + int len) +{ + struct rk_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd)); + int offs = 0; + void __iomem *bank_base = nfc->regs + NANDC_REG_V6_BANK0 + + nfc->selected_cs * 0x100; + + for (offs = 0; offs < len; offs++) + writeb(buf[offs], bank_base); +} + +static u8 rk_nfc_read_byte(struct mtd_info *mtd) +{ + u8 ret; + + rk_nfc_read_buf(mtd, &ret, 1); + + return ret; +} + +static int rk_nfc_hw_ecc_setup(struct mtd_info *mtd, + struct nand_ecc_ctrl *ecc, + uint32_t strength) +{ + struct rk_nfc *nfc = nand_get_controller_data(mtd_to_nand(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, nfc->regs + NANDC_REG_V6_BCHCTL); + + return 0; +} + +static int rk_nfc_hw_ecc_ctrl_init(struct mtd_info *mtd, + struct nand_ecc_ctrl *ecc) +{ + static const u8 strengths[] = {60, 40, 24, 16}; + struct nand_chip *nand = mtd_to_nand(mtd); + struct rk_nfc *nfc = nand_get_controller_data(nand); + int max_strength; + int i; + int ver; + + ecc->size = 1024; + ecc->prepad = 4; + ecc->steps = mtd->writesize / ecc->size; + max_strength = ((mtd->oobsize / ecc->steps) - 4) * 8 / 14; + nfc->max_ecc_strength = 60; + + ver = readl(nfc->regs + NANDC_REG_V6_VER); + if (ver == 0x801) + nfc->max_ecc_strength = 16; + else if (ver == 0x56363232 || ver == 0x56383030 || ver == 0x56363030) + nfc->max_ecc_strength = 60; + else + dev_err(nfc->dev, "unsupported nfc version %x\n", ver); + + if (max_strength > nfc->max_ecc_strength) + max_strength = nfc->max_ecc_strength; + + nfc->page_buf = kmalloc(mtd->writesize, GFP_KERNEL | GFP_DMA); + if (!nfc->page_buf) + return -ENOMEM; + nfc->oob_buf = kmalloc(ecc->steps * 128, GFP_KERNEL | GFP_DMA); + if (!nfc->oob_buf) { + kfree(nfc->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(nfc->dev, "unsupported strength\n"); + return -ENOTSUPP; + } + + nfc->ecc_mode = strengths[i]; + rk_nfc_hw_ecc_setup(mtd, ecc, nfc->ecc_mode); + + return 0; +} + +static int rk_nfc_block_bad(struct mtd_info *mtd, loff_t ofs) +{ + int page, res = 0; + struct nand_chip *nand = mtd_to_nand(mtd); + u16 bad = 0xff; + u8 *data_buf = nand->data_buf; + int chipnr = (int)(ofs >> nand->chip_shift); + + page = (int)(ofs >> nand->page_shift) & nand->pagemask; + nand->select_chip(mtd, chipnr); + nand->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page); + if (rk_nfc_hw_syndrome_ecc_read_page(mtd, nand, data_buf, 0, + page) == -1) { + /* first page of the block*/ + nand->cmdfunc(mtd, NAND_CMD_READOOB, nand->badblockpos, page); + bad = nand->read_byte(mtd); + if (bad != 0xFF) { + res = 1; + goto out; + } + /* second page of the block*/ + nand->cmdfunc(mtd, NAND_CMD_READOOB, nand->badblockpos, + page + 1); + bad = nand->read_byte(mtd); + if (bad != 0xFF) { + res = 1; + goto out; + } + /* last page of the block */ + page += ((mtd->erasesize - mtd->writesize) >> nand->chip_shift); + page--; + nand->cmdfunc(mtd, NAND_CMD_READOOB, nand->badblockpos, page); + bad = nand->read_byte(mtd); + if (bad != 0xFF) { + res = 1; + goto out; + } + } +out: + nand->select_chip(mtd, -1); + + return res; +} + +static int rk_nfc_dev_ready(struct mtd_info *mtd) +{ + struct rk_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd)); + u32 reg; + + reg = readl(nfc->regs + NANDC_REG_V6_FMCTL); + + return (reg & NANDC_V6_RDY); +} + +static int rk_nfc_setup_data_interface(struct mtd_info *mtd, int csline, + const struct nand_data_interface *conf) +{ + struct rk_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd)); + const struct nand_sdr_timings *timings; + + timings = nand_get_sdr_timings(conf); + if (IS_ERR(timings)) + return -ENOTSUPP; + + if (csline == NAND_DATA_IFACE_CHECK_ONLY) + return 0; + + writel(0x10801, nfc->regs + NANDC_REG_V6_FMWAIT); + + return 0; +} + +static int rk_nand_attach_chip(struct nand_chip *nand) +{ + struct mtd_info *mtd = nand_to_mtd(nand); + struct nand_ecc_ctrl *ecc = &nand->ecc; + int ret; + + if (!ecc->size) { + ecc->size = nand->ecc_step_ds; + ecc->strength = nand->ecc_strength_ds; + } + + if (!ecc->size || !ecc->strength) + return -EINVAL; + + switch (ecc->mode) { + case NAND_ECC_HW: + ret = rk_nfc_hw_ecc_ctrl_init(mtd, ecc); + if (ret) + return ret; + break; + case NAND_ECC_NONE: + case NAND_ECC_SOFT: + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct nand_controller_ops rk_nand_controller_ops = { + .attach_chip = rk_nand_attach_chip, +}; + +static int rk_nand_chip_init(struct device *dev, struct rk_nfc *nfc, + struct device_node *np) +{ + struct rk_nand_chip *chip; + struct mtd_info *mtd; + struct nand_chip *nand; + int nsels; + int ret; + int i; + u32 tmp; + + if (!of_get_property(np, "reg", &nsels)) + return -EINVAL; + + nsels /= sizeof(u32); + if (!nsels) { + dev_err(dev, "invalid reg property size\n"); + return -EINVAL; + } + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->nsels = nsels; + chip->selected = -1; + + for (i = 0; i < nsels; i++) { + ret = of_property_read_u32_index(np, "reg", i, &tmp); + if (ret) { + dev_err(dev, "could not retrieve reg property: %d\n", + ret); + return ret; + } + + if (tmp > NANDC_V6_NUM_CHIPS) { + dev_err(dev, + "invalid reg value: %u (max CS = 7)\n", + tmp); + return -EINVAL; + } + + if (test_and_set_bit(tmp, &nfc->assigned_cs)) { + dev_err(dev, "CS %d already assigned\n", tmp); + return -EINVAL; + } + + chip->sels[i] = tmp; + } + + nand = &chip->nand; + /* Default tR value specified in the ONFI spec (chapter 4.15.1) */ + nand->chip_delay = 200; + nand->controller = &nfc->controller; + nand->controller->ops = &rk_nand_controller_ops; + + nand_set_flash_node(nand, np); + nand_set_controller_data(nand, nfc); + + nand->select_chip = rk_nfc_select_chip; + nand->cmd_ctrl = rk_nfc_cmd_ctrl; + nand->read_buf = rk_nfc_read_buf; + nand->write_buf = rk_nfc_write_buf; + nand->read_byte = rk_nfc_read_byte; + nand->setup_data_interface = rk_nfc_setup_data_interface; + nand->block_bad = rk_nfc_block_bad; + nand->dev_ready = rk_nfc_dev_ready; + nand->bbt_options = NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB; + nand->options = NAND_NO_SUBPAGE_WRITE | NAND_USE_BOUNCE_BUFFER; + + /* set default mode in case dt entry is missing */ + nand->ecc.mode = NAND_ECC_HW; + + nand->ecc.write_page = rk_nfc_hw_syndrome_ecc_write_page; + nand->ecc.write_oob = rk_nfc_hw_ecc_write_oob; + nand->ecc.read_page = rk_nfc_hw_syndrome_ecc_read_page; + nand->ecc.read_oob = rk_nfc_hw_ecc_read_oob; + + mtd = nand_to_mtd(nand); + mtd->dev.parent = dev; + mtd->name = "rk-nand"; + + ret = nand_scan(mtd, nsels); + if (ret) + return ret; + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + dev_err(dev, "failed to register mtd device: %d\n", ret); + nand_release(mtd); + return ret; + } + + list_add_tail(&chip->node, &nfc->chips); + + return 0; +} + +static int rk_nfc_chips_init(struct device *dev, struct rk_nfc *nfc) +{ + struct device_node *np = dev->of_node; + struct device_node *nand_np; + int nchips = of_get_child_count(np); + int ret; + + if (nchips > 8) { + dev_err(dev, "too many NAND chips: %d (max = 8)\n", nchips); + return -EINVAL; + } + + for_each_child_of_node(np, nand_np) { + ret = rk_nand_chip_init(dev, nfc, nand_np); + if (ret) { + of_node_put(nand_np); + return ret; + } + } + + return 0; +} + +static void rk_nand_chips_cleanup(struct rk_nfc *nfc) +{ + struct rk_nand_chip *chip; + + while (!list_empty(&nfc->chips)) { + chip = list_first_entry(&nfc->chips, struct rk_nand_chip, + node); + nand_release(nand_to_mtd(&chip->nand)); + list_del(&chip->node); + } +} + +static int rk_nfc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *r; + struct rk_nfc *nfc; + int irq; + int ret; + int clock_frequency; + + nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL); + if (!nfc) + return -ENOMEM; + + nfc->dev = dev; + nand_controller_init(&nfc->controller); + INIT_LIST_HEAD(&nfc->chips); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + nfc->regs = devm_ioremap_resource(dev, r); + if (IS_ERR(nfc->regs)) + return PTR_ERR(nfc->regs); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "failed to retrieve irq\n"); + return irq; + } + + nfc->hclk = devm_clk_get(dev, "hclk_nandc"); + if (IS_ERR(nfc->hclk)) { + dev_err(dev, "failed to retrieve hclk_nfc %p\n", nfc->hclk); + return PTR_ERR(nfc->hclk); + } + + ret = clk_prepare_enable(nfc->hclk); + if (ret) + return ret; + + nfc->clk = devm_clk_get(dev, "clk_nandc"); + if (IS_ERR(nfc->clk)) { + dev_err(dev, "failed to retrieve nfc clk\n"); + ret = PTR_ERR(nfc->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(nfc->clk, clock_frequency); + + ret = clk_prepare_enable(nfc->clk); + if (ret) + goto out_ahb_clk_unprepare; + nfc->clk_rate = clk_get_rate(nfc->clk); + + nfc->gclk = devm_clk_get(&pdev->dev, "g_clk_nandc"); + if (!(IS_ERR(nfc->gclk))) + clk_prepare_enable(nfc->gclk); + + rk_nfc_init(nfc); + + writel(0, nfc->regs + NANDC_REG_V6_INTEN); + ret = devm_request_irq(dev, irq, rk_nfc_interrupt, + 0, "rk-nand", nfc); + if (ret) + goto out_nfc_clk_unprepare; + + platform_set_drvdata(pdev, nfc); + + ret = rk_nfc_chips_init(dev, nfc); + if (ret) { + dev_err(dev, "failed to init nand chips\n"); + goto out_nfc_clk_unprepare; + } + return 0; + +out_nfc_clk_unprepare: + clk_disable_unprepare(nfc->clk); +out_ahb_clk_unprepare: + clk_disable_unprepare(nfc->hclk); + + return ret; +} + +static int rk_nfc_remove(struct platform_device *pdev) +{ + struct rk_nfc *nfc = platform_get_drvdata(pdev); + + rk_nand_chips_cleanup(nfc); + kfree(nfc->page_buf); + kfree(nfc->oob_buf); + clk_disable_unprepare(nfc->clk); + clk_disable_unprepare(nfc->hclk); + if (!(IS_ERR(nfc->gclk))) + clk_disable_unprepare(nfc->gclk); + + return 0; +} + +static const struct of_device_id rk_nfc_ids[] = { + {.compatible = "rockchip,rk-nandc"}, + {.compatible = "rockchip,rk-nandc-v6"}, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rk_nfc_ids); + +static struct platform_driver rk_nfc_driver = { + .driver = { + .name = "rk-nand", + .of_match_table = rk_nfc_ids, + }, + .probe = rk_nfc_probe, + .remove = rk_nfc_remove, +}; +module_platform_driver(rk_nfc_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yifeng Zhao "); +MODULE_DESCRIPTION("MTD NAND driver for Rockchip SoC");