mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 06:44:00 -04:00
mtd: rawnand: sunxi: introduce maximize variable user data length
In Allwinner SoCs, user data can be added in OOB before each ECC data.
For older SoCs like A10, the user data size was the size of a register
(4 bytes) and was mandatory before each ECC step.
So, the A10 OOB Layout is:
[4Bytes USER_DATA_STEP0] [ECC_STEP0 bytes]
[4bytes USER_DATA_STEP1] [ECC_STEP1 bytes]
...
NB: the BBM is stored at the beginning of the USER_DATA_STEP0.
Now, for H6/H616 NAND flash controller, this user data can have a
different size for each step.
So, we are maximizing the user data length to use as many OOB bytes as
possible.
Fixes: 88fd4e4dea ("mtd: rawnand: sunxi: Add support for H616 nand controller")
Signed-off-by: Richard Genoud <richard.genoud@bootlin.com>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
This commit is contained in:
committed by
Miquel Raynal
parent
a22f40d9eb
commit
54dcd6aa69
@@ -209,9 +209,8 @@
|
||||
|
||||
/*
|
||||
* On A10/A23, this is the size of the NDFC User Data Register, containing the
|
||||
* mandatory user data bytes following the ECC for each ECC step.
|
||||
* mandatory user data bytes preceding the ECC for each ECC step.
|
||||
* Thus, for each ECC step, we need the ECC bytes + USER_DATA_SZ.
|
||||
* Those bits are currently unsused, and kept as default value 0xffffffff.
|
||||
*
|
||||
* On H6/H616, this size became configurable, from 0 bytes to 32, via the
|
||||
* USER_DATA_LEN registers.
|
||||
@@ -249,6 +248,7 @@ struct sunxi_nand_hw_ecc {
|
||||
* @timing_ctl: TIMING_CTL register value for this NAND chip
|
||||
* @nsels: number of CS lines required by the NAND chip
|
||||
* @sels: array of CS lines descriptions
|
||||
* @user_data_bytes: array of user data lengths for all ECC steps
|
||||
*/
|
||||
struct sunxi_nand_chip {
|
||||
struct list_head node;
|
||||
@@ -257,6 +257,7 @@ struct sunxi_nand_chip {
|
||||
unsigned long clk_rate;
|
||||
u32 timing_cfg;
|
||||
u32 timing_ctl;
|
||||
u8 *user_data_bytes;
|
||||
int nsels;
|
||||
struct sunxi_nand_chip_sel sels[] __counted_by(nsels);
|
||||
};
|
||||
@@ -823,12 +824,50 @@ static inline u32 sunxi_nfc_buf_to_user_data(const u8 *buf)
|
||||
return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
|
||||
}
|
||||
|
||||
static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob,
|
||||
int step, bool bbm, int page)
|
||||
static u8 sunxi_nfc_user_data_sz(struct sunxi_nand_chip *sunxi_nand, int step)
|
||||
{
|
||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
||||
if (!sunxi_nand->user_data_bytes)
|
||||
return USER_DATA_SZ;
|
||||
|
||||
sunxi_nfc_user_data_to_buf(readl(nfc->regs + NFC_REG_USER_DATA(nfc, step)), oob);
|
||||
return sunxi_nand->user_data_bytes[step];
|
||||
}
|
||||
|
||||
static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob,
|
||||
int step, bool bbm, int page,
|
||||
unsigned int user_data_sz)
|
||||
{
|
||||
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
||||
u32 user_data;
|
||||
|
||||
if (!nfc->caps->reg_user_data_len) {
|
||||
/*
|
||||
* For A10, the user data for step n is in the nth
|
||||
* REG_USER_DATA
|
||||
*/
|
||||
user_data = readl(nfc->regs + NFC_REG_USER_DATA(nfc, step));
|
||||
sunxi_nfc_user_data_to_buf(user_data, oob);
|
||||
} else {
|
||||
/*
|
||||
* For H6 NAND controller, the user data for all steps is
|
||||
* contained in 32 user data registers, but not at a specific
|
||||
* offset for each step, they are just concatenated.
|
||||
*/
|
||||
unsigned int user_data_off = 0;
|
||||
unsigned int reg_off;
|
||||
u8 *ptr = oob;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < step; i++)
|
||||
user_data_off += sunxi_nfc_user_data_sz(sunxi_nand, i);
|
||||
|
||||
user_data_off /= 4;
|
||||
for (i = 0; i < user_data_sz / 4; i++, ptr += 4) {
|
||||
reg_off = NFC_REG_USER_DATA(nfc, user_data_off + i);
|
||||
user_data = readl(nfc->regs + reg_off);
|
||||
sunxi_nfc_user_data_to_buf(user_data, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/* De-randomize the Bad Block Marker. */
|
||||
if (bbm && (nand->options & NAND_NEED_SCRAMBLING))
|
||||
@@ -887,17 +926,46 @@ static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand,
|
||||
bool bbm, int page)
|
||||
{
|
||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
||||
u8 user_data[USER_DATA_SZ];
|
||||
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||
unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
|
||||
u8 *user_data = NULL;
|
||||
|
||||
/* Randomize the Bad Block Marker. */
|
||||
if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) {
|
||||
memcpy(user_data, oob, sizeof(user_data));
|
||||
user_data = kmalloc(user_data_sz, GFP_KERNEL);
|
||||
memcpy(user_data, oob, user_data_sz);
|
||||
sunxi_nfc_randomize_bbm(nand, page, user_data);
|
||||
oob = user_data;
|
||||
}
|
||||
|
||||
writel(sunxi_nfc_buf_to_user_data(oob),
|
||||
nfc->regs + NFC_REG_USER_DATA(nfc, step));
|
||||
if (!nfc->caps->reg_user_data_len) {
|
||||
/*
|
||||
* For A10, the user data for step n is in the nth
|
||||
* REG_USER_DATA
|
||||
*/
|
||||
writel(sunxi_nfc_buf_to_user_data(oob),
|
||||
nfc->regs + NFC_REG_USER_DATA(nfc, step));
|
||||
} else {
|
||||
/*
|
||||
* For H6 NAND controller, the user data for all steps is
|
||||
* contained in 32 user data registers, but not at a specific
|
||||
* offset for each step, they are just concatenated.
|
||||
*/
|
||||
unsigned int user_data_off = 0;
|
||||
const u8 *ptr = oob;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < step; i++)
|
||||
user_data_off += sunxi_nfc_user_data_sz(sunxi_nand, i);
|
||||
|
||||
user_data_off /= 4;
|
||||
for (i = 0; i < user_data_sz / 4; i++, ptr += 4) {
|
||||
writel(sunxi_nfc_buf_to_user_data(ptr),
|
||||
nfc->regs + NFC_REG_USER_DATA(nfc, user_data_off + i));
|
||||
}
|
||||
}
|
||||
|
||||
kfree(user_data);
|
||||
}
|
||||
|
||||
static void sunxi_nfc_hw_ecc_update_stats(struct nand_chip *nand,
|
||||
@@ -918,6 +986,8 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
|
||||
bool *erased)
|
||||
{
|
||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
||||
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||
unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
|
||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||
u32 tmp;
|
||||
|
||||
@@ -940,7 +1010,7 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
|
||||
memset(data, pattern, ecc->size);
|
||||
|
||||
if (oob)
|
||||
memset(oob, pattern, ecc->bytes + USER_DATA_SZ);
|
||||
memset(oob, pattern, ecc->bytes + user_data_sz);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -955,12 +1025,15 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
|
||||
u8 *oob, int oob_off,
|
||||
int *cur_off,
|
||||
unsigned int *max_bitflips,
|
||||
bool bbm, bool oob_required, int page)
|
||||
int step, bool oob_required, int page)
|
||||
{
|
||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
||||
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||
unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
|
||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||
int raw_mode = 0;
|
||||
u32 pattern_found;
|
||||
bool bbm = !step;
|
||||
bool erased;
|
||||
int ret;
|
||||
/* From the controller point of view, we are at step 0 */
|
||||
@@ -978,8 +1051,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
sunxi_nfc_reset_user_data_len(nfc);
|
||||
sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, nfc_step);
|
||||
sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step);
|
||||
sunxi_nfc_randomizer_config(nand, page, false);
|
||||
sunxi_nfc_randomizer_enable(nand);
|
||||
writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ECC_OP,
|
||||
@@ -990,7 +1062,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*cur_off = oob_off + ecc->bytes + USER_DATA_SZ;
|
||||
*cur_off = oob_off + ecc->bytes + user_data_sz;
|
||||
|
||||
pattern_found = readl(nfc->regs + nfc->caps->reg_pat_found);
|
||||
pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found);
|
||||
@@ -1014,10 +1086,10 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
|
||||
ecc->size);
|
||||
|
||||
nand_change_read_column_op(nand, oob_off, oob,
|
||||
ecc->bytes + USER_DATA_SZ, false);
|
||||
ecc->bytes + user_data_sz, false);
|
||||
|
||||
ret = nand_check_erased_ecc_chunk(data, ecc->size, oob,
|
||||
ecc->bytes + USER_DATA_SZ,
|
||||
ecc->bytes + user_data_sz,
|
||||
NULL, 0, ecc->strength);
|
||||
if (ret >= 0)
|
||||
raw_mode = 1;
|
||||
@@ -1027,11 +1099,11 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
|
||||
if (oob_required) {
|
||||
nand_change_read_column_op(nand, oob_off, NULL, 0,
|
||||
false);
|
||||
sunxi_nfc_randomizer_read_buf(nand, oob, ecc->bytes + USER_DATA_SZ,
|
||||
sunxi_nfc_randomizer_read_buf(nand, oob, ecc->bytes + user_data_sz,
|
||||
true, page);
|
||||
|
||||
sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, nfc_step,
|
||||
bbm, page);
|
||||
bbm, page, user_data_sz);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1040,13 +1112,42 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
|
||||
return raw_mode;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the offset of the OOB for each step.
|
||||
* (it includes the user data before the ECC data.)
|
||||
*/
|
||||
static int sunxi_get_oob_offset(struct sunxi_nand_chip *sunxi_nand,
|
||||
struct nand_ecc_ctrl *ecc, int step)
|
||||
{
|
||||
int ecc_off = step * ecc->bytes;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < step; i++)
|
||||
ecc_off += sunxi_nfc_user_data_sz(sunxi_nand, i);
|
||||
|
||||
return ecc_off;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the offset of the ECC for each step.
|
||||
* So, it's the same as sunxi_get_oob_offset(),
|
||||
* but it skips the next user data.
|
||||
*/
|
||||
static int sunxi_get_ecc_offset(struct sunxi_nand_chip *sunxi_nand,
|
||||
struct nand_ecc_ctrl *ecc, int step)
|
||||
{
|
||||
return sunxi_get_oob_offset(sunxi_nand, ecc, step) +
|
||||
sunxi_nfc_user_data_sz(sunxi_nand, step);
|
||||
}
|
||||
|
||||
static void sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand,
|
||||
u8 *oob, int *cur_off,
|
||||
bool randomize, int page)
|
||||
{
|
||||
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||
struct mtd_info *mtd = nand_to_mtd(nand);
|
||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||
int offset = ((ecc->bytes + USER_DATA_SZ) * ecc->steps);
|
||||
int offset = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps);
|
||||
int len = mtd->oobsize - offset;
|
||||
|
||||
if (len <= 0)
|
||||
@@ -1071,6 +1172,7 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
|
||||
int nchunks)
|
||||
{
|
||||
bool randomized = nand->options & NAND_NEED_SCRAMBLING;
|
||||
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
||||
struct mtd_info *mtd = nand_to_mtd(nand);
|
||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||
@@ -1090,7 +1192,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
|
||||
|
||||
sunxi_nfc_hw_ecc_enable(nand);
|
||||
sunxi_nfc_reset_user_data_len(nfc);
|
||||
sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, 0);
|
||||
for (i = 0; i < nchunks; i++)
|
||||
sunxi_nfc_set_user_data_len(nfc, sunxi_nfc_user_data_sz(sunxi_nand, i), i);
|
||||
sunxi_nfc_randomizer_config(nand, page, false);
|
||||
sunxi_nfc_randomizer_enable(nand);
|
||||
|
||||
@@ -1125,7 +1228,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
|
||||
|
||||
for (i = 0; i < nchunks; i++) {
|
||||
int data_off = i * ecc->size;
|
||||
int oob_off = i * (ecc->bytes + USER_DATA_SZ);
|
||||
unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i);
|
||||
int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
|
||||
u8 *data = buf + data_off;
|
||||
u8 *oob = nand->oob_poi + oob_off;
|
||||
bool erased;
|
||||
@@ -1143,10 +1247,10 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
|
||||
/* TODO: use DMA to retrieve OOB */
|
||||
nand_change_read_column_op(nand,
|
||||
mtd->writesize + oob_off,
|
||||
oob, ecc->bytes + USER_DATA_SZ, false);
|
||||
oob, ecc->bytes + user_data_sz, false);
|
||||
|
||||
sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i,
|
||||
!i, page);
|
||||
sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, !i,
|
||||
page, user_data_sz);
|
||||
}
|
||||
|
||||
if (erased)
|
||||
@@ -1158,7 +1262,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
|
||||
if (status & NFC_ECC_ERR_MSK(nfc)) {
|
||||
for (i = 0; i < nchunks; i++) {
|
||||
int data_off = i * ecc->size;
|
||||
int oob_off = i * (ecc->bytes + USER_DATA_SZ);
|
||||
unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i);
|
||||
int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
|
||||
u8 *data = buf + data_off;
|
||||
u8 *oob = nand->oob_poi + oob_off;
|
||||
|
||||
@@ -1178,10 +1283,10 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
|
||||
/* TODO: use DMA to retrieve OOB */
|
||||
nand_change_read_column_op(nand,
|
||||
mtd->writesize + oob_off,
|
||||
oob, ecc->bytes + USER_DATA_SZ, false);
|
||||
oob, ecc->bytes + user_data_sz, false);
|
||||
|
||||
ret = nand_check_erased_ecc_chunk(data, ecc->size, oob,
|
||||
ecc->bytes + USER_DATA_SZ,
|
||||
ecc->bytes + user_data_sz,
|
||||
NULL, 0,
|
||||
ecc->strength);
|
||||
if (ret >= 0)
|
||||
@@ -1202,11 +1307,14 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
|
||||
static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
|
||||
const u8 *data, int data_off,
|
||||
const u8 *oob, int oob_off,
|
||||
int *cur_off, bool bbm,
|
||||
int *cur_off, int step,
|
||||
int page)
|
||||
{
|
||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
||||
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||
unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
|
||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||
bool bbm = !step;
|
||||
int ret;
|
||||
/* From the controller point of view, we are at step 0 */
|
||||
const int nfc_step = 0;
|
||||
@@ -1225,8 +1333,7 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
|
||||
|
||||
sunxi_nfc_randomizer_config(nand, page, false);
|
||||
sunxi_nfc_randomizer_enable(nand);
|
||||
sunxi_nfc_reset_user_data_len(nfc);
|
||||
sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, nfc_step);
|
||||
sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step);
|
||||
sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, nfc_step, bbm, page);
|
||||
|
||||
writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD |
|
||||
@@ -1238,7 +1345,7 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*cur_off = oob_off + ecc->bytes + USER_DATA_SZ;
|
||||
*cur_off = oob_off + ecc->bytes + user_data_sz;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1248,8 +1355,9 @@ static void sunxi_nfc_hw_ecc_write_extra_oob(struct nand_chip *nand,
|
||||
int page)
|
||||
{
|
||||
struct mtd_info *mtd = nand_to_mtd(nand);
|
||||
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||
int offset = ((ecc->bytes + USER_DATA_SZ) * ecc->steps);
|
||||
int offset = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps);
|
||||
int len = mtd->oobsize - offset;
|
||||
|
||||
if (len <= 0)
|
||||
@@ -1268,6 +1376,8 @@ static void sunxi_nfc_hw_ecc_write_extra_oob(struct nand_chip *nand,
|
||||
static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
|
||||
int oob_required, int page)
|
||||
{
|
||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
||||
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||
struct mtd_info *mtd = nand_to_mtd(nand);
|
||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||
unsigned int max_bitflips = 0;
|
||||
@@ -1280,16 +1390,17 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
|
||||
|
||||
sunxi_nfc_hw_ecc_enable(nand);
|
||||
|
||||
sunxi_nfc_reset_user_data_len(nfc);
|
||||
for (i = 0; i < ecc->steps; i++) {
|
||||
int data_off = i * ecc->size;
|
||||
int oob_off = i * (ecc->bytes + USER_DATA_SZ);
|
||||
int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
|
||||
u8 *data = buf + data_off;
|
||||
u8 *oob = nand->oob_poi + oob_off;
|
||||
|
||||
ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off, oob,
|
||||
oob_off + mtd->writesize,
|
||||
&cur_off, &max_bitflips,
|
||||
!i, oob_required, page);
|
||||
i, oob_required, page);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else if (ret)
|
||||
@@ -1327,6 +1438,8 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
|
||||
u32 data_offs, u32 readlen,
|
||||
u8 *bufpoi, int page)
|
||||
{
|
||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
||||
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||
struct mtd_info *mtd = nand_to_mtd(nand);
|
||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||
int ret, i, cur_off = 0;
|
||||
@@ -1338,17 +1451,18 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
|
||||
|
||||
sunxi_nfc_hw_ecc_enable(nand);
|
||||
|
||||
sunxi_nfc_reset_user_data_len(nfc);
|
||||
for (i = data_offs / ecc->size;
|
||||
i < DIV_ROUND_UP(data_offs + readlen, ecc->size); i++) {
|
||||
int data_off = i * ecc->size;
|
||||
int oob_off = i * (ecc->bytes + USER_DATA_SZ);
|
||||
int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
|
||||
u8 *data = bufpoi + data_off;
|
||||
u8 *oob = nand->oob_poi + oob_off;
|
||||
|
||||
ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off,
|
||||
oob,
|
||||
oob_off + mtd->writesize,
|
||||
&cur_off, &max_bitflips, !i,
|
||||
&cur_off, &max_bitflips, i,
|
||||
false, page);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
@@ -1383,6 +1497,8 @@ static int sunxi_nfc_hw_ecc_write_page(struct nand_chip *nand,
|
||||
const uint8_t *buf, int oob_required,
|
||||
int page)
|
||||
{
|
||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
||||
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||
struct mtd_info *mtd = nand_to_mtd(nand);
|
||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||
int ret, i, cur_off = 0;
|
||||
@@ -1393,15 +1509,16 @@ static int sunxi_nfc_hw_ecc_write_page(struct nand_chip *nand,
|
||||
|
||||
sunxi_nfc_hw_ecc_enable(nand);
|
||||
|
||||
sunxi_nfc_reset_user_data_len(nfc);
|
||||
for (i = 0; i < ecc->steps; i++) {
|
||||
int data_off = i * ecc->size;
|
||||
int oob_off = i * (ecc->bytes + USER_DATA_SZ);
|
||||
int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
|
||||
const u8 *data = buf + data_off;
|
||||
const u8 *oob = nand->oob_poi + oob_off;
|
||||
|
||||
ret = sunxi_nfc_hw_ecc_write_chunk(nand, data, data_off, oob,
|
||||
oob_off + mtd->writesize,
|
||||
&cur_off, !i, page);
|
||||
&cur_off, i, page);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@@ -1420,6 +1537,8 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct nand_chip *nand,
|
||||
const u8 *buf, int oob_required,
|
||||
int page)
|
||||
{
|
||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
||||
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||
struct mtd_info *mtd = nand_to_mtd(nand);
|
||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||
int ret, i, cur_off = 0;
|
||||
@@ -1430,16 +1549,17 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct nand_chip *nand,
|
||||
|
||||
sunxi_nfc_hw_ecc_enable(nand);
|
||||
|
||||
sunxi_nfc_reset_user_data_len(nfc);
|
||||
for (i = data_offs / ecc->size;
|
||||
i < DIV_ROUND_UP(data_offs + data_len, ecc->size); i++) {
|
||||
int data_off = i * ecc->size;
|
||||
int oob_off = i * (ecc->bytes + USER_DATA_SZ);
|
||||
int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
|
||||
const u8 *data = buf + data_off;
|
||||
const u8 *oob = nand->oob_poi + oob_off;
|
||||
|
||||
ret = sunxi_nfc_hw_ecc_write_chunk(nand, data, data_off, oob,
|
||||
oob_off + mtd->writesize,
|
||||
&cur_off, !i, page);
|
||||
&cur_off, i, page);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@@ -1455,6 +1575,7 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
|
||||
int page)
|
||||
{
|
||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
||||
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||
struct scatterlist sg;
|
||||
u32 wait;
|
||||
@@ -1473,10 +1594,12 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
|
||||
|
||||
sunxi_nfc_reset_user_data_len(nfc);
|
||||
for (i = 0; i < ecc->steps; i++) {
|
||||
const u8 *oob = nand->oob_poi + (i * (ecc->bytes + USER_DATA_SZ));
|
||||
unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i);
|
||||
int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
|
||||
const u8 *oob = nand->oob_poi + oob_off;
|
||||
|
||||
sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, i, !i, page);
|
||||
sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, i);
|
||||
sunxi_nfc_set_user_data_len(nfc, user_data_sz, i);
|
||||
}
|
||||
|
||||
nand_prog_page_begin_op(nand, page, 0, NULL, 0);
|
||||
@@ -1740,11 +1863,12 @@ static int sunxi_nand_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
{
|
||||
struct nand_chip *nand = mtd_to_nand(mtd);
|
||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||
|
||||
if (section >= ecc->steps)
|
||||
return -ERANGE;
|
||||
|
||||
oobregion->offset = section * (ecc->bytes + USER_DATA_SZ) + USER_DATA_SZ;
|
||||
oobregion->offset = sunxi_get_ecc_offset(sunxi_nand, ecc, section);
|
||||
oobregion->length = ecc->bytes;
|
||||
|
||||
return 0;
|
||||
@@ -1755,6 +1879,8 @@ static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section,
|
||||
{
|
||||
struct nand_chip *nand = mtd_to_nand(mtd);
|
||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||
unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, section);
|
||||
|
||||
/*
|
||||
* The controller does not provide access to OOB bytes
|
||||
@@ -1765,18 +1891,18 @@ static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section,
|
||||
|
||||
/*
|
||||
* The first 2 bytes are used for BB markers, hence we
|
||||
* only have USER_DATA_SZ - 2 bytes available in the first user data
|
||||
* only have user_data_sz - 2 bytes available in the first user data
|
||||
* section.
|
||||
*/
|
||||
if (section == 0) {
|
||||
oobregion->offset = 2;
|
||||
oobregion->length = USER_DATA_SZ - 2;
|
||||
oobregion->length = user_data_sz - 2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
oobregion->offset = section * (ecc->bytes + USER_DATA_SZ);
|
||||
oobregion->length = USER_DATA_SZ;
|
||||
oobregion->offset = sunxi_get_ecc_offset(sunxi_nand, ecc, section);
|
||||
oobregion->length = user_data_sz;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1786,6 +1912,43 @@ static const struct mtd_ooblayout_ops sunxi_nand_ooblayout_ops = {
|
||||
.free = sunxi_nand_ooblayout_free,
|
||||
};
|
||||
|
||||
static void sunxi_nand_detach_chip(struct nand_chip *nand)
|
||||
{
|
||||
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
||||
|
||||
devm_kfree(nfc->dev, sunxi_nand->user_data_bytes);
|
||||
sunxi_nand->user_data_bytes = NULL;
|
||||
}
|
||||
|
||||
static int sunxi_nfc_maximize_user_data(struct nand_chip *nand, uint32_t oobsize,
|
||||
int ecc_bytes, int nsectors)
|
||||
{
|
||||
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
||||
const struct sunxi_nfc_caps *c = nfc->caps;
|
||||
int remaining_bytes = oobsize - (ecc_bytes * nsectors);
|
||||
int i, step;
|
||||
|
||||
sunxi_nand->user_data_bytes = devm_kzalloc(nfc->dev, nsectors,
|
||||
GFP_KERNEL);
|
||||
if (!sunxi_nand->user_data_bytes)
|
||||
return -ENOMEM;
|
||||
|
||||
for (step = 0; (step < nsectors) && (remaining_bytes > 0); step++) {
|
||||
for (i = 0; i < c->nuser_data_tab; i++) {
|
||||
if (c->user_data_len_tab[i] > remaining_bytes)
|
||||
break;
|
||||
sunxi_nand->user_data_bytes[step] = c->user_data_len_tab[i];
|
||||
}
|
||||
remaining_bytes -= sunxi_nand->user_data_bytes[step];
|
||||
if (sunxi_nand->user_data_bytes[step] == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
|
||||
struct nand_ecc_ctrl *ecc,
|
||||
struct device_node *np)
|
||||
@@ -1795,33 +1958,50 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
|
||||
const u8 *strengths = nfc->caps->ecc_strengths;
|
||||
struct mtd_info *mtd = nand_to_mtd(nand);
|
||||
struct nand_device *nanddev = mtd_to_nanddev(mtd);
|
||||
int total_user_data_sz = 0;
|
||||
int nsectors;
|
||||
int ecc_mode;
|
||||
int i;
|
||||
|
||||
if (nanddev->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH) {
|
||||
int bytes;
|
||||
int bytes = mtd->oobsize;
|
||||
|
||||
ecc->size = 1024;
|
||||
nsectors = mtd->writesize / ecc->size;
|
||||
|
||||
/*
|
||||
* The 2 BBM bytes should not be removed from the grand total,
|
||||
* because they are part of the USER_DATA_SZ.
|
||||
* But we can't modify that for older platform since it may
|
||||
* result in a stronger ECC at the end, and break the
|
||||
* compatibility.
|
||||
*/
|
||||
if (nfc->caps->legacy_max_strength)
|
||||
bytes = (mtd->oobsize - 2) / nsectors;
|
||||
else
|
||||
bytes = mtd->oobsize / nsectors;
|
||||
if (!nfc->caps->reg_user_data_len) {
|
||||
/*
|
||||
* If there's a fixed user data length, subtract it before
|
||||
* computing the max ECC strength
|
||||
*/
|
||||
|
||||
for (i = 0; i < nsectors; i++)
|
||||
total_user_data_sz += sunxi_nfc_user_data_sz(sunxi_nand, i);
|
||||
|
||||
/*
|
||||
* The 2 BBM bytes should not be removed from the grand total,
|
||||
* because they are part of the USER_DATA_SZ.
|
||||
* But we can't modify that for older platform since it may
|
||||
* result in a stronger ECC at the end, and break the
|
||||
* compatibility.
|
||||
*/
|
||||
if (nfc->caps->legacy_max_strength)
|
||||
bytes -= 2;
|
||||
|
||||
bytes -= total_user_data_sz;
|
||||
} else {
|
||||
/*
|
||||
* remove at least the BBM size before computing the
|
||||
* max ECC
|
||||
*/
|
||||
bytes -= 2;
|
||||
}
|
||||
|
||||
/*
|
||||
* USER_DATA_SZ non-ECC bytes are added before each ECC bytes
|
||||
* section, they contain the 2 BBM bytes
|
||||
* Once all user data has been subtracted, the rest can be used
|
||||
* for ECC bytes
|
||||
*/
|
||||
bytes -= USER_DATA_SZ;
|
||||
bytes /= nsectors;
|
||||
|
||||
/* and bytes has to be even. */
|
||||
if (bytes % 2)
|
||||
@@ -1874,7 +2054,19 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
|
||||
|
||||
nsectors = mtd->writesize / ecc->size;
|
||||
|
||||
if (mtd->oobsize < ((ecc->bytes + USER_DATA_SZ) * nsectors))
|
||||
/*
|
||||
* The rationale for variable data length is to prioritize maximum ECC
|
||||
* strength, and then use the remaining space for user data.
|
||||
*/
|
||||
if (nfc->caps->reg_user_data_len)
|
||||
sunxi_nfc_maximize_user_data(nand, mtd->oobsize, ecc->bytes,
|
||||
nsectors);
|
||||
|
||||
if (total_user_data_sz == 0)
|
||||
for (i = 0; i < nsectors; i++)
|
||||
total_user_data_sz += sunxi_nfc_user_data_sz(sunxi_nand, i);
|
||||
|
||||
if (mtd->oobsize < (ecc->bytes * nsectors + total_user_data_sz))
|
||||
return -EINVAL;
|
||||
|
||||
ecc->read_oob = sunxi_nfc_hw_ecc_read_oob;
|
||||
@@ -2104,6 +2296,7 @@ static int sunxi_nfc_exec_op(struct nand_chip *nand,
|
||||
|
||||
static const struct nand_controller_ops sunxi_nand_controller_ops = {
|
||||
.attach_chip = sunxi_nand_attach_chip,
|
||||
.detach_chip = sunxi_nand_detach_chip,
|
||||
.setup_interface = sunxi_nfc_setup_interface,
|
||||
.exec_op = sunxi_nfc_exec_op,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user