From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail.free-electrons.com ([62.4.15.54]) by bombadil.infradead.org with esmtp (Exim 4.89 #1 (Red Hat Linux)) id 1eoHR3-0003zp-97 for linux-mtd@lists.infradead.org; Tue, 20 Feb 2018 23:35:00 +0000 Date: Wed, 21 Feb 2018 00:34:38 +0100 From: Miquel Raynal To: Stefan Agner Cc: Boris Brezillon , computersforpeace@gmail.com, dwmw2@infradead.org, marek.vasut@gmail.com, cyrille.pitchen@wedev4u.fr, richard@nod.at, bpringlemeir@gmail.com, marcel.ziswiler@toradex.com, linux-mtd@lists.infradead.org Subject: Re: [RFC PATCH v3 2/3] mtd: nand: vf610_nfc: make use of ->exec_op() Message-ID: <20180221003438.17a57d9a@xps13> In-Reply-To: <8898b2bd6b9b904f24b862d6df55ac40@agner.ch> References: <20180208235921.31840-1-stefan@agner.ch> <20180208235921.31840-3-stefan@agner.ch> <20180211115452.29cee45e@bbrezillon> <8898b2bd6b9b904f24b862d6df55ac40@agner.ch> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable List-Id: Linux MTD discussion mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Hi Stefan, On Wed, 21 Feb 2018 00:15:18 +0100, Stefan Agner wrote: > On 11.02.2018 11:54, Boris Brezillon wrote: > > On Fri, 9 Feb 2018 00:59:20 +0100 > > Stefan Agner wrote: > > =20 > >> This reworks the driver to make use of ->exec_op() callback. The > >> command sequencer of the VF610 NFC aligns well with the new ops > >> interface. > >> > >> The ops are translated to a NFC command code while filling the > >> necessary registers. Instead of using the special status and > >> read id command codes (which require the status/id form special > >> registers) the driver now uses the main data buffer for all > >> commands. This simplifies the driver as no special casing is > >> needed. > >> > >> For control data (status byte, id bytes and parameter page) the > >> driver needs to reverse byte order for little endian CPUs since > >> the controller seems to store the bytes in big endian order in > >> the data buffer. > >> > >> The current state seems to pass MTD tests on a Colibri VF61. > >> > >> Signed-off-by: Stefan Agner > >> --- > >> drivers/mtd/nand/vf610_nfc.c | 279 ++++++++++++++++++++++++++++++++++= +++++++-- > >> 1 file changed, 267 insertions(+), 12 deletions(-) > >> > >> diff --git a/drivers/mtd/nand/vf610_nfc.c b/drivers/mtd/nand/vf610_nfc= .c > >> index 2fa61cbdbaf7..eae95877422d 100644 > >> --- a/drivers/mtd/nand/vf610_nfc.c > >> +++ b/drivers/mtd/nand/vf610_nfc.c > >> @@ -74,6 +74,21 @@ > >> #define RESET_CMD_CODE 0x4040 > >> #define STATUS_READ_CMD_CODE 0x4068 > >> > >> +/* NFC_CMD2[CODE] controller cycle bit masks */ > >> +#define COMMAND_CMD_BYTE1 BIT(14) > >> +#define COMMAND_CAR_BYTE1 BIT(13) > >> +#define COMMAND_CAR_BYTE2 BIT(12) > >> +#define COMMAND_RAR_BYTE1 BIT(11) > >> +#define COMMAND_RAR_BYTE2 BIT(10) > >> +#define COMMAND_RAR_BYTE3 BIT(9) > >> +#define COMMAND_WRITE_DATA BIT(8) > >> +#define COMMAND_CMD_BYTE2 BIT(7) > >> +#define COMMAND_RB_HANDSHAKE BIT(6) > >> +#define COMMAND_READ_DATA BIT(5) > >> +#define COMMAND_CMD_BYTE3 BIT(4) > >> +#define COMMAND_READ_STATUS BIT(3) > >> +#define COMMAND_READ_ID BIT(2) > >> + > >> /* NFC ECC mode define */ > >> #define ECC_BYPASS 0 > >> #define ECC_45_BYTE 6 > >> @@ -97,10 +112,14 @@ > >> /* NFC_COL_ADDR Field */ > >> #define COL_ADDR_MASK 0x0000FFFF > >> #define COL_ADDR_SHIFT 0 > >> +#define COL_ADDR(pos, val) ((val & 0xFF) << (8 * (pos))) > >> + > >> > >> /* NFC_ROW_ADDR Field */ > >> #define ROW_ADDR_MASK 0x00FFFFFF > >> #define ROW_ADDR_SHIFT 0 > >> +#define ROW_ADDR(pos, val) ((val & 0xFF) << (8 * (pos))) > >> + > >> #define ROW_ADDR_CHIP_SEL_RB_MASK 0xF0000000 > >> #define ROW_ADDR_CHIP_SEL_RB_SHIFT 28 > >> #define ROW_ADDR_CHIP_SEL_MASK 0x0F000000 > >> @@ -173,6 +192,11 @@ static inline struct vf610_nfc *mtd_to_nfc(struct= mtd_info *mtd) > >> return container_of(mtd_to_nand(mtd), struct vf610_nfc, chip); > >> } > >> > >> +static inline struct vf610_nfc *chip_to_nfc(struct nand_chip *chip) > >> +{ > >> + return container_of(chip, struct vf610_nfc, chip); > >> +} > >> + > >> static inline u32 vf610_nfc_read(struct vf610_nfc *nfc, uint reg) > >> { > >> return readl(nfc->regs + reg); > >> @@ -489,6 +513,184 @@ static int vf610_nfc_dev_ready(struct mtd_info *= mtd) > >> return 1; > >> } > >> > >> +static inline void vf610_nfc_run(struct vf610_nfc *nfc, u32 col, u32 = row, u32 cmd1, u32 cmd2, u32 trfr_sz) =20 > >=20 > > Nitpick: please wrap the line to make checkpatch happy. > > =20 >=20 > Ok. >=20 > >> +{ > >> + vf610_nfc_set_field(nfc, NFC_COL_ADDR, COL_ADDR_MASK, > >> + COL_ADDR_SHIFT, col); > >> + > >> + vf610_nfc_set_field(nfc, NFC_ROW_ADDR, ROW_ADDR_MASK, > >> + ROW_ADDR_SHIFT, row); > >> + > >> + vf610_nfc_write(nfc, NFC_SECTOR_SIZE, trfr_sz); > >> + vf610_nfc_write(nfc, NFC_FLASH_CMD1, cmd1); > >> + vf610_nfc_write(nfc, NFC_FLASH_CMD2, cmd2); > >> + > >> + dev_dbg(nfc->dev, "col 0x%08x, row 0x%08x, cmd1 0x%08x, cmd2 0x%08x,= trfr_sz %d\n", > >> + col, row, cmd1, cmd2, trfr_sz); > >> + > >> + vf610_nfc_done(nfc); > >> +} > >> + > >> +static inline const struct nand_op_instr *vf610_get_next_instr( > >> + const struct nand_subop *subop, int *op_id) > >> +{ > >> + if (*op_id + 1 >=3D subop->ninstrs) > >> + return NULL; > >> + > >> + (*op_id)++; > >> + > >> + return &subop->instrs[*op_id]; > >> +} > >> + > >> +static int vf610_nfc_cmd(struct nand_chip *chip, > >> + const struct nand_subop *subop) > >> +{ > >> + const struct nand_op_instr *instr; > >> + struct vf610_nfc *nfc =3D chip_to_nfc(chip); > >> + struct mtd_info *mtd =3D nand_to_mtd(chip); > >> + int op_id =3D -1, addr =3D 0, trfr_sz =3D 0; > >> + u32 col =3D 0, row =3D 0, cmd1 =3D 0, cmd2 =3D 0, code =3D 0; > >> + > >> + /* Some ops are optional, but they need to be in order */ > >> + instr =3D vf610_get_next_instr(subop, &op_id); > >> + if (!instr) > >> + return -EINVAL; > >> + > >> + if (instr && instr->type =3D=3D NAND_OP_CMD_INSTR) { > >> + dev_dbg(nfc->dev, "OP_CMD: code 0x%02x\n", instr->ctx.cmd.opcode); > >> + cmd2 |=3D instr->ctx.cmd.opcode << CMD_BYTE1_SHIFT; > >> + code |=3D COMMAND_CMD_BYTE1; > >> + > >> + instr =3D vf610_get_next_instr(subop, &op_id); > >> + } > >> + > >> + if (instr && instr->type =3D=3D NAND_OP_ADDR_INSTR) { > >> + =20 > >=20 > > Hm, you're still checking the sequencing consistency here. This should > > have been taken care by the core parser already, so really, this is not > > needed. > > =20 >=20 > Since those operations are optional, I have to check... >=20 > >> + if (instr->ctx.addr.naddrs > 1) { > >> + col =3D COL_ADDR(0, instr->ctx.addr.addrs[addr++]); > >> + code |=3D COMMAND_CAR_BYTE1; > >> + > >> + if (mtd->writesize > 512) { > >> + col |=3D COL_ADDR(1, instr->ctx.addr.addrs[addr++]); > >> + code |=3D COMMAND_CAR_BYTE2; > >> + } =20 > >=20 > > No, you shoudn't do that. Just put as many ADDR cycles as requested > > without trying to figure out if they fit in the column or row field. We > > really don't care here, as long as the ADDR cycles are issued on the > > bus. > > =20 >=20 > Ok, so on a bus level column/row really does not matter? I wonder why > the controller makes a difference then? I remember that the controller > has auto increment feature, might that be the reason? Afaik we cannot > use such a feature currently...? If I remember correctly, yes, this is the use case: auto-increment for sequential cached accesses (not supported yet). >=20 > >> + } > >> + > >> + row =3D ROW_ADDR(0, instr->ctx.addr.addrs[addr++]); > >> + code |=3D COMMAND_RAR_BYTE1; > >> + if (addr < instr->ctx.addr.naddrs) { > >> + row |=3D ROW_ADDR(1, instr->ctx.addr.addrs[addr++]); > >> + code |=3D COMMAND_RAR_BYTE2; > >> + } > >> + if (addr < instr->ctx.addr.naddrs) { > >> + row |=3D ROW_ADDR(2, instr->ctx.addr.addrs[addr++]); > >> + code |=3D COMMAND_RAR_BYTE3; > >> + } > >> + > >> + dev_dbg(nfc->dev, "OP_ADDR: col %d, row %d\n", col, row); > >> + > >> + instr =3D vf610_get_next_instr(subop, &op_id); > >> + } > >> + > >> + if (instr && instr->type =3D=3D NAND_OP_DATA_OUT_INSTR) { > >> + int len =3D nand_subop_get_data_len(subop, op_id); > >> + int offset =3D nand_subop_get_data_start_off(subop, op_id); > >> + > >> + dev_dbg(nfc->dev, "OP_DATA_OUT: len %d, offset %d\n", len, offset); > >> + > >> + vf610_nfc_memcpy(nfc->regs + NFC_MAIN_AREA(0) + offset, > >> + instr->ctx.data.buf.in + offset, > >> + len); =20 > >=20 > > I think you have the same endianness problem you have for the READ > > path. For example, I doubt SET_FEATURES will work properly if you're > > in LE. So I repeat my initial suggestion: always do the byte swapping > > when you're transfering data to/from the SRAM from vf610_nfc_cmd() > > and use vf610_nfc_memcpy() only in the ->read/write_page() > > implementations.=20 > > =20 >=20 > Hm, but doesn't that leads to wrong order of data when using e.g. raw > read/write page...? I am not comfortable with endianness, I'll let Boris answer this one. >=20 > >> + code |=3D COMMAND_WRITE_DATA; > >> + trfr_sz +=3D len; > >> + > >> + instr =3D vf610_get_next_instr(subop, &op_id); > >> + } > >> + > >> + if (instr && instr->type =3D=3D NAND_OP_CMD_INSTR) { > >> + cmd1 |=3D instr->ctx.cmd.opcode << CMD_BYTE2_SHIFT; > >> + code |=3D COMMAND_CMD_BYTE2; > >> + > >> + instr =3D vf610_get_next_instr(subop, &op_id); > >> + } > >> + > >> + if (instr && instr->type =3D=3D NAND_OP_WAITRDY_INSTR) { > >> + //dev_dbg(nfc->dev, "WAITRDY [max %d ms]\n", > >> + // instr->ctx.waitrdy.timeout_ms); =20 > >=20 > > I guess this should go away. > > =20 > >> + code |=3D COMMAND_RB_HANDSHAKE; > >> + > >> + instr =3D vf610_get_next_instr(subop, &op_id); > >> + } > >> + > >> + if (instr && instr->type =3D=3D NAND_OP_DATA_IN_INSTR) { > >> + int len =3D nand_subop_get_data_len(subop, op_id); > >> + code |=3D COMMAND_READ_DATA; > >> + trfr_sz +=3D len; > >> + } > >> + > >> + cmd2 |=3D code << CMD_CODE_SHIFT; > >> + > >> + vf610_nfc_ecc_mode(nfc, ECC_BYPASS); > >> + vf610_nfc_run(nfc, col, row, cmd1, cmd2, trfr_sz); > >> + > >> + if (instr && instr->type =3D=3D NAND_OP_DATA_IN_INSTR) { > >> + int len =3D nand_subop_get_data_len(subop, op_id); > >> + int offset =3D nand_subop_get_data_start_off(subop, op_id); > >> + bool fix_byte_order =3D false; > >> + > >> +#ifdef __LITTLE_ENDIAN > >> + fix_byte_order =3D true; > >> +#endif > >> + dev_dbg(nfc->dev, "OP_DATA_IN: 8-bit: %d, len %d, offset %d\n", > >> + instr->ctx.data.force_8bit , len, offset); > >> + > >> + /* > >> + * HACK: force_8bit indicates reading of the parameter, status > >> + * or id data. The controllers seems to store data in big endian > >> + * byte order to the buffers. Reverse on little endian machines. > >> + */ > >> + if (instr->ctx.data.force_8bit && fix_byte_order) { =20 > >=20 > > I'm still convinced this is not the right solution to choose between > > swap vs don't swap bytes. See my explanation above. > > =20 > >> + u8 *buf =3D instr->ctx.data.buf.in; > >> + > >> + while (len--) { > >> + int c =3D offset ^ 0x3; > >> + > >> + *buf =3D *((u8 *)(nfc->regs + NFC_MAIN_AREA(0) + c)); > >> + buf++; offset++; > >> + } > >> + } else { > >> + vf610_nfc_memcpy(instr->ctx.data.buf.in + offset, > >> + nfc->regs + NFC_MAIN_AREA(0) + offset, > >> + len); > >> + } =20 > >=20 > > I think the "copy to/from SRAM and swap bytes if needed" code should > > should be move in dedicated functions. > > =20 > >> + } > >> + > >> + return 0; > >> +} > >> + > >> +static const struct nand_op_parser vf610_nfc_op_parser =3D NAND_OP_PA= RSER( > >> + NAND_OP_PARSER_PATTERN( > >> + vf610_nfc_cmd, > >> + NAND_OP_PARSER_PAT_CMD_ELEM(true), > >> + NAND_OP_PARSER_PAT_ADDR_ELEM(true, 5), > >> + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(true, PAGE_2K + OOB_MAX), > >> + NAND_OP_PARSER_PAT_CMD_ELEM(true), > >> + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true), > >> + NAND_OP_PARSER_PAT_DATA_IN_ELEM(true, PAGE_2K + OOB_MAX)), =20 > >=20 > > Are you sure you can do both a read and a write in a single pass? I > > doubt this is the case, because the logic seems to share the same SRAM > > for both operations, so I'd recommend splitting in 2 patterns: > > =20 >=20 > Hm, yes I currently use the same buffer, so in the current state this > would not work. But the controller actually has four buffers, so I > *could* use different ones for reading and writing. But is reading and > writing in a single go something we need often that such an optimization > would make worthwhile? I don't think this is ever used. You should probably go for the simples approach. >=20 > > NAND_OP_PARSER_PATTERN( > > vf610_nfc_cmd, > > NAND_OP_PARSER_PAT_CMD_ELEM(true),=20 > > NAND_OP_PARSER_PAT_ADDR_ELEM(true, 5),=20 > > NAND_OP_PARSER_PAT_DATA_OUT_ELEM(true, PAGE_2K + > > OOB_MAX), NAND_OP_PARSER_PAT_CMD_ELEM(true), > > NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)), > > NAND_OP_PARSER_PATTERN( > > vf610_nfc_cmd, > > NAND_OP_PARSER_PAT_CMD_ELEM(true), > > NAND_OP_PARSER_PAT_ADDR_ELEM(true, 5), > > NAND_OP_PARSER_PAT_CMD_ELEM(true), > > NAND_OP_PARSER_PAT_WAITRDY_ELEM(true), > > NAND_OP_PARSER_PAT_DATA_IN_ELEM(true, PAGE_2K + > > OOB_MAX)), > > =20 > >> + ); =20 > >=20 > > To sum-up, here is a diff [1] that applies on top of your patch and > > illustrate what I have in mind. > > =20 > >> + > >> +static int vf610_nfc_exec_op(struct nand_chip *chip, > >> + const struct nand_operation *op, > >> + bool check_only) > >> +{ > >> + struct vf610_nfc *nfc =3D chip_to_nfc(chip); > >> + > >> + dev_dbg(nfc->dev, "exec_op, opcode 0x%02x\n", op->instrs[0].ctx.cmd.= opcode); > >> + > >> + return nand_op_parser_exec_op(chip, &vf610_nfc_op_parser, op, check_= only); > >> +} > >> + > >> + > >> /* > >> * This function supports Vybrid only (MPC5125 would have full RB and= four CS) > >> */ > >> @@ -527,8 +729,7 @@ static inline int vf610_nfc_correct_data(struct mt= d_info *mtd, uint8_t *dat, > >> return ecc_count; > >> > >> /* Read OOB without ECC unit enabled */ > >> - vf610_nfc_command(mtd, NAND_CMD_READOOB, 0, page); > >> - vf610_nfc_read_buf(mtd, oob, mtd->oobsize); > >> + nand_read_oob_op(&nfc->chip, page, 0, oob, mtd->oobsize); > >> > >> /* > >> * On an erased page, bit count (including OOB) should be zero or > >> @@ -542,12 +743,42 @@ static inline int vf610_nfc_correct_data(struct = mtd_info *mtd, uint8_t *dat, > >> static int vf610_nfc_read_page(struct mtd_info *mtd, struct nand_chip= *chip, > >> uint8_t *buf, int oob_required, int page) > >> { > >> - int eccsize =3D chip->ecc.size; > >> + struct vf610_nfc *nfc =3D mtd_to_nfc(mtd); > >> + int trfr_sz =3D mtd->writesize + mtd->oobsize; > >> + u32 col =3D 0, row =3D 0, cmd1 =3D 0, cmd2 =3D 0, code =3D 0; =20 > >=20 > > col is not needed here, just pass 0 to vf610_nfc_run() and you should > > be good. > > =20 >=20 > Ok. >=20 > >> int stat; > >> > >> - nand_read_page_op(chip, page, 0, buf, eccsize); > >> + cmd2 |=3D NAND_CMD_READ0 << CMD_BYTE1_SHIFT; > >> + code |=3D COMMAND_CMD_BYTE1; > >> + > >> + code |=3D COMMAND_CAR_BYTE1; > >> + code |=3D COMMAND_CAR_BYTE2; > >> + > >> + row =3D ROW_ADDR(0, page & 0xff); > >> + code |=3D COMMAND_RAR_BYTE1; > >> + row |=3D ROW_ADDR(1, page >> 8); > >> + code |=3D COMMAND_RAR_BYTE2; > >> + > >> + if (chip->options & NAND_ROW_ADDR_3) { > >> + row |=3D ROW_ADDR(2, page >> 16); > >> + code |=3D COMMAND_RAR_BYTE3; > >> + } =20 > >=20 > > The address setting could be simplified a bit: > >=20 > > row =3D page; > > code |=3D COMMAND_CAR_BYTE1 | COMMAND_CAR_BYTE2 | > > COMMAND_RAR_BYTE1| COMMAND_RAR_BYTE2; > > if (chip->options & NAND_ROW_ADDR_3) > > code |=3D COMMAND_RAR_BYTE3; > > =20 >=20 > Agreed, and I will move that in a shared function since it is the same > for read/write. >=20 > >> + > >> + cmd1 |=3D NAND_CMD_READSTART << CMD_BYTE2_SHIFT; > >> + code |=3D COMMAND_CMD_BYTE2; > >> + > >> + code |=3D COMMAND_RB_HANDSHAKE; > >> + code |=3D COMMAND_READ_DATA; > >> + cmd2 |=3D code << CMD_CODE_SHIFT; > >> + > >> + vf610_nfc_ecc_mode(nfc, nfc->ecc_mode); > >> + vf610_nfc_run(nfc, col, row, cmd1, cmd2, trfr_sz); > >> + > >> + vf610_nfc_memcpy(buf, nfc->regs + NFC_MAIN_AREA(0), mtd->writesize); > >> if (oob_required) > >> - vf610_nfc_read_buf(mtd, chip->oob_poi, mtd->oobsize); > >> + vf610_nfc_memcpy(chip->oob_poi, > >> + nfc->regs + NFC_MAIN_AREA(0) + mtd->writesize, > >> + mtd->oobsize); > >> > >> stat =3D vf610_nfc_correct_data(mtd, buf, chip->oob_poi, page); =20 > >=20 > > You should disable ECC here and not in vf610_nfc_cmd(): > >=20 > > vf610_nfc_ecc_mode(nfc, ECC_BYPASS); > >=20 > > =20 >=20 > Hm, but then we would have to also clear it at initialization time since > boot loader could have left it in any state. I somewhat prefer the > current state.... I completely agree with Boris on that, it is much clearer if you activate/deactivate it by hand explicitly each time you need the ECC engine enabled. Of course you have to disable it at probe time. >=20 > >> > >> @@ -564,16 +795,39 @@ static int vf610_nfc_write_page(struct mtd_info = *mtd, struct nand_chip *chip, > >> const uint8_t *buf, int oob_required, int page) > >> { > >> struct vf610_nfc *nfc =3D mtd_to_nfc(mtd); > >> + int trfr_sz =3D mtd->writesize + mtd->oobsize; > >> + u32 col =3D 0, row =3D 0, cmd1 =3D 0, cmd2 =3D 0, code =3D 0; > >> + int ret =3D 0; > >> + > >> + cmd2 |=3D NAND_CMD_SEQIN << CMD_BYTE1_SHIFT; > >> + code |=3D COMMAND_CMD_BYTE1; > >> + > >> + code |=3D COMMAND_CAR_BYTE1; > >> + code |=3D COMMAND_CAR_BYTE2; > >> + > >> + row =3D ROW_ADDR(0, page & 0xff); > >> + code |=3D COMMAND_RAR_BYTE1; > >> + row |=3D ROW_ADDR(1, page >> 8); > >> + code |=3D COMMAND_RAR_BYTE2; > >> + if (chip->options & NAND_ROW_ADDR_3) { > >> + row |=3D ROW_ADDR(2, page >> 16); > >> + code |=3D COMMAND_RAR_BYTE3; > >> + } =20 > >=20 > > See my comment in _read_page(). > > =20 > >> > >> - nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize); > >> - if (oob_required) > >> - vf610_nfc_write_buf(mtd, chip->oob_poi, mtd->oobsize); > >> + cmd1 |=3D NAND_CMD_PAGEPROG << CMD_BYTE2_SHIFT; > >> + code |=3D COMMAND_CMD_BYTE2; > >> + > >> + code |=3D COMMAND_WRITE_DATA; > >> + > >> + vf610_nfc_memcpy(nfc->regs + NFC_MAIN_AREA(0), buf, mtd->writesize); > >> + > >> + code |=3D COMMAND_RB_HANDSHAKE; > >> + cmd2 |=3D code << CMD_CODE_SHIFT; > >> > >> - /* Always write whole page including OOB due to HW ECC */ > >> - nfc->use_hw_ecc =3D true; > >> - nfc->write_sz =3D mtd->writesize + mtd->oobsize; > >> + vf610_nfc_ecc_mode(nfc, nfc->ecc_mode); > >> + vf610_nfc_run(nfc, col, row, cmd1, cmd2, trfr_sz); > >> > >> - return nand_prog_page_end_op(chip); > >> + return ret; > >> } > >> > >> static const struct of_device_id vf610_nfc_dt_ids[] =3D { > >> @@ -686,6 +940,7 @@ static int vf610_nfc_probe(struct platform_device = *pdev) > >> chip->read_word =3D vf610_nfc_read_word; > >> chip->read_buf =3D vf610_nfc_read_buf; > >> chip->write_buf =3D vf610_nfc_write_buf; > >> + chip->exec_op =3D vf610_nfc_exec_op; > >> chip->select_chip =3D vf610_nfc_select_chip; > >> chip->onfi_set_features =3D nand_onfi_get_set_features_notsupp; > >> chip->onfi_get_features =3D nand_onfi_get_set_features_notsupp; =20 > >=20 > > That's all I have for now. > >=20 > > Regards, > >=20 > > Boris > >=20 > > [1]http://code.bulix.org/90iikz-278094 =20 >=20 > Thanks, and thanks for reviewing! >=20 > -- > Stefan Thanks, Miqu=C3=A8l --=20 Miquel Raynal, Bootlin (formerly Free Electrons) Embedded Linux and Kernel engineering http://bootlin.com