mtd: rawnand: denali: refactor raw page accessors

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.

The Denali IP cannot reuse nand_{read,write}_page_raw_syndrome()
in nand_base.c because its hardware ECC engine skips some of first
bytes in OOB. That is why this driver implements specially-crafted
*_page_raw() and *_oob() hooks.

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 complexity is partly due to the DMA transfer
used for better performance of *_page_raw() accessors.

On second thought, we do not need to care about their performance
because MTD_OPS_RAW is rarely used.

Let's focus on code cleanups rather than the performance. This commit
removes the internal buffer for DMA, and factors out as much code as
possible.

Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
This commit is contained in:
Masahiro Yamada 2019-04-02 13:03:02 +09:00 committed by Miquel Raynal
parent 750f69b826
commit 0e604fc9cf
2 changed files with 182 additions and 265 deletions

View File

@ -287,6 +287,182 @@ static void denali_cmd_ctrl(struct nand_chip *chip, int dat, unsigned int ctrl)
denali->host_write(denali, DENALI_BANK(denali) | type, dat); denali->host_write(denali, DENALI_BANK(denali) | type, dat);
} }
static int denali_change_column(struct nand_chip *chip, unsigned int offset,
void *buf, unsigned int len, bool write)
{
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);
}
static int denali_payload_xfer(struct nand_chip *chip, void *buf, bool write)
{
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 = denali_change_column(chip, pos, buf,
writesize - pos, write);
if (ret)
return ret;
buf += writesize - pos;
len -= writesize - pos;
pos = writesize + oob_skip;
}
ret = denali_change_column(chip, pos, buf, len, write);
if (ret)
return ret;
buf += len;
}
return 0;
}
static int denali_oob_xfer(struct nand_chip *chip, void *buf, bool write)
{
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;
int oob_skip = denali->oob_skip_bytes;
int ret, i, pos, len;
/* BBM at the beginning of the OOB area */
ret = denali_change_column(chip, writesize, buf, oob_skip, write);
if (ret)
return ret;
buf += oob_skip;
for (i = 0; i < ecc->steps; i++) {
pos = ecc->size + i * (ecc->size + ecc->bytes);
if (i == ecc->steps - 1)
/* The last chunk includes OOB free */
len = writesize + oobsize - pos - oob_skip;
else
len = ecc->bytes;
if (pos >= writesize) {
pos += oob_skip;
} else if (pos + len > writesize) {
/* This chunk overwraps the BBM area. Must be split */
ret = denali_change_column(chip, pos, buf,
writesize - pos, write);
if (ret)
return ret;
buf += writesize - pos;
len -= writesize - pos;
pos = writesize + oob_skip;
}
ret = denali_change_column(chip, pos, buf, len, write);
if (ret)
return ret;
buf += len;
}
return 0;
}
static int denali_read_raw(struct nand_chip *chip, void *buf, void *oob_buf,
int page)
{
int ret;
if (!buf && !oob_buf)
return -EINVAL;
ret = nand_read_page_op(chip, page, 0, NULL, 0);
if (ret)
return ret;
if (buf) {
ret = denali_payload_xfer(chip, buf, false);
if (ret)
return ret;
}
if (oob_buf) {
ret = denali_oob_xfer(chip, oob_buf, false);
if (ret)
return ret;
}
return 0;
}
static int denali_write_raw(struct nand_chip *chip, const void *buf,
const void *oob_buf, int page)
{
int ret;
if (!buf && !oob_buf)
return -EINVAL;
ret = nand_prog_page_begin_op(chip, page, 0, NULL, 0);
if (ret)
return ret;
if (buf) {
ret = denali_payload_xfer(chip, (void *)buf, true);
if (ret)
return ret;
}
if (oob_buf) {
ret = denali_oob_xfer(chip, (void *)oob_buf, true);
if (ret)
return ret;
}
return nand_prog_page_end_op(chip);
}
static int denali_read_page_raw(struct nand_chip *chip, u8 *buf,
int oob_required, int page)
{
return denali_read_raw(chip, buf, oob_required ? chip->oob_poi : NULL,
page);
}
static int denali_write_page_raw(struct nand_chip *chip, const u8 *buf,
int oob_required, int page)
{
return denali_write_raw(chip, buf, oob_required ? chip->oob_poi : NULL,
page);
}
static int denali_read_oob(struct nand_chip *chip, int page)
{
return denali_read_raw(chip, NULL, chip->oob_poi, page);
}
static int denali_write_oob(struct nand_chip *chip, int page)
{
return denali_write_raw(chip, NULL, chip->oob_poi, page);
}
static int denali_check_erased_page(struct nand_chip *chip, u8 *buf, static int denali_check_erased_page(struct nand_chip *chip, u8 *buf,
unsigned long uncor_ecc_flags, unsigned long uncor_ecc_flags,
unsigned int max_bitflips) unsigned int max_bitflips)
@ -593,178 +769,17 @@ static int denali_dma_xfer(struct denali_nand_info *denali, void *buf,
return ret; return ret;
} }
static int denali_data_xfer(struct nand_chip *chip, void *buf, size_t size, static int denali_page_xfer(struct nand_chip *chip, void *buf, size_t size,
int page, int raw, int write) int page, int write)
{ {
struct denali_nand_info *denali = to_denali(chip); struct denali_nand_info *denali = to_denali(chip);
iowrite32(raw ? 0 : ECC_ENABLE__FLAG, denali->reg + ECC_ENABLE);
iowrite32(raw ? TRANSFER_SPARE_REG__FLAG : 0,
denali->reg + TRANSFER_SPARE_REG);
if (denali->dma_avail) if (denali->dma_avail)
return denali_dma_xfer(denali, buf, size, page, write); return denali_dma_xfer(denali, buf, size, page, write);
else else
return denali_pio_xfer(denali, buf, size, page, write); 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)
{
struct denali_nand_info *denali = mtd_to_denali(mtd);
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;
/* 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;
/* 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;
if (write)
nand_change_write_column_op(chip, pos, bufpoi, len,
false);
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;
}
}
/* 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);
}
static int denali_read_page_raw(struct nand_chip *chip, 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 ret, i, pos, len;
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;
}
}
}
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);
}
return 0;
}
static int denali_read_oob(struct nand_chip *chip, int page)
{
struct mtd_info *mtd = nand_to_mtd(chip);
denali_oob_xfer(mtd, chip, page, 0);
return 0;
}
static int denali_write_oob(struct nand_chip *chip, int page)
{
struct mtd_info *mtd = nand_to_mtd(chip);
denali_oob_xfer(mtd, chip, page, 1);
return nand_prog_page_end_op(chip);
}
static int denali_read_page(struct nand_chip *chip, uint8_t *buf, static int denali_read_page(struct nand_chip *chip, uint8_t *buf,
int oob_required, int page) int oob_required, int page)
{ {
@ -774,7 +789,7 @@ static int denali_read_page(struct nand_chip *chip, uint8_t *buf,
int stat = 0; int stat = 0;
int ret; int ret;
ret = denali_data_xfer(chip, buf, mtd->writesize, page, 0, 0); ret = denali_page_xfer(chip, buf, mtd->writesize, page, 0);
if (ret && ret != -EBADMSG) if (ret && ret != -EBADMSG)
return ret; return ret;
@ -798,92 +813,12 @@ static int denali_read_page(struct nand_chip *chip, uint8_t *buf,
return stat; 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, static int denali_write_page(struct nand_chip *chip, const uint8_t *buf,
int oob_required, int page) int oob_required, int page)
{ {
struct mtd_info *mtd = nand_to_mtd(chip); struct mtd_info *mtd = nand_to_mtd(chip);
return denali_data_xfer(chip, (void *)buf, mtd->writesize, page, return denali_page_xfer(chip, (void *)buf, mtd->writesize, page, 1);
0, 1);
} }
static void denali_select_chip(struct nand_chip *chip, int cs) static void denali_select_chip(struct nand_chip *chip, int cs)
@ -1051,9 +986,10 @@ static void denali_hw_init(struct denali_nand_info *denali)
} }
denali_detect_max_banks(denali); denali_detect_max_banks(denali);
iowrite32(0, denali->reg + TRANSFER_SPARE_REG);
iowrite32(0x0F, denali->reg + RB_PIN_ENABLED); iowrite32(0x0F, denali->reg + RB_PIN_ENABLED);
iowrite32(CHIP_EN_DONT_CARE__FLAG, denali->reg + CHIP_ENABLE_DONT_CARE); iowrite32(CHIP_EN_DONT_CARE__FLAG, denali->reg + CHIP_ENABLE_DONT_CARE);
iowrite32(ECC_ENABLE__FLAG, denali->reg + ECC_ENABLE);
iowrite32(0xffff, denali->reg + SPARE_AREA_MARKER); iowrite32(0xffff, denali->reg + SPARE_AREA_MARKER);
} }
@ -1237,29 +1173,11 @@ static int denali_attach_chip(struct nand_chip *chip)
if (ret) if (ret)
return ret; return ret;
/*
* This buffer is DMA-mapped by denali_{read,write}_page_raw. Do not
* use devm_kmalloc() because the memory allocated by devm_ does not
* guarantee DMA-safe alignment.
*/
denali->buf = kmalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL);
if (!denali->buf)
return -ENOMEM;
return 0; return 0;
} }
static void denali_detach_chip(struct nand_chip *chip)
{
struct mtd_info *mtd = nand_to_mtd(chip);
struct denali_nand_info *denali = mtd_to_denali(mtd);
kfree(denali->buf);
}
static const struct nand_controller_ops denali_controller_ops = { static const struct nand_controller_ops denali_controller_ops = {
.attach_chip = denali_attach_chip, .attach_chip = denali_attach_chip,
.detach_chip = denali_detach_chip,
.setup_data_interface = denali_setup_data_interface, .setup_data_interface = denali_setup_data_interface,
}; };

View File

@ -303,7 +303,6 @@ struct denali_nand_info {
u32 irq_mask; /* interrupts we are waiting for */ u32 irq_mask; /* interrupts we are waiting for */
u32 irq_status; /* interrupts that have happened */ u32 irq_status; /* interrupts that have happened */
int irq; int irq;
void *buf; /* for syndrome layout conversion */
int dma_avail; /* can support DMA? */ int dma_avail; /* can support DMA? */
int devs_per_cs; /* devices connected in parallel */ int devs_per_cs; /* devices connected in parallel */
int oob_skip_bytes; /* number of bytes reserved for BBM */ int oob_skip_bytes; /* number of bytes reserved for BBM */