[PATCH v5] mtd: nand: tango: import driver for tango chips
Mason
slash.tmp at free.fr
Mon Sep 19 15:37:06 PDT 2016
On 19/09/2016 19:06, Boris Brezillon wrote:
> Marc Gonzalez <marc_gonzalez at sigmadesigns.com> wrote:
>
>> This driver supports the NAND Flash controller embedded in recent
>> Tango chips, such as SMP8758 and SMP8759.
>
> Please send another patch to document the DT bindings, and Cc the DT
> maintainers.
OK.
>> +struct tango_chip {
>> + struct nand_chip chip;
>> + void __iomem *base;
>
> I'm still not convinced this is needed (->base can be calculated where
> needed based on the chip->cs and nfc->reg_base values), but if you want
> to keep it, let's keep it.
I thought it was bad form to duplicate the code computing
chip->base, especially if I need to update that function.
>> +static void tango_select_chip(struct mtd_info *mtd, int idx)
>> +{
>> + struct nand_chip *nand = mtd_to_nand(mtd);
>> + struct tango_nfc *nfc = to_tango_nfc(nand->controller);
>> + struct tango_chip *chip = to_tango_chip(nand);
>> +
>> + if (idx < 0) {
>> + chip->base = NULL;
>> + return;
>> + }
>> +
>> + chip->base = nfc->pbus_base + (chip->cs * 256);
>
> You support only one CS per chip, so this is something you can
> configure at init time.
>
> When I asked you to remove the chip->base field, I had multi-CS chips
> in mind, but given this implementation, there's no need to set/reset
> the chip->base field each time ->select_chip() is called.
OK. I'll set chip->cs and chip->base in the probe function.
Just to be sure, I'll ask internally whether we want to
support multi-CS chips right now, or if this can be added
later on.
>> + writel_relaxed(chip->timing1, nfc->reg_base + NFC_TIMING1);
>> + writel_relaxed(chip->timing2, nfc->reg_base + NFC_TIMING2);
>> + writel_relaxed(chip->xfer_cfg, nfc->reg_base + NFC_XFER_CFG);
>> + writel_relaxed(chip->pkt_0_cfg, nfc->reg_base + NFC_PKT_0_CFG);
>> + writel_relaxed(chip->pkt_n_cfg, nfc->reg_base + NFC_PKT_N_CFG);
>> + writel_relaxed(chip->bb_cfg, nfc->reg_base + NFC_BB_CFG);
>
> Nit: can you avoid these weird alignments. Use a single space after the
> comma.
I was trying to highlight the fact that all these writes are
within the reg_base area. I'll do as you ask, since you are
the gatekeeper.
>> +static int do_dma(struct tango_nfc *nfc, int dir, int cmd,
>> + const void *buf, int len, int page)
>> +{
>> + struct dma_async_tx_descriptor *desc;
>> + struct scatterlist sg;
>> + struct completion tx_done;
>> + int err = -EIO;
>> + u32 res, val;
>> +
>> + sg_init_one(&sg, buf, len);
>> + if (dma_map_sg(nfc->chan->device->dev, &sg, 1, dir) != 1)
>> + goto leave;
>
> Return -EIO directly instead of creating this 'leave' label.
OK. I wasn't sure it was good form to make the first test
special, especially if someone adds a new alloc before this
one later on. But I don't care either way.
>> +
>> + desc = dmaengine_prep_slave_sg(nfc->chan, &sg, 1, dir, DMA_PREP_INTERRUPT);
>
> Put DMA_PREP_INTERRUPT on a second line to avoid >80 char lines.
I was told some maintainers don't enforce the >80 char line rule.
Am I to understand you're not one of them? :-)
>> + if (!desc)
>> + goto dma_unmap;
>> +
>> + desc->callback = tango_dma_callback;
>> + desc->callback_param = &tx_done;
>> + init_completion(&tx_done);
>> +
>> + writel_relaxed(MODE_NFC, nfc->pbus_base + PBUS_PAD_MODE);
>> +
>> + writel_relaxed(page, nfc->reg_base + NFC_ADDR_PAGE);
>> + writel_relaxed( 0, nfc->reg_base + NFC_ADDR_OFFSET);
>> + writel_relaxed( cmd, nfc->reg_base + NFC_FLASH_CMD);
>> +
>> + dmaengine_submit(desc);
>> + dma_async_issue_pending(nfc->chan);
>> +
>> + res = wait_for_completion_timeout(&tx_done, HZ);
>> + if (res > 0) {
>> + void __iomem *addr = nfc->reg_base + NFC_STATUS;
>> + err = readl_poll_timeout(addr, val, val & CMD_READY, 0, 1000);
>
> Why do you need this local variable?
>
> err = readl_poll_timeout(nfc->reg_base + NFC_STATUS,
> val, val & CMD_READY, 0, 1000);
I find it hard to read when function arguments are split
over multiple lines. So if there is a hard "80 char" limit,
I prefer using temp variables for "long" arguments.
>> + }
>> +
>> + writel_relaxed(MODE_RAW, nfc->pbus_base + PBUS_PAD_MODE);
>
> Add an extra blank line here.
OK.
>> +static int tango_read_page(struct mtd_info *mtd, struct nand_chip *chip,
>> + uint8_t *buf, int oob_required, int page)
>> +{
>> + struct tango_nfc *nfc = to_tango_nfc(chip->controller);
>> + int err, res, len = mtd->writesize;
>> +
>> + err = do_dma(nfc, DMA_FROM_DEVICE, NFC_READ, buf, len, page);
>> + if (err)
>> + return err;
>> +
>> + if (oob_required)
>> + chip->ecc.read_oob(mtd, chip, page);
>> +
>> + res = decode_error_report(nfc);
>> + if (res >= 0)
>> + return res;
>> +
>> + chip->ecc.read_oob(mtd, chip, page);
>
> There's no different in your case, but it should be read in raw mode.
> How about calling ecc.read_oob_raw() so that you're safe even if you
> want to differentiate the read_oob() and read_oob_raw() cases at some
> point (IIUC, the METADATA section is ECC protected).
OK.
>> + res = nand_check_erased_ecc_chunk(buf, len, chip->oob_poi, mtd->oobsize,
>> + NULL, 0, chip->ecc.strength);
>
> You should check each ECC packet/chunk independently, because ECC
> strength is referring to an ECC packet not a full page.
Because of our weird physical layout, accessing data chunks is
really awkward. We are willing to sacrifice a few bad pages
by using a sub-optimal check (allowing only "strength" bitflips
over the entire page, instead of over individual packets).
>> +static int tango_write_page(struct mtd_info *mtd, struct nand_chip *chip,
>> + const uint8_t *buf, int oob_required, int page)
>> +{
>> + struct tango_nfc *nfc = to_tango_nfc(chip->controller);
>> + int err, len = mtd->writesize;
>> +
>> + writel_relaxed(0xffffffff, nfc->mem_base + METADATA);
>> + err = do_dma(nfc, DMA_TO_DEVICE, NFC_WRITE, buf, len, page);
>> + if (err)
>> + return err;
>> +
>> + if (oob_required)
>> + chip->ecc.write_oob(mtd, chip, page);
I suppose we should use write_oob_raw here?
>> +enum { OP_SKIP, OP_READ, OP_WRITE };
>> +
>> +static void raw_aux(struct mtd_info *mtd, u8 **buf, int len, int op, int *lim)
>
> Nit: please pass struct nand_chip *chip in first argument, and extract
> the mtd using nand_to_mtd(). I know it's just a detail, but I'd like to
> avoid passing mtd pointers, especially in internal functions.
OK.
>> +{
>> + struct nand_chip *chip = mtd_to_nand(mtd);
>> + int pos = mtd->writesize - *lim;
>> +
>> + if (op == OP_SKIP)
>> + chip->cmdfunc(mtd, NAND_CMD_RNDOUT, pos + len, -1);
>
> Okay, we already discussed that one, and I already showed that this was
> not working: NAND_CMD_RNDOUT is only valid for read accesses.
>
> And I told you I'd like to avoid the const to non-const cast in write
> accessors.
Note: I don't use SKIP for writes, therefore there's no need
to implement that code.
>> + else if (op == OP_READ)
>> + tango_read_buf(mtd, *buf, len);
>> + else if (op == OP_WRITE)
>> + tango_write_buf(mtd, *buf, len);
>> +
>> + *lim -= len;
>> + *buf += len;
>> +}
>
> I'm not sure factorizing this code is really important, but since you
> insist, how about doing the following instead:
If the code is not factorized, then it must be duplicated for
read/write_raw and read/write_oob, I think (i.e. 4 times).
> enum tango_raw_op { OP_READ, OP_WRITE };
>
> union tango_raw_buffer {
> void *in;
> const void *out;
> };
>
> struct tango_raw_access {
> enum tango_raw_op op;
> union tango_raw_buffer *buf;
> int pos;
> };
>
> static void raw_aux(struct nand_chip *chip,
> struct tango_raw_access *info,
> union tango_raw_buffer *buf, int len)
> {
> struct mtd_info *mtd = nand_to_mtd(chip);
>
> if (!buf) {
> if (info->op == OP_READ)
> cmd = NAND_CMD_RNDOUT;
> else
> cmd = NAND_CMD_SEQIN;
>
> chip->cmdfunc(mtd, cmd, info->pos + len, -1);
> } else {
> if (info->op == OP_READ)
> tango_read_buf(mtd, buf->out, len);
> else
> tango_write_buf(mtd, buf->in, len);
>
> buf->in += len;
> }
>
> info->pos += len;
> }
I'll take a closer look tomorrow.
>> +static int raw_access(struct mtd_info *mtd, uint8_t *buf, uint8_t *oob, int op)
>> +{
>> + struct nand_chip *chip = mtd_to_nand(mtd);
>> + int pkt_size = chip->ecc.size;
>> + int ecc_size = chip->ecc.bytes;
>> + int buf_op = buf ? op : OP_SKIP;
>> + int oob_op = oob ? op : OP_SKIP;
>> + int rem, lim = mtd->writesize;
>> + u8 *oob_orig = oob;
>> +
>> + oob += BBM_SIZE;
>> + raw_aux(mtd, &oob, METADATA_SIZE, oob_op, &lim);
>> +
>> + while (lim > pkt_size)
>> + {
>> + raw_aux(mtd, &buf, pkt_size, buf_op, &lim);
>> + raw_aux(mtd, &oob, ecc_size, oob_op, &lim);
>> + }
>> +
>> + rem = pkt_size - lim;
>> + raw_aux(mtd, &buf, lim, buf_op, &lim);
>> + raw_aux(mtd, &oob_orig, BBM_SIZE, oob_op, &lim);
>> + raw_aux(mtd, &buf, rem, buf_op, &lim);
>> + raw_aux(mtd, &oob, ecc_size, oob_op, &lim);
>> +
>> + return 0;
>> +}
>
> With the above changes this gives:
>
> static int raw_access(struct nand_chip *chip,
> union tango_raw_buffer *buf,
> union tango_raw_buffer *oob,
> int op)
> {
> struct mtd_info *mtd = nand_to_mtd(chip);
> int pkt_size = chip->ecc.size;
> int ecc_size = chip->ecc.bytes;
> int steps = 0, rem = 0;
> struct tango_raw_access info = {
> .op = op,
> .pos = 0,
> };
> union tango_raw_buffer oob_orig;
>
> if (oob) {
> oob->in = chip->oob_poi + BBM_SIZE;
> oob_orig.in = chip->oob_poi;
> }
>
> raw_aux(chip, &info, oob, METADATA_SIZE);
>
> while (info.pos + pkt_size + ecc_size <= mtd->writesize) {
> raw_aux(chip, &info, buf, pkt_size);
> raw_aux(mtd, &info, oob, ecc_size);
> steps++;
> }
>
> if (steps < chip->ecc.steps)
> rem = pkt_size - (mtd->writesize - info.pos);
> raw_aux(mtd, &info, buf, mtd->writesize - info.pos);
> }
I don't think we need the 'steps' variable (I didn't need one).
> raw_aux(mtd, &info, oob ? &oob_orig : NULL, BBM_SIZE);
> raw_aux(mtd, &info, buf, rem);
>
> rem = mtd->writesize + mtd->oobsize - info.pos;
> raw_aux(mtd, &info, oob, rem);
>
> return 0;
> }
I'll take a closer look tomorrow.
>> +static u32 to_ticks(int kHz, int ps)
>> +{
>> + return DIV_ROUND_UP_ULL((u64)kHz * ps, NSEC_PER_SEC);
>> +}
>> +
>> +static int set_timings(struct tango_chip *p, int kHz)
>> +{
>> + u32 Trdy, Textw, Twc, Twpw, Tacc, Thold, Trpw, Textr;
>> + const struct nand_sdr_timings *sdr;
>> + int mode = onfi_get_async_timing_mode(&p->chip);
>> +
>> + if (mode & ONFI_TIMING_MODE_UNKNOWN)
>> + return -ENODEV;
>> +
>> + sdr = onfi_async_timing_mode_to_sdr_timings(fls(mode) - 1);
>
> We recently introduced a generic interface to automate nand timings
> configuration [1].
> Can you make use of it (the patches are in my nand/next branch [2], you
> can rebase your patch series on top of this branch).
I'll take a look. I still need to have this driver working
on 4.7 for the time being...
>> +
>> + Trdy = to_ticks(kHz, sdr->tCEA_max - sdr->tREA_max);
>> + Textw = to_ticks(kHz, sdr->tWB_max);
>> + Twc = to_ticks(kHz, sdr->tWC_min);
>> + Twpw = to_ticks(kHz, sdr->tWC_min - sdr->tWP_min);
>> +
>> + Tacc = to_ticks(kHz, sdr->tREA_max);
>> + Thold = to_ticks(kHz, sdr->tREH_min);
>> + Trpw = to_ticks(kHz, sdr->tRC_min - sdr->tREH_min);
>> + Textr = to_ticks(kHz, sdr->tRHZ_max);
>
> Can you please be consistent in your indentation?
> You're using spaces before '=' almost everywhere except in a few
> places (like here).
When initializing fields of a struct, most code I've seen
uses tabs to align the equal signs. I thought it should be
the same when assigning the fields of a struct, or assigning
several variables of the same nature (such as above).
Are you saying you prefer
Trdy = to_ticks(kHz, sdr->tCEA_max - sdr->tREA_max);
Textw = to_ticks(kHz, sdr->tWB_max);
Twc = to_ticks(kHz, sdr->tWC_min);
Thus the to_ticks calls are not aligned, and it's less obvious
that the first argument is always the same.
>> +static int chip_init(struct device *dev, struct device_node *np, int kHz)
>> +{
>> + int err;
>> + u32 cs, ecc_bits;
>> + struct nand_ecc_ctrl *ecc;
>> + struct tango_nfc *nfc = dev_get_drvdata(dev);
>> + struct tango_chip *p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL);
>> + if (!p)
>> + return -ENOMEM;
>> +
>> + err = of_property_read_u32_index(np, "reg", 0, &cs);
>> + if (err)
>> + return err;
>> +
>
> Can you please check that you only have a single value in reg? Just to
> make sure the user doesn't try to define a multi-CS chip.
OK.
>> + if (cs >= MAX_CS)
>> + return -ERANGE;
>> +
>> + p->chip.read_byte = tango_read_byte;
>> + p->chip.read_buf = tango_read_buf;
>> + p->chip.select_chip = tango_select_chip;
>> + p->chip.cmd_ctrl = tango_cmd_ctrl;
>> + p->chip.dev_ready = tango_dev_ready;
>> + p->chip.options = NAND_USE_BOUNCE_BUFFER | NAND_NO_SUBPAGE_WRITE;
>> + p->chip.controller = &nfc->hw;
>
> Again, be consistent in you coding style: use spaces.
OK. I'll take another look at the sunxi driver.
It makes no sense to me that we should tab-align when using
struct initialization with named fields, but space-align
when using assignment.
>> +
>> + ecc = &p->chip.ecc;
>> + ecc->mode = NAND_ECC_HW;
>> + ecc->algo = NAND_ECC_BCH;
>> + ecc->read_page_raw = tango_read_page_raw;
>> + ecc->write_page_raw = tango_write_page_raw;
>> + ecc->read_page = tango_read_page;
>> + ecc->write_page = tango_write_page;
>> + ecc->read_oob = tango_read_oob;
>> + ecc->write_oob = tango_write_oob;
>
> Can you move this part after nand_scan_ident(). I'd also suggest to
> support software ECC (it's just taking a few more lines), but that's up
> to you.
OK. What does it mean to support SW ECC?
>> +
>> + nand_set_flash_node(&p->chip, np);
>> + mtd_set_ooblayout(&p->chip.mtd, &tango_nand_ooblayout_ops);
>> + err = nand_scan_ident(&p->chip.mtd, 1, NULL);
>> + if (err)
>> + return err;
>> +
>> + ecc_bits = ecc->strength * FIELD_ORDER;
>> + p->chip.ecc.bytes = DIV_ROUND_UP(ecc_bits, BITS_PER_BYTE);
>> + set_timings(p, kHz);
>
> You won't need that one if you implement the
> chip->setup_data_interface() method.
I'll take a closer look.
>> + p->xfer_cfg = XFER_CFG(cs, 1, ecc->steps, METADATA_SIZE);
>> + p->pkt_0_cfg = PKT_CFG(ecc->size + METADATA_SIZE, ecc->strength);
>> + p->pkt_n_cfg = PKT_CFG(ecc->size, ecc->strength);
>> + p->bb_cfg = BB_CFG(p->chip.mtd.writesize, BBM_SIZE);
>
> This should go before nand_scan_tail(), because, as soon as you call
> mtd_device_register(), someone might use your NAND device.
>
>> + p->cs = cs;
>
> And this one should go before nand_scan_ident().
OK to both.
>> + for_each_child_of_node(pdev->dev.of_node, np) {
>> + int err = chip_init(&pdev->dev, np, kHz);
>> + if (err)
>> + return err;
>
> You should unregister all NAND/MTD devices before returning an error.
Good catch. I was used to devm_* wrappers for everything in
some frameworks. I'll call tango_nand_remove on error.
Thanks for the detailed review.
Regards.
More information about the linux-mtd
mailing list