[U-Boot] [U-Boot, v4, 10/19] mtd: nand: add the rockchip nand controller driver
Paweł Jarosz
paweljarosz3691 at gmail.com
Fri Aug 18 18:14:38 UTC 2017
Hi,
W dniu 18.08.2017 o 14:45, Philipp Tomsich pisze:
>
>
> On Thu, 17 Aug 2017, Paweł Jarosz wrote:
>
>> 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 <paweljarosz3691 at gmail.com>
>> ---
>> Changes since v1:
>> - none
>>
>> Changes since v2:
>> - fixed correct ecc checking
>>
>> Changes since v3:
>> - 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 71d678f..c308497 100644
>> --- a/drivers/mtd/nand/Kconfig
>> +++ b/drivers/mtd/nand/Kconfig
>> @@ -69,6 +69,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..d8f4439
>> --- /dev/null
>> +++ b/drivers/mtd/nand/rockchip_nand.c
>> @@ -0,0 +1,660 @@
>> +/*
>> + * Copyright (c) 2017 Yifeng Zhao <yifeng.zhao at rock-chips.com>
>> + * Copyright (c) 2017 Paweł Jarosz <paweljarosz3691 at gmail.com>
>> + *
>> + * SPDX-License-Identifier: GPL-2.0+
>> + */
>> +
>> +#include <common.h>
>> +#include <fdtdec.h>
>> +#include <inttypes.h>
>> +#include <nand.h>
>> +#include <linux/kernel.h>
>> +#include <linux/mtd/mtd.h>
>> +#include <linux/mtd/nand.h>
>> +#include <linux/mtd/partitions.h>
>> +#include <linux/io.h>
>> +
>> +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
>
> For the Rockchip drivers we have usually used structures to represent
> the layout. I know that this will be very sparse for this particular
> case, but it seems to be worth this for consistency.
>
If it's not a problem i would leave this as is. The reason for that is
rockchip kernel nand driver (currently in backing stage) as it would
be easier to maintain both of them and add new features.
At least one driver in u-boot (sunxi nand) is doing the same thing.
>> +
>> +#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))
>
> BIT(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)
>
> This might benefit from using functions from bitfield.h?
>
>> +
>> +struct rk_nand {
>> + uint32_t banks[NANDC_V6_NUM_BANKS];
>
> Is the number of banks fixed or should this be dynamically allocated
> based
> on a device-tree property (or driver-data, if it is dependent on how
> the controller was synthesized)?
>
This should be dynamically allocated and i will change this in the next
version.
>> + 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} }
>
> The indentation looks odd on those.
>
>> +};
>> +
>> +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);
>
> Why the 0x1081?
0x1081 is the default (and safe) async timing register value set by old
rockchip loaders.
Currently nand driver doesn't support setting timings. This will change
in the feature as it is on my TODO list for this driver.
>
>> +}
>> +
>> +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;
>
> Once converted to a structure, this could be as simple to read as
> bank_base = &rknand->bank[banknr];
>
>> +
>> + chip->IO_ADDR_R = bank_base;
>> + chip->IO_ADDR_W = bank_base;
>> +
>> + reg |= 1 << banknr;
>> + }
>> + writel(reg, rknand->regs + NANDC_REG_V6_FMCTL);
>
> Why not a clrsetbits?
>
>> +
>> + 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;
>
> See above.
>
>> +
>> + if (ctrl & NAND_CTRL_CHANGE) {
>> + if (ctrl & NAND_ALE)
>> + bank_base += NANDC_REG_V6_ADDR;
>
> This is not the bank_base you are modifying.
> So this really is
> if (ctrl & NAND_ALE)
> chip->IO_ADDR_W = &rknand->bank[i].addr;
> else if (ctrl & NAND_CLE)
> chip->IO_ADDR_W = &rknand->bank[i].cmd;
> else
> chip->IO_ADDR_W = &rknand->bank[i];
>
Correct. Banks have three separate registers for io, addr cycle, cmd cycle.
When writing to addr: Nand pin ALE is being set to high.
To cmd: Nand pin CLE is being set to high. IO is for data.
Offsets from bank base are:
#define NANDC_REG_V6_DATA 0x00
#define NANDC_REG_V6_ADDR 0x04
#define NANDC_REG_V6_CMD 0x08
>> + 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);
>> +}
>
> See above.
>
>> +
>> +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;
>> + }
>
> Could you use symbolic constants?
I will change this in the next version.
>
>> + 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);
>
> clrsetbits?
>
>> +
>> + 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);
>
> setbits?
>
>> +}
>> +
>> +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 107a892..4a8a8d7 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)
>>
Regards,
Paweł
More information about the U-Boot
mailing list