summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/mtd/nand/raw/sunxi_nand.c291
-rw-r--r--drivers/mtd/nand/raw/sunxi_nand.h3
-rw-r--r--drivers/mtd/nand/raw/sunxi_nand_spl.c129
-rw-r--r--drivers/pinctrl/sunxi/pinctrl-sunxi.c1
-rw-r--r--drivers/spi/spi-sunxi.c9
5 files changed, 331 insertions, 102 deletions
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index ef27a4b7a36..49748fddf80 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -114,6 +114,7 @@ struct sunxi_nand_hw_ecc {
* @clk_rate: clk_rate required for this NAND chip
* @timing_cfg TIMING_CFG register value for this NAND chip
* @selected: current active CS
+ * @user_data_bytes array of user data lengths for all ECC steps
* @nsels: number of CS lines required by the NAND chip
* @sels: array of CS lines descriptions
*/
@@ -128,6 +129,7 @@ struct sunxi_nand_chip {
u32 addr[2];
int cmd_cycles;
u8 cmd[2];
+ u8 *user_data_bytes;
int nsels;
struct sunxi_nand_chip_sel sels[0];
};
@@ -744,20 +746,76 @@ static void sunxi_nfc_set_user_data_len(struct sunxi_nfc *nfc,
writel(val, nfc->regs + NFC_REG_USER_DATA_LEN(nfc, step));
}
+static u8 sunxi_nfc_user_data_sz(const struct sunxi_nand_chip *sunxi_nand, int step)
+{
+ if (!sunxi_nand->user_data_bytes)
+ return USER_DATA_SZ;
+
+ 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)
+ sunxi_nfc_randomize_bbm(&nand->mtd, page, oob);
+}
+
static int sunxi_nfc_hw_ecc_read_chunk(struct mtd_info *mtd,
u8 *data, int data_off,
u8 *oob, int oob_off,
int *cur_off,
unsigned int *max_bitflips,
- bool bbm, int page)
+ int step, int page)
{
struct nand_chip *nand = mtd_to_nand(mtd);
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 status;
u32 pattern_found;
+ bool bbm = !step;
int ret;
+ /* From the controller point of view, we are at step 0 */
+ const int nfc_step = 0;
if (*cur_off != data_off)
nand->cmdfunc(mtd, NAND_CMD_RNDOUT, data_off, -1);
@@ -771,8 +829,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct mtd_info *mtd,
if (ret)
return ret;
- sunxi_nfc_reset_user_data_len(nfc);
- sunxi_nfc_set_user_data_len(nfc, 4, 0);
+ sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step);
sunxi_nfc_randomizer_enable(mtd);
writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ECC_OP,
@@ -783,31 +840,31 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct mtd_info *mtd,
if (ret)
return ret;
- *cur_off = oob_off + ecc->bytes + 4;
+ *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);
- if (pattern_found & NFC_ECC_PAT_FOUND(0)) {
+ if (pattern_found & NFC_ECC_PAT_FOUND(nfc_step)) {
u8 pattern = 0xff;
if (unlikely(!(readl(nfc->regs + NFC_REG_PAT_ID(nfc)) & 0x1)))
pattern = 0x0;
memset(data, pattern, ecc->size);
- memset(oob, pattern, ecc->bytes + 4);
+ memset(oob, pattern, ecc->bytes + user_data_sz);
return 1;
}
- ret = NFC_ECC_ERR_CNT(0, readl(nfc->regs + NFC_REG_ECC_ERR_CNT(nfc, 0)));
+ ret = NFC_ECC_ERR_CNT(nfc_step, readl(nfc->regs + NFC_REG_ECC_ERR_CNT(nfc, nfc_step)));
memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE, ecc->size);
nand->cmdfunc(mtd, NAND_CMD_RNDOUT, oob_off, -1);
- sunxi_nfc_randomizer_read_buf(mtd, oob, ecc->bytes + 4, true, page);
+ sunxi_nfc_randomizer_read_buf(mtd, oob, ecc->bytes + user_data_sz, true, page);
status = readl(nfc->regs + NFC_REG_ECC_ST);
- if (status & NFC_ECC_ERR(0)) {
+ if (status & NFC_ECC_ERR(nfc_step)) {
/*
* Re-read the data with the randomizer disabled to identify
* bitflips in erased pages.
@@ -816,26 +873,21 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct mtd_info *mtd,
nand->cmdfunc(mtd, NAND_CMD_RNDOUT, data_off, -1);
nand->read_buf(mtd, data, ecc->size);
nand->cmdfunc(mtd, NAND_CMD_RNDOUT, oob_off, -1);
- nand->read_buf(mtd, oob, ecc->bytes + 4);
+ nand->read_buf(mtd, oob, ecc->bytes + user_data_sz);
}
ret = nand_check_erased_ecc_chunk(data, ecc->size,
- oob, ecc->bytes + 4,
+ oob, ecc->bytes + user_data_sz,
NULL, 0, ecc->strength);
if (ret >= 0)
raw_mode = 1;
} else {
/*
- * The engine protects 4 bytes of OOB data per chunk.
+ * The engine protects user_data_sz bytes of OOB data per chunk.
* Retrieve the corrected OOB bytes.
*/
- sunxi_nfc_user_data_to_buf(readl(nfc->regs +
- NFC_REG_USER_DATA(nfc, 0)),
- oob);
-
- /* De-randomize the Bad Block Marker. */
- if (bbm && nand->options & NAND_NEED_SCRAMBLING)
- sunxi_nfc_randomize_bbm(mtd, page, oob);
+ sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, nfc_step,
+ bbm, page, user_data_sz);
}
if (ret < 0) {
@@ -848,13 +900,30 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct mtd_info *mtd,
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;
+}
+
static void sunxi_nfc_hw_ecc_read_extra_oob(struct mtd_info *mtd,
u8 *oob, int *cur_off,
bool randomize, int page)
{
struct nand_chip *nand = mtd_to_nand(mtd);
struct nand_ecc_ctrl *ecc = &nand->ecc;
- int offset = ((ecc->bytes + 4) * ecc->steps);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ int offset = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps);
int len = mtd->oobsize - offset;
if (len <= 0)
@@ -881,13 +950,18 @@ static inline u32 sunxi_nfc_buf_to_user_data(const u8 *buf)
static int sunxi_nfc_hw_ecc_write_chunk(struct mtd_info *mtd,
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 nand_chip *nand = mtd_to_nand(mtd);
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->cmdfunc(mtd, NAND_CMD_RNDIN, data_off, -1);
@@ -896,15 +970,20 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct mtd_info *mtd,
/* Fill OOB data in */
if ((nand->options & NAND_NEED_SCRAMBLING) && bbm) {
- u8 user_data[4];
+ u8 *user_data;
+
+ user_data = kzalloc(user_data_sz, GFP_KERNEL);
+ if (!user_data)
+ return -ENOMEM;
- memcpy(user_data, oob, 4);
+ memcpy(user_data, oob, user_data_sz);
sunxi_nfc_randomize_bbm(mtd, page, user_data);
writel(sunxi_nfc_buf_to_user_data(user_data),
- nfc->regs + NFC_REG_USER_DATA(nfc, 0));
+ nfc->regs + NFC_REG_USER_DATA(nfc, nfc_step));
+ kfree(user_data);
} else {
writel(sunxi_nfc_buf_to_user_data(oob),
- nfc->regs + NFC_REG_USER_DATA(nfc, 0));
+ nfc->regs + NFC_REG_USER_DATA(nfc, nfc_step));
}
if (data_off + ecc->size != oob_off)
@@ -914,8 +993,7 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct mtd_info *mtd,
if (ret)
return ret;
- sunxi_nfc_reset_user_data_len(nfc);
- sunxi_nfc_set_user_data_len(nfc, 4, 0);
+ sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step);
sunxi_nfc_randomizer_enable(mtd);
writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD |
@@ -927,7 +1005,7 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct mtd_info *mtd,
if (ret)
return ret;
- *cur_off = oob_off + ecc->bytes + 4;
+ *cur_off = oob_off + ecc->bytes + user_data_sz;
return 0;
}
@@ -938,7 +1016,8 @@ static void sunxi_nfc_hw_ecc_write_extra_oob(struct mtd_info *mtd,
{
struct nand_chip *nand = mtd_to_nand(mtd);
struct nand_ecc_ctrl *ecc = &nand->ecc;
- int offset = ((ecc->bytes + 4) * ecc->steps);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ int offset = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps);
int len = mtd->oobsize - offset;
if (len <= 0)
@@ -957,6 +1036,8 @@ static int sunxi_nfc_hw_ecc_read_page(struct mtd_info *mtd,
struct nand_chip *chip, uint8_t *buf,
int oob_required, int page)
{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(chip);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
struct nand_ecc_ctrl *ecc = &chip->ecc;
unsigned int max_bitflips = 0;
int ret, i, cur_off = 0;
@@ -964,16 +1045,17 @@ static int sunxi_nfc_hw_ecc_read_page(struct mtd_info *mtd,
sunxi_nfc_hw_ecc_enable(mtd);
+ 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 + 4);
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
u8 *data = buf + data_off;
u8 *oob = chip->oob_poi + oob_off;
ret = sunxi_nfc_hw_ecc_read_chunk(mtd, data, data_off, oob,
oob_off + mtd->writesize,
&cur_off, &max_bitflips,
- !i, page);
+ i, page);
if (ret < 0)
return ret;
else if (ret)
@@ -994,23 +1076,26 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct mtd_info *mtd,
uint32_t data_offs, uint32_t readlen,
uint8_t *bufpoi, int page)
{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(chip);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
struct nand_ecc_ctrl *ecc = &chip->ecc;
int ret, i, cur_off = 0;
unsigned int max_bitflips = 0;
sunxi_nfc_hw_ecc_enable(mtd);
+ sunxi_nfc_reset_user_data_len(nfc);
chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
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 + 4);
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
u8 *data = bufpoi + data_off;
u8 *oob = chip->oob_poi + oob_off;
ret = sunxi_nfc_hw_ecc_read_chunk(mtd, data, data_off,
oob, oob_off + mtd->writesize,
- &cur_off, &max_bitflips, !i, page);
+ &cur_off, &max_bitflips, i, page);
if (ret < 0)
return ret;
}
@@ -1025,20 +1110,23 @@ static int sunxi_nfc_hw_ecc_write_page(struct mtd_info *mtd,
const uint8_t *buf, int oob_required,
int page)
{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(chip);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
struct nand_ecc_ctrl *ecc = &chip->ecc;
int ret, i, cur_off = 0;
sunxi_nfc_hw_ecc_enable(mtd);
+ 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 + 4);
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
const u8 *data = buf + data_off;
const u8 *oob = chip->oob_poi + oob_off;
ret = sunxi_nfc_hw_ecc_write_chunk(mtd, data, data_off, oob,
oob_off + mtd->writesize,
- &cur_off, !i, page);
+ &cur_off, i, page);
if (ret)
return ret;
}
@@ -1058,21 +1146,24 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct mtd_info *mtd,
const u8 *buf, int oob_required,
int page)
{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(chip);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
struct nand_ecc_ctrl *ecc = &chip->ecc;
int ret, i, cur_off = 0;
sunxi_nfc_hw_ecc_enable(mtd);
+ 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 + 4);
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
const u8 *data = buf + data_off;
const u8 *oob = chip->oob_poi + oob_off;
ret = sunxi_nfc_hw_ecc_write_chunk(mtd, data, data_off, oob,
oob_off + mtd->writesize,
- &cur_off, !i, page);
+ &cur_off, i, page);
if (ret)
return ret;
}
@@ -1087,18 +1178,23 @@ static int sunxi_nfc_hw_syndrome_ecc_read_page(struct mtd_info *mtd,
uint8_t *buf, int oob_required,
int page)
{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(chip);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
struct nand_ecc_ctrl *ecc = &chip->ecc;
unsigned int max_bitflips = 0;
int ret, i, cur_off = 0;
bool raw_mode = false;
+ /* With hw_syndrome, user data length is fixed */
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, 0);
sunxi_nfc_hw_ecc_enable(mtd);
+ sunxi_nfc_reset_user_data_len(nfc);
for (i = 0; i < ecc->steps; i++) {
- int data_off = i * (ecc->size + ecc->bytes + 4);
+ int data_off = i * (ecc->size + ecc->bytes + user_data_sz);
int oob_off = data_off + ecc->size;
u8 *data = buf + (i * ecc->size);
- u8 *oob = chip->oob_poi + (i * (ecc->bytes + 4));
+ u8 *oob = chip->oob_poi + (i * (ecc->bytes + user_data_sz));
ret = sunxi_nfc_hw_ecc_read_chunk(mtd, data, data_off, oob,
oob_off, &cur_off,
@@ -1123,16 +1219,19 @@ static int sunxi_nfc_hw_syndrome_ecc_write_page(struct mtd_info *mtd,
const uint8_t *buf,
int oob_required, int page)
{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(chip);
struct nand_ecc_ctrl *ecc = &chip->ecc;
int ret, i, cur_off = 0;
+ /* With hw_syndrome, user data length is fixed */
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, 0);
sunxi_nfc_hw_ecc_enable(mtd);
for (i = 0; i < ecc->steps; i++) {
- int data_off = i * (ecc->size + ecc->bytes + 4);
+ int data_off = i * (ecc->size + ecc->bytes + user_data_sz);
int oob_off = data_off + ecc->size;
const u8 *data = buf + (i * ecc->size);
- const u8 *oob = chip->oob_poi + (i * (ecc->bytes + 4));
+ const u8 *oob = chip->oob_poi + (i * (ecc->bytes + user_data_sz));
ret = sunxi_nfc_hw_ecc_write_chunk(mtd, data, data_off,
oob, oob_off, &cur_off,
@@ -1334,6 +1433,34 @@ static int sunxi_nand_chip_init_timings(struct sunxi_nfc *nfc,
return sunxi_nand_chip_set_timings(nfc, chip, timings);
}
+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_common_ecc_ctrl_init(struct mtd_info *mtd,
struct nand_ecc_ctrl *ecc)
{
@@ -1342,6 +1469,7 @@ static int sunxi_nand_hw_common_ecc_ctrl_init(struct mtd_info *mtd,
struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
struct sunxi_nand_hw_ecc *data;
struct nand_ecclayout *layout;
+ unsigned int total_user_data_sz = 0;
int nsectors;
int ret;
int i;
@@ -1390,7 +1518,15 @@ static int sunxi_nand_hw_common_ecc_ctrl_init(struct mtd_info *mtd,
layout = &data->layout;
nsectors = mtd->writesize / ecc->size;
- if (mtd->oobsize < ((ecc->bytes + 4) * nsectors)) {
+ /* Use the remaining OOB space for user data */
+ if (nfc->caps->reg_user_data_len)
+ sunxi_nfc_maximize_user_data(nand, mtd->oobsize, ecc->bytes,
+ nsectors);
+
+ 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) {
ret = -EINVAL;
goto err;
}
@@ -1404,6 +1540,8 @@ static int sunxi_nand_hw_common_ecc_ctrl_init(struct mtd_info *mtd,
err:
kfree(data);
+ devm_kfree(nfc->dev, sunxi_nand->user_data_bytes);
+ sunxi_nand->user_data_bytes = NULL;
return ret;
}
@@ -1418,7 +1556,10 @@ static void sunxi_nand_hw_common_ecc_ctrl_cleanup(struct nand_ecc_ctrl *ecc)
static int sunxi_nand_hw_ecc_ctrl_init(struct mtd_info *mtd,
struct nand_ecc_ctrl *ecc)
{
+ struct nand_chip *nand = mtd_to_nand(mtd);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct nand_ecclayout *layout;
+ unsigned int total_user_data_sz = 0;
int nsectors;
int i, j;
int ret;
@@ -1440,14 +1581,14 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct mtd_info *mtd,
layout->oobfree[i - 1].offset +
layout->oobfree[i - 1].length +
ecc->bytes;
- layout->oobfree[i].length = 4;
+ layout->oobfree[i].length = sunxi_nfc_user_data_sz(sunxi_nand, i);
} else {
/*
* The first 2 bytes are used for BB markers, hence we
- * only have 2 bytes available in the first user data
- * section.
+ * only have user_data_len(0) - 2 bytes available in the
+ * first user data section.
*/
- layout->oobfree[i].length = 2;
+ layout->oobfree[i].length = sunxi_nfc_user_data_sz(sunxi_nand, i) - 2;
layout->oobfree[i].offset = 2;
}
@@ -1457,13 +1598,16 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct mtd_info *mtd,
layout->oobfree[i].length + j;
}
- if (mtd->oobsize > (ecc->bytes + 4) * nsectors) {
+ 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) {
layout->oobfree[nsectors].offset =
layout->oobfree[nsectors - 1].offset +
layout->oobfree[nsectors - 1].length +
ecc->bytes;
layout->oobfree[nsectors].length = mtd->oobsize -
- ((ecc->bytes + 4) * nsectors);
+ (ecc->bytes * nsectors + total_user_data_sz);
}
return 0;
@@ -1472,6 +1616,8 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct mtd_info *mtd,
static int sunxi_nand_hw_syndrome_ecc_ctrl_init(struct mtd_info *mtd,
struct nand_ecc_ctrl *ecc)
{
+ struct nand_chip *nand = mtd_to_nand(mtd);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct nand_ecclayout *layout;
int nsectors;
int i;
@@ -1481,7 +1627,13 @@ static int sunxi_nand_hw_syndrome_ecc_ctrl_init(struct mtd_info *mtd,
if (ret)
return ret;
- ecc->prepad = 4;
+ for (i = 0; i < nsectors; i++)
+ if (sunxi_nfc_user_data_sz(sunxi_nand, i) !=
+ sunxi_nfc_user_data_sz(sunxi_nand, 0)) {
+ dev_err(mtd->dev, "Variable user data length not upported with NAND_ECC_HW_SYNDROME\n");
+ return -EOPNOTSUPP;
+ }
+ ecc->prepad = sunxi_nfc_user_data_sz(sunxi_nand, 0);
ecc->read_page = sunxi_nfc_hw_syndrome_ecc_read_page;
ecc->write_page = sunxi_nfc_hw_syndrome_ecc_write_page;
@@ -1596,21 +1748,20 @@ static int sunxi_nand_chip_init(struct udevice *dev, struct sunxi_nfc *nfc,
if (ret) {
dev_err(dev, "could not retrieve reg property: %d\n",
ret);
- kfree(chip);
- return ret;
+ goto err_free;
}
if (tmp > NFC_MAX_CS) {
dev_err(dev,
"invalid reg value: %u (max CS = 7)\n", tmp);
- kfree(chip);
- return -EINVAL;
+ ret = -EINVAL;
+ goto err_free;
}
if (test_and_set_bit(tmp, &nfc->assigned_cs)) {
dev_err(dev, "CS %d already assigned\n", tmp);
- kfree(chip);
- return -EINVAL;
+ ret = -EINVAL;
+ goto err_free;
}
chip->sels[i].cs = tmp;
@@ -1636,15 +1787,13 @@ static int sunxi_nand_chip_init(struct udevice *dev, struct sunxi_nfc *nfc,
dev_err(dev,
"could not retrieve timings for ONFI mode 0: %d\n",
ret);
- kfree(chip);
- return ret;
+ goto err_free;
}
ret = sunxi_nand_chip_set_timings(nfc, chip, timings);
if (ret) {
dev_err(dev, "could not configure chip timings: %d\n", ret);
- kfree(chip);
- return ret;
+ goto err_free;
}
nand = &chip->nand;
@@ -1665,10 +1814,8 @@ static int sunxi_nand_chip_init(struct udevice *dev, struct sunxi_nfc *nfc,
mtd = nand_to_mtd(nand);
ret = nand_scan_ident(mtd, nsels, NULL);
- if (ret) {
- kfree(chip);
- return ret;
- }
+ if (ret)
+ goto err_free;
if (nand->bbt_options & NAND_BBT_USE_FLASH)
nand->bbt_options |= NAND_BBT_NO_OOB;
@@ -1681,34 +1828,35 @@ static int sunxi_nand_chip_init(struct udevice *dev, struct sunxi_nfc *nfc,
ret = sunxi_nand_chip_init_timings(nfc, chip);
if (ret) {
dev_err(dev, "could not configure chip timings: %d\n", ret);
- kfree(chip);
- return ret;
+ goto err_free;
}
ret = sunxi_nand_ecc_init(mtd, &nand->ecc);
if (ret) {
dev_err(dev, "ECC init failed: %d\n", ret);
- kfree(chip);
- return ret;
+ goto err_free;
}
ret = nand_scan_tail(mtd);
if (ret) {
dev_err(dev, "nand_scan_tail failed: %d\n", ret);
- kfree(chip);
- return ret;
+ goto err_free;
}
ret = nand_register(devnum, mtd);
if (ret) {
dev_err(dev, "failed to register mtd device: %d\n", ret);
- kfree(chip);
- return ret;
+ goto err_free;
}
list_add_tail(&chip->node, &nfc->chips);
return 0;
+
+err_free:
+ kfree(chip);
+
+ return ret;
}
static int sunxi_nand_chips_init(struct udevice *dev, struct sunxi_nfc *nfc)
@@ -1736,6 +1884,7 @@ static void sunxi_nand_chips_cleanup(struct sunxi_nfc *nfc)
nand_release(&chip->mtd);
sunxi_nand_ecc_cleanup(&chip->nand.ecc);
list_del(&chip->node);
+ devm_kfree(nfc->dev, chip->user_data_bytes);
kfree(chip);
}
}
diff --git a/drivers/mtd/nand/raw/sunxi_nand.h b/drivers/mtd/nand/raw/sunxi_nand.h
index 6ee3ea14ee1..d7a8b3dd40c 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.h
+++ b/drivers/mtd/nand/raw/sunxi_nand.h
@@ -181,6 +181,9 @@
#define NFC_MAX_CS 7
+/* On A10, the user data length register is 4 bytes */
+#define USER_DATA_SZ 4
+
/*
* NAND Controller capabilities structure: stores NAND controller capabilities
* for distinction between compatible strings.
diff --git a/drivers/mtd/nand/raw/sunxi_nand_spl.c b/drivers/mtd/nand/raw/sunxi_nand_spl.c
index 67f7d22ed2c..cf351de4e8d 100644
--- a/drivers/mtd/nand/raw/sunxi_nand_spl.c
+++ b/drivers/mtd/nand/raw/sunxi_nand_spl.c
@@ -28,6 +28,7 @@ struct nfc_config {
bool randomize;
bool valid;
const struct sunxi_nfc_caps *caps;
+ u8 *user_data_bytes;
};
/* minimal "boot0" style NAND support for Allwinner A20 */
@@ -223,22 +224,6 @@ static int nand_change_column(u16 column)
return 0;
}
-/*
- * On H6/H616 the user_data length has to be set in specific registers
- * before writing.
- */
-static void sunxi_nfc_reset_user_data_len(const struct nfc_config *nfc)
-{
- int loop_step = NFC_REG_USER_DATA_LEN_CAPACITY;
-
- /* not all SoCs have this register */
- if (!NFC_REG_USER_DATA_LEN(nfc, 0))
- return;
-
- for (int i = 0; i < nfc->caps->max_ecc_steps; i += loop_step)
- writel_nfc(0, NFC_REG_USER_DATA_LEN(nfc, i));
-}
-
static void sunxi_nfc_set_user_data_len(const struct nfc_config *nfc,
int len, int step)
{
@@ -269,13 +254,19 @@ static void sunxi_nfc_set_user_data_len(const struct nfc_config *nfc,
writel_nfc(val, NFC_REG_USER_DATA_LEN(nfc, step));
}
+/*
+ * Values in this table are obtained by doing:
+ * DIV_ROUND_UP(info->ecc_strength * 14, 8)
+ * So it's the number of bytes needed for ECC one step
+ * (not counting the user data length)
+ */
#if defined(CONFIG_MACH_SUN50I_H616) || defined(CONFIG_MACH_SUN50I_H6)
static const int ecc_bytes[] = {
- 32, 46, 54, 60, 74, 82, 88, 96, 102, 110, 116, 124, 130, 138, 144
+ 28, 42, 50, 56, 70, 78, 84, 92, 98, 106, 112, 120, 126, 134, 140
};
#else
static const int ecc_bytes[] = {
- 32, 46, 54, 60, 74, 88, 102, 110, 116
+ 28, 42, 50, 56, 70, 84, 98, 106, 112
};
#endif
@@ -288,6 +279,14 @@ static void nand_readlcpy(u32 *dest, u32 * __iomem src, size_t len)
*dest++ = readl(src++);
}
+static u8 nand_user_data_sz(const struct nfc_config *conf, int step)
+{
+ if (!conf->user_data_bytes)
+ return USER_DATA_SZ;
+
+ return conf->user_data_bytes[step];
+}
+
static int nand_read_page(const struct nfc_config *conf, u32 offs,
void *dest, int len)
{
@@ -295,8 +294,11 @@ static int nand_read_page(const struct nfc_config *conf, u32 offs,
u16 rand_seed = 0;
int oob_chunk_sz = ecc_bytes[conf->ecc_strength];
int page = offs / conf->page_size;
+ int oob_off = conf->page_size;
u32 ecc_st, pattern_found;
int i;
+ /* From the controller point of view, we are at step 0 */
+ const int nfc_step = 0;
if (offs % conf->page_size || len % conf->ecc_size ||
len > conf->page_size || len < 0)
@@ -309,9 +311,9 @@ static int nand_read_page(const struct nfc_config *conf, u32 offs,
/* Retrieve data from SRAM (PIO) */
for (i = 0; i < nsectors; i++) {
int data_off = i * conf->ecc_size;
- int oob_off = conf->page_size + (i * oob_chunk_sz);
u8 *data = dest + data_off;
u32 ecc512_bit = 0;
+ unsigned int user_data_sz = nand_user_data_sz(conf, i);
if (conf->caps->has_ecc_block_512 && conf->ecc_size == 512)
ecc512_bit = NFC_ECC_BLOCK_512;
@@ -337,8 +339,7 @@ static int nand_read_page(const struct nfc_config *conf, u32 offs,
*/
nand_change_column(oob_off);
- sunxi_nfc_reset_user_data_len(conf);
- sunxi_nfc_set_user_data_len(conf, 4, 0);
+ sunxi_nfc_set_user_data_len(conf, user_data_sz, nfc_step);
nand_exec_cmd(NFC_DATA_TRANS | NFC_ECC_OP);
/* Get the ECC status */
@@ -356,7 +357,7 @@ static int nand_read_page(const struct nfc_config *conf, u32 offs,
pattern_found = readl_nfc(conf->caps->reg_pat_found);
pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(conf),
pattern_found);
- if (pattern_found & NFC_ECC_PAT_FOUND(0))
+ if (pattern_found & NFC_ECC_PAT_FOUND(nfc_step))
return 1;
}
@@ -364,13 +365,61 @@ static int nand_read_page(const struct nfc_config *conf, u32 offs,
nand_readlcpy((u32 *)data,
(void *)(uintptr_t)SUNXI_NFC_BASE + NFC_RAM0_BASE,
conf->ecc_size);
-
/* Stop the ECC engine */
writel_nfc(readl_nfc(NFC_REG_ECC_CTL) & ~NFC_ECC_EN,
NFC_REG_ECC_CTL);
if (data_off + conf->ecc_size >= len)
break;
+
+ oob_off += oob_chunk_sz + user_data_sz;
+ }
+
+ return 0;
+}
+
+static int nand_min_user_data_sz(struct nfc_config *conf, int nsectors)
+{
+ const struct sunxi_nfc_caps *c = conf->caps;
+ int min_user_data_sz = 0;
+ int i;
+
+ if (!c->reg_user_data_len) {
+ for (i = 0; i < nsectors; i++)
+ min_user_data_sz += nand_user_data_sz(conf, i);
+ } else {
+ for (i = 0; i < c->nuser_data_tab; i++)
+ /* We want at least enough size for the BBM */
+ if (c->user_data_len_tab[i] >= 2)
+ break;
+ min_user_data_sz = c->user_data_len_tab[i];
+ }
+
+ return min_user_data_sz;
+}
+
+static int nand_maximize_user_data(struct nfc_config *conf, uint32_t oobsize,
+ int ecc_len, int nsectors)
+{
+ const struct sunxi_nfc_caps *c = conf->caps;
+ int remaining_bytes = oobsize - (ecc_len * nsectors);
+ int i, step;
+
+ kfree(conf->user_data_bytes);
+
+ conf->user_data_bytes = kzalloc(nsectors, GFP_KERNEL);
+ if (!conf->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;
+ conf->user_data_bytes[step] = c->user_data_len_tab[i];
+ }
+ remaining_bytes -= conf->user_data_bytes[step];
+ if (conf->user_data_bytes[step] == 0)
+ break;
}
return 0;
@@ -380,7 +429,8 @@ static int nand_max_ecc_strength(struct nfc_config *conf)
{
int max_oobsize, max_ecc_bytes;
int nsectors = conf->page_size / conf->ecc_size;
- int i;
+ unsigned int total_user_data_sz = 0;
+ int ecc_idx, i;
/*
* ECC strength is limited by the size of the OOB area which is
@@ -405,15 +455,38 @@ static int nand_max_ecc_strength(struct nfc_config *conf)
max_ecc_bytes = max_oobsize / nsectors;
- for (i = 0; i < ARRAY_SIZE(ecc_bytes); i++) {
- if (ecc_bytes[i] > max_ecc_bytes)
+ /*
+ * nand_min_user_data_sz() will return the total_user_data_sz in case
+ * of a fixed user data length, or the minimal usable user data size
+ * in case of variable data length (with at least enough space for the
+ * BBM.
+ */
+ total_user_data_sz = nand_min_user_data_sz(conf, nsectors);
+
+ for (ecc_idx = 0; ecc_idx < ARRAY_SIZE(ecc_bytes); ecc_idx++) {
+ if (ecc_bytes[ecc_idx] + total_user_data_sz > max_ecc_bytes)
break;
}
- if (!i)
+ if (!ecc_idx)
return -EINVAL;
- return i - 1;
+ ecc_idx--;
+
+ /*
+ * The rationale for variable data length is to prioritize maximum ECC
+ * strength, and then use the remaining space for user data.
+ */
+ if (conf->caps->reg_user_data_len) {
+ nand_maximize_user_data(conf, max_oobsize,
+ ecc_bytes[ecc_idx], nsectors);
+
+ total_user_data_sz = 0;
+ for (i = 0; i < nsectors; i++)
+ total_user_data_sz += nand_user_data_sz(conf, i);
+ }
+
+ return ecc_idx;
}
static int nand_detect_ecc_config(struct nfc_config *conf, u32 offs,
diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.c b/drivers/pinctrl/sunxi/pinctrl-sunxi.c
index fd357ab0d4e..a8632aeaf8f 100644
--- a/drivers/pinctrl/sunxi/pinctrl-sunxi.c
+++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.c
@@ -603,6 +603,7 @@ static const struct sunxi_pinctrl_function sun20i_d1_pinctrl_functions[] = {
{ "gpio_in", 0 },
{ "gpio_out", 1 },
{ "i2c0", 4 }, /* PB10-PB11 */
+ { "i2c3", 3 }, /* PG10-PG11 */
{ "mmc0", 2 }, /* PF0-PF5 */
{ "mmc1", 2 }, /* PG0-PG5 */
{ "mmc2", 3 }, /* PC2-PC7 */
diff --git a/drivers/spi/spi-sunxi.c b/drivers/spi/spi-sunxi.c
index 0bdc112d249..08b603f04a2 100644
--- a/drivers/spi/spi-sunxi.c
+++ b/drivers/spi/spi-sunxi.c
@@ -344,7 +344,7 @@ static int sun4i_spi_xfer(struct udevice *dev, unsigned int bitlen,
struct sun4i_spi_priv *priv = dev_get_priv(bus);
struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev);
- u32 len = bitlen / 8;
+ u32 rst, val, len = bitlen / 8;
u8 nbytes;
int ret;
@@ -360,8 +360,11 @@ static int sun4i_spi_xfer(struct udevice *dev, unsigned int bitlen,
sun4i_spi_set_cs(bus, slave_plat->cs[0], true);
/* Reset FIFOs */
- setbits_le32(SPI_REG(priv, SPI_FCR), SPI_BIT(priv, SPI_FCR_RF_RST) |
- SPI_BIT(priv, SPI_FCR_TF_RST));
+ rst = SPI_BIT(priv, SPI_FCR_RF_RST) | SPI_BIT(priv, SPI_FCR_TF_RST);
+ setbits_le32(SPI_REG(priv, SPI_FCR), rst);
+ ret = readl_poll_timeout(SPI_REG(priv, SPI_FCR), val, !(rst & val), 20);
+ if (ret)
+ return -EBUSY;
while (len) {
/* Setup the transfer now... */