[U-Boot] [U-Boot, v4, 10/19] mtd: nand: add the rockchip nand controller driver

Dr. Philipp Tomsich philipp.tomsich at theobroma-systems.com
Mon Aug 21 18:03:08 UTC 2017


> On 18 Aug 2017, at 20:14, Paweł Jarosz <paweljarosz3691 at gmail.com> wrote:
> 
> 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.

I feel strongly about this one, as this is a matter of maintainability
and readability (and looking at the sunxi NAND driver, I would have
similar concerns… although at least they don’t multiply-add banks
into addresses).

I really don’t see the point of living with code sequences like:
	void __iomem *bank_base = rknand->regs + NANDC_REG_V6_BANK0 + rknand->selected_bank * 0x100;
	 if (ctrl & NAND_ALE)
		bank_base += NANDC_REG_V6_ADDR;
	chip->IO_ADDR_W = bank_base;
when you in fact you want to write:
	chip->IO_ADDR_W = &regs->bank[selected_bank].addr;

The only reason for using the “base + OFFSET” addressing would be
if you tried to support two closely related controllers that only differed
in the offset of registers (in which it would actually read more like
“base + driver_data->reg_offsets[REGISTER_NAME]”… and even
then, I’d insist on hiding this in a wrapper around readl/writel. 

So for the sake of clarity, please change this to either
(a)	use a structure to reflect the register layout
(b)	add read/write accessor functions, so the address arithmetic is
	properly abstracted away.

> 
>>> +
>>> +#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.

I had to look up the FMWAIT register in one of the chip manuals to figure out
what exactly this is trying to do. And if I see this correctly, you are setting
CSRW, RWPW and RWCS timings at once.

Also: Why is this a safe value and are the reset-values of the controller
unsafe? You might want to leave at least a comment here as a starting
point for people who might need to touch this driver in the future.

> 
>> 
>>> +}
>>> +
>>> +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”))

I didn’t see documentation for the DTS binding?
This I just miss this?

>>> +                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