diff mbox series

[v3,2/9] mtd: rawnand: denali: refactor syndrome layout handling for raw access

Message ID 1552380290-19951-3-git-send-email-yamada.masahiro@socionext.com
State Changes Requested
Delegated to: Miquel Raynal
Headers show
Series mtd: rawnand: denali: exec_op(), controller/chip separation, and cleanups | expand

Commit Message

Masahiro Yamada March 12, 2019, 8:44 a.m. UTC
The Denali IP adopts the syndrome page layout (payload and ECC are
interleaved). The *_page_raw() and *_oob() callbacks are complicated
because they must hide the underlying layout used by the hardware,
and always return contiguous in-band and out-of-band data.

Currently, similar code is duplicated to reorganize the data layout.
For example, denali_read_page_raw() and denali_write_page_raw() look
almost the same.

The idea for refactoring is to split the code into two parts:
  [1] conversion of page layout
  [2] what to do at every ECC chunk boundary

For [1], I wrote denali_raw_payload_op() and denali_raw_oob_op().
They manipulate data for the Denali controller's specific page layout
of in-band, out-of-band, respectively.

The difference between write and read is just the operation at
ECC chunk boundaries. For example, denali_read_oob() calls
nand_change_read_column_op(), whereas denali_write_oob() calls
nand_change_write_column_op(). So, I implemented [2] as a callback
passed into [1].

Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
---

Changes in v3:
  - Add comments to denali_raw_payload_op() and denali_oob_payload_op()

Changes in v2: None

 drivers/mtd/nand/raw/denali.c | 380 +++++++++++++++++++++---------------------
 1 file changed, 189 insertions(+), 191 deletions(-)

Comments

Miquel Raynal March 12, 2019, 10:28 a.m. UTC | #1
Hi Masahiro,

Masahiro Yamada <yamada.masahiro@socionext.com> wrote on Tue, 12 Mar
2019 17:44:43 +0900:

> The Denali IP adopts the syndrome page layout (payload and ECC are
> interleaved). The *_page_raw() and *_oob() callbacks are complicated
> because they must hide the underlying layout used by the hardware,
> and always return contiguous in-band and out-of-band data.
> 
> Currently, similar code is duplicated to reorganize the data layout.
> For example, denali_read_page_raw() and denali_write_page_raw() look
> almost the same.
> 
> The idea for refactoring is to split the code into two parts:
>   [1] conversion of page layout
>   [2] what to do at every ECC chunk boundary
> 
> For [1], I wrote denali_raw_payload_op() and denali_raw_oob_op().
> They manipulate data for the Denali controller's specific page layout
> of in-band, out-of-band, respectively.
> 
> The difference between write and read is just the operation at
> ECC chunk boundaries. For example, denali_read_oob() calls
> nand_change_read_column_op(), whereas denali_write_oob() calls
> nand_change_write_column_op(). So, I implemented [2] as a callback
> passed into [1].
> 
> Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
> ---
> 

[...]

