[PATCH v5 2/3] mtd: nand: Add support for Arasan Nand Flash Controller
Boris Brezillon
boris.brezillon at free-electrons.com
Mon Dec 5 01:01:20 PST 2016
+Marek who reviewed v6
Hi Punnaiah,
I see you sent a v6, but you never answered the questions/remarks I had
in this email.
Can you please try to answer them (I'd like to understand how the
controller works)?
Thanks,
Boris
On Wed, 9 Mar 2016 10:50:07 +0100
Boris Brezillon <boris.brezillon at free-electrons.com> wrote:
> Hi Punnaiah,
>
> On Wed, 9 Mar 2016 00:10:52 +0530
> punnaiah choudary kalluri <punnaia at xilinx.com> wrote:
>
>
> > >> +static const struct anfc_ecc_matrix ecc_matrix[] = {
> > >> + {512, 512, 1, 0, 0x3},
> > >> + {512, 512, 4, 1, 0x7},
> > >> + {512, 512, 8, 1, 0xD},
> > >> + /* 2K byte page */
> > >> + {2048, 512, 1, 0, 0xC},
> > >> + {2048, 512, 4, 1, 0x1A},
> > >> + {2048, 512, 8, 1, 0x34},
> > >> + {2048, 512, 12, 1, 0x4E},
> > >> + {2048, 1024, 24, 1, 0x54},
> > >> + /* 4K byte page */
> > >> + {4096, 512, 1, 0, 0x18},
> > >> + {4096, 512, 4, 1, 0x34},
> > >> + {4096, 512, 8, 1, 0x68},
> > >> + {4096, 512, 12, 1, 0x9C},
> > >> + {4096, 1024, 4, 1, 0xA8},
> > >> + /* 8K byte page */
> > >> + {8192, 512, 1, 0, 0x30},
> > >> + {8192, 512, 4, 1, 0x68},
> > >> + {8192, 512, 8, 1, 0xD0},
> > >> + {8192, 512, 12, 1, 0x138},
> > >> + {8192, 1024, 24, 1, 0x150},
> > >> + /* 16K byte page */
> > >> + {16384, 512, 1, 0, 0x60},
> > >> + {16384, 512, 4, 1, 0xD0},
> > >> + {16384, 512, 8, 1, 0x1A0},
> > >> + {16384, 512, 12, 1, 0x270},
> > >> + {16384, 1024, 24, 1, 0x2A0}
> > >> +};
> > >
> > > Do you really need this association table. IMO, those are
> > > information you could deduce from nand_chip info (ecc_strength,
> > > ecc_step_size, ...). eccsize can be calculated this way:
> > >
> > > info->pagesize = mtd->writesize;
> > > info->codeword_size = chip->ecc_step_ds;
> > > info->eccbits = chip->ecc_strength_ds;
> > >
> > > if (info->eccbits > 1)
> > > info->bch = true;
> > >
> > > steps = info->pagesize / info->codeword_size;
> > >
> > > if (!bch)
> > > info->eccsize = 3 * steps;
> > > else
> > > info->eccsize =
> > > DIV_ROUND_UP(fls(8 * info->codeword_size) *
> > > ecc->strength * steps, 8);
> > >
> > > And, too bad we have to deal with another engine operating at the bit
> > > level instead of aligning each ECC step to the next byte (GPMI engine
> > > does that too, and it's a pain to deal with).
> > >
> >
> > 1. There is no direct formula for calculating the ecc size. For bch
> > ecc algorithms,
> > the ecc size can vary based on the chosen polynomial.
>
> I checked for all the table entries, and this formula works (note that
> it takes the codeword_size into account, which is probably what you
> are talking about when mentioning the different polynomials).
>
> >
> > 2. This controller supports only the page sizes, number of ecc bits
> > and code word size
> > defined in the table. driver returns error if there is no match for
> > the page size and codeword size.
>
> I agree for the pagesize, ECC step_size and ECC strength limitations,
> but that's something you can check without having this association
> table. And from my experience, keeping such tables to describe all the
> possible associations is not a good idea :-/.
>
> [...]
>
> > >> +static int anfc_read_page_hwecc(struct mtd_info *mtd,
> > >> + struct nand_chip *chip, uint8_t *buf,
> > >> + int oob_required, int page)
> > >> +{
> > >> + u32 val;
> > >> + struct anfc *nfc = to_anfc(mtd);
> > >> +
> > >> + anfc_set_eccsparecmd(nfc, NAND_CMD_RNDOUT, NAND_CMD_RNDOUTSTART);
> > >> +
> > >> + val = readl(nfc->base + CMD_OFST);
> > >> + val = val | ECC_ENABLE;
> > >> + writel(val, nfc->base + CMD_OFST);
> > >> +
> > >> + if (nfc->dma)
> > >> + nfc->rdintrmask = XFER_COMPLETE;
> > >> + else
> > >> + nfc->rdintrmask = READ_READY;
> > >> +
> > >> + if (!nfc->bch)
> > >> + nfc->rdintrmask = MBIT_ERROR;
> > >> +
> > >> + chip->read_buf(mtd, buf, mtd->writesize);
> > >> +
> > >> + val = readl(nfc->base + ECC_ERR_CNT_OFST);
> > >> + if (nfc->bch) {
> > >> + mtd->ecc_stats.corrected += val & PAGE_ERR_CNT_MASK;
> > >> + } else {
> > >> + val = readl(nfc->base + ECC_ERR_CNT_1BIT_OFST);
> > >> + mtd->ecc_stats.corrected += val;
> > >> + val = readl(nfc->base + ECC_ERR_CNT_2BIT_OFST);
> > >> + mtd->ecc_stats.failed += val;
> > >> + /* Clear ecc error count register 1Bit, 2Bit */
> > >> + writel(0x0, nfc->base + ECC_ERR_CNT_1BIT_OFST);
> > >> + writel(0x0, nfc->base + ECC_ERR_CNT_2BIT_OFST);
> > >> + }
> > >
> > > Not sure if your controller already handles the 'bitflips in erased
> > > pages' case, but you might want to have a look at the
> > > nand_check_erased_ecc_chunk() [4] function if that's not the case.
> > >
> >
> > it will not handle the bitflips in erased pages. I will check this one.
> >
> > >> + nfc->err = false;
> > >> +
> > >> + if (oob_required)
> > >> + chip->ecc.read_oob(mtd, chip, page);
> > >
> > > You should disable the ECC engine before leaving the function.
> > >
> >
> > Not needed. Because ecc state need to be configured for every nand command.
> > so, no need to disable explicitly.
>
> Except, you don't know what the next NAND command will be, and if it's
> a raw access ECC_ENABLE bit has to be cleared.
> Also, you don't know when the NAND command is sent, which type of read
> will take place, so putting that in ->cmdfunc() is not a solution.
>
> >
> > >> +
> > >> + return 0;
> > >> +}
> > >> +
>
> [...]
>
> > >> +
> > >> +static u8 anfc_read_byte(struct mtd_info *mtd)
> > >> +{
> > >> + struct anfc *nfc = to_anfc(mtd);
> > >> +
> > >> + return nfc->buf[nfc->bufshift++];
> > >
> > > ->read_byte() should normally be a wrapper around ->read_buf(), because
> > > you don't know ahead of time, how many time ->read_byte() will be
> > > called after the CMD/ADDR cycles, and thus cannot deduce how much
> > > should be put in the temporary buffer.
> > > So I'd suggest you put the logic to fill nfc->buf[] directly in there
> > > instead of putting it in ->cmdfunc().
> > >
> >
> > Controller doesn't support dma for readid, parameter page commands
> > and the response for these commands shall be read from the fifo which is
> > of 32 bit width.
>
> Okay, but I was not referring to DMA here. If ->read_buf() always
> implies using DMA, then maybe you should rework it to make DMA
> operations optional, and only activate them when ->read_page() is used.
>
> >
> > Also for status command, the response shall be read from the
> > controller register.
> >
> > Arasan controller will not allow byte reads. So, i have opted this
> > implemetation.
> >
> > In either case, number of bytes to be read will not ne known. For now,
> > it estimates
> > the number of bytes to be read based on the command that is issued and
> > resets the
> > buffer shift counter if it see a new command request.
>
> Which is not a good idea. As I said, you can't guess how many bytes the
> framework will read after a specific command (CCed Ezequiel, who,
> IIRC, had this kind of problems with the pxa3xx driver).
>
> >
> > Also its not a generic controller, supports only the commands defined in the
> > program register.
> >
>
> Hm, are you sure there's no way to send raw commands, or CMD and ADDR
> cycles independently?
> AFAICS, you're still configuring the ADDR and CMD cycles manually (in
> anfc_prepare_cmd()), which seems pretty generic to me...
>
> >
> >
> > >> +}
>
> [...]
>
> > >> +static void anfc_cmd_function(struct mtd_info *mtd,
> > >> + unsigned int cmd, int column, int page_addr)
> > >> +{
> > >> + struct anfc *nfc = to_anfc(mtd);
> > >> + bool wait = false, read = false;
> > >> + u32 addrcycles, prog;
> > >> + u32 *bufptr = (u32 *)nfc->buf;
> > >> +
> > >> + nfc->bufshift = 0;
> > >> + nfc->curr_cmd = cmd;
> > >> +
> > >> + if (page_addr == -1)
> > >> + page_addr = 0;
> > >> + if (column == -1)
> > >> + column = 0;
> > >> +
> > >> + switch (cmd) {
> > >> + case NAND_CMD_RESET:
> > >> + anfc_prepare_cmd(nfc, cmd, 0, 0, 0, 0);
> > >> + prog = PROG_RST;
> > >> + wait = true;
> > >> + break;
> > >> + case NAND_CMD_SEQIN:
> > >> + addrcycles = nfc->raddr_cycles + nfc->caddr_cycles;
> > >> + anfc_prepare_cmd(nfc, cmd, NAND_CMD_PAGEPROG, 1,
> > >> + mtd->writesize, addrcycles);
> > >> + anfc_setpagecoladdr(nfc, page_addr, column);
> > >> + break;
> > >> + case NAND_CMD_READOOB:
> > >> + column += mtd->writesize;
> > >> + case NAND_CMD_READ0:
> > >> + case NAND_CMD_READ1:
> > >> + addrcycles = nfc->raddr_cycles + nfc->caddr_cycles;
> > >> + anfc_prepare_cmd(nfc, NAND_CMD_READ0, NAND_CMD_READSTART, 1,
> > >> + mtd->writesize, addrcycles);
> > >> + anfc_setpagecoladdr(nfc, page_addr, column);
> > >> + break;
> > >> + case NAND_CMD_RNDOUT:
> > >> + anfc_prepare_cmd(nfc, cmd, NAND_CMD_RNDOUTSTART, 1,
> > >> + mtd->writesize, 2);
> > >> + anfc_setpagecoladdr(nfc, page_addr, column);
> > >> + if (nfc->dma)
> > >> + nfc->rdintrmask = XFER_COMPLETE;
> > >> + else
> > >> + nfc->rdintrmask = READ_READY;
> > >> + break;
> > >> + case NAND_CMD_PARAM:
> > >> + anfc_prepare_cmd(nfc, cmd, 0, 0, 0, 1);
> > >> + anfc_setpagecoladdr(nfc, page_addr, column);
> > >> + anfc_setpktszcnt(nfc, sizeof(struct nand_onfi_params), 1);
> > >> + anfc_readfifo(nfc, PROG_RDPARAM,
> > >> + sizeof(struct nand_onfi_params));
> > >> + break;
> > >> + case NAND_CMD_READID:
> > >> + anfc_prepare_cmd(nfc, cmd, 0, 0, 0, 1);
> > >> + anfc_setpagecoladdr(nfc, page_addr, column);
> > >> + anfc_setpktszcnt(nfc, ONFI_ID_LEN, 1);
> > >> + anfc_readfifo(nfc, PROG_RDID, ONFI_ID_LEN);
> > >> + break;
> > >> + case NAND_CMD_ERASE1:
> > >> + addrcycles = nfc->raddr_cycles;
> > >> + prog = PROG_ERASE;
> > >> + anfc_prepare_cmd(nfc, cmd, NAND_CMD_ERASE2, 0, 0, addrcycles);
> > >> + column = page_addr & 0xffff;
> > >> + page_addr = (page_addr >> PG_ADDR_SHIFT) & 0xffff;
> > >> + anfc_setpagecoladdr(nfc, page_addr, column);
> > >> + wait = true;
> > >> + break;
> > >> + case NAND_CMD_STATUS:
> > >> + anfc_prepare_cmd(nfc, cmd, 0, 0, 0, 0);
> > >> + anfc_setpktszcnt(nfc, nfc->spktsize/4, 1);
> > >> + anfc_setpagecoladdr(nfc, page_addr, column);
> > >> + prog = PROG_STATUS;
> > >> + wait = read = true;
> > >> + break;
> > >> + case NAND_CMD_GET_FEATURES:
> > >> + anfc_prepare_cmd(nfc, cmd, 0, 0, 0, 1);
> > >> + anfc_setpagecoladdr(nfc, page_addr, column);
> > >> + anfc_setpktszcnt(nfc, nfc->spktsize, 1);
> > >> + anfc_readfifo(nfc, PROG_GET_FEATURE, 4);
> > >> + break;
> > >> + case NAND_CMD_SET_FEATURES:
> > >> + anfc_prepare_cmd(nfc, cmd, 0, 0, 0, 1);
> > >> + anfc_setpagecoladdr(nfc, page_addr, column);
> > >> + anfc_setpktszcnt(nfc, nfc->spktsize, 1);
> > >> + break;
> > >> + default:
> > >> + return;
> > >> + }
> > >> +
> > >> + if (wait) {
> > >> + anfc_enable_intrs(nfc, XFER_COMPLETE);
> > >> + writel(prog, nfc->base + PROG_OFST);
> > >> + anfc_wait_for_event(nfc, XFER_COMPLETE);
> > >> + }
> > >> +
> > >> + if (read)
> > >> + bufptr[0] = readl(nfc->base + FLASH_STS_OFST);
> > >> +}
> > >
> > > Can you try to implement ->cmd_ctrl() instead of ->cmdfunc(). This way
> > > you'll benefit from all the improvements we'll to the default
> > > nand_command_lp() and nand_command() implementation, and this also ease
> > > maintenance on our side.
> > > According to what I've seen so far this should be doable.
> > >
> >
> > I see your point but since this controller is providing the glue logic
> > for issuing the nand
> > commands, i doubt adopting cmd_ctrl would be not stright forward. IMHO, cmd_ctrl
> > shall be used for controllers that provide low level access and allow
> > more confgurable options.
>
> And as pointed above, your controller seems to be able to do that, but
> maybe I'm missing something.
>
> I know it implies reworking your driver, but as I said, if we keep
> adding new drivers which are not able to send generic CMDs, then we'll
> be screwed when we'll want to add support for newer NANDs (MLC NANDs),
> which are usually providing private/vendor-specific commands for
> common functions (like read-retry).
>
> Patching all NAND controllers to support those new NANDs is not a
> viable option, this is why I'd like to avoid those custom ->cmdfunc()
> implementations in new NAND drivers, unless I'm proven this is really
> impossible to do.
>
> >
> >
>
> [...]
>
> > >> +
> > >> +static int anfc_init_timing_mode(struct anfc *nfc)
> > >> +{
> > >> + int mode, err;
> > >> + unsigned int feature[2], regval, i;
> > >> + struct nand_chip *chip = &nfc->chip;
> > >> + struct mtd_info *mtd = &nfc->mtd;
> > >> +
> > >> + memset(feature, 0, NVDDR_MODE_PACKET_SIZE);
> > >> + /* Get nvddr timing modes */
> > >> + mode = onfi_get_sync_timing_mode(chip) & 0xff;
> > >> + if (!mode) {
> > >> + mode = fls(onfi_get_async_timing_mode(&nfc->chip)) - 1;
> > >> + regval = mode;
> > >> + } else {
> > >> + mode = fls(mode) - 1;
> > >> + regval = NVDDR_MODE | mode << NVDDR_TIMING_MODE_SHIFT;
> > >> + mode |= ONFI_DATA_INTERFACE_NVDDR;
> > >> + }
> > >> +
> > >> + feature[0] = mode;
> > >> + for (i = 0; i < nfc->num_cs; i++) {
> > >> + chip->select_chip(mtd, i);
>
> You select the chip here, but it's never de-selected, which means the
> last chip in the array stay selected until someone send a new command.
>
> > >> + err = chip->onfi_set_features(mtd, chip,
> > >> + ONFI_FEATURE_ADDR_TIMING_MODE,
> > >> + (uint8_t *)feature);
> > >> + if (err)
> > >> + return err;
> > >> + }
> > >> + writel(regval, nfc->base + DATA_INTERFACE_REG);
> > >> +
> > >> + if (mode & ONFI_DATA_INTERFACE_NVDDR)
> > >> + nfc->spktsize = NVDDR_MODE_PACKET_SIZE;
> > >
> > > You seem to switch from SDR to DDR mode, but I don't see any timing
> > > configuration. How is your controller able to adapt to different NAND
> > > timings?
> > >
> >
> > it is doing the timing mode configuration. it will adapt to the timing
> > parameters
> > defined in the ONFI 3.1 spec for each of tje timing mode i.e 0-5.
>
> I know what ONFI timings mode are, but usually when you change the mode
> on the NAND side, you have to adapt your timings on the controller
> side, and I don't see anything related to timings config on the
> controller side here, hence my question.
>
> >
> > >> +
> > >> + return 0;
> > >> +}
> > >> +
> > >> +static int anfc_probe(struct platform_device *pdev)
> > >> +{
> > >> + struct anfc *nfc;
> > >> + struct mtd_info *mtd;
> > >> + struct nand_chip *nand_chip;
> > >> + struct resource *res;
> > >> + struct mtd_part_parser_data ppdata;
> > >> + int err;
> > >> +
> > >> + nfc = devm_kzalloc(&pdev->dev, sizeof(*nfc), GFP_KERNEL);
> > >> + if (!nfc)
> > >> + return -ENOMEM;
> > >> +
> > >> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > >> + nfc->base = devm_ioremap_resource(&pdev->dev, res);
> > >> + if (IS_ERR(nfc->base))
> > >> + return PTR_ERR(nfc->base);
> > >> +
> > >> + mtd = &nfc->mtd;
> > >> + nand_chip = &nfc->chip;
> > >> + nand_chip->priv = nfc;
> > >> + mtd->priv = nand_chip;
> > >> + mtd->name = DRIVER_NAME;
> > >> + nfc->dev = &pdev->dev;
> > >> + mtd->dev.parent = &pdev->dev;
> > >> +
> > >> + nand_chip->cmdfunc = anfc_cmd_function;
> > >> + nand_chip->waitfunc = anfc_device_ready;
> > >
> > > You can leave nand_chip->waitfunc to NULL since your implementation is
> > > doing exactly what the default implementation does.
> > >
> > >> + nand_chip->chip_delay = 30;
> > >> + nand_chip->read_buf = anfc_read_buf;
> > >> + nand_chip->write_buf = anfc_write_buf;
> > >> + nand_chip->read_byte = anfc_read_byte;
> > >> + nand_chip->options = NAND_BUSWIDTH_AUTO | NAND_NO_SUBPAGE_WRITE;
> > >> + nand_chip->bbt_options = NAND_BBT_USE_FLASH;
> > >> + nand_chip->select_chip = anfc_select_chip;
> > >> + nand_chip->onfi_set_features = anfc_onfi_set_features;
> > >> + nfc->dma = of_property_read_bool(pdev->dev.of_node,
> > >> + "arasan,has-mdma");
> > >> + nfc->num_cs = 1;
> > >> + of_property_read_u32(pdev->dev.of_node, "num-cs", &nfc->num_cs);
> > >
> > > Already mentioned by Brian, but you should have a look at the
> > > sunxi-nand binding to see how to represent the NAND controller and its
> > > chips in the DT.
> > >
> >
> > Ok. i will check the implementation.
> >
> > >> + platform_set_drvdata(pdev, nfc);
> > >> + init_completion(&nfc->bufrdy);
> > >> + init_completion(&nfc->xfercomp);
> > >> + nfc->irq = platform_get_irq(pdev, 0);
> > >> + if (nfc->irq < 0) {
> > >> + dev_err(&pdev->dev, "platform_get_irq failed\n");
> > >> + return -ENXIO;
> > >> + }
> > >> + err = devm_request_irq(&pdev->dev, nfc->irq, anfc_irq_handler,
> > >> + 0, "arasannfc", nfc);
> > >> + if (err)
> > >> + return err;
> > >> + nfc->clk_sys = devm_clk_get(&pdev->dev, "clk_sys");
> > >> + if (IS_ERR(nfc->clk_sys)) {
> > >> + dev_err(&pdev->dev, "sys clock not found.\n");
> > >> + return PTR_ERR(nfc->clk_sys);
> > >> + }
> > >> +
> > >> + nfc->clk_flash = devm_clk_get(&pdev->dev, "clk_flash");
> > >> + if (IS_ERR(nfc->clk_flash)) {
> > >> + dev_err(&pdev->dev, "flash clock not found.\n");
> > >> + return PTR_ERR(nfc->clk_flash);
> > >> + }
> > >> +
> > >> + err = clk_prepare_enable(nfc->clk_sys);
> > >> + if (err) {
> > >> + dev_err(&pdev->dev, "Unable to enable sys clock.\n");
> > >> + return err;
> > >> + }
> > >> +
> > >> + err = clk_prepare_enable(nfc->clk_flash);
> > >> + if (err) {
> > >> + dev_err(&pdev->dev, "Unable to enable flash clock.\n");
> > >> + goto clk_dis_sys;
> > >> + }
> > >> +
> > >> + nfc->spktsize = SDR_MODE_PACKET_SIZE;
> > >> + err = nand_scan_ident(mtd, nfc->num_cs, NULL);
> > >> + if (err) {
> > >> + dev_err(&pdev->dev, "nand_scan_ident for NAND failed\n");
> > >> + goto clk_dis_all;
> > >> + }
> > >> + if (nand_chip->onfi_version) {
> > >> + nfc->raddr_cycles = nand_chip->onfi_params.addr_cycles & 0xf;
> > >> + nfc->caddr_cycles =
> > >> + (nand_chip->onfi_params.addr_cycles >> 4) & 0xf;
> > >> + } else {
> > >> + /*For non-ONFI devices, configuring the address cyles as 5 */
> > >> + nfc->raddr_cycles = nfc->caddr_cycles = 5;
> > >> + }
> > >
> > > Hm, this should not be dependent on ONFI support. It all depends on the
> > > CHIP size, which you can deduce from mtd->writesize.
> > >
> >
> > Understood. but i have kept those values as fallback. In general this
> > controller talks
> > with onfi devices only.
>
> Hm, not sure this is a good argument. Your NAND controller should work
> with both ONFI and non-ONFI NANDs: you don't know which NAND will be
> available on future board designs...
>
> Anyway, the check you're looking for is in nand_base.c IIRC.
>
> Best Regards,
>
> Boris
>
>
More information about the linux-mtd
mailing list