From patchwork Fri Aug 11 20:56:34 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Pawe=C5=82_Jarosz?= X-Patchwork-Id: 800749 X-Patchwork-Delegate: philipp.tomsich@theobroma-systems.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=lists.denx.de (client-ip=81.169.180.215; helo=lists.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="edYADEf/"; dkim-atps=neutral Received: from lists.denx.de (dione.denx.de [81.169.180.215]) by ozlabs.org (Postfix) with ESMTP id 3xTcqg56Mfz9sRq for ; Sat, 12 Aug 2017 07:01:19 +1000 (AEST) Received: by lists.denx.de (Postfix, from userid 105) id 5B5F1C21DDC; Fri, 11 Aug 2017 20:59:24 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on lists.denx.de X-Spam-Level: X-Spam-Status: No, score=-0.0 required=5.0 tests=FREEMAIL_FROM, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, T_DKIM_INVALID autolearn=unavailable autolearn_force=no version=3.4.0 Received: from lists.denx.de (localhost [IPv6:::1]) by lists.denx.de (Postfix) with ESMTP id 41DFFC21E26; Fri, 11 Aug 2017 20:57:13 +0000 (UTC) Received: by lists.denx.de (Postfix, from userid 105) id 856EFC21C2B; Fri, 11 Aug 2017 20:56:30 +0000 (UTC) Received: from mail-lf0-f67.google.com (mail-lf0-f67.google.com [209.85.215.67]) by lists.denx.de (Postfix) with ESMTPS id 8F6ADC21E14 for ; Fri, 11 Aug 2017 20:56:21 +0000 (UTC) Received: by mail-lf0-f67.google.com with SMTP id 65so2995299lfa.0 for ; Fri, 11 Aug 2017 13:56:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=date:from:to:cc:subject:message-id:references:mime-version :content-disposition:content-transfer-encoding:in-reply-to :user-agent; bh=XJPpb431TT7h9epnoAk1Va0GArdV3jWShSZmwi1qKWI=; b=edYADEf/IZfDSfP6Z6r/NxhBwtrqTFvzPc60/o3F/K9qhS8sr7MrJ0R8OXSvqleW51 z6Phc8poafSJRo7FSkGAGuXnQBMJoQ08HPhCT+nAwSmuqAzh81xxvLAaGYaayWNouqwR iKzI3RYU34RugifMDqv8TfzKgtomAHf1yCZJ8DL+UHf03Qke8T1UaX8em8bGg/G1GUO/ mqoxzrqvIqVCL2M5xLpO/99eQhmrqx+cnNZDjAGbRxHvTnDGy2JyHbPD6ZdI4u5N5+Dj 4uQknrM98UIFXztumC36+rIwIiY/G6EBAz8hr2sCTbneNrpMhJ7D2m/aK7WexnLUX8uY m5/A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:content-transfer-encoding :in-reply-to:user-agent; bh=XJPpb431TT7h9epnoAk1Va0GArdV3jWShSZmwi1qKWI=; b=DdQjW0smDHTDEr+9/CwAgk/OzyBkDpNOx6FNjFHC+el6O5QdAWKTS+ibh4tBsfmgfp ziXIBL8qOglty4z/gUaHK6DLc7Bcg9Z6RBJw0PwR5c7/hA4FNMPGUTUZEkgtPMRGswSR lmjYedBnbV1DrO73TgIWbeZLS7BY+yprDZuChXbs7LhYPsJMVNqCP8+FNSSWQH0PZeHf S9+pUDjOhVZGp1ar1fcW9diGuQcA4p+ws2ho5+2QkOF8Rh7qAVi/bDeQvK7q55+cP+GY TBrU+ErxMQZHbHPU8GO0fLaqLoe8RbjRCjdAHduyWXHspuOoy/oQPPvMY74cwAtsVYMv vyCQ== X-Gm-Message-State: AHYfb5h1PX/zot+EfqmkB78Qw1zdHTsGB1dCGPM2FJO+YcUEISQi23jT dtmNcEJ/3gPJDg== X-Received: by 10.46.78.25 with SMTP id c25mr5197511ljb.94.1502484980913; Fri, 11 Aug 2017 13:56:20 -0700 (PDT) Received: from vaio-ubuntu (admf2.neoplus.adsl.tpnet.pl. [79.185.35.2]) by smtp.gmail.com with ESMTPSA id k70sm291835lfe.73.2017.08.11.13.56.19 (version=TLS1 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 11 Aug 2017 13:56:20 -0700 (PDT) Date: Fri, 11 Aug 2017 22:56:34 +0200 From: =?utf-8?B?UGF3ZcWC?= Jarosz To: u-boot@lists.denx.de Message-ID: <7198c43de74cf91efaa4dfc5f94952d82b0fa862.1502483336.git.paweljarosz3691@gmail.com> References: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: User-Agent: Mutt/1.5.24 (2015-08-30) Subject: [U-Boot] [PATCH v2 10/19] mtd: nand: add the rockchip nand controller driver X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.18 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" Add basic Rockchip nand driver. Driver in current state has 16, 24, 40, 60 per 1024B BCH/ECC ability and 8 bit asynchronous flash interface support. Other features will come later. Signed-off-by: Paweł Jarosz --- Changes since v1: - none drivers/mtd/nand/Kconfig | 6 + drivers/mtd/nand/Makefile | 1 + drivers/mtd/nand/rockchip_nand.c | 660 +++++++++++++++++++++++++++++++++++++++ include/fdtdec.h | 1 + lib/fdtdec.c | 1 + 5 files changed, 669 insertions(+) create mode 100644 drivers/mtd/nand/rockchip_nand.c diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 3508c62..57a870d 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -66,6 +66,12 @@ config NAND_PXA3XX This enables the driver for the NAND flash device found on PXA3xx processors (NFCv1) and also on Armada 370/XP (NFCv2). +config NAND_ROCKCHIP + bool "Support for NAND on Rockchip SoCs" + select SYS_NAND_SELF_INIT + ---help--- + Enable support for Rockchip nand. + config NAND_SUNXI bool "Support for NAND on Allwinner SoCs" depends on MACH_SUN4I || MACH_SUN5I || MACH_SUN7I diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index c3d4a99..0659253 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -65,6 +65,7 @@ obj-$(CONFIG_NAND_OMAP_ELM) += omap_elm.o obj-$(CONFIG_NAND_PLAT) += nand_plat.o obj-$(CONFIG_NAND_SUNXI) += sunxi_nand.o obj-$(CONFIG_NAND_ZYNQ) += zynq_nand.o +obj-$(CONFIG_NAND_ROCKCHIP) += rockchip_nand.o else # minimal SPL drivers diff --git a/drivers/mtd/nand/rockchip_nand.c b/drivers/mtd/nand/rockchip_nand.c new file mode 100644 index 0000000..c997c43 --- /dev/null +++ b/drivers/mtd/nand/rockchip_nand.c @@ -0,0 +1,660 @@ +/* + * Copyright (c) 2017 Yifeng Zhao + * Copyright (c) 2017 Paweł Jarosz + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +#define NANDC_V6_BOOTROM_ECC 24 +#define NANDC_V6_NUM_BANKS 8 +#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_SRAM0 0x1000 +#define NANDC_REG_V6_SRAM_SIZE 0x400 + +#define NANDC_REG_V6_DATA 0x00 +#define NANDC_REG_V6_ADDR 0x04 +#define NANDC_REG_V6_CMD 0x08 + +/* FMCTL */ +#define NANDC_V6_FM_WP BIT(8) +#define NANDC_V6_FM_CE_SEL_M 0xFF +#define NANDC_V6_FM_CE_SEL(x) (1 << (x)) +#define NANDC_V6_FM_FREADY 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) + +struct rk_nand { + uint32_t banks[NANDC_V6_NUM_BANKS]; + struct nand_hw_control controller; + uint32_t ecc_strength; + struct mtd_info mtd; + bool bootromblocks; + void __iomem *regs; + int selected_bank; +}; + +static struct nand_ecclayout nand_oob_fix = { + .eccbytes = 24, + .eccpos = { + 4, 5, 6, 7, 8, 9, 10}, + .oobfree = { + {.offset = 0, + .length = 4} } +}; + +static inline struct rk_nand *to_rknand(struct nand_hw_control *ctrl) +{ + return container_of(ctrl, struct rk_nand, controller); +} + +static void rockchip_nand_init(struct rk_nand *rknand) +{ + writel(0, rknand->regs + NANDC_REG_V6_RANDMZ); + writel(0, rknand->regs + NANDC_REG_V6_DMA_CFG); + writel(0, rknand->regs + NANDC_REG_V6_BCHCTL); + writel(NANDC_V6_FM_WP, rknand->regs + NANDC_REG_V6_FMCTL); + writel(0x1081, rknand->regs + NANDC_REG_V6_FMWAIT); +} + +static void rockchip_nand_select_chip(struct mtd_info *mtd, int chipnr) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct rk_nand *rknand = to_rknand(chip->controller); + void __iomem *bank_base; + uint32_t reg; + int banknr; + + reg = readl(rknand->regs + NANDC_REG_V6_FMCTL); + reg &= ~NANDC_V6_FM_CE_SEL_M; + + if (chipnr == -1) { + banknr = -1; + } else { + banknr = rknand->banks[chipnr]; + bank_base = rknand->regs + NANDC_REG_V6_BANK0 + banknr * 0x100; + + chip->IO_ADDR_R = bank_base; + chip->IO_ADDR_W = bank_base; + + reg |= 1 << banknr; + } + writel(reg, rknand->regs + NANDC_REG_V6_FMCTL); + + rknand->selected_bank = banknr; +} + +static void rockchip_nand_cmd_ctrl(struct mtd_info *mtd, + int dat, + unsigned int ctrl) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct rk_nand *rknand = to_rknand(chip->controller); + void __iomem *bank_base = rknand->regs + NANDC_REG_V6_BANK0 + + rknand->selected_bank * 0x100; + + if (ctrl & NAND_CTRL_CHANGE) { + 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; + } + + if (dat != NAND_CMD_NONE) + writeb(dat & 0xFF, chip->IO_ADDR_W); +} + +static void rockchip_nand_read_buf(struct mtd_info *mtd, + uint8_t *buf, + int len) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct rk_nand *rknand = to_rknand(chip->controller); + int offs = 0; + void __iomem *bank_base = rknand->regs + NANDC_REG_V6_BANK0 + + rknand->selected_bank * 0x100; + + for (offs = 0; offs < len; offs++) + buf[offs] = readb(bank_base); +} + +static void rockchip_nand_write_buf(struct mtd_info *mtd, + const uint8_t *buf, + int len) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct rk_nand *rknand = to_rknand(chip->controller); + int offs = 0; + void __iomem *bank_base = rknand->regs + NANDC_REG_V6_BANK0 + + rknand->selected_bank * 0x100; + + for (offs = 0; offs < len; offs++) + writeb(buf[offs], bank_base); +} + +static uint8_t rockchip_nand_read_byte(struct mtd_info *mtd) +{ + uint8_t ret; + + rockchip_nand_read_buf(mtd, &ret, 1); + + return ret; +} + +static int rockchip_nand_dev_ready(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct rk_nand *rknand = to_rknand(chip->controller); + + if (readl(rknand->regs + NANDC_REG_V6_FMCTL) & NANDC_V6_FM_FREADY) + return 1; + + return 0; +} + +static int rockchip_nand_hw_ecc_setup(struct mtd_info *mtd, + struct nand_ecc_ctrl *ecc, + uint32_t strength) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct rk_nand *rknand = to_rknand(chip->controller); + u32 reg; + + ecc->strength = strength; + ecc->bytes = DIV_ROUND_UP(ecc->strength * 14, 8); + 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, rknand->regs + NANDC_REG_V6_BCHCTL); + + return 0; +} + +static void rockchip_nand_pio_xfer_start(struct rk_nand *rknand, + u8 dir, + u8 st_buf) +{ + u32 reg; + + reg = readl(rknand->regs + NANDC_REG_V6_BCHCTL); + reg = (reg & (~(NAND_V6_BCH_REGION_M << NAND_V6_BCH_REGION_S))) | + (rknand->selected_bank << NAND_V6_BCH_REGION_S); + writel(reg, rknand->regs + NANDC_REG_V6_BCHCTL); + + reg = (dir << NANDC_V6_FL_DIR_S) | (st_buf << NANDC_V6_FL_ST_BUF_S) | + NANDC_V6_FL_XFER_EN | NANDC_V6_FL_XFER_COUNT | + NANDC_V6_FL_ACORRECT; + writel(reg, rknand->regs + NANDC_REG_V6_FLCTL); + + reg |= NANDC_V6_FL_XFER_START; + writel(reg, rknand->regs + NANDC_REG_V6_FLCTL); +} + +static int rockchip_nand_wait_pio_xfer_done(struct rk_nand *rknand) +{ + int timeout = NANDC_V6_DEF_TIMEOUT; + int reg; + + while (timeout--) { + reg = readl(rknand->regs + NANDC_REG_V6_FLCTL); + + if ((reg & NANDC_V6_FL_XFER_READY) != 0) + break; + + udelay(1); + } + + if (timeout == 0) + return -1; + + return 0; +} + +static void rockchip_nand_read_extra_oob(struct mtd_info *mtd, u8 *oob) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct nand_ecc_ctrl *ecc = &chip->ecc; + int offset = ((ecc->bytes + ecc->prepad) * ecc->steps); + int len = mtd->oobsize - offset; + + if (len <= 0) + return; + + chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset + mtd->writesize, -1); + + rockchip_nand_read_buf(mtd, oob + offset, len); +} + +static void rockchip_nand_write_extra_oob(struct mtd_info *mtd, u8 *oob) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct nand_ecc_ctrl *ecc = &chip->ecc; + int offset = ((ecc->bytes + ecc->prepad) * ecc->steps); + int len = mtd->oobsize - offset; + + if (len <= 0) + return; + + chip->cmdfunc(mtd, NAND_CMD_RNDIN, offset + mtd->writesize, -1); + + rockchip_nand_write_buf(mtd, oob + offset, len); +} + + +static int rockchip_nand_hw_syndrome_pio_read_page(struct mtd_info *mtd, + struct nand_chip *chip, + uint8_t *buf, + int oob_required, + int page) +{ + struct rk_nand *rknand = to_rknand(chip->controller); + struct nand_ecc_ctrl *ecc = &chip->ecc; + void __iomem *sram_base = rknand->regs + NANDC_REG_V6_SRAM0; + unsigned int max_bitflips = 0; + int ret, step, bch_st; + int offset = page * mtd->writesize; + + if (rknand->bootromblocks && (offset < (7 * mtd->erasesize))) + rockchip_nand_hw_ecc_setup(mtd, ecc, NANDC_V6_BOOTROM_ECC); + + rockchip_nand_pio_xfer_start(rknand, NANDC_V6_READ, 0); + + for (step = 0; step < ecc->steps; step++) { + int data_off = step * ecc->size; + int oob_off = step * (ecc->bytes + ecc->prepad); + u8 *data = buf + data_off; + u8 *oob = chip->oob_poi + oob_off; + + ret = rockchip_nand_wait_pio_xfer_done(rknand); + if (ret) + return ret; + + bch_st = readl(rknand->regs + NANDC_REG_V6_BCHST); + + if (bch_st & NANDC_V6_BCH0_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); + } + + if ((step + 1) < ecc->steps) + rockchip_nand_pio_xfer_start(rknand, NANDC_V6_READ, + (step + 1) & 0x1); + + memcpy_fromio(data, sram_base + NANDC_REG_V6_SRAM_SIZE * + (step & 1), ecc->size); + + if (step & 1) + memcpy_fromio(oob, rknand->regs + NANDC_REG_V6_SPARE1, 4); + else + memcpy_fromio(oob, rknand->regs + NANDC_REG_V6_SPARE0, 4); + } + + rockchip_nand_read_extra_oob(mtd, chip->oob_poi); + + if (rknand->bootromblocks) + rockchip_nand_hw_ecc_setup(mtd, ecc, rknand->ecc_strength); + + return max_bitflips; +} + +static uint32_t rockchip_nand_make_bootrom_compat(struct mtd_info *mtd, + int page, + const u8 *oob, + bool bootromblocks) +{ + int pages_per_block = mtd->erasesize / mtd->writesize; + int offset = page * mtd->writesize; + + if ((offset < (2 * mtd->erasesize)) || !(page % 2) || + (offset >= (7 * mtd->erasesize)) || !bootromblocks) + return oob[3] | (oob[2] << 8) | (oob[1] << 16) | (oob[0] << 24); + + return (page % pages_per_block + 1) * 4; +} + +static int rockchip_nand_hw_syndrome_pio_write_page(struct mtd_info *mtd, + struct nand_chip *chip, + const uint8_t *buf, + int oob_required, + int page) +{ + struct rk_nand *rknand = to_rknand(chip->controller); + struct nand_ecc_ctrl *ecc = &chip->ecc; + void __iomem *sram_base = rknand->regs + NANDC_REG_V6_SRAM0; + int ret, index, step = 0; + int offset = page * mtd->writesize; + int data_off = step * ecc->size; + int oob_off = step * (ecc->bytes + ecc->prepad); + const u8 *data = buf + data_off; + const u8 *oob = chip->oob_poi + oob_off; + + if (rknand->bootromblocks && (offset < (7 * mtd->erasesize))) + rockchip_nand_hw_ecc_setup(mtd, ecc, NANDC_V6_BOOTROM_ECC); + + index = rockchip_nand_make_bootrom_compat(mtd, page, oob, + rknand->bootromblocks); + + memcpy_toio(sram_base, data, ecc->size); + memcpy_toio(rknand->regs + NANDC_REG_V6_SPARE0, &index, ecc->prepad); + + for (step = 1; step <= ecc->steps; step++) { + rockchip_nand_pio_xfer_start(rknand, NANDC_V6_WRITE, + (step - 1) & 0x1); + + data_off = step * ecc->size; + oob_off = step * (ecc->bytes + ecc->prepad); + data = buf + data_off; + oob = chip->oob_poi + oob_off; + + if (step < ecc->steps) { + memcpy_toio(sram_base + NANDC_REG_V6_SRAM_SIZE * + (step & 1), data, ecc->size); + if (step & 1) + memcpy_toio(rknand->regs + NANDC_REG_V6_SPARE1, + oob, ecc->prepad); + else + memcpy_toio(rknand->regs + NANDC_REG_V6_SPARE0, + oob, ecc->prepad); + } + + ret = rockchip_nand_wait_pio_xfer_done(rknand); + if (ret) + return ret; + } + + rockchip_nand_write_extra_oob(mtd, chip->oob_poi); + + rockchip_nand_hw_ecc_setup(mtd, ecc, rknand->ecc_strength); + + return 0; +} + +static const u8 strengths[] = {60, 40, 24, 16}; + +static int rockchip_nand_ecc_max_strength(struct mtd_info *mtd, + struct nand_ecc_ctrl *ecc) +{ + uint32_t max_strength, index; + + max_strength = ((mtd->oobsize / ecc->steps) - ecc->prepad) * 8 / 14; + + for (index = 0; index < ARRAY_SIZE(strengths); index++) + if (max_strength >= strengths[index]) + break; + + if (index >= ARRAY_SIZE(strengths)) + return -ENOTSUPP; + + return strengths[index]; +} + +static bool rockchip_nand_strength_is_valid(int strength) +{ + uint32_t index; + + for (index = 0; index < ARRAY_SIZE(strengths); index++) + if (strength >= strengths[index]) + break; + + if (index >= ARRAY_SIZE(strengths)) + return false; + + return true; +} + +static int rockchip_nand_hw_ecc_ctrl_init(struct mtd_info *mtd, + struct nand_ecc_ctrl *ecc) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct rk_nand *rknand = to_rknand(chip->controller); + uint32_t strength; + int index; + + ecc->prepad = 4; + ecc->steps = mtd->writesize / ecc->size; + + if (fdtdec_get_bool(gd->fdt_blob, chip->flash_node, + "rockchip,protect-bootrom-blocks")) + rknand->bootromblocks = true; + else + rknand->bootromblocks = false; + + if (rockchip_nand_strength_is_valid(ecc->strength)) + strength = ecc->strength; + else + strength = rockchip_nand_ecc_max_strength(mtd, ecc); + + rockchip_nand_hw_ecc_setup(mtd, ecc, strength); + + rknand->ecc_strength = ecc->strength; + + nand_oob_fix.eccbytes = ecc->bytes * ecc->steps; + for (index = 0; index < ecc->bytes; index++) + nand_oob_fix.eccpos[index] = index + ecc->prepad; + ecc->layout = &nand_oob_fix; + + if (mtd->oobsize < ((ecc->bytes + ecc->prepad) * ecc->steps)) { + return -EINVAL; + } + + return 0; +} + +static int rockchip_nand_ecc_init(struct mtd_info *mtd, + struct nand_ecc_ctrl *ecc) +{ + int ret; + + switch (ecc->mode) { + case NAND_ECC_HW_SYNDROME: + ret = rockchip_nand_hw_ecc_ctrl_init(mtd, ecc); + if (ret) + return ret; + ecc->read_page = rockchip_nand_hw_syndrome_pio_read_page; + ecc->write_page = rockchip_nand_hw_syndrome_pio_write_page; + break; + case NAND_ECC_SOFT_BCH: + case NAND_ECC_NONE: + case NAND_ECC_SOFT: + break; + default: + return -EINVAL; + } + + return 0; +} + +static int rockchip_nand_chip_init(int node, struct rk_nand *rknand, int devnum) +{ + const void *blob = gd->fdt_blob; + struct nand_chip *chip; + struct mtd_info *mtd; + int ret; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + + chip->chip_delay = 50; + chip->flash_node = node; + chip->select_chip = rockchip_nand_select_chip; + chip->cmd_ctrl = rockchip_nand_cmd_ctrl; + chip->read_buf = rockchip_nand_read_buf; + chip->write_buf = rockchip_nand_write_buf; + chip->read_byte = rockchip_nand_read_byte; + chip->dev_ready = rockchip_nand_dev_ready; + chip->controller = &rknand->controller; + + rknand->banks[devnum] = fdtdec_get_int(blob, node, "reg", -1); + + if (rknand->banks[devnum] < 0) + return -EINVAL; + + mtd = nand_to_mtd(chip); + mtd->name = "rknand"; + + ret = nand_scan_ident(mtd, 1, NULL); + if (ret) + return ret; + + ret = rockchip_nand_ecc_init(mtd, &chip->ecc); + if (ret) { + debug("rockchip_nand_ecc_init failed: %d\n", ret); + return ret; + } + + ret = nand_scan_tail(mtd); + if (ret) { + debug("nand_scan_tail failed: %d\n", ret); + return ret; + } + + ret = nand_register(devnum, mtd); + if (ret) { + debug("Failed to register mtd device: %d\n", ret); + return ret; + } + + return 0; +} + +static int rockchip_nand_chips_init(int node, struct rk_nand *rknand) +{ + const void *blob = gd->fdt_blob; + int nand_node; + int ret, i = 0; + + for (nand_node = fdt_first_subnode(blob, node); nand_node >= 0; + nand_node = fdt_next_subnode(blob, nand_node)) { + ret = rockchip_nand_chip_init(nand_node, rknand, i++); + if (ret) + return ret; + } + + return 0; +} + +void board_nand_init(void) +{ + const void *blob = gd->fdt_blob; + struct rk_nand *rknand; + fdt_addr_t regs; + int node; + int ret; + + rknand = kzalloc(sizeof(*rknand), GFP_KERNEL); + + node = fdtdec_next_compatible(blob, 0, COMPAT_ROCKCHIP_NANDC); + + if (node < 0) { + debug("Nand node not found\n"); + goto err; + } + + if (!fdtdec_get_is_enabled(blob, node)) { + debug("Nand disabled in device tree\n"); + goto err; + } + + regs = fdtdec_get_addr(blob, node, "reg"); + if (regs == FDT_ADDR_T_NONE) { + debug("Nand address not found\n"); + goto err; + } + + rknand->regs = (void *)regs; + + spin_lock_init(&rknand->controller.lock); + init_waitqueue_head(&rknand->controller.wq); + + rockchip_nand_init(rknand); + + ret = rockchip_nand_chips_init(node, rknand); + if (ret) { + debug("Failed to init nand chips\n"); + goto err; + } + + return; +err: + kfree(rknand); +} + +int nand_spl_load_image(uint32_t offs, unsigned int size, void *dst) +{ + struct mtd_info *mtd; + + mtd = get_nand_dev_by_index(0); + return nand_read_skip_bad(mtd, offs, &size, NULL, size, (u_char *)dst); +} + +void nand_deselect(void) {} diff --git a/include/fdtdec.h b/include/fdtdec.h index 4a0947c..0e68788 100644 --- a/include/fdtdec.h +++ b/include/fdtdec.h @@ -157,6 +157,7 @@ enum fdt_compat_id { COMPAT_ALTERA_SOCFPGA_F2SDR0, /* SoCFPGA fpga2SDRAM0 bridge */ COMPAT_ALTERA_SOCFPGA_F2SDR1, /* SoCFPGA fpga2SDRAM1 bridge */ COMPAT_ALTERA_SOCFPGA_F2SDR2, /* SoCFPGA fpga2SDRAM2 bridge */ + COMPAT_ROCKCHIP_NANDC, /* Rockchip NAND controller */ COMPAT_COUNT, }; diff --git a/lib/fdtdec.c b/lib/fdtdec.c index d2dbd0f..6e814cf 100644 --- a/lib/fdtdec.c +++ b/lib/fdtdec.c @@ -70,6 +70,7 @@ static const char * const compat_names[COMPAT_COUNT] = { COMPAT(ALTERA_SOCFPGA_F2SDR0, "altr,socfpga-fpga2sdram0-bridge"), COMPAT(ALTERA_SOCFPGA_F2SDR1, "altr,socfpga-fpga2sdram1-bridge"), COMPAT(ALTERA_SOCFPGA_F2SDR2, "altr,socfpga-fpga2sdram2-bridge"), + COMPAT(ROCKCHIP_NANDC, "rockchip,nandc"), }; const char *fdtdec_get_compatible(enum fdt_compat_id id)