>  static int denali_read_page_raw(struct nand_chip *chip, uint8_t *buf,
>  				int oob_required, int page)
>  {
> +	struct denali_nand_info *denali = to_denali(chip);
>  	struct mtd_info *mtd = nand_to_mtd(chip);
> -	struct denali_nand_info *denali = mtd_to_denali(mtd);
> -	int writesize = mtd->writesize;
> -	int oobsize = mtd->oobsize;
> -	int ecc_steps = chip->ecc.steps;
> -	int ecc_size = chip->ecc.size;
> -	int ecc_bytes = chip->ecc.bytes;
>  	void *tmp_buf = denali->buf;
> -	int oob_skip = denali->oob_skip_bytes;
> -	size_t size = writesize + oobsize;
> -	int ret, i, pos, len;
> +	size_t size = mtd->writesize + mtd->oobsize;
> +	int ret;
> +
> +	if (!buf)
> +		return -EINVAL;
>  
>  	ret = denali_data_xfer(chip, tmp_buf, size, page, 1, 0);
>  	if (ret)
>  		return ret;
>  
> -	/* Arrange the buffer for syndrome payload/ecc layout */
> -	if (buf) {
> -		for (i = 0; i < ecc_steps; i++) {
> -			pos = i * (ecc_size + ecc_bytes);
> -			len = ecc_size;
> -
> -			if (pos >= writesize)
> -				pos += oob_skip;
> -			else if (pos + len > writesize)
> -				len = writesize - pos;
> -
> -			memcpy(buf, tmp_buf + pos, len);
> -			buf += len;
> -			if (len < ecc_size) {
> -				len = ecc_size - len;
> -				memcpy(buf, tmp_buf + writesize + oob_skip,
> -				       len);
> -				buf += len;
> -			}
> -		}
> -	}
> +	ret = denali_raw_payload_op(chip, buf, denali_memcpy_in, tmp_buf);

Honestly, I still don't like passing denali_memcpy_in/out as parameter.

Besides that, once you'll have added helpers to avoid abusing the
ternary operator in 4/9, the rest looks fine by me.

Thanks,
Miquèl
Masahiro Yamada March 12, 2019, 10:51 a.m. UTC | #2
On Tue, Mar 12, 2019 at 7:28 PM Miquel Raynal <miquel.raynal@bootlin.com> wrote:
>
> Hi Masahiro,
>
> Masahiro Yamada <yamada.masahiro@socionext.com> wrote on Tue, 12 Mar
> 2019 17:44:43 +0900:
>
> > The Denali IP adopts the syndrome page layout (payload and ECC are
> > interleaved). The *_page_raw() and *_oob() callbacks are complicated
> > because they must hide the underlying layout used by the hardware,
> > and always return contiguous in-band and out-of-band data.
> >
> > Currently, similar code is duplicated to reorganize the data layout.
> > For example, denali_read_page_raw() and denali_write_page_raw() look
> > almost the same.
> >
> > The idea for refactoring is to split the code into two parts:
> >   [1] conversion of page layout
> >   [2] what to do at every ECC chunk boundary
> >
> > For [1], I wrote denali_raw_payload_op() and denali_raw_oob_op().
> > They manipulate data for the Denali controller's specific page layout
> > of in-band, out-of-band, respectively.
> >
> > The difference between write and read is just the operation at
> > ECC chunk boundaries. For example, denali_read_oob() calls
> > nand_change_read_column_op(), whereas denali_write_oob() calls
> > nand_change_write_column_op(). So, I implemented [2] as a callback
> > passed into [1].
> >
> > Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
> > ---
> >
>
> [...]
>
> >  static int denali_read_page_raw(struct nand_chip *chip, uint8_t *buf,
> >                               int oob_required, int page)
> >  {
> > +     struct denali_nand_info *denali = to_denali(chip);
> >       struct mtd_info *mtd = nand_to_mtd(chip);
> > -     struct denali_nand_info *denali = mtd_to_denali(mtd);
> > -     int writesize = mtd->writesize;
> > -     int oobsize = mtd->oobsize;
> > -     int ecc_steps = chip->ecc.steps;
> > -     int ecc_size = chip->ecc.size;
> > -     int ecc_bytes = chip->ecc.bytes;
> >       void *tmp_buf = denali->buf;
> > -     int oob_skip = denali->oob_skip_bytes;
> > -     size_t size = writesize + oobsize;
> > -     int ret, i, pos, len;
> > +     size_t size = mtd->writesize + mtd->oobsize;
> > +     int ret;
> > +
> > +     if (!buf)
> > +             return -EINVAL;
> >
> >       ret = denali_data_xfer(chip, tmp_buf, size, page, 1, 0);
> >       if (ret)
> >               return ret;
> >
> > -     /* Arrange the buffer for syndrome payload/ecc layout */
> > -     if (buf) {
> > -             for (i = 0; i < ecc_steps; i++) {
> > -                     pos = i * (ecc_size + ecc_bytes);
> > -                     len = ecc_size;
> > -
> > -                     if (pos >= writesize)
> > -                             pos += oob_skip;
> > -                     else if (pos + len > writesize)
> > -                             len = writesize - pos;
> > -
> > -                     memcpy(buf, tmp_buf + pos, len);
> > -                     buf += len;
> > -                     if (len < ecc_size) {
> > -                             len = ecc_size - len;
> > -                             memcpy(buf, tmp_buf + writesize + oob_skip,
> > -                                    len);
> > -                             buf += len;
> > -                     }
> > -             }
> > -     }
> > +     ret = denali_raw_payload_op(chip, buf, denali_memcpy_in, tmp_buf);
>
> Honestly, I still don't like passing denali_memcpy_in/out as parameter.
>
> Besides that, once you'll have added helpers to avoid abusing the
> ternary operator in 4/9, the rest looks fine by me.
>


Do you have any suggestion?

There are 4 callbacks depending on the combination
of full-raw/oob, read/write.

I do not know how to make it cleaner.
Miquel Raynal March 12, 2019, 10:54 a.m. UTC | #3
Hi Masahiro,

Masahiro Yamada <yamada.masahiro@socionext.com> wrote on Tue, 12 Mar
2019 19:51:21 +0900:

> On Tue, Mar 12, 2019 at 7:28 PM Miquel Raynal <miquel.raynal@bootlin.com> wrote:
> >
> > Hi Masahiro,
> >
> > Masahiro Yamada <yamada.masahiro@socionext.com> wrote on Tue, 12 Mar
> > 2019 17:44:43 +0900:
> >  
> > > The Denali IP adopts the syndrome page layout (payload and ECC are
> > > interleaved). The *_page_raw() and *_oob() callbacks are complicated
> > > because they must hide the underlying layout used by the hardware,
> > > and always return contiguous in-band and out-of-band data.
> > >
> > > Currently, similar code is duplicated to reorganize the data layout.
> > > For example, denali_read_page_raw() and denali_write_page_raw() look
> > > almost the same.
> > >
> > > The idea for refactoring is to split the code into two parts:
> > >   [1] conversion of page layout
> > >   [2] what to do at every ECC chunk boundary
> > >
> > > For [1], I wrote denali_raw_payload_op() and denali_raw_oob_op().
> > > They manipulate data for the Denali controller's specific page layout
> > > of in-band, out-of-band, respectively.
> > >
> > > The difference between write and read is just the operation at
> > > ECC chunk boundaries. For example, denali_read_oob() calls
> > > nand_change_read_column_op(), whereas denali_write_oob() calls
> > > nand_change_write_column_op(). So, I implemented [2] as a callback
> > > passed into [1].
> > >
> > > Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
> > > ---
> > >  
> >
> > [...]
> >  
> > >  static int denali_read_page_raw(struct nand_chip *chip, uint8_t *buf,
> > >                               int oob_required, int page)
> > >  {
> > > +     struct denali_nand_info *denali = to_denali(chip);
> > >       struct mtd_info *mtd = nand_to_mtd(chip);
> > > -     struct denali_nand_info *denali = mtd_to_denali(mtd);
> > > -     int writesize = mtd->writesize;
> > > -     int oobsize = mtd->oobsize;
> > > -     int ecc_steps = chip->ecc.steps;
> > > -     int ecc_size = chip->ecc.size;
> > > -     int ecc_bytes = chip->ecc.bytes;
> > >       void *tmp_buf = denali->buf;
> > > -     int oob_skip = denali->oob_skip_bytes;
> > > -     size_t size = writesize + oobsize;
> > > -     int ret, i, pos, len;
> > > +     size_t size = mtd->writesize + mtd->oobsize;
> > > +     int ret;
> > > +
> > > +     if (!buf)
> > > +             return -EINVAL;
> > >
> > >       ret = denali_data_xfer(chip, tmp_buf, size, page, 1, 0);
> > >       if (ret)
> > >               return ret;
> > >
> > > -     /* Arrange the buffer for syndrome payload/ecc layout */
> > > -     if (buf) {
> > > -             for (i = 0; i < ecc_steps; i++) {
> > > -                     pos = i * (ecc_size + ecc_bytes);
> > > -                     len = ecc_size;
> > > -
> > > -                     if (pos >= writesize)
> > > -                             pos += oob_skip;
> > > -                     else if (pos + len > writesize)
> > > -                             len = writesize - pos;
> > > -
> > > -                     memcpy(buf, tmp_buf + pos, len);
> > > -                     buf += len;
> > > -                     if (len < ecc_size) {
> > > -                             len = ecc_size - len;
> > > -                             memcpy(buf, tmp_buf + writesize + oob_skip,
> > > -                                    len);
> > > -                             buf += len;
> > > -                     }
> > > -             }
> > > -     }
> > > +     ret = denali_raw_payload_op(chip, buf, denali_memcpy_in, tmp_buf);  
> >
> > Honestly, I still don't like passing denali_memcpy_in/out as parameter.
> >
> > Besides that, once you'll have added helpers to avoid abusing the
> > ternary operator in 4/9, the rest looks fine by me.
> >  
> 
> 
> Do you have any suggestion?

Maybe register these two helpers at probe as controller specific hooks,
then just pass an in/out boolean to the function?

Thanks,
Miquèl
Masahiro Yamada March 12, 2019, 11:07 a.m. UTC | #4
Hi Miquel,


On Tue, Mar 12, 2019 at 7:54 PM Miquel Raynal <miquel.raynal@bootlin.com> wrote:
>
> Hi Masahiro,
>
> Masahiro Yamada <yamada.masahiro@socionext.com> wrote on Tue, 12 Mar
> 2019 19:51:21 +0900:
>
> > On Tue, Mar 12, 2019 at 7:28 PM Miquel Raynal <miquel.raynal@bootlin.com> wrote:
> > >
> > > Hi Masahiro,
> > >
> > > Masahiro Yamada <yamada.masahiro@socionext.com> wrote on Tue, 12 Mar
> > > 2019 17:44:43 +0900:
> > >
> > > > The Denali IP adopts the syndrome page layout (payload and ECC are
> > > > interleaved). The *_page_raw() and *_oob() callbacks are complicated
> > > > because they must hide the underlying layout used by the hardware,
> > > > and always return contiguous in-band and out-of-band data.
> > > >
> > > > Currently, similar code is duplicated to reorganize the data layout.
> > > > For example, denali_read_page_raw() and denali_write_page_raw() look
> > > > almost the same.
> > > >
> > > > The idea for refactoring is to split the code into two parts:
> > > >   [1] conversion of page layout
> > > >   [2] what to do at every ECC chunk boundary
> > > >
> > > > For [1], I wrote denali_raw_payload_op() and denali_raw_oob_op().
> > > > They manipulate data for the Denali controller's specific page layout
> > > > of in-band, out-of-band, respectively.
> > > >
> > > > The difference between write and read is just the operation at
> > > > ECC chunk boundaries. For example, denali_read_oob() calls
> > > > nand_change_read_column_op(), whereas denali_write_oob() calls
> > > > nand_change_write_column_op(). So, I implemented [2] as a callback
> > > > passed into [1].
> > > >
> > > > Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
> > > > ---
> > > >
> > >
> > > [...]
> > >
> > > >  static int denali_read_page_raw(struct nand_chip *chip, uint8_t *buf,
> > > >                               int oob_required, int page)
> > > >  {
> > > > +     struct denali_nand_info *denali = to_denali(chip);
> > > >       struct mtd_info *mtd = nand_to_mtd(chip);
> > > > -     struct denali_nand_info *denali = mtd_to_denali(mtd);
> > > > -     int writesize = mtd->writesize;
> > > > -     int oobsize = mtd->oobsize;
> > > > -     int ecc_steps = chip->ecc.steps;
> > > > -     int ecc_size = chip->ecc.size;
> > > > -     int ecc_bytes = chip->ecc.bytes;
> > > >       void *tmp_buf = denali->buf;
> > > > -     int oob_skip = denali->oob_skip_bytes;
> > > > -     size_t size = writesize + oobsize;
> > > > -     int ret, i, pos, len;
> > > > +     size_t size = mtd->writesize + mtd->oobsize;
> > > > +     int ret;
> > > > +
> > > > +     if (!buf)
> > > > +             return -EINVAL;
> > > >
> > > >       ret = denali_data_xfer(chip, tmp_buf, size, page, 1, 0);
> > > >       if (ret)
> > > >               return ret;
> > > >
> > > > -     /* Arrange the buffer for syndrome payload/ecc layout */
> > > > -     if (buf) {
> > > > -             for (i = 0; i < ecc_steps; i++) {
> > > > -                     pos = i * (ecc_size + ecc_bytes);
> > > > -                     len = ecc_size;
> > > > -
> > > > -                     if (pos >= writesize)
> > > > -                             pos += oob_skip;
> > > > -                     else if (pos + len > writesize)
> > > > -                             len = writesize - pos;
> > > > -
> > > > -                     memcpy(buf, tmp_buf + pos, len);
> > > > -                     buf += len;
> > > > -                     if (len < ecc_size) {
> > > > -                             len = ecc_size - len;
> > > > -                             memcpy(buf, tmp_buf + writesize + oob_skip,
> > > > -                                    len);
> > > > -                             buf += len;
> > > > -                     }
> > > > -             }
> > > > -     }
> > > > +     ret = denali_raw_payload_op(chip, buf, denali_memcpy_in, tmp_buf);
> > >
> > > Honestly, I still don't like passing denali_memcpy_in/out as parameter.
> > >
> > > Besides that, once you'll have added helpers to avoid abusing the
> > > ternary operator in 4/9, the rest looks fine by me.
> > >
> >
> >
> > Do you have any suggestion?
>
> Maybe register these two helpers at probe as controller specific hooks,
> then just pass an in/out boolean to the function?
>

Sorry, I do not understand.

Are you suggesting to do like follows in probe ?

denali->change_column_read_raw = denali_memcpy_in;
denali->change_column_write_raw = denali_memcpy_out;
denali->change_column_read_oob = denali_change_read_column_op;
denali->change_column_write_oob = denali_change_write_column_op;


All the 4 hooks are always needed
regardless of any probed features.


The result is just textual replacement
denali_* with denali->*.

What's the point of copying fixed function addresses
to denali structure?
Miquel Raynal March 12, 2019, 1:13 p.m. UTC | #5
Hi Masahiro,

Masahiro Yamada <yamada.masahiro@socionext.com> wrote on Tue, 12 Mar
2019 20:07:27 +0900:

> Hi Miquel,
> 
> 
> On Tue, Mar 12, 2019 at 7:54 PM Miquel Raynal <miquel.raynal@bootlin.com> wrote:
> >
> > Hi Masahiro,
> >
> > Masahiro Yamada <yamada.masahiro@socionext.com> wrote on Tue, 12 Mar
> > 2019 19:51:21 +0900:
> >  
> > > On Tue, Mar 12, 2019 at 7:28 PM Miquel Raynal <miquel.raynal@bootlin.com> wrote:  
> > > >
> > > > Hi Masahiro,
> > > >
> > > > Masahiro Yamada <yamada.masahiro@socionext.com> wrote on Tue, 12 Mar
> > > > 2019 17:44:43 +0900:
> > > >  
> > > > > The Denali IP adopts the syndrome page layout (payload and ECC are
> > > > > interleaved). The *_page_raw() and *_oob() callbacks are complicated
> > > > > because they must hide the underlying layout used by the hardware,
> > > > > and always return contiguous in-band and out-of-band data.
> > > > >
> > > > > Currently, similar code is duplicated to reorganize the data layout.
> > > > > For example, denali_read_page_raw() and denali_write_page_raw() look
> > > > > almost the same.
> > > > >
> > > > > The idea for refactoring is to split the code into two parts:
> > > > >   [1] conversion of page layout
> > > > >   [2] what to do at every ECC chunk boundary
> > > > >
> > > > > For [1], I wrote denali_raw_payload_op() and denali_raw_oob_op().
> > > > > They manipulate data for the Denali controller's specific page layout
> > > > > of in-band, out-of-band, respectively.
> > > > >
> > > > > The difference between write and read is just the operation at
> > > > > ECC chunk boundaries. For example, denali_read_oob() calls
> > > > > nand_change_read_column_op(), whereas denali_write_oob() calls
> > > > > nand_change_write_column_op(). So, I implemented [2] as a callback
> > > > > passed into [1].
> > > > >
> > > > > Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
> > > > > ---
> > > > >  
> > > >
> > > > [...]
> > > >  
> > > > >  static int denali_read_page_raw(struct nand_chip *chip, uint8_t *buf,
> > > > >                               int oob_required, int page)
> > > > >  {
> > > > > +     struct denali_nand_info *denali = to_denali(chip);
> > > > >       struct mtd_info *mtd = nand_to_mtd(chip);
> > > > > -     struct denali_nand_info *denali = mtd_to_denali(mtd);
> > > > > -     int writesize = mtd->writesize;
> > > > > -     int oobsize = mtd->oobsize;
> > > > > -     int ecc_steps = chip->ecc.steps;
> > > > > -     int ecc_size = chip->ecc.size;
> > > > > -     int ecc_bytes = chip->ecc.bytes;
> > > > >       void *tmp_buf = denali->buf;
> > > > > -     int oob_skip = denali->oob_skip_bytes;
> > > > > -     size_t size = writesize + oobsize;
> > > > > -     int ret, i, pos, len;
> > > > > +     size_t size = mtd->writesize + mtd->oobsize;
> > > > > +     int ret;
> > > > > +
> > > > > +     if (!buf)
> > > > > +             return -EINVAL;
> > > > >
> > > > >       ret = denali_data_xfer(chip, tmp_buf, size, page, 1, 0);
> > > > >       if (ret)
> > > > >               return ret;
> > > > >
> > > > > -     /* Arrange the buffer for syndrome payload/ecc layout */
> > > > > -     if (buf) {
> > > > > -             for (i = 0; i < ecc_steps; i++) {
> > > > > -                     pos = i * (ecc_size + ecc_bytes);
> > > > > -                     len = ecc_size;
> > > > > -
> > > > > -                     if (pos >= writesize)
> > > > > -                             pos += oob_skip;
> > > > > -                     else if (pos + len > writesize)
> > > > > -                             len = writesize - pos;
> > > > > -
> > > > > -                     memcpy(buf, tmp_buf + pos, len);
> > > > > -                     buf += len;
> > > > > -                     if (len < ecc_size) {
> > > > > -                             len = ecc_size - len;
> > > > > -                             memcpy(buf, tmp_buf + writesize + oob_skip,
> > > > > -                                    len);
> > > > > -                             buf += len;
> > > > > -                     }
> > > > > -             }
> > > > > -     }
> > > > > +     ret = denali_raw_payload_op(chip, buf, denali_memcpy_in, tmp_buf);  
> > > >
> > > > Honestly, I still don't like passing denali_memcpy_in/out as parameter.
> > > >
> > > > Besides that, once you'll have added helpers to avoid abusing the
> > > > ternary operator in 4/9, the rest looks fine by me.
> > > >  
> > >
> > >
> > > Do you have any suggestion?  
> >
> > Maybe register these two helpers at probe as controller specific hooks,
> > then just pass an in/out boolean to the function?
> >  
> 
> Sorry, I do not understand.
> 
> Are you suggesting to do like follows in probe ?
> 
> denali->change_column_read_raw = denali_memcpy_in;
> denali->change_column_write_raw = denali_memcpy_out;
> denali->change_column_read_oob = denali_change_read_column_op;
> denali->change_column_write_oob = denali_change_write_column_op;
> 
> 
> All the 4 hooks are always needed
> regardless of any probed features.
> 
> 
> The result is just textual replacement
> denali_* with denali->*.
> 
> What's the point of copying fixed function addresses
> to denali structure?
> 
> 

What I don't like is the function pointer as a function parameter. You
can use the functions defined statically if you prefer as long as the
parameter is just a boolean for instance?

Thanks,
Miquèl
Masahiro Yamada March 14, 2019, 8:24 a.m. UTC | #6
Hi Miquel,

On Tue, Mar 12, 2019 at 10:13 PM Miquel Raynal
<miquel.raynal@bootlin.com> wrote:
>
> Hi Masahiro,
>
> Masahiro Yamada <yamada.masahiro@socionext.com> wrote on Tue, 12 Mar
> 2019 20:07:27 +0900:
>
> > Hi Miquel,
> >
> >
> > On Tue, Mar 12, 2019 at 7:54 PM Miquel Raynal <miquel.raynal@bootlin.com> wrote:
> > >
> > > Hi Masahiro,
> > >
> > > Masahiro Yamada <yamada.masahiro@socionext.com> wrote on Tue, 12 Mar
> > > 2019 19:51:21 +0900:
> > >
> > > > On Tue, Mar 12, 2019 at 7:28 PM Miquel Raynal <miquel.raynal@bootlin.com> wrote:
> > > > >
> > > > > Hi Masahiro,
> > > > >
> > > > > Masahiro Yamada <yamada.masahiro@socionext.com> wrote on Tue, 12 Mar
> > > > > 2019 17:44:43 +0900:
> > > > >
> > > > > > The Denali IP adopts the syndrome page layout (payload and ECC are
> > > > > > interleaved). The *_page_raw() and *_oob() callbacks are complicated
> > > > > > because they must hide the underlying layout used by the hardware,
> > > > > > and always return contiguous in-band and out-of-band data.
> > > > > >
> > > > > > Currently, similar code is duplicated to reorganize the data layout.
> > > > > > For example, denali_read_page_raw() and denali_write_page_raw() look
> > > > > > almost the same.
> > > > > >
> > > > > > The idea for refactoring is to split the code into two parts:
> > > > > >   [1] conversion of page layout
> > > > > >   [2] what to do at every ECC chunk boundary
> > > > > >
> > > > > > For [1], I wrote denali_raw_payload_op() and denali_raw_oob_op().
> > > > > > They manipulate data for the Denali controller's specific page layout
> > > > > > of in-band, out-of-band, respectively.
> > > > > >
> > > > > > The difference between write and read is just the operation at
> > > > > > ECC chunk boundaries. For example, denali_read_oob() calls
> > > > > > nand_change_read_column_op(), whereas denali_write_oob() calls
> > > > > > nand_change_write_column_op(). So, I implemented [2] as a callback
> > > > > > passed into [1].
> > > > > >
> > > > > > Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
> > > > > > ---
> > > > > >
> > > > >
> > > > > [...]
> > > > >
> > > > > >  static int denali_read_page_raw(struct nand_chip *chip, uint8_t *buf,
> > > > > >                               int oob_required, int page)
> > > > > >  {
> > > > > > +     struct denali_nand_info *denali = to_denali(chip);
> > > > > >       struct mtd_info *mtd = nand_to_mtd(chip);
> > > > > > -     struct denali_nand_info *denali = mtd_to_denali(mtd);
> > > > > > -     int writesize = mtd->writesize;
> > > > > > -     int oobsize = mtd->oobsize;
> > > > > > -     int ecc_steps = chip->ecc.steps;
> > > > > > -     int ecc_size = chip->ecc.size;
> > > > > > -     int ecc_bytes = chip->ecc.bytes;
> > > > > >       void *tmp_buf = denali->buf;
> > > > > > -     int oob_skip = denali->oob_skip_bytes;
> > > > > > -     size_t size = writesize + oobsize;
> > > > > > -     int ret, i, pos, len;
> > > > > > +     size_t size = mtd->writesize + mtd->oobsize;
> > > > > > +     int ret;
> > > > > > +
> > > > > > +     if (!buf)
> > > > > > +             return -EINVAL;
> > > > > >
> > > > > >       ret = denali_data_xfer(chip, tmp_buf, size, page, 1, 0);
> > > > > >       if (ret)
> > > > > >               return ret;
> > > > > >
> > > > > > -     /* Arrange the buffer for syndrome payload/ecc layout */
> > > > > > -     if (buf) {
> > > > > > -             for (i = 0; i < ecc_steps; i++) {
> > > > > > -                     pos = i * (ecc_size + ecc_bytes);
> > > > > > -                     len = ecc_size;
> > > > > > -
> > > > > > -                     if (pos >= writesize)
> > > > > > -                             pos += oob_skip;
> > > > > > -                     else if (pos + len > writesize)
> > > > > > -                             len = writesize - pos;
> > > > > > -
> > > > > > -                     memcpy(buf, tmp_buf + pos, len);
> > > > > > -                     buf += len;
> > > > > > -                     if (len < ecc_size) {
> > > > > > -                             len = ecc_size - len;
> > > > > > -                             memcpy(buf, tmp_buf + writesize + oob_skip,
> > > > > > -                                    len);
> > > > > > -                             buf += len;
> > > > > > -                     }
> > > > > > -             }
> > > > > > -     }
> > > > > > +     ret = denali_raw_payload_op(chip, buf, denali_memcpy_in, tmp_buf);
> > > > >
> > > > > Honestly, I still don't like passing denali_memcpy_in/out as parameter.
> > > > >
> > > > > Besides that, once you'll have added helpers to avoid abusing the
> > > > > ternary operator in 4/9, the rest looks fine by me.
> > > > >
> > > >
> > > >
> > > > Do you have any suggestion?
> > >
> > > Maybe register these two helpers at probe as controller specific hooks,
> > > then just pass an in/out boolean to the function?
> > >
> >
> > Sorry, I do not understand.
> >
> > Are you suggesting to do like follows in probe ?
> >
> > denali->change_column_read_raw = denali_memcpy_in;
> > denali->change_column_write_raw = denali_memcpy_out;
> > denali->change_column_read_oob = denali_change_read_column_op;
> > denali->change_column_write_oob = denali_change_write_column_op;
> >
> >
> > All the 4 hooks are always needed
> > regardless of any probed features.
> >
> >
> > The result is just textual replacement
> > denali_* with denali->*.
> >
> > What's the point of copying fixed function addresses
> > to denali structure?
> >
> >
>
> What I don't like is the function pointer as a function parameter.

This is a usual way to handle callback.

> You
> can use the functions defined statically if you prefer as long as the
> parameter is just a boolean for instance?



I still do not understand your concern,
but if you ban the use of function pointer,
the following is the best I can do
since there are 4 hooks depending on the
combination of oob/raw, write/read.



if (oob) {
        if (write)
                return nand_change_write_column_op(chip, offset, buf,
                                                   len, false);
        else
                return nand_change_read_column_op(chip, offset, buf,
                                                  len, false);
}

if (write)
        memcpy(denali->buf + offset, buf, len);
else
        memcpy(buf, denali->buf + offset, len);

return 0;




BTW, when are .read_page_raw / .write_page_raw used?

Currently, I use "whole page access && memcpy" for better performance.

If those hooks are rarely used, I use
nand_change_write_column_op / nand_change_read_column_op,
which will reduce the if-conditional.




>
> Thanks,
> Miquèl
>
> ______________________________________________________
> Linux MTD discussion mailing list
> http://lists.infradead.org/mailman/listinfo/linux-mtd/
Miquel Raynal March 15, 2019, 8:34 a.m. UTC | #7
Hi Masahiro,

Masahiro Yamada <yamada.masahiro@socionext.com> wrote on Thu, 14 Mar
2019 17:24:41 +0900:

> Hi Miquel,
> 
> On Tue, Mar 12, 2019 at 10:13 PM Miquel Raynal
> <miquel.raynal@bootlin.com> wrote:
> >
> > Hi Masahiro,
> >
> > Masahiro Yamada <yamada.masahiro@socionext.com> wrote on Tue, 12 Mar
> > 2019 20:07:27 +0900:
> >  
> > > Hi Miquel,
> > >
> > >
> > > On Tue, Mar 12, 2019 at 7:54 PM Miquel Raynal <miquel.raynal@bootlin.com> wrote:  
> > > >
> > > > Hi Masahiro,
> > > >
> > > > Masahiro Yamada <yamada.masahiro@socionext.com> wrote on Tue, 12 Mar
> > > > 2019 19:51:21 +0900:
> > > >  
> > > > > On Tue, Mar 12, 2019 at 7:28 PM Miquel Raynal <miquel.raynal@bootlin.com> wrote:  
> > > > > >
> > > > > > Hi Masahiro,
> > > > > >
> > > > > > Masahiro Yamada <yamada.masahiro@socionext.com> wrote on Tue, 12 Mar
> > > > > > 2019 17:44:43 +0900:
> > > > > >  
> > > > > > > The Denali IP adopts the syndrome page layout (payload and ECC are
> > > > > > > interleaved). The *_page_raw() and *_oob() callbacks are complicated
> > > > > > > because they must hide the underlying layout used by the hardware,
> > > > > > > and always return contiguous in-band and out-of-band data.
> > > > > > >
> > > > > > > Currently, similar code is duplicated to reorganize the data layout.
> > > > > > > For example, denali_read_page_raw() and denali_write_page_raw() look
> > > > > > > almost the same.
> > > > > > >
> > > > > > > The idea for refactoring is to split the code into two parts:
> > > > > > >   [1] conversion of page layout
> > > > > > >   [2] what to do at every ECC chunk boundary
> > > > > > >
> > > > > > > For [1], I wrote denali_raw_payload_op() and denali_raw_oob_op().
> > > > > > > They manipulate data for the Denali controller's specific page layout
> > > > > > > of in-band, out-of-band, respectively.
> > > > > > >
> > > > > > > The difference between write and read is just the operation at
> > > > > > > ECC chunk boundaries. For example, denali_read_oob() calls
> > > > > > > nand_change_read_column_op(), whereas denali_write_oob() calls
> > > > > > > nand_change_write_column_op(). So, I implemented [2] as a callback
> > > > > > > passed into [1].
> > > > > > >
> > > > > > > Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
> > > > > > > ---
> > > > > > >  
> > > > > >
> > > > > > [...]
> > > > > >  
> > > > > > >  static int denali_read_page_raw(struct nand_chip *chip, uint8_t *buf,
> > > > > > >                               int oob_required, int page)
> > > > > > >  {
> > > > > > > +     struct denali_nand_info *denali = to_denali(chip);
> > > > > > >       struct mtd_info *mtd = nand_to_mtd(chip);
> > > > > > > -     struct denali_nand_info *denali = mtd_to_denali(mtd);
> > > > > > > -     int writesize = mtd->writesize;
> > > > > > > -     int oobsize = mtd->oobsize;
> > > > > > > -     int ecc_steps = chip->ecc.steps;
> > > > > > > -     int ecc_size = chip->ecc.size;
> > > > > > > -     int ecc_bytes = chip->ecc.bytes;
> > > > > > >       void *tmp_buf = denali->buf;
> > > > > > > -     int oob_skip = denali->oob_skip_bytes;
> > > > > > > -     size_t size = writesize + oobsize;
> > > > > > > -     int ret, i, pos, len;
> > > > > > > +     size_t size = mtd->writesize + mtd->oobsize;
> > > > > > > +     int ret;
> > > > > > > +
> > > > > > > +     if (!buf)
> > > > > > > +             return -EINVAL;
> > > > > > >
> > > > > > >       ret = denali_data_xfer(chip, tmp_buf, size, page, 1, 0);
> > > > > > >       if (ret)
> > > > > > >               return ret;
> > > > > > >
> > > > > > > -     /* Arrange the buffer for syndrome payload/ecc layout */
> > > > > > > -     if (buf) {
> > > > > > > -             for (i = 0; i < ecc_steps; i++) {
> > > > > > > -                     pos = i * (ecc_size + ecc_bytes);
> > > > > > > -                     len = ecc_size;
> > > > > > > -
> > > > > > > -                     if (pos >= writesize)
> > > > > > > -                             pos += oob_skip;
> > > > > > > -                     else if (pos + len > writesize)
> > > > > > > -                             len = writesize - pos;
> > > > > > > -
> > > > > > > -                     memcpy(buf, tmp_buf + pos, len);
> > > > > > > -                     buf += len;
> > > > > > > -                     if (len < ecc_size) {
> > > > > > > -                             len = ecc_size - len;
> > > > > > > -                             memcpy(buf, tmp_buf + writesize + oob_skip,
> > > > > > > -                                    len);
> > > > > > > -                             buf += len;
> > > > > > > -                     }
> > > > > > > -             }
> > > > > > > -     }
> > > > > > > +     ret = denali_raw_payload_op(chip, buf, denali_memcpy_in, tmp_buf);  
> > > > > >
> > > > > > Honestly, I still don't like passing denali_memcpy_in/out as parameter.
> > > > > >
> > > > > > Besides that, once you'll have added helpers to avoid abusing the
> > > > > > ternary operator in 4/9, the rest looks fine by me.
> > > > > >  
> > > > >
> > > > >
> > > > > Do you have any suggestion?  
> > > >
> > > > Maybe register these two helpers at probe as controller specific hooks,
> > > > then just pass an in/out boolean to the function?
> > > >  
> > >
> > > Sorry, I do not understand.
> > >
> > > Are you suggesting to do like follows in probe ?
> > >
> > > denali->change_column_read_raw = denali_memcpy_in;
> > > denali->change_column_write_raw = denali_memcpy_out;
> > > denali->change_column_read_oob = denali_change_read_column_op;
> > > denali->change_column_write_oob = denali_change_write_column_op;
> > >
> > >
> > > All the 4 hooks are always needed
> > > regardless of any probed features.
> > >
> > >
> > > The result is just textual replacement
> > > denali_* with denali->*.
> > >
> > > What's the point of copying fixed function addresses
> > > to denali structure?
> > >
> > >  
> >
> > What I don't like is the function pointer as a function parameter.  
> 
> This is a usual way to handle callback.
> 
> > You
> > can use the functions defined statically if you prefer as long as the
> > parameter is just a boolean for instance?  
> 
> 
> 
> I still do not understand your concern,
> but if you ban the use of function pointer,
> the following is the best I can do
> since there are 4 hooks depending on the
> combination of oob/raw, write/read.
> 
> 
> 
> if (oob) {
>         if (write)
>                 return nand_change_write_column_op(chip, offset, buf,
>                                                    len, false);
>         else
>                 return nand_change_read_column_op(chip, offset, buf,
>                                                   len, false);
> }
> 
> if (write)
>         memcpy(denali->buf + offset, buf, len);
> else
>         memcpy(buf, denali->buf + offset, len);
> 
> return 0;

No, I meant passing a boolean to denali_raw_payload_op() instead of a
function pointer. Then from denali_raw_payload_op(), intead of doing

ret = cb();
if (ret)
        ...

doing:

if (read)
        ret = denali_memcpy_in()
else
        ret = denali_memcpy_out()

if (ret)
        ...

But nevermind, if this is bothering you too much let's keep the current
form, it's fine.

> 
> 
> BTW, when are .read_page_raw / .write_page_raw used?

I'm not sure what is the question here but these hooks are important
and allow to test the driver. nandbiterrs use them (although we do
not care about the performance in these hooks).

> 
> Currently, I use "whole page access && memcpy" for better performance.
> 
> If those hooks are rarely used, I use
> nand_change_write_column_op / nand_change_read_column_op,
> which will reduce the if-conditional.

Yes you can. We do not care about performance in raw accessors.

Thanks,
Miquèl
Masahiro Yamada March 29, 2019, 7:02 a.m. UTC | #8
Hi Miquel,


On Fri, Mar 15, 2019 at 5:34 PM Miquel Raynal <miquel.raynal@bootlin.com> wrote:
>
> Hi Masahiro,
>
> Masahiro Yamada <yamada.masahiro@socionext.com> wrote on Thu, 14 Mar
> 2019 17:24:41 +0900:
>
> > Hi Miquel,
> >
> > On Tue, Mar 12, 2019 at 10:13 PM Miquel Raynal
> > <miquel.raynal@bootlin.com> wrote:
> > >
> > > Hi Masahiro,
> > >
> > > Masahiro Yamada <yamada.masahiro@socionext.com> wrote on Tue, 12 Mar
> > > 2019 20:07:27 +0900:
> > >
> > > > Hi Miquel,
> > > >
> > > >
> > > > On Tue, Mar 12, 2019 at 7:54 PM Miquel Raynal <miquel.raynal@bootlin.com> wrote:
> > > > >
> > > > > Hi Masahiro,
> > > > >
> > > > > Masahiro Yamada <yamada.masahiro@socionext.com> wrote on Tue, 12 Mar
> > > > > 2019 19:51:21 +0900:
> > > > >
> > > > > > On Tue, Mar 12, 2019 at 7:28 PM Miquel Raynal <miquel.raynal@bootlin.com> wrote:
> > > > > > >
> > > > > > > Hi Masahiro,
> > > > > > >
> > > > > > > Masahiro Yamada <yamada.masahiro@socionext.com> wrote on Tue, 12 Mar
> > > > > > > 2019 17:44:43 +0900:
> > > > > > >
> > > > > > > > The Denali IP adopts the syndrome page layout (payload and ECC are
> > > > > > > > interleaved). The *_page_raw() and *_oob() callbacks are complicated
> > > > > > > > because they must hide the underlying layout used by the hardware,
> > > > > > > > and always return contiguous in-band and out-of-band data.
> > > > > > > >
> > > > > > > > Currently, similar code is duplicated to reorganize the data layout.
> > > > > > > > For example, denali_read_page_raw() and denali_write_page_raw() look
> > > > > > > > almost the same.
> > > > > > > >
> > > > > > > > The idea for refactoring is to split the code into two parts:
> > > > > > > >   [1] conversion of page layout
> > > > > > > >   [2] what to do at every ECC chunk boundary
> > > > > > > >
> > > > > > > > For [1], I wrote denali_raw_payload_op() and denali_raw_oob_op().
> > > > > > > > They manipulate data for the Denali controller's specific page layout
> > > > > > > > of in-band, out-of-band, respectively.
> > > > > > > >
> > > > > > > > The difference between write and read is just the operation at
> > > > > > > > ECC chunk boundaries. For example, denali_read_oob() calls
> > > > > > > > nand_change_read_column_op(), whereas denali_write_oob() calls
> > > > > > > > nand_change_write_column_op(). So, I implemented [2] as a callback
> > > > > > > > passed into [1].
> > > > > > > >
> > > > > > > > Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
> > > > > > > > ---
> > > > > > > >
> > > > > > >
> > > > > > > [...]
> > > > > > >
> > > > > > > >  static int denali_read_page_raw(struct nand_chip *chip, uint8_t *buf,
> > > > > > > >                               int oob_required, int page)
> > > > > > > >  {
> > > > > > > > +     struct denali_nand_info *denali = to_denali(chip);
> > > > > > > >       struct mtd_info *mtd = nand_to_mtd(chip);
> > > > > > > > -     struct denali_nand_info *denali = mtd_to_denali(mtd);
> > > > > > > > -     int writesize = mtd->writesize;
> > > > > > > > -     int oobsize = mtd->oobsize;
> > > > > > > > -     int ecc_steps = chip->ecc.steps;
> > > > > > > > -     int ecc_size = chip->ecc.size;
> > > > > > > > -     int ecc_bytes = chip->ecc.bytes;
> > > > > > > >       void *tmp_buf = denali->buf;
> > > > > > > > -     int oob_skip = denali->oob_skip_bytes;
> > > > > > > > -     size_t size = writesize + oobsize;
> > > > > > > > -     int ret, i, pos, len;
> > > > > > > > +     size_t size = mtd->writesize + mtd->oobsize;
> > > > > > > > +     int ret;
> > > > > > > > +
> > > > > > > > +     if (!buf)
> > > > > > > > +             return -EINVAL;
> > > > > > > >
> > > > > > > >       ret = denali_data_xfer(chip, tmp_buf, size, page, 1, 0);
> > > > > > > >       if (ret)
> > > > > > > >               return ret;
> > > > > > > >
> > > > > > > > -     /* Arrange the buffer for syndrome payload/ecc layout */
> > > > > > > > -     if (buf) {
> > > > > > > > -             for (i = 0; i < ecc_steps; i++) {
> > > > > > > > -                     pos = i * (ecc_size + ecc_bytes);
> > > > > > > > -                     len = ecc_size;
> > > > > > > > -
> > > > > > > > -                     if (pos >= writesize)
> > > > > > > > -                             pos += oob_skip;
> > > > > > > > -                     else if (pos + len > writesize)
> > > > > > > > -                             len = writesize - pos;
> > > > > > > > -
> > > > > > > > -                     memcpy(buf, tmp_buf + pos, len);
> > > > > > > > -                     buf += len;
> > > > > > > > -                     if (len < ecc_size) {
> > > > > > > > -                             len = ecc_size - len;
> > > > > > > > -                             memcpy(buf, tmp_buf + writesize + oob_skip,
> > > > > > > > -                                    len);
> > > > > > > > -                             buf += len;
> > > > > > > > -                     }
> > > > > > > > -             }
> > > > > > > > -     }
> > > > > > > > +     ret = denali_raw_payload_op(chip, buf, denali_memcpy_in, tmp_buf);
> > > > > > >
> > > > > > > Honestly, I still don't like passing denali_memcpy_in/out as parameter.
> > > > > > >
> > > > > > > Besides that, once you'll have added helpers to avoid abusing the
> > > > > > > ternary operator in 4/9, the rest looks fine by me.
> > > > > > >
> > > > > >
> > > > > >
> > > > > > Do you have any suggestion?
> > > > >
> > > > > Maybe register these two helpers at probe as controller specific hooks,
> > > > > then just pass an in/out boolean to the function?
> > > > >
> > > >
> > > > Sorry, I do not understand.
> > > >
> > > > Are you suggesting to do like follows in probe ?
> > > >
> > > > denali->change_column_read_raw = denali_memcpy_in;
> > > > denali->change_column_write_raw = denali_memcpy_out;
> > > > denali->change_column_read_oob = denali_change_read_column_op;
> > > > denali->change_column_write_oob = denali_change_write_column_op;
> > > >
> > > >
> > > > All the 4 hooks are always needed
> > > > regardless of any probed features.
> > > >
> > > >
> > > > The result is just textual replacement
> > > > denali_* with denali->*.
> > > >
> > > > What's the point of copying fixed function addresses
> > > > to denali structure?
> > > >
> > > >
> > >
> > > What I don't like is the function pointer as a function parameter.
> >
> > This is a usual way to handle callback.
> >
> > > You
> > > can use the functions defined statically if you prefer as long as the
> > > parameter is just a boolean for instance?
> >
> >
> >
> > I still do not understand your concern,
> > but if you ban the use of function pointer,
> > the following is the best I can do
> > since there are 4 hooks depending on the
> > combination of oob/raw, write/read.
> >
> >
> >
> > if (oob) {
> >         if (write)
> >                 return nand_change_write_column_op(chip, offset, buf,
> >                                                    len, false);
> >         else
> >                 return nand_change_read_column_op(chip, offset, buf,
> >                                                   len, false);
> > }
> >
> > if (write)
> >         memcpy(denali->buf + offset, buf, len);
> > else
> >         memcpy(buf, denali->buf + offset, len);
> >
> > return 0;
>
> No, I meant passing a boolean to denali_raw_payload_op() instead of a
> function pointer. Then from denali_raw_payload_op(), intead of doing
>
> ret = cb();
> if (ret)
>         ...
>
> doing:
>
> if (read)
>         ret = denali_memcpy_in()
> else
>         ret = denali_memcpy_out()
>
> if (ret)
>         ...


If you look at my code closely,
you will notice 4 callbacks passed in
denali_raw_payload_op().

So, if-conditional would end up like follows:


    if (oob) {
            if (write)
                    ret = nand_change_write_column_op(chip, offset, buf,
                                                   len, false);
            else
                    ret = nand_change_read_column_op(chip, offset, buf,
                                                  len, false);
            if (ret)
                    return ret;
     } else {
            if (write)
                  memcpy(denali->buf + offset, buf, len);
            else
                  memcpy(buf, denali->buf + offset, len);
     }


This is extremely ugly.
That's why I passed a function pointer
instead of two boolean parameters 'oob', 'write'.




> But nevermind, if this is bothering you too much let's keep the current
> form, it's fine.
>
> >
> >
> > BTW, when are .read_page_raw / .write_page_raw used?
>
> I'm not sure what is the question here but these hooks are important
> and allow to test the driver. nandbiterrs use them (although we do
> not care about the performance in these hooks).


Currently, I use DMA transfer + memcpy()
in order to get better performance for
.read_page_raw() and .write_page_raw()


nand_change_write_column_op() and
nand_change_read_column_op() are slow
since they are low-level hardware accesses.


If we do not have to care about the performance,
I will only use nand_change_{write,read}_column_op().




> >
> > Currently, I use "whole page access && memcpy" for better performance.
> >
> > If those hooks are rarely used, I use
> > nand_change_write_column_op / nand_change_read_column_op,
> > which will reduce the if-conditional.
>
> Yes you can. We do not care about performance in raw accessors.

OK, I will do this in v4.




> Thanks,
> Miquèl
>
> ______________________________________________________
> Linux MTD discussion mailing list
> http://lists.infradead.org/mailman/listinfo/linux-mtd/
diff mbox series

Patch

diff --git a/drivers/mtd/nand/raw/denali.c b/drivers/mtd/nand/raw/denali.c
index 4ac1314..ebeedbd 100644
--- a/drivers/mtd/nand/raw/denali.c
+++ b/drivers/mtd/nand/raw/denali.c
@@ -608,159 +608,236 @@  static int denali_data_xfer(struct nand_chip *chip, void *buf, size_t size,
 		return denali_pio_xfer(denali, buf, size, page, write);
 }
 
-static void denali_oob_xfer(struct mtd_info *mtd, struct nand_chip *chip,
-			    int page, int write)
+typedef int denali_change_column_callback(void *buf, unsigned int offset,
+					  unsigned int len, void *priv);
+
+/**
+ * denali_raw_payload_op() - arrange the payload data for syndrome layout
+ *
+ * The NAND framework passes the payload and ECC separately, but the Denali ECC
+ * engine cannot handle it directly because it writes/reads page data in the
+ * syndrome layout (payload and ECC are interleaved). This helper is useful to
+ * convert the layout between the two formats.
+ *
+ * @chip: NAND chip structure
+ * @buf:  buffer passed from the NAND framework
+ * @cb:   callback invoked whenever the column address is changed
+ * @priv: private data passed to @cb
+ */
+static int denali_raw_payload_op(struct nand_chip *chip, void *buf,
+				 denali_change_column_callback *cb, void *priv)
 {
-	struct denali_nand_info *denali = mtd_to_denali(mtd);
+	struct denali_nand_info *denali = to_denali(chip);
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	struct nand_ecc_ctrl *ecc = &chip->ecc;
+	int writesize = mtd->writesize;
+	int oob_skip = denali->oob_skip_bytes;
+	int ret, i, pos, len;
+
+	for (i = 0; i < ecc->steps; i++) {
+		pos = i * (ecc->size + ecc->bytes);
+		len = ecc->size;
+
+		if (pos >= writesize) {
+			pos += oob_skip;
+		} else if (pos + len > writesize) {
+			/* This chunk overwraps the BBM area. Must be split */
+			ret = cb(buf, pos, writesize - pos, priv);
+			if (ret)
+				return ret;
+
+			buf += writesize - pos;
+			len -= writesize - pos;
+			pos = writesize + oob_skip;
+		}
+
+		ret = cb(buf, pos, len, priv);
+		if (ret)
+			return ret;
+
+		buf += len;
+	}
+
+	return 0;
+}
+
+/**
+ * denali_raw_oob_op() - arrange the oob data for syndrome layout
+ *
+ * The NAND framework passes the payload and ECC separately, but the Denali ECC
+ * engine cannot handle it directly because it writes/reads page data in the
+ * syndrome layout (payload and ECC are interleaved). This helper is useful to
+ * convert the layout between the two formats.
+ *
+ * @chip: NAND chip structure
+ * @buf:  buffer passed from the NAND framework
+ * @cb:   callback invoked whenever the column address is changed
+ * @priv: private data passed to @cb
+ */
+static int denali_raw_oob_op(struct nand_chip *chip, void *buf,
+			     denali_change_column_callback *cb, void *priv)
+{
+	struct denali_nand_info *denali = to_denali(chip);
+	struct mtd_info *mtd = nand_to_mtd(chip);
+	struct nand_ecc_ctrl *ecc = &chip->ecc;
 	int writesize = mtd->writesize;
 	int oobsize = mtd->oobsize;
-	uint8_t *bufpoi = chip->oob_poi;
-	int ecc_steps = chip->ecc.steps;
-	int ecc_size = chip->ecc.size;
-	int ecc_bytes = chip->ecc.bytes;
 	int oob_skip = denali->oob_skip_bytes;
-	size_t size = writesize + oobsize;
-	int i, pos, len;
+	int ret, i, pos, len;
 
 	/* BBM at the beginning of the OOB area */
-	if (write)
-		nand_prog_page_begin_op(chip, page, writesize, bufpoi,
-					oob_skip);
-	else
-		nand_read_page_op(chip, page, writesize, bufpoi, oob_skip);
-	bufpoi += oob_skip;
+	ret = cb(buf, writesize, oob_skip, priv);
+	if (ret)
+		return ret;
 
-	/* OOB ECC */
-	for (i = 0; i < ecc_steps; i++) {
-		pos = ecc_size + i * (ecc_size + ecc_bytes);
-		len = ecc_bytes;
+	buf += oob_skip;
 
-		if (pos >= writesize)
-			pos += oob_skip;
-		else if (pos + len > writesize)
-			len = writesize - pos;
+	for (i = 0; i < ecc->steps; i++) {
+		pos = ecc->size + i * (ecc->size + ecc->bytes);
 
-		if (write)
-			nand_change_write_column_op(chip, pos, bufpoi, len,
-						    false);
+		if (i == ecc->steps - 1)
+			/* The last chunk includes OOB free */
+			len = writesize + oobsize - pos - oob_skip;
 		else
-			nand_change_read_column_op(chip, pos, bufpoi, len,
-						   false);
-		bufpoi += len;
-		if (len < ecc_bytes) {
-			len = ecc_bytes - len;
-			if (write)
-				nand_change_write_column_op(chip, writesize +
-							    oob_skip, bufpoi,
-							    len, false);
-			else
-				nand_change_read_column_op(chip, writesize +
-							   oob_skip, bufpoi,
-							   len, false);
-			bufpoi += len;
+			len = ecc->bytes;
+
+		if (pos >= writesize) {
+			pos += oob_skip;
+		} else if (pos + len > writesize) {
+			/* This chunk overwraps the BBM area. Must be split */
+			ret = cb(buf, pos, writesize - pos, priv);
+			if (ret)
+				return ret;
+
+			buf += writesize - pos;
+			len -= writesize - pos;
+			pos = writesize + oob_skip;
 		}
+
+		ret = cb(buf, pos, len, priv);
+		if (ret)
+			return ret;
+
+		buf += len;
 	}
 
-	/* OOB free */
-	len = oobsize - (bufpoi - chip->oob_poi);
-	if (write)
-		nand_change_write_column_op(chip, size - len, bufpoi, len,
-					    false);
-	else
-		nand_change_read_column_op(chip, size - len, bufpoi, len,
-					   false);
+	return 0;
+}
+
+static int denali_memcpy_in(void *buf, unsigned int offset, unsigned int len,
+			    void *priv)
+{
+	memcpy(buf, priv + offset, len);
+	return 0;
 }
 
 static int denali_read_page_raw(struct nand_chip *chip, uint8_t *buf,
 				int oob_required, int page)
 {
+	struct denali_nand_info *denali = to_denali(chip);
 	struct mtd_info *mtd = nand_to_mtd(chip);
-	struct denali_nand_info *denali = mtd_to_denali(mtd);
-	int writesize = mtd->writesize;
-	int oobsize = mtd->oobsize;
-	int ecc_steps = chip->ecc.steps;
-	int ecc_size = chip->ecc.size;
-	int ecc_bytes = chip->ecc.bytes;
 	void *tmp_buf = denali->buf;
-	int oob_skip = denali->oob_skip_bytes;
-	size_t size = writesize + oobsize;
-	int ret, i, pos, len;
+	size_t size = mtd->writesize + mtd->oobsize;
+	int ret;
+
+	if (!buf)
+		return -EINVAL;
 
 	ret = denali_data_xfer(chip, tmp_buf, size, page, 1, 0);
 	if (ret)
 		return ret;
 
-	/* Arrange the buffer for syndrome payload/ecc layout */
-	if (buf) {
-		for (i = 0; i < ecc_steps; i++) {
-			pos = i * (ecc_size + ecc_bytes);
-			len = ecc_size;
-
-			if (pos >= writesize)
-				pos += oob_skip;
-			else if (pos + len > writesize)
-				len = writesize - pos;
-
-			memcpy(buf, tmp_buf + pos, len);
-			buf += len;
-			if (len < ecc_size) {
-				len = ecc_size - len;
-				memcpy(buf, tmp_buf + writesize + oob_skip,
-				       len);
-				buf += len;
-			}
-		}
-	}
+	ret = denali_raw_payload_op(chip, buf, denali_memcpy_in, tmp_buf);
+	if (ret)
+		return ret;
 
 	if (oob_required) {
-		uint8_t *oob = chip->oob_poi;
-
-		/* BBM at the beginning of the OOB area */
-		memcpy(oob, tmp_buf + writesize, oob_skip);
-		oob += oob_skip;
-
-		/* OOB ECC */
-		for (i = 0; i < ecc_steps; i++) {
-			pos = ecc_size + i * (ecc_size + ecc_bytes);
-			len = ecc_bytes;
-
-			if (pos >= writesize)
-				pos += oob_skip;
-			else if (pos + len > writesize)
-				len = writesize - pos;
-
-			memcpy(oob, tmp_buf + pos, len);
-			oob += len;
-			if (len < ecc_bytes) {
-				len = ecc_bytes - len;
-				memcpy(oob, tmp_buf + writesize + oob_skip,
-				       len);
-				oob += len;
-			}
-		}
-
-		/* OOB free */
-		len = oobsize - (oob - chip->oob_poi);
-		memcpy(oob, tmp_buf + size - len, len);
+		ret = denali_raw_oob_op(chip, chip->oob_poi, denali_memcpy_in,
+					tmp_buf);
+		if (ret)
+			return ret;
 	}
 
 	return 0;
 }
 
-static int denali_read_oob(struct nand_chip *chip, int page)
+static int denali_memcpy_out(void *buf, unsigned int offset, unsigned int len,
+			     void *priv)
 {
+	memcpy(priv + offset, buf, len);
+	return 0;
+}
+
+static int denali_write_page_raw(struct nand_chip *chip, const uint8_t *buf,
+				 int oob_required, int page)
+{
+	struct denali_nand_info *denali = to_denali(chip);
 	struct mtd_info *mtd = nand_to_mtd(chip);
+	void *tmp_buf = denali->buf;
+	size_t size = mtd->writesize + mtd->oobsize;
+	int ret;
 
-	denali_oob_xfer(mtd, chip, page, 0);
+	if (!buf)
+		return -EINVAL;
 
-	return 0;
+	/*
+	 * Fill the buffer with 0xff first except the full page transfer.
+	 * This simplifies the logic.
+	 */
+	if (!oob_required)
+		memset(tmp_buf, 0xff, size);
+
+	ret = denali_raw_payload_op(chip, (void *)buf, denali_memcpy_out,
+				    tmp_buf);
+	if (ret)
+		return ret;
+
+	if (oob_required) {
+		ret = denali_raw_oob_op(chip, chip->oob_poi, denali_memcpy_out,
+					tmp_buf);
+		if (ret)
+			return ret;
+	}
+
+	return denali_data_xfer(chip, tmp_buf, size, page, 1, 1);
+}
+
+static int denali_change_read_column_op(void *buf, unsigned int offset,
+					unsigned int len, void *priv)
+{
+	return nand_change_read_column_op(priv, offset, buf, len, false);
+}
+
+static int denali_read_oob(struct nand_chip *chip, int page)
+{
+	int ret;
+
+	ret = nand_read_page_op(chip, page, 0, NULL, 0);
+	if (ret)
+		return ret;
+
+	return denali_raw_oob_op(chip, chip->oob_poi,
+				 denali_change_read_column_op, chip);
+}
+
+static int denali_change_write_column_op(void *buf, unsigned int offset,
+					 unsigned int len, void *priv)
+{
+	return nand_change_write_column_op(priv, offset, buf, len, false);
 }
 
 static int denali_write_oob(struct nand_chip *chip, int page)
 {
-	struct mtd_info *mtd = nand_to_mtd(chip);
+	int ret;
+
+	ret = nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+	if (ret)
+		return ret;
 
-	denali_oob_xfer(mtd, chip, page, 1);
+	ret = denali_raw_oob_op(chip, chip->oob_poi,
+				denali_change_write_column_op, chip);
+	if (ret)
+		return ret;
 
 	return nand_prog_page_end_op(chip);
 }
@@ -798,85 +875,6 @@  static int denali_read_page(struct nand_chip *chip, uint8_t *buf,
 	return stat;
 }
 
-static int denali_write_page_raw(struct nand_chip *chip, const uint8_t *buf,
-				 int oob_required, int page)
-{
-	struct mtd_info *mtd = nand_to_mtd(chip);
-	struct denali_nand_info *denali = mtd_to_denali(mtd);
-	int writesize = mtd->writesize;
-	int oobsize = mtd->oobsize;
-	int ecc_steps = chip->ecc.steps;
-	int ecc_size = chip->ecc.size;
-	int ecc_bytes = chip->ecc.bytes;
-	void *tmp_buf = denali->buf;
-	int oob_skip = denali->oob_skip_bytes;
-	size_t size = writesize + oobsize;
-	int i, pos, len;
-
-	/*
-	 * Fill the buffer with 0xff first except the full page transfer.
-	 * This simplifies the logic.
-	 */
-	if (!buf || !oob_required)
-		memset(tmp_buf, 0xff, size);
-
-	/* Arrange the buffer for syndrome payload/ecc layout */
-	if (buf) {
-		for (i = 0; i < ecc_steps; i++) {
-			pos = i * (ecc_size + ecc_bytes);
-			len = ecc_size;
-
-			if (pos >= writesize)
-				pos += oob_skip;
-			else if (pos + len > writesize)
-				len = writesize - pos;
-
-			memcpy(tmp_buf + pos, buf, len);
-			buf += len;
-			if (len < ecc_size) {
-				len = ecc_size - len;
-				memcpy(tmp_buf + writesize + oob_skip, buf,
-				       len);
-				buf += len;
-			}
-		}
-	}
-
-	if (oob_required) {
-		const uint8_t *oob = chip->oob_poi;
-
-		/* BBM at the beginning of the OOB area */
-		memcpy(tmp_buf + writesize, oob, oob_skip);
-		oob += oob_skip;
-
-		/* OOB ECC */
-		for (i = 0; i < ecc_steps; i++) {
-			pos = ecc_size + i * (ecc_size + ecc_bytes);
-			len = ecc_bytes;
-
-			if (pos >= writesize)
-				pos += oob_skip;
-			else if (pos + len > writesize)
-				len = writesize - pos;
-
-			memcpy(tmp_buf + pos, oob, len);
-			oob += len;
-			if (len < ecc_bytes) {
-				len = ecc_bytes - len;
-				memcpy(tmp_buf + writesize + oob_skip, oob,
-				       len);
-				oob += len;
-			}
-		}
-
-		/* OOB free */
-		len = oobsize - (oob - chip->oob_poi);
-		memcpy(tmp_buf + size - len, oob, len);
-	}
-
-	return denali_data_xfer(chip, tmp_buf, size, page, 1, 1);
-}
-
 static int denali_write_page(struct nand_chip *chip, const uint8_t *buf,
 			     int oob_required, int page)
 {