|
|
|
|
@@ -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);
|
|
|
|
|
};
|
|
|
|
|
@@ -272,9 +273,11 @@ static inline struct sunxi_nand_chip *to_sunxi_nand(struct nand_chip *nand)
|
|
|
|
|
*
|
|
|
|
|
* @has_mdma: Use mbus dma mode, otherwise general dma
|
|
|
|
|
* through MBUS on A23/A33 needs extra configuration.
|
|
|
|
|
* @has_ecc_block_512: If the ECC can handle 512B or only 1024B chuncks
|
|
|
|
|
* @has_ecc_block_512: If the ECC can handle 512B or only 1024B chunks
|
|
|
|
|
* @has_ecc_clk: If the controller needs an ECC clock.
|
|
|
|
|
* @has_mbus_clk: If the controller needs a mbus clock.
|
|
|
|
|
* @legacy_max_strength:If the maximize strength function was off by 2 bytes
|
|
|
|
|
* NB: this should not be used in new controllers
|
|
|
|
|
* @reg_io_data: I/O data register
|
|
|
|
|
* @reg_ecc_err_cnt: ECC error counter register
|
|
|
|
|
* @reg_user_data: User data register
|
|
|
|
|
@@ -292,7 +295,7 @@ static inline struct sunxi_nand_chip *to_sunxi_nand(struct nand_chip *nand)
|
|
|
|
|
* @nstrengths: Size of @ecc_strengths
|
|
|
|
|
* @max_ecc_steps: Maximum supported steps for ECC, this is also the
|
|
|
|
|
* number of user data registers
|
|
|
|
|
* @user_data_len_tab: Table of lenghts supported by USER_DATA_LEN register
|
|
|
|
|
* @user_data_len_tab: Table of lengths supported by USER_DATA_LEN register
|
|
|
|
|
* The table index is the value to set in NFC_USER_DATA_LEN
|
|
|
|
|
* registers, and the corresponding value is the number of
|
|
|
|
|
* bytes to write
|
|
|
|
|
@@ -304,6 +307,7 @@ struct sunxi_nfc_caps {
|
|
|
|
|
bool has_ecc_block_512;
|
|
|
|
|
bool has_ecc_clk;
|
|
|
|
|
bool has_mbus_clk;
|
|
|
|
|
bool legacy_max_strength;
|
|
|
|
|
unsigned int reg_io_data;
|
|
|
|
|
unsigned int reg_ecc_err_cnt;
|
|
|
|
|
unsigned int reg_user_data;
|
|
|
|
|
@@ -820,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))
|
|
|
|
|
@@ -884,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,
|
|
|
|
|
@@ -915,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;
|
|
|
|
|
|
|
|
|
|
@@ -937,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;
|
|
|
|
|
}
|
|
|
|
|
@@ -952,14 +1025,19 @@ 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 */
|
|
|
|
|
const int nfc_step = 0;
|
|
|
|
|
|
|
|
|
|
if (*cur_off != data_off)
|
|
|
|
|
nand_change_read_column_op(nand, data_off, NULL, 0, false);
|
|
|
|
|
@@ -973,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, 0);
|
|
|
|
|
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,
|
|
|
|
|
@@ -985,15 +1062,14 @@ 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);
|
|
|
|
|
|
|
|
|
|
ret = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL, 0,
|
|
|
|
|
readl(nfc->regs + NFC_REG_ECC_ST),
|
|
|
|
|
pattern_found,
|
|
|
|
|
&erased);
|
|
|
|
|
ret = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL,
|
|
|
|
|
nfc_step, readl(nfc->regs + NFC_REG_ECC_ST),
|
|
|
|
|
pattern_found, &erased);
|
|
|
|
|
if (erased)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
@@ -1010,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;
|
|
|
|
|
@@ -1023,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, 0,
|
|
|
|
|
bbm, page);
|
|
|
|
|
sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, nfc_step,
|
|
|
|
|
bbm, page, user_data_sz);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1036,21 +1112,50 @@ 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 + 4) * ecc->steps);
|
|
|
|
|
int offset = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps);
|
|
|
|
|
int len = mtd->oobsize - offset;
|
|
|
|
|
|
|
|
|
|
if (len <= 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (!cur_off || *cur_off != offset)
|
|
|
|
|
nand_change_read_column_op(nand, mtd->writesize, NULL, 0,
|
|
|
|
|
false);
|
|
|
|
|
if (!cur_off || *cur_off != (offset + mtd->writesize))
|
|
|
|
|
nand_change_read_column_op(nand, mtd->writesize + offset,
|
|
|
|
|
NULL, 0, false);
|
|
|
|
|
|
|
|
|
|
if (!randomize)
|
|
|
|
|
sunxi_nfc_read_buf(nand, oob + offset, len);
|
|
|
|
|
@@ -1067,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;
|
|
|
|
|
@@ -1086,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);
|
|
|
|
|
|
|
|
|
|
@@ -1121,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;
|
|
|
|
|
@@ -1139,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)
|
|
|
|
|
@@ -1154,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;
|
|
|
|
|
|
|
|
|
|
@@ -1174,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)
|
|
|
|
|
@@ -1198,12 +1307,17 @@ 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;
|
|
|
|
|
|
|
|
|
|
if (data_off != *cur_off)
|
|
|
|
|
nand_change_write_column_op(nand, data_off, NULL, 0, false);
|
|
|
|
|
@@ -1219,9 +1333,8 @@ 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, 0);
|
|
|
|
|
sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, 0, bbm, page);
|
|
|
|
|
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 |
|
|
|
|
|
NFC_ACCESS_DIR | NFC_ECC_OP,
|
|
|
|
|
@@ -1232,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;
|
|
|
|
|
}
|
|
|
|
|
@@ -1242,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)
|
|
|
|
|
@@ -1262,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;
|
|
|
|
|
@@ -1274,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)
|
|
|
|
|
@@ -1321,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;
|
|
|
|
|
@@ -1332,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;
|
|
|
|
|
@@ -1377,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;
|
|
|
|
|
@@ -1387,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;
|
|
|
|
|
}
|
|
|
|
|
@@ -1414,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;
|
|
|
|
|
@@ -1424,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;
|
|
|
|
|
}
|
|
|
|
|
@@ -1449,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;
|
|
|
|
|
@@ -1467,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);
|
|
|
|
|
@@ -1734,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) + 4;
|
|
|
|
|
oobregion->offset = sunxi_get_ecc_offset(sunxi_nand, ecc, section);
|
|
|
|
|
oobregion->length = ecc->bytes;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
@@ -1749,35 +1879,30 @@ 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;
|
|
|
|
|
|
|
|
|
|
if (section > ecc->steps)
|
|
|
|
|
return -ERANGE;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The first 2 bytes are used for BB markers, hence we
|
|
|
|
|
* only have 2 bytes available in the first user data
|
|
|
|
|
* section.
|
|
|
|
|
*/
|
|
|
|
|
if (!section && ecc->engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) {
|
|
|
|
|
oobregion->offset = 2;
|
|
|
|
|
oobregion->length = 2;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
* past the end of the ECC data.
|
|
|
|
|
*/
|
|
|
|
|
if (section == ecc->steps && ecc->engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST)
|
|
|
|
|
if (section >= ecc->steps)
|
|
|
|
|
return -ERANGE;
|
|
|
|
|
|
|
|
|
|
oobregion->offset = section * (ecc->bytes + USER_DATA_SZ);
|
|
|
|
|
/*
|
|
|
|
|
* The first 2 bytes are used for BB markers, hence we
|
|
|
|
|
* 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;
|
|
|
|
|
|
|
|
|
|
if (section < ecc->steps)
|
|
|
|
|
oobregion->length = USER_DATA_SZ;
|
|
|
|
|
else
|
|
|
|
|
oobregion->length = mtd->oobsize - oobregion->offset;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
oobregion->offset = sunxi_get_ecc_offset(sunxi_nand, ecc, section);
|
|
|
|
|
oobregion->length = user_data_sz;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
@@ -1787,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)
|
|
|
|
|
@@ -1796,20 +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;
|
|
|
|
|
|
|
|
|
|
/* Reserve 2 bytes for the BBM */
|
|
|
|
|
bytes = (mtd->oobsize - 2) / nsectors;
|
|
|
|
|
if (!nfc->caps->reg_user_data_len) {
|
|
|
|
|
/*
|
|
|
|
|
* If there's a fixed user data length, subtract it before
|
|
|
|
|
* computing the max ECC strength
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* 4 non-ECC bytes are added before each ECC bytes section */
|
|
|
|
|
bytes -= USER_DATA_SZ;
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Once all user data has been subtracted, the rest can be used
|
|
|
|
|
* for ECC bytes
|
|
|
|
|
*/
|
|
|
|
|
bytes /= nsectors;
|
|
|
|
|
|
|
|
|
|
/* and bytes has to be even. */
|
|
|
|
|
if (bytes % 2)
|
|
|
|
|
@@ -1838,18 +2030,18 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Add ECC info retrieval from DT */
|
|
|
|
|
for (i = 0; i < nfc->caps->nstrengths; i++) {
|
|
|
|
|
if (ecc->strength <= strengths[i]) {
|
|
|
|
|
for (ecc_mode = 0; ecc_mode < nfc->caps->nstrengths; ecc_mode++) {
|
|
|
|
|
if (ecc->strength <= strengths[ecc_mode]) {
|
|
|
|
|
/*
|
|
|
|
|
* Update ecc->strength value with the actual strength
|
|
|
|
|
* that will be used by the ECC engine.
|
|
|
|
|
*/
|
|
|
|
|
ecc->strength = strengths[i];
|
|
|
|
|
ecc->strength = strengths[ecc_mode];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (i >= nfc->caps->nstrengths) {
|
|
|
|
|
if (ecc_mode >= nfc->caps->nstrengths) {
|
|
|
|
|
dev_err(nfc->dev, "unsupported strength\n");
|
|
|
|
|
return -ENOTSUPP;
|
|
|
|
|
}
|
|
|
|
|
@@ -1862,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;
|
|
|
|
|
@@ -1885,7 +2089,7 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
|
|
|
|
|
ecc->read_oob_raw = nand_read_oob_std;
|
|
|
|
|
ecc->write_oob_raw = nand_write_oob_std;
|
|
|
|
|
|
|
|
|
|
sunxi_nand->ecc.ecc_ctl = NFC_ECC_MODE(nfc, i) | NFC_ECC_EXCEPTION |
|
|
|
|
|
sunxi_nand->ecc.ecc_ctl = NFC_ECC_MODE(nfc, ecc_mode) | NFC_ECC_EXCEPTION |
|
|
|
|
|
NFC_ECC_PIPELINE | NFC_ECC_EN;
|
|
|
|
|
|
|
|
|
|
if (ecc->size == 512) {
|
|
|
|
|
@@ -2092,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,
|
|
|
|
|
};
|
|
|
|
|
@@ -2373,6 +2578,7 @@ static const u8 sunxi_user_data_len_h6[] = {
|
|
|
|
|
|
|
|
|
|
static const struct sunxi_nfc_caps sunxi_nfc_a10_caps = {
|
|
|
|
|
.has_ecc_block_512 = true,
|
|
|
|
|
.legacy_max_strength = true,
|
|
|
|
|
.reg_io_data = NFC_REG_A10_IO_DATA,
|
|
|
|
|
.reg_ecc_err_cnt = NFC_REG_A10_ECC_ERR_CNT,
|
|
|
|
|
.reg_user_data = NFC_REG_A10_USER_DATA,
|
|
|
|
|
@@ -2394,6 +2600,7 @@ static const struct sunxi_nfc_caps sunxi_nfc_a10_caps = {
|
|
|
|
|
static const struct sunxi_nfc_caps sunxi_nfc_a23_caps = {
|
|
|
|
|
.has_mdma = true,
|
|
|
|
|
.has_ecc_block_512 = true,
|
|
|
|
|
.legacy_max_strength = true,
|
|
|
|
|
.reg_io_data = NFC_REG_A23_IO_DATA,
|
|
|
|
|
.reg_ecc_err_cnt = NFC_REG_A10_ECC_ERR_CNT,
|
|
|
|
|
.reg_user_data = NFC_REG_A10_USER_DATA,
|
|
|
|
|
|