|
@@ -213,6 +213,8 @@ nandc_set_reg(nandc, NAND_READ_LOCATION_##reg, \
|
|
|
#define QPIC_PER_CW_CMD_SGL 32
|
|
#define QPIC_PER_CW_CMD_SGL 32
|
|
|
#define QPIC_PER_CW_DATA_SGL 8
|
|
#define QPIC_PER_CW_DATA_SGL 8
|
|
|
|
|
|
|
|
|
|
+#define QPIC_NAND_COMPLETION_TIMEOUT msecs_to_jiffies(2000)
|
|
|
|
|
+
|
|
|
/*
|
|
/*
|
|
|
* Flags used in DMA descriptor preparation helper functions
|
|
* Flags used in DMA descriptor preparation helper functions
|
|
|
* (i.e. read_reg_dma/write_reg_dma/read_data_dma/write_data_dma)
|
|
* (i.e. read_reg_dma/write_reg_dma/read_data_dma/write_data_dma)
|
|
@@ -245,6 +247,11 @@ nandc_set_reg(nandc, NAND_READ_LOCATION_##reg, \
|
|
|
* @tx_sgl_start - start index in data sgl for tx.
|
|
* @tx_sgl_start - start index in data sgl for tx.
|
|
|
* @rx_sgl_pos - current index in data sgl for rx.
|
|
* @rx_sgl_pos - current index in data sgl for rx.
|
|
|
* @rx_sgl_start - start index in data sgl for rx.
|
|
* @rx_sgl_start - start index in data sgl for rx.
|
|
|
|
|
+ * @wait_second_completion - wait for second DMA desc completion before making
|
|
|
|
|
+ * the NAND transfer completion.
|
|
|
|
|
+ * @txn_done - completion for NAND transfer.
|
|
|
|
|
+ * @last_data_desc - last DMA desc in data channel (tx/rx).
|
|
|
|
|
+ * @last_cmd_desc - last DMA desc in command channel.
|
|
|
*/
|
|
*/
|
|
|
struct bam_transaction {
|
|
struct bam_transaction {
|
|
|
struct bam_cmd_element *bam_ce;
|
|
struct bam_cmd_element *bam_ce;
|
|
@@ -258,6 +265,10 @@ struct bam_transaction {
|
|
|
u32 tx_sgl_start;
|
|
u32 tx_sgl_start;
|
|
|
u32 rx_sgl_pos;
|
|
u32 rx_sgl_pos;
|
|
|
u32 rx_sgl_start;
|
|
u32 rx_sgl_start;
|
|
|
|
|
+ bool wait_second_completion;
|
|
|
|
|
+ struct completion txn_done;
|
|
|
|
|
+ struct dma_async_tx_descriptor *last_data_desc;
|
|
|
|
|
+ struct dma_async_tx_descriptor *last_cmd_desc;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -354,7 +365,7 @@ struct nandc_regs {
|
|
|
* from all connected NAND devices pagesize
|
|
* from all connected NAND devices pagesize
|
|
|
*/
|
|
*/
|
|
|
struct qcom_nand_controller {
|
|
struct qcom_nand_controller {
|
|
|
- struct nand_hw_control controller;
|
|
|
|
|
|
|
+ struct nand_controller controller;
|
|
|
struct list_head host_list;
|
|
struct list_head host_list;
|
|
|
|
|
|
|
|
struct device *dev;
|
|
struct device *dev;
|
|
@@ -504,6 +515,8 @@ alloc_bam_transaction(struct qcom_nand_controller *nandc)
|
|
|
|
|
|
|
|
bam_txn->data_sgl = bam_txn_buf;
|
|
bam_txn->data_sgl = bam_txn_buf;
|
|
|
|
|
|
|
|
|
|
+ init_completion(&bam_txn->txn_done);
|
|
|
|
|
+
|
|
|
return bam_txn;
|
|
return bam_txn;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -523,11 +536,33 @@ static void clear_bam_transaction(struct qcom_nand_controller *nandc)
|
|
|
bam_txn->tx_sgl_start = 0;
|
|
bam_txn->tx_sgl_start = 0;
|
|
|
bam_txn->rx_sgl_pos = 0;
|
|
bam_txn->rx_sgl_pos = 0;
|
|
|
bam_txn->rx_sgl_start = 0;
|
|
bam_txn->rx_sgl_start = 0;
|
|
|
|
|
+ bam_txn->last_data_desc = NULL;
|
|
|
|
|
+ bam_txn->wait_second_completion = false;
|
|
|
|
|
|
|
|
sg_init_table(bam_txn->cmd_sgl, nandc->max_cwperpage *
|
|
sg_init_table(bam_txn->cmd_sgl, nandc->max_cwperpage *
|
|
|
QPIC_PER_CW_CMD_SGL);
|
|
QPIC_PER_CW_CMD_SGL);
|
|
|
sg_init_table(bam_txn->data_sgl, nandc->max_cwperpage *
|
|
sg_init_table(bam_txn->data_sgl, nandc->max_cwperpage *
|
|
|
QPIC_PER_CW_DATA_SGL);
|
|
QPIC_PER_CW_DATA_SGL);
|
|
|
|
|
+
|
|
|
|
|
+ reinit_completion(&bam_txn->txn_done);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* Callback for DMA descriptor completion */
|
|
|
|
|
+static void qpic_bam_dma_done(void *data)
|
|
|
|
|
+{
|
|
|
|
|
+ struct bam_transaction *bam_txn = data;
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * In case of data transfer with NAND, 2 callbacks will be generated.
|
|
|
|
|
+ * One for command channel and another one for data channel.
|
|
|
|
|
+ * If current transaction has data descriptors
|
|
|
|
|
+ * (i.e. wait_second_completion is true), then set this to false
|
|
|
|
|
+ * and wait for second DMA descriptor completion.
|
|
|
|
|
+ */
|
|
|
|
|
+ if (bam_txn->wait_second_completion)
|
|
|
|
|
+ bam_txn->wait_second_completion = false;
|
|
|
|
|
+ else
|
|
|
|
|
+ complete(&bam_txn->txn_done);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static inline struct qcom_nand_host *to_qcom_nand_host(struct nand_chip *chip)
|
|
static inline struct qcom_nand_host *to_qcom_nand_host(struct nand_chip *chip)
|
|
@@ -756,6 +791,12 @@ static int prepare_bam_async_desc(struct qcom_nand_controller *nandc,
|
|
|
|
|
|
|
|
desc->dma_desc = dma_desc;
|
|
desc->dma_desc = dma_desc;
|
|
|
|
|
|
|
|
|
|
+ /* update last data/command descriptor */
|
|
|
|
|
+ if (chan == nandc->cmd_chan)
|
|
|
|
|
+ bam_txn->last_cmd_desc = dma_desc;
|
|
|
|
|
+ else
|
|
|
|
|
+ bam_txn->last_data_desc = dma_desc;
|
|
|
|
|
+
|
|
|
list_add_tail(&desc->node, &nandc->desc_list);
|
|
list_add_tail(&desc->node, &nandc->desc_list);
|
|
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
@@ -1055,7 +1096,8 @@ static void config_nand_page_read(struct qcom_nand_controller *nandc)
|
|
|
* Helper to prepare DMA descriptors for configuring registers
|
|
* Helper to prepare DMA descriptors for configuring registers
|
|
|
* before reading each codeword in NAND page.
|
|
* before reading each codeword in NAND page.
|
|
|
*/
|
|
*/
|
|
|
-static void config_nand_cw_read(struct qcom_nand_controller *nandc)
|
|
|
|
|
|
|
+static void
|
|
|
|
|
+config_nand_cw_read(struct qcom_nand_controller *nandc, bool use_ecc)
|
|
|
{
|
|
{
|
|
|
if (nandc->props->is_bam)
|
|
if (nandc->props->is_bam)
|
|
|
write_reg_dma(nandc, NAND_READ_LOCATION_0, 4,
|
|
write_reg_dma(nandc, NAND_READ_LOCATION_0, 4,
|
|
@@ -1064,19 +1106,25 @@ static void config_nand_cw_read(struct qcom_nand_controller *nandc)
|
|
|
write_reg_dma(nandc, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL);
|
|
write_reg_dma(nandc, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL);
|
|
|
write_reg_dma(nandc, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
|
|
write_reg_dma(nandc, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
|
|
|
|
|
|
|
|
- read_reg_dma(nandc, NAND_FLASH_STATUS, 2, 0);
|
|
|
|
|
- read_reg_dma(nandc, NAND_ERASED_CW_DETECT_STATUS, 1,
|
|
|
|
|
- NAND_BAM_NEXT_SGL);
|
|
|
|
|
|
|
+ if (use_ecc) {
|
|
|
|
|
+ read_reg_dma(nandc, NAND_FLASH_STATUS, 2, 0);
|
|
|
|
|
+ read_reg_dma(nandc, NAND_ERASED_CW_DETECT_STATUS, 1,
|
|
|
|
|
+ NAND_BAM_NEXT_SGL);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ read_reg_dma(nandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
/*
|
|
|
* Helper to prepare dma descriptors to configure registers needed for reading a
|
|
* Helper to prepare dma descriptors to configure registers needed for reading a
|
|
|
* single codeword in page
|
|
* single codeword in page
|
|
|
*/
|
|
*/
|
|
|
-static void config_nand_single_cw_page_read(struct qcom_nand_controller *nandc)
|
|
|
|
|
|
|
+static void
|
|
|
|
|
+config_nand_single_cw_page_read(struct qcom_nand_controller *nandc,
|
|
|
|
|
+ bool use_ecc)
|
|
|
{
|
|
{
|
|
|
config_nand_page_read(nandc);
|
|
config_nand_page_read(nandc);
|
|
|
- config_nand_cw_read(nandc);
|
|
|
|
|
|
|
+ config_nand_cw_read(nandc, use_ecc);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -1157,7 +1205,7 @@ static int nandc_param(struct qcom_nand_host *host)
|
|
|
nandc->buf_count = 512;
|
|
nandc->buf_count = 512;
|
|
|
memset(nandc->data_buffer, 0xff, nandc->buf_count);
|
|
memset(nandc->data_buffer, 0xff, nandc->buf_count);
|
|
|
|
|
|
|
|
- config_nand_single_cw_page_read(nandc);
|
|
|
|
|
|
|
+ config_nand_single_cw_page_read(nandc, false);
|
|
|
|
|
|
|
|
read_data_dma(nandc, FLASH_BUF_ACC, nandc->data_buffer,
|
|
read_data_dma(nandc, FLASH_BUF_ACC, nandc->data_buffer,
|
|
|
nandc->buf_count, 0);
|
|
nandc->buf_count, 0);
|
|
@@ -1273,10 +1321,20 @@ static int submit_descs(struct qcom_nand_controller *nandc)
|
|
|
cookie = dmaengine_submit(desc->dma_desc);
|
|
cookie = dmaengine_submit(desc->dma_desc);
|
|
|
|
|
|
|
|
if (nandc->props->is_bam) {
|
|
if (nandc->props->is_bam) {
|
|
|
|
|
+ bam_txn->last_cmd_desc->callback = qpic_bam_dma_done;
|
|
|
|
|
+ bam_txn->last_cmd_desc->callback_param = bam_txn;
|
|
|
|
|
+ if (bam_txn->last_data_desc) {
|
|
|
|
|
+ bam_txn->last_data_desc->callback = qpic_bam_dma_done;
|
|
|
|
|
+ bam_txn->last_data_desc->callback_param = bam_txn;
|
|
|
|
|
+ bam_txn->wait_second_completion = true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
dma_async_issue_pending(nandc->tx_chan);
|
|
dma_async_issue_pending(nandc->tx_chan);
|
|
|
dma_async_issue_pending(nandc->rx_chan);
|
|
dma_async_issue_pending(nandc->rx_chan);
|
|
|
|
|
+ dma_async_issue_pending(nandc->cmd_chan);
|
|
|
|
|
|
|
|
- if (dma_sync_wait(nandc->cmd_chan, cookie) != DMA_COMPLETE)
|
|
|
|
|
|
|
+ if (!wait_for_completion_timeout(&bam_txn->txn_done,
|
|
|
|
|
+ QPIC_NAND_COMPLETION_TIMEOUT))
|
|
|
return -ETIMEDOUT;
|
|
return -ETIMEDOUT;
|
|
|
} else {
|
|
} else {
|
|
|
if (dma_sync_wait(nandc->chan, cookie) != DMA_COMPLETE)
|
|
if (dma_sync_wait(nandc->chan, cookie) != DMA_COMPLETE)
|
|
@@ -1512,20 +1570,180 @@ struct read_stats {
|
|
|
__le32 erased_cw;
|
|
__le32 erased_cw;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+/* reads back FLASH_STATUS register set by the controller */
|
|
|
|
|
+static int check_flash_errors(struct qcom_nand_host *host, int cw_cnt)
|
|
|
|
|
+{
|
|
|
|
|
+ struct nand_chip *chip = &host->chip;
|
|
|
|
|
+ struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
|
|
|
|
|
+ int i;
|
|
|
|
|
+
|
|
|
|
|
+ for (i = 0; i < cw_cnt; i++) {
|
|
|
|
|
+ u32 flash = le32_to_cpu(nandc->reg_read_buf[i]);
|
|
|
|
|
+
|
|
|
|
|
+ if (flash & (FS_OP_ERR | FS_MPU_ERR))
|
|
|
|
|
+ return -EIO;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* performs raw read for one codeword */
|
|
|
|
|
+static int
|
|
|
|
|
+qcom_nandc_read_cw_raw(struct mtd_info *mtd, struct nand_chip *chip,
|
|
|
|
|
+ u8 *data_buf, u8 *oob_buf, int page, int cw)
|
|
|
|
|
+{
|
|
|
|
|
+ struct qcom_nand_host *host = to_qcom_nand_host(chip);
|
|
|
|
|
+ struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
|
|
|
|
|
+ struct nand_ecc_ctrl *ecc = &chip->ecc;
|
|
|
|
|
+ int data_size1, data_size2, oob_size1, oob_size2;
|
|
|
|
|
+ int ret, reg_off = FLASH_BUF_ACC, read_loc = 0;
|
|
|
|
|
+
|
|
|
|
|
+ nand_read_page_op(chip, page, 0, NULL, 0);
|
|
|
|
|
+ host->use_ecc = false;
|
|
|
|
|
+
|
|
|
|
|
+ clear_bam_transaction(nandc);
|
|
|
|
|
+ set_address(host, host->cw_size * cw, page);
|
|
|
|
|
+ update_rw_regs(host, 1, true);
|
|
|
|
|
+ config_nand_page_read(nandc);
|
|
|
|
|
+
|
|
|
|
|
+ data_size1 = mtd->writesize - host->cw_size * (ecc->steps - 1);
|
|
|
|
|
+ oob_size1 = host->bbm_size;
|
|
|
|
|
+
|
|
|
|
|
+ if (cw == (ecc->steps - 1)) {
|
|
|
|
|
+ data_size2 = ecc->size - data_size1 -
|
|
|
|
|
+ ((ecc->steps - 1) * 4);
|
|
|
|
|
+ oob_size2 = (ecc->steps * 4) + host->ecc_bytes_hw +
|
|
|
|
|
+ host->spare_bytes;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ data_size2 = host->cw_data - data_size1;
|
|
|
|
|
+ oob_size2 = host->ecc_bytes_hw + host->spare_bytes;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (nandc->props->is_bam) {
|
|
|
|
|
+ nandc_set_read_loc(nandc, 0, read_loc, data_size1, 0);
|
|
|
|
|
+ read_loc += data_size1;
|
|
|
|
|
+
|
|
|
|
|
+ nandc_set_read_loc(nandc, 1, read_loc, oob_size1, 0);
|
|
|
|
|
+ read_loc += oob_size1;
|
|
|
|
|
+
|
|
|
|
|
+ nandc_set_read_loc(nandc, 2, read_loc, data_size2, 0);
|
|
|
|
|
+ read_loc += data_size2;
|
|
|
|
|
+
|
|
|
|
|
+ nandc_set_read_loc(nandc, 3, read_loc, oob_size2, 1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ config_nand_cw_read(nandc, false);
|
|
|
|
|
+
|
|
|
|
|
+ read_data_dma(nandc, reg_off, data_buf, data_size1, 0);
|
|
|
|
|
+ reg_off += data_size1;
|
|
|
|
|
+
|
|
|
|
|
+ read_data_dma(nandc, reg_off, oob_buf, oob_size1, 0);
|
|
|
|
|
+ reg_off += oob_size1;
|
|
|
|
|
+
|
|
|
|
|
+ read_data_dma(nandc, reg_off, data_buf + data_size1, data_size2, 0);
|
|
|
|
|
+ reg_off += data_size2;
|
|
|
|
|
+
|
|
|
|
|
+ read_data_dma(nandc, reg_off, oob_buf + oob_size1, oob_size2, 0);
|
|
|
|
|
+
|
|
|
|
|
+ ret = submit_descs(nandc);
|
|
|
|
|
+ free_descs(nandc);
|
|
|
|
|
+ if (ret) {
|
|
|
|
|
+ dev_err(nandc->dev, "failure to read raw cw %d\n", cw);
|
|
|
|
|
+ return ret;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return check_flash_errors(host, 1);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/*
|
|
|
|
|
+ * Bitflips can happen in erased codewords also so this function counts the
|
|
|
|
|
+ * number of 0 in each CW for which ECC engine returns the uncorrectable
|
|
|
|
|
+ * error. The page will be assumed as erased if this count is less than or
|
|
|
|
|
+ * equal to the ecc->strength for each CW.
|
|
|
|
|
+ *
|
|
|
|
|
+ * 1. Both DATA and OOB need to be checked for number of 0. The
|
|
|
|
|
+ * top-level API can be called with only data buf or OOB buf so use
|
|
|
|
|
+ * chip->data_buf if data buf is null and chip->oob_poi if oob buf
|
|
|
|
|
+ * is null for copying the raw bytes.
|
|
|
|
|
+ * 2. Perform raw read for all the CW which has uncorrectable errors.
|
|
|
|
|
+ * 3. For each CW, check the number of 0 in cw_data and usable OOB bytes.
|
|
|
|
|
+ * The BBM and spare bytes bit flip won’t affect the ECC so don’t check
|
|
|
|
|
+ * the number of bitflips in this area.
|
|
|
|
|
+ */
|
|
|
|
|
+static int
|
|
|
|
|
+check_for_erased_page(struct qcom_nand_host *host, u8 *data_buf,
|
|
|
|
|
+ u8 *oob_buf, unsigned long uncorrectable_cws,
|
|
|
|
|
+ int page, unsigned int max_bitflips)
|
|
|
|
|
+{
|
|
|
|
|
+ struct nand_chip *chip = &host->chip;
|
|
|
|
|
+ struct mtd_info *mtd = nand_to_mtd(chip);
|
|
|
|
|
+ struct nand_ecc_ctrl *ecc = &chip->ecc;
|
|
|
|
|
+ u8 *cw_data_buf, *cw_oob_buf;
|
|
|
|
|
+ int cw, data_size, oob_size, ret = 0;
|
|
|
|
|
+
|
|
|
|
|
+ if (!data_buf) {
|
|
|
|
|
+ data_buf = chip->data_buf;
|
|
|
|
|
+ chip->pagebuf = -1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!oob_buf) {
|
|
|
|
|
+ oob_buf = chip->oob_poi;
|
|
|
|
|
+ chip->pagebuf = -1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for_each_set_bit(cw, &uncorrectable_cws, ecc->steps) {
|
|
|
|
|
+ if (cw == (ecc->steps - 1)) {
|
|
|
|
|
+ data_size = ecc->size - ((ecc->steps - 1) * 4);
|
|
|
|
|
+ oob_size = (ecc->steps * 4) + host->ecc_bytes_hw;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ data_size = host->cw_data;
|
|
|
|
|
+ oob_size = host->ecc_bytes_hw;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* determine starting buffer address for current CW */
|
|
|
|
|
+ cw_data_buf = data_buf + (cw * host->cw_data);
|
|
|
|
|
+ cw_oob_buf = oob_buf + (cw * ecc->bytes);
|
|
|
|
|
+
|
|
|
|
|
+ ret = qcom_nandc_read_cw_raw(mtd, chip, cw_data_buf,
|
|
|
|
|
+ cw_oob_buf, page, cw);
|
|
|
|
|
+ if (ret)
|
|
|
|
|
+ return ret;
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * make sure it isn't an erased page reported
|
|
|
|
|
+ * as not-erased by HW because of a few bitflips
|
|
|
|
|
+ */
|
|
|
|
|
+ ret = nand_check_erased_ecc_chunk(cw_data_buf, data_size,
|
|
|
|
|
+ cw_oob_buf + host->bbm_size,
|
|
|
|
|
+ oob_size, NULL,
|
|
|
|
|
+ 0, ecc->strength);
|
|
|
|
|
+ if (ret < 0) {
|
|
|
|
|
+ mtd->ecc_stats.failed++;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ mtd->ecc_stats.corrected += ret;
|
|
|
|
|
+ max_bitflips = max_t(unsigned int, max_bitflips, ret);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return max_bitflips;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/*
|
|
/*
|
|
|
* reads back status registers set by the controller to notify page read
|
|
* reads back status registers set by the controller to notify page read
|
|
|
* errors. this is equivalent to what 'ecc->correct()' would do.
|
|
* errors. this is equivalent to what 'ecc->correct()' would do.
|
|
|
*/
|
|
*/
|
|
|
static int parse_read_errors(struct qcom_nand_host *host, u8 *data_buf,
|
|
static int parse_read_errors(struct qcom_nand_host *host, u8 *data_buf,
|
|
|
- u8 *oob_buf)
|
|
|
|
|
|
|
+ u8 *oob_buf, int page)
|
|
|
{
|
|
{
|
|
|
struct nand_chip *chip = &host->chip;
|
|
struct nand_chip *chip = &host->chip;
|
|
|
struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
|
|
struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
|
|
|
struct mtd_info *mtd = nand_to_mtd(chip);
|
|
struct mtd_info *mtd = nand_to_mtd(chip);
|
|
|
struct nand_ecc_ctrl *ecc = &chip->ecc;
|
|
struct nand_ecc_ctrl *ecc = &chip->ecc;
|
|
|
- unsigned int max_bitflips = 0;
|
|
|
|
|
|
|
+ unsigned int max_bitflips = 0, uncorrectable_cws = 0;
|
|
|
struct read_stats *buf;
|
|
struct read_stats *buf;
|
|
|
|
|
+ bool flash_op_err = false, erased;
|
|
|
int i;
|
|
int i;
|
|
|
|
|
+ u8 *data_buf_start = data_buf, *oob_buf_start = oob_buf;
|
|
|
|
|
|
|
|
buf = (struct read_stats *)nandc->reg_read_buf;
|
|
buf = (struct read_stats *)nandc->reg_read_buf;
|
|
|
nandc_read_buffer_sync(nandc, true);
|
|
nandc_read_buffer_sync(nandc, true);
|
|
@@ -1546,48 +1764,49 @@ static int parse_read_errors(struct qcom_nand_host *host, u8 *data_buf,
|
|
|
buffer = le32_to_cpu(buf->buffer);
|
|
buffer = le32_to_cpu(buf->buffer);
|
|
|
erased_cw = le32_to_cpu(buf->erased_cw);
|
|
erased_cw = le32_to_cpu(buf->erased_cw);
|
|
|
|
|
|
|
|
- if (flash & (FS_OP_ERR | FS_MPU_ERR)) {
|
|
|
|
|
- bool erased;
|
|
|
|
|
-
|
|
|
|
|
- /* ignore erased codeword errors */
|
|
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Check ECC failure for each codeword. ECC failure can
|
|
|
|
|
+ * happen in either of the following conditions
|
|
|
|
|
+ * 1. If number of bitflips are greater than ECC engine
|
|
|
|
|
+ * capability.
|
|
|
|
|
+ * 2. If this codeword contains all 0xff for which erased
|
|
|
|
|
+ * codeword detection check will be done.
|
|
|
|
|
+ */
|
|
|
|
|
+ if ((flash & FS_OP_ERR) && (buffer & BS_UNCORRECTABLE_BIT)) {
|
|
|
|
|
+ /*
|
|
|
|
|
+ * For BCH ECC, ignore erased codeword errors, if
|
|
|
|
|
+ * ERASED_CW bits are set.
|
|
|
|
|
+ */
|
|
|
if (host->bch_enabled) {
|
|
if (host->bch_enabled) {
|
|
|
erased = (erased_cw & ERASED_CW) == ERASED_CW ?
|
|
erased = (erased_cw & ERASED_CW) == ERASED_CW ?
|
|
|
true : false;
|
|
true : false;
|
|
|
- } else {
|
|
|
|
|
|
|
+ /*
|
|
|
|
|
+ * For RS ECC, HW reports the erased CW by placing
|
|
|
|
|
+ * special characters at certain offsets in the buffer.
|
|
|
|
|
+ * These special characters will be valid only if
|
|
|
|
|
+ * complete page is read i.e. data_buf is not NULL.
|
|
|
|
|
+ */
|
|
|
|
|
+ } else if (data_buf) {
|
|
|
erased = erased_chunk_check_and_fixup(data_buf,
|
|
erased = erased_chunk_check_and_fixup(data_buf,
|
|
|
data_len);
|
|
data_len);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ erased = false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (erased) {
|
|
|
|
|
- data_buf += data_len;
|
|
|
|
|
- if (oob_buf)
|
|
|
|
|
- oob_buf += oob_len + ecc->bytes;
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (buffer & BS_UNCORRECTABLE_BIT) {
|
|
|
|
|
- int ret, ecclen, extraooblen;
|
|
|
|
|
- void *eccbuf;
|
|
|
|
|
-
|
|
|
|
|
- eccbuf = oob_buf ? oob_buf + oob_len : NULL;
|
|
|
|
|
- ecclen = oob_buf ? host->ecc_bytes_hw : 0;
|
|
|
|
|
- extraooblen = oob_buf ? oob_len : 0;
|
|
|
|
|
-
|
|
|
|
|
- /*
|
|
|
|
|
- * make sure it isn't an erased page reported
|
|
|
|
|
- * as not-erased by HW because of a few bitflips
|
|
|
|
|
- */
|
|
|
|
|
- ret = nand_check_erased_ecc_chunk(data_buf,
|
|
|
|
|
- data_len, eccbuf, ecclen, oob_buf,
|
|
|
|
|
- extraooblen, ecc->strength);
|
|
|
|
|
- if (ret < 0) {
|
|
|
|
|
- mtd->ecc_stats.failed++;
|
|
|
|
|
- } else {
|
|
|
|
|
- mtd->ecc_stats.corrected += ret;
|
|
|
|
|
- max_bitflips =
|
|
|
|
|
- max_t(unsigned int, max_bitflips, ret);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (!erased)
|
|
|
|
|
+ uncorrectable_cws |= BIT(i);
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Check if MPU or any other operational error (timeout,
|
|
|
|
|
+ * device failure, etc.) happened for this codeword and
|
|
|
|
|
+ * make flash_op_err true. If flash_op_err is set, then
|
|
|
|
|
+ * EIO will be returned for page read.
|
|
|
|
|
+ */
|
|
|
|
|
+ } else if (flash & (FS_OP_ERR | FS_MPU_ERR)) {
|
|
|
|
|
+ flash_op_err = true;
|
|
|
|
|
+ /*
|
|
|
|
|
+ * No ECC or operational errors happened. Check the number of
|
|
|
|
|
+ * bits corrected and update the ecc_stats.corrected.
|
|
|
|
|
+ */
|
|
|
} else {
|
|
} else {
|
|
|
unsigned int stat;
|
|
unsigned int stat;
|
|
|
|
|
|
|
@@ -1596,12 +1815,21 @@ static int parse_read_errors(struct qcom_nand_host *host, u8 *data_buf,
|
|
|
max_bitflips = max(max_bitflips, stat);
|
|
max_bitflips = max(max_bitflips, stat);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- data_buf += data_len;
|
|
|
|
|
|
|
+ if (data_buf)
|
|
|
|
|
+ data_buf += data_len;
|
|
|
if (oob_buf)
|
|
if (oob_buf)
|
|
|
oob_buf += oob_len + ecc->bytes;
|
|
oob_buf += oob_len + ecc->bytes;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return max_bitflips;
|
|
|
|
|
|
|
+ if (flash_op_err)
|
|
|
|
|
+ return -EIO;
|
|
|
|
|
+
|
|
|
|
|
+ if (!uncorrectable_cws)
|
|
|
|
|
+ return max_bitflips;
|
|
|
|
|
+
|
|
|
|
|
+ return check_for_erased_page(host, data_buf_start, oob_buf_start,
|
|
|
|
|
+ uncorrectable_cws, page,
|
|
|
|
|
+ max_bitflips);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -1609,11 +1837,12 @@ static int parse_read_errors(struct qcom_nand_host *host, u8 *data_buf,
|
|
|
* ecc->read_oob()
|
|
* ecc->read_oob()
|
|
|
*/
|
|
*/
|
|
|
static int read_page_ecc(struct qcom_nand_host *host, u8 *data_buf,
|
|
static int read_page_ecc(struct qcom_nand_host *host, u8 *data_buf,
|
|
|
- u8 *oob_buf)
|
|
|
|
|
|
|
+ u8 *oob_buf, int page)
|
|
|
{
|
|
{
|
|
|
struct nand_chip *chip = &host->chip;
|
|
struct nand_chip *chip = &host->chip;
|
|
|
struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
|
|
struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
|
|
|
struct nand_ecc_ctrl *ecc = &chip->ecc;
|
|
struct nand_ecc_ctrl *ecc = &chip->ecc;
|
|
|
|
|
+ u8 *data_buf_start = data_buf, *oob_buf_start = oob_buf;
|
|
|
int i, ret;
|
|
int i, ret;
|
|
|
|
|
|
|
|
config_nand_page_read(nandc);
|
|
config_nand_page_read(nandc);
|
|
@@ -1644,7 +1873,7 @@ static int read_page_ecc(struct qcom_nand_host *host, u8 *data_buf,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- config_nand_cw_read(nandc);
|
|
|
|
|
|
|
+ config_nand_cw_read(nandc, true);
|
|
|
|
|
|
|
|
if (data_buf)
|
|
if (data_buf)
|
|
|
read_data_dma(nandc, FLASH_BUF_ACC, data_buf,
|
|
read_data_dma(nandc, FLASH_BUF_ACC, data_buf,
|
|
@@ -1674,12 +1903,14 @@ static int read_page_ecc(struct qcom_nand_host *host, u8 *data_buf,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
ret = submit_descs(nandc);
|
|
ret = submit_descs(nandc);
|
|
|
- if (ret)
|
|
|
|
|
- dev_err(nandc->dev, "failure to read page/oob\n");
|
|
|
|
|
-
|
|
|
|
|
free_descs(nandc);
|
|
free_descs(nandc);
|
|
|
|
|
|
|
|
- return ret;
|
|
|
|
|
|
|
+ if (ret) {
|
|
|
|
|
+ dev_err(nandc->dev, "failure to read page/oob\n");
|
|
|
|
|
+ return ret;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return parse_read_errors(host, data_buf_start, oob_buf_start, page);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -1704,7 +1935,7 @@ static int copy_last_cw(struct qcom_nand_host *host, int page)
|
|
|
set_address(host, host->cw_size * (ecc->steps - 1), page);
|
|
set_address(host, host->cw_size * (ecc->steps - 1), page);
|
|
|
update_rw_regs(host, 1, true);
|
|
update_rw_regs(host, 1, true);
|
|
|
|
|
|
|
|
- config_nand_single_cw_page_read(nandc);
|
|
|
|
|
|
|
+ config_nand_single_cw_page_read(nandc, host->use_ecc);
|
|
|
|
|
|
|
|
read_data_dma(nandc, FLASH_BUF_ACC, nandc->data_buffer, size, 0);
|
|
read_data_dma(nandc, FLASH_BUF_ACC, nandc->data_buffer, size, 0);
|
|
|
|
|
|
|
@@ -1724,20 +1955,14 @@ static int qcom_nandc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
|
|
|
struct qcom_nand_host *host = to_qcom_nand_host(chip);
|
|
struct qcom_nand_host *host = to_qcom_nand_host(chip);
|
|
|
struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
|
|
struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
|
|
|
u8 *data_buf, *oob_buf = NULL;
|
|
u8 *data_buf, *oob_buf = NULL;
|
|
|
- int ret;
|
|
|
|
|
|
|
|
|
|
nand_read_page_op(chip, page, 0, NULL, 0);
|
|
nand_read_page_op(chip, page, 0, NULL, 0);
|
|
|
data_buf = buf;
|
|
data_buf = buf;
|
|
|
oob_buf = oob_required ? chip->oob_poi : NULL;
|
|
oob_buf = oob_required ? chip->oob_poi : NULL;
|
|
|
|
|
|
|
|
clear_bam_transaction(nandc);
|
|
clear_bam_transaction(nandc);
|
|
|
- ret = read_page_ecc(host, data_buf, oob_buf);
|
|
|
|
|
- if (ret) {
|
|
|
|
|
- dev_err(nandc->dev, "failure to read page\n");
|
|
|
|
|
- return ret;
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- return parse_read_errors(host, data_buf, oob_buf);
|
|
|
|
|
|
|
+ return read_page_ecc(host, data_buf, oob_buf, page);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* implements ecc->read_page_raw() */
|
|
/* implements ecc->read_page_raw() */
|
|
@@ -1746,77 +1971,20 @@ static int qcom_nandc_read_page_raw(struct mtd_info *mtd,
|
|
|
int oob_required, int page)
|
|
int oob_required, int page)
|
|
|
{
|
|
{
|
|
|
struct qcom_nand_host *host = to_qcom_nand_host(chip);
|
|
struct qcom_nand_host *host = to_qcom_nand_host(chip);
|
|
|
- struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
|
|
|
|
|
- u8 *data_buf, *oob_buf;
|
|
|
|
|
struct nand_ecc_ctrl *ecc = &chip->ecc;
|
|
struct nand_ecc_ctrl *ecc = &chip->ecc;
|
|
|
- int i, ret;
|
|
|
|
|
- int read_loc;
|
|
|
|
|
-
|
|
|
|
|
- nand_read_page_op(chip, page, 0, NULL, 0);
|
|
|
|
|
- data_buf = buf;
|
|
|
|
|
- oob_buf = chip->oob_poi;
|
|
|
|
|
-
|
|
|
|
|
- host->use_ecc = false;
|
|
|
|
|
|
|
+ int cw, ret;
|
|
|
|
|
+ u8 *data_buf = buf, *oob_buf = chip->oob_poi;
|
|
|
|
|
|
|
|
- clear_bam_transaction(nandc);
|
|
|
|
|
- update_rw_regs(host, ecc->steps, true);
|
|
|
|
|
- config_nand_page_read(nandc);
|
|
|
|
|
-
|
|
|
|
|
- for (i = 0; i < ecc->steps; i++) {
|
|
|
|
|
- int data_size1, data_size2, oob_size1, oob_size2;
|
|
|
|
|
- int reg_off = FLASH_BUF_ACC;
|
|
|
|
|
-
|
|
|
|
|
- data_size1 = mtd->writesize - host->cw_size * (ecc->steps - 1);
|
|
|
|
|
- oob_size1 = host->bbm_size;
|
|
|
|
|
-
|
|
|
|
|
- if (i == (ecc->steps - 1)) {
|
|
|
|
|
- data_size2 = ecc->size - data_size1 -
|
|
|
|
|
- ((ecc->steps - 1) << 2);
|
|
|
|
|
- oob_size2 = (ecc->steps << 2) + host->ecc_bytes_hw +
|
|
|
|
|
- host->spare_bytes;
|
|
|
|
|
- } else {
|
|
|
|
|
- data_size2 = host->cw_data - data_size1;
|
|
|
|
|
- oob_size2 = host->ecc_bytes_hw + host->spare_bytes;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (nandc->props->is_bam) {
|
|
|
|
|
- read_loc = 0;
|
|
|
|
|
- nandc_set_read_loc(nandc, 0, read_loc, data_size1, 0);
|
|
|
|
|
- read_loc += data_size1;
|
|
|
|
|
-
|
|
|
|
|
- nandc_set_read_loc(nandc, 1, read_loc, oob_size1, 0);
|
|
|
|
|
- read_loc += oob_size1;
|
|
|
|
|
-
|
|
|
|
|
- nandc_set_read_loc(nandc, 2, read_loc, data_size2, 0);
|
|
|
|
|
- read_loc += data_size2;
|
|
|
|
|
-
|
|
|
|
|
- nandc_set_read_loc(nandc, 3, read_loc, oob_size2, 1);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- config_nand_cw_read(nandc);
|
|
|
|
|
-
|
|
|
|
|
- read_data_dma(nandc, reg_off, data_buf, data_size1, 0);
|
|
|
|
|
- reg_off += data_size1;
|
|
|
|
|
- data_buf += data_size1;
|
|
|
|
|
-
|
|
|
|
|
- read_data_dma(nandc, reg_off, oob_buf, oob_size1, 0);
|
|
|
|
|
- reg_off += oob_size1;
|
|
|
|
|
- oob_buf += oob_size1;
|
|
|
|
|
-
|
|
|
|
|
- read_data_dma(nandc, reg_off, data_buf, data_size2, 0);
|
|
|
|
|
- reg_off += data_size2;
|
|
|
|
|
- data_buf += data_size2;
|
|
|
|
|
|
|
+ for (cw = 0; cw < ecc->steps; cw++) {
|
|
|
|
|
+ ret = qcom_nandc_read_cw_raw(mtd, chip, data_buf, oob_buf,
|
|
|
|
|
+ page, cw);
|
|
|
|
|
+ if (ret)
|
|
|
|
|
+ return ret;
|
|
|
|
|
|
|
|
- read_data_dma(nandc, reg_off, oob_buf, oob_size2, 0);
|
|
|
|
|
- oob_buf += oob_size2;
|
|
|
|
|
|
|
+ data_buf += host->cw_data;
|
|
|
|
|
+ oob_buf += ecc->bytes;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- ret = submit_descs(nandc);
|
|
|
|
|
- if (ret)
|
|
|
|
|
- dev_err(nandc->dev, "failure to read raw page\n");
|
|
|
|
|
-
|
|
|
|
|
- free_descs(nandc);
|
|
|
|
|
-
|
|
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1827,7 +1995,6 @@ static int qcom_nandc_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
|
|
|
struct qcom_nand_host *host = to_qcom_nand_host(chip);
|
|
struct qcom_nand_host *host = to_qcom_nand_host(chip);
|
|
|
struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
|
|
struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
|
|
|
struct nand_ecc_ctrl *ecc = &chip->ecc;
|
|
struct nand_ecc_ctrl *ecc = &chip->ecc;
|
|
|
- int ret;
|
|
|
|
|
|
|
|
|
|
clear_read_regs(nandc);
|
|
clear_read_regs(nandc);
|
|
|
clear_bam_transaction(nandc);
|
|
clear_bam_transaction(nandc);
|
|
@@ -1836,11 +2003,7 @@ static int qcom_nandc_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
|
|
|
set_address(host, 0, page);
|
|
set_address(host, 0, page);
|
|
|
update_rw_regs(host, ecc->steps, true);
|
|
update_rw_regs(host, ecc->steps, true);
|
|
|
|
|
|
|
|
- ret = read_page_ecc(host, NULL, chip->oob_poi);
|
|
|
|
|
- if (ret)
|
|
|
|
|
- dev_err(nandc->dev, "failure to read oob\n");
|
|
|
|
|
-
|
|
|
|
|
- return ret;
|
|
|
|
|
|
|
+ return read_page_ecc(host, NULL, chip->oob_poi, page);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* implements ecc->write_page() */
|
|
/* implements ecc->write_page() */
|
|
@@ -1988,11 +2151,9 @@ static int qcom_nandc_write_page_raw(struct mtd_info *mtd,
|
|
|
/*
|
|
/*
|
|
|
* implements ecc->write_oob()
|
|
* implements ecc->write_oob()
|
|
|
*
|
|
*
|
|
|
- * the NAND controller cannot write only data or only oob within a codeword,
|
|
|
|
|
- * since ecc is calculated for the combined codeword. we first copy the
|
|
|
|
|
- * entire contents for the last codeword(data + oob), replace the old oob
|
|
|
|
|
- * with the new one in chip->oob_poi, and then write the entire codeword.
|
|
|
|
|
- * this read-copy-write operation results in a slight performance loss.
|
|
|
|
|
|
|
+ * the NAND controller cannot write only data or only OOB within a codeword
|
|
|
|
|
+ * since ECC is calculated for the combined codeword. So update the OOB from
|
|
|
|
|
+ * chip->oob_poi, and pad the data area with OxFF before writing.
|
|
|
*/
|
|
*/
|
|
|
static int qcom_nandc_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
|
|
static int qcom_nandc_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
|
|
|
int page)
|
|
int page)
|
|
@@ -2005,19 +2166,13 @@ static int qcom_nandc_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
|
|
|
int ret;
|
|
int ret;
|
|
|
|
|
|
|
|
host->use_ecc = true;
|
|
host->use_ecc = true;
|
|
|
-
|
|
|
|
|
- clear_bam_transaction(nandc);
|
|
|
|
|
- ret = copy_last_cw(host, page);
|
|
|
|
|
- if (ret)
|
|
|
|
|
- return ret;
|
|
|
|
|
-
|
|
|
|
|
- clear_read_regs(nandc);
|
|
|
|
|
clear_bam_transaction(nandc);
|
|
clear_bam_transaction(nandc);
|
|
|
|
|
|
|
|
/* calculate the data and oob size for the last codeword/step */
|
|
/* calculate the data and oob size for the last codeword/step */
|
|
|
data_size = ecc->size - ((ecc->steps - 1) << 2);
|
|
data_size = ecc->size - ((ecc->steps - 1) << 2);
|
|
|
oob_size = mtd->oobavail;
|
|
oob_size = mtd->oobavail;
|
|
|
|
|
|
|
|
|
|
+ memset(nandc->data_buffer, 0xff, host->cw_data);
|
|
|
/* override new oob content to last codeword */
|
|
/* override new oob content to last codeword */
|
|
|
mtd_ooblayout_get_databytes(mtd, nandc->data_buffer + data_size, oob,
|
|
mtd_ooblayout_get_databytes(mtd, nandc->data_buffer + data_size, oob,
|
|
|
0, mtd->oobavail);
|
|
0, mtd->oobavail);
|
|
@@ -2049,7 +2204,6 @@ static int qcom_nandc_block_bad(struct mtd_info *mtd, loff_t ofs)
|
|
|
struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
|
|
struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
|
|
|
struct nand_ecc_ctrl *ecc = &chip->ecc;
|
|
struct nand_ecc_ctrl *ecc = &chip->ecc;
|
|
|
int page, ret, bbpos, bad = 0;
|
|
int page, ret, bbpos, bad = 0;
|
|
|
- u32 flash_status;
|
|
|
|
|
|
|
|
|
|
page = (int)(ofs >> chip->page_shift) & chip->pagemask;
|
|
page = (int)(ofs >> chip->page_shift) & chip->pagemask;
|
|
|
|
|
|
|
@@ -2066,9 +2220,7 @@ static int qcom_nandc_block_bad(struct mtd_info *mtd, loff_t ofs)
|
|
|
if (ret)
|
|
if (ret)
|
|
|
goto err;
|
|
goto err;
|
|
|
|
|
|
|
|
- flash_status = le32_to_cpu(nandc->reg_read_buf[0]);
|
|
|
|
|
-
|
|
|
|
|
- if (flash_status & (FS_OP_ERR | FS_MPU_ERR)) {
|
|
|
|
|
|
|
+ if (check_flash_errors(host, 1)) {
|
|
|
dev_warn(nandc->dev, "error when trying to read BBM\n");
|
|
dev_warn(nandc->dev, "error when trying to read BBM\n");
|
|
|
goto err;
|
|
goto err;
|
|
|
}
|
|
}
|
|
@@ -2315,27 +2467,40 @@ static const struct mtd_ooblayout_ops qcom_nand_ooblayout_ops = {
|
|
|
.free = qcom_nand_ooblayout_free,
|
|
.free = qcom_nand_ooblayout_free,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-static int qcom_nand_host_setup(struct qcom_nand_host *host)
|
|
|
|
|
|
|
+static int
|
|
|
|
|
+qcom_nandc_calc_ecc_bytes(int step_size, int strength)
|
|
|
|
|
+{
|
|
|
|
|
+ return strength == 4 ? 12 : 16;
|
|
|
|
|
+}
|
|
|
|
|
+NAND_ECC_CAPS_SINGLE(qcom_nandc_ecc_caps, qcom_nandc_calc_ecc_bytes,
|
|
|
|
|
+ NANDC_STEP_SIZE, 4, 8);
|
|
|
|
|
+
|
|
|
|
|
+static int qcom_nand_attach_chip(struct nand_chip *chip)
|
|
|
{
|
|
{
|
|
|
- struct nand_chip *chip = &host->chip;
|
|
|
|
|
struct mtd_info *mtd = nand_to_mtd(chip);
|
|
struct mtd_info *mtd = nand_to_mtd(chip);
|
|
|
|
|
+ struct qcom_nand_host *host = to_qcom_nand_host(chip);
|
|
|
struct nand_ecc_ctrl *ecc = &chip->ecc;
|
|
struct nand_ecc_ctrl *ecc = &chip->ecc;
|
|
|
struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
|
|
struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
|
|
|
- int cwperpage, bad_block_byte;
|
|
|
|
|
|
|
+ int cwperpage, bad_block_byte, ret;
|
|
|
bool wide_bus;
|
|
bool wide_bus;
|
|
|
int ecc_mode = 1;
|
|
int ecc_mode = 1;
|
|
|
|
|
|
|
|
|
|
+ /* controller only supports 512 bytes data steps */
|
|
|
|
|
+ ecc->size = NANDC_STEP_SIZE;
|
|
|
|
|
+ wide_bus = chip->options & NAND_BUSWIDTH_16 ? true : false;
|
|
|
|
|
+ cwperpage = mtd->writesize / NANDC_STEP_SIZE;
|
|
|
|
|
+
|
|
|
/*
|
|
/*
|
|
|
- * the controller requires each step consists of 512 bytes of data.
|
|
|
|
|
- * bail out if DT has populated a wrong step size.
|
|
|
|
|
|
|
+ * Each CW has 4 available OOB bytes which will be protected with ECC
|
|
|
|
|
+ * so remaining bytes can be used for ECC.
|
|
|
*/
|
|
*/
|
|
|
- if (ecc->size != NANDC_STEP_SIZE) {
|
|
|
|
|
- dev_err(nandc->dev, "invalid ecc size\n");
|
|
|
|
|
- return -EINVAL;
|
|
|
|
|
|
|
+ ret = nand_ecc_choose_conf(chip, &qcom_nandc_ecc_caps,
|
|
|
|
|
+ mtd->oobsize - (cwperpage * 4));
|
|
|
|
|
+ if (ret) {
|
|
|
|
|
+ dev_err(nandc->dev, "No valid ECC settings possible\n");
|
|
|
|
|
+ return ret;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- wide_bus = chip->options & NAND_BUSWIDTH_16 ? true : false;
|
|
|
|
|
-
|
|
|
|
|
if (ecc->strength >= 8) {
|
|
if (ecc->strength >= 8) {
|
|
|
/* 8 bit ECC defaults to BCH ECC on all platforms */
|
|
/* 8 bit ECC defaults to BCH ECC on all platforms */
|
|
|
host->bch_enabled = true;
|
|
host->bch_enabled = true;
|
|
@@ -2403,7 +2568,6 @@ static int qcom_nand_host_setup(struct qcom_nand_host *host)
|
|
|
|
|
|
|
|
mtd_set_ooblayout(mtd, &qcom_nand_ooblayout_ops);
|
|
mtd_set_ooblayout(mtd, &qcom_nand_ooblayout_ops);
|
|
|
|
|
|
|
|
- cwperpage = mtd->writesize / ecc->size;
|
|
|
|
|
nandc->max_cwperpage = max_t(unsigned int, nandc->max_cwperpage,
|
|
nandc->max_cwperpage = max_t(unsigned int, nandc->max_cwperpage,
|
|
|
cwperpage);
|
|
cwperpage);
|
|
|
|
|
|
|
@@ -2419,12 +2583,6 @@ static int qcom_nand_host_setup(struct qcom_nand_host *host)
|
|
|
* for 8 bit ECC
|
|
* for 8 bit ECC
|
|
|
*/
|
|
*/
|
|
|
host->cw_size = host->cw_data + ecc->bytes;
|
|
host->cw_size = host->cw_data + ecc->bytes;
|
|
|
-
|
|
|
|
|
- if (ecc->bytes * (mtd->writesize / ecc->size) > mtd->oobsize) {
|
|
|
|
|
- dev_err(nandc->dev, "ecc data doesn't fit in OOB area\n");
|
|
|
|
|
- return -EINVAL;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
bad_block_byte = mtd->writesize - host->cw_size * (cwperpage - 1) + 1;
|
|
bad_block_byte = mtd->writesize - host->cw_size * (cwperpage - 1) + 1;
|
|
|
|
|
|
|
|
host->cfg0 = (cwperpage - 1) << CW_PER_PAGE
|
|
host->cfg0 = (cwperpage - 1) << CW_PER_PAGE
|
|
@@ -2482,6 +2640,10 @@ static int qcom_nand_host_setup(struct qcom_nand_host *host)
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+static const struct nand_controller_ops qcom_nandc_ops = {
|
|
|
|
|
+ .attach_chip = qcom_nand_attach_chip,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
static int qcom_nandc_alloc(struct qcom_nand_controller *nandc)
|
|
static int qcom_nandc_alloc(struct qcom_nand_controller *nandc)
|
|
|
{
|
|
{
|
|
|
int ret;
|
|
int ret;
|
|
@@ -2570,7 +2732,8 @@ static int qcom_nandc_alloc(struct qcom_nand_controller *nandc)
|
|
|
INIT_LIST_HEAD(&nandc->desc_list);
|
|
INIT_LIST_HEAD(&nandc->desc_list);
|
|
|
INIT_LIST_HEAD(&nandc->host_list);
|
|
INIT_LIST_HEAD(&nandc->host_list);
|
|
|
|
|
|
|
|
- nand_hw_control_init(&nandc->controller);
|
|
|
|
|
|
|
+ nand_controller_init(&nandc->controller);
|
|
|
|
|
+ nandc->controller.ops = &qcom_nandc_ops;
|
|
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
@@ -2623,9 +2786,9 @@ static int qcom_nandc_setup(struct qcom_nand_controller *nandc)
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-static int qcom_nand_host_init(struct qcom_nand_controller *nandc,
|
|
|
|
|
- struct qcom_nand_host *host,
|
|
|
|
|
- struct device_node *dn)
|
|
|
|
|
|
|
+static int qcom_nand_host_init_and_register(struct qcom_nand_controller *nandc,
|
|
|
|
|
+ struct qcom_nand_host *host,
|
|
|
|
|
+ struct device_node *dn)
|
|
|
{
|
|
{
|
|
|
struct nand_chip *chip = &host->chip;
|
|
struct nand_chip *chip = &host->chip;
|
|
|
struct mtd_info *mtd = nand_to_mtd(chip);
|
|
struct mtd_info *mtd = nand_to_mtd(chip);
|
|
@@ -2672,30 +2835,13 @@ static int qcom_nand_host_init(struct qcom_nand_controller *nandc,
|
|
|
/* set up initial status value */
|
|
/* set up initial status value */
|
|
|
host->status = NAND_STATUS_READY | NAND_STATUS_WP;
|
|
host->status = NAND_STATUS_READY | NAND_STATUS_WP;
|
|
|
|
|
|
|
|
- ret = nand_scan_ident(mtd, 1, NULL);
|
|
|
|
|
- if (ret)
|
|
|
|
|
- return ret;
|
|
|
|
|
-
|
|
|
|
|
- ret = qcom_nand_host_setup(host);
|
|
|
|
|
-
|
|
|
|
|
- return ret;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-static int qcom_nand_mtd_register(struct qcom_nand_controller *nandc,
|
|
|
|
|
- struct qcom_nand_host *host,
|
|
|
|
|
- struct device_node *dn)
|
|
|
|
|
-{
|
|
|
|
|
- struct nand_chip *chip = &host->chip;
|
|
|
|
|
- struct mtd_info *mtd = nand_to_mtd(chip);
|
|
|
|
|
- int ret;
|
|
|
|
|
-
|
|
|
|
|
- ret = nand_scan_tail(mtd);
|
|
|
|
|
|
|
+ ret = nand_scan(mtd, 1);
|
|
|
if (ret)
|
|
if (ret)
|
|
|
return ret;
|
|
return ret;
|
|
|
|
|
|
|
|
ret = mtd_device_register(mtd, NULL, 0);
|
|
ret = mtd_device_register(mtd, NULL, 0);
|
|
|
if (ret)
|
|
if (ret)
|
|
|
- nand_cleanup(mtd_to_nand(mtd));
|
|
|
|
|
|
|
+ nand_cleanup(chip);
|
|
|
|
|
|
|
|
return ret;
|
|
return ret;
|
|
|
}
|
|
}
|
|
@@ -2704,28 +2850,9 @@ static int qcom_probe_nand_devices(struct qcom_nand_controller *nandc)
|
|
|
{
|
|
{
|
|
|
struct device *dev = nandc->dev;
|
|
struct device *dev = nandc->dev;
|
|
|
struct device_node *dn = dev->of_node, *child;
|
|
struct device_node *dn = dev->of_node, *child;
|
|
|
- struct qcom_nand_host *host, *tmp;
|
|
|
|
|
|
|
+ struct qcom_nand_host *host;
|
|
|
int ret;
|
|
int ret;
|
|
|
|
|
|
|
|
- for_each_available_child_of_node(dn, child) {
|
|
|
|
|
- host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
|
|
|
|
|
- if (!host) {
|
|
|
|
|
- of_node_put(child);
|
|
|
|
|
- return -ENOMEM;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- ret = qcom_nand_host_init(nandc, host, child);
|
|
|
|
|
- if (ret) {
|
|
|
|
|
- devm_kfree(dev, host);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- list_add_tail(&host->node, &nandc->host_list);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (list_empty(&nandc->host_list))
|
|
|
|
|
- return -ENODEV;
|
|
|
|
|
-
|
|
|
|
|
if (nandc->props->is_bam) {
|
|
if (nandc->props->is_bam) {
|
|
|
free_bam_transaction(nandc);
|
|
free_bam_transaction(nandc);
|
|
|
nandc->bam_txn = alloc_bam_transaction(nandc);
|
|
nandc->bam_txn = alloc_bam_transaction(nandc);
|
|
@@ -2736,12 +2863,20 @@ static int qcom_probe_nand_devices(struct qcom_nand_controller *nandc)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- list_for_each_entry_safe(host, tmp, &nandc->host_list, node) {
|
|
|
|
|
- ret = qcom_nand_mtd_register(nandc, host, child);
|
|
|
|
|
|
|
+ for_each_available_child_of_node(dn, child) {
|
|
|
|
|
+ host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
|
|
|
|
|
+ if (!host) {
|
|
|
|
|
+ of_node_put(child);
|
|
|
|
|
+ return -ENOMEM;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ret = qcom_nand_host_init_and_register(nandc, host, child);
|
|
|
if (ret) {
|
|
if (ret) {
|
|
|
- list_del(&host->node);
|
|
|
|
|
devm_kfree(dev, host);
|
|
devm_kfree(dev, host);
|
|
|
|
|
+ continue;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ list_add_tail(&host->node, &nandc->host_list);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (list_empty(&nandc->host_list))
|
|
if (list_empty(&nandc->host_list))
|
|
@@ -2799,14 +2934,6 @@ static int qcom_nandc_probe(struct platform_device *pdev)
|
|
|
|
|
|
|
|
nandc->props = dev_data;
|
|
nandc->props = dev_data;
|
|
|
|
|
|
|
|
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
|
|
- nandc->base = devm_ioremap_resource(dev, res);
|
|
|
|
|
- if (IS_ERR(nandc->base))
|
|
|
|
|
- return PTR_ERR(nandc->base);
|
|
|
|
|
-
|
|
|
|
|
- nandc->base_phys = res->start;
|
|
|
|
|
- nandc->base_dma = phys_to_dma(dev, (phys_addr_t)res->start);
|
|
|
|
|
-
|
|
|
|
|
nandc->core_clk = devm_clk_get(dev, "core");
|
|
nandc->core_clk = devm_clk_get(dev, "core");
|
|
|
if (IS_ERR(nandc->core_clk))
|
|
if (IS_ERR(nandc->core_clk))
|
|
|
return PTR_ERR(nandc->core_clk);
|
|
return PTR_ERR(nandc->core_clk);
|
|
@@ -2819,9 +2946,21 @@ static int qcom_nandc_probe(struct platform_device *pdev)
|
|
|
if (ret)
|
|
if (ret)
|
|
|
return ret;
|
|
return ret;
|
|
|
|
|
|
|
|
|
|
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
|
|
+ nandc->base = devm_ioremap_resource(dev, res);
|
|
|
|
|
+ if (IS_ERR(nandc->base))
|
|
|
|
|
+ return PTR_ERR(nandc->base);
|
|
|
|
|
+
|
|
|
|
|
+ nandc->base_phys = res->start;
|
|
|
|
|
+ nandc->base_dma = dma_map_resource(dev, res->start,
|
|
|
|
|
+ resource_size(res),
|
|
|
|
|
+ DMA_BIDIRECTIONAL, 0);
|
|
|
|
|
+ if (!nandc->base_dma)
|
|
|
|
|
+ return -ENXIO;
|
|
|
|
|
+
|
|
|
ret = qcom_nandc_alloc(nandc);
|
|
ret = qcom_nandc_alloc(nandc);
|
|
|
if (ret)
|
|
if (ret)
|
|
|
- goto err_core_clk;
|
|
|
|
|
|
|
+ goto err_nandc_alloc;
|
|
|
|
|
|
|
|
ret = clk_prepare_enable(nandc->core_clk);
|
|
ret = clk_prepare_enable(nandc->core_clk);
|
|
|
if (ret)
|
|
if (ret)
|
|
@@ -2847,6 +2986,9 @@ err_aon_clk:
|
|
|
clk_disable_unprepare(nandc->core_clk);
|
|
clk_disable_unprepare(nandc->core_clk);
|
|
|
err_core_clk:
|
|
err_core_clk:
|
|
|
qcom_nandc_unalloc(nandc);
|
|
qcom_nandc_unalloc(nandc);
|
|
|
|
|
+err_nandc_alloc:
|
|
|
|
|
+ dma_unmap_resource(dev, res->start, resource_size(res),
|
|
|
|
|
+ DMA_BIDIRECTIONAL, 0);
|
|
|
|
|
|
|
|
return ret;
|
|
return ret;
|
|
|
}
|
|
}
|
|
@@ -2854,16 +2996,21 @@ err_core_clk:
|
|
|
static int qcom_nandc_remove(struct platform_device *pdev)
|
|
static int qcom_nandc_remove(struct platform_device *pdev)
|
|
|
{
|
|
{
|
|
|
struct qcom_nand_controller *nandc = platform_get_drvdata(pdev);
|
|
struct qcom_nand_controller *nandc = platform_get_drvdata(pdev);
|
|
|
|
|
+ struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
struct qcom_nand_host *host;
|
|
struct qcom_nand_host *host;
|
|
|
|
|
|
|
|
list_for_each_entry(host, &nandc->host_list, node)
|
|
list_for_each_entry(host, &nandc->host_list, node)
|
|
|
nand_release(nand_to_mtd(&host->chip));
|
|
nand_release(nand_to_mtd(&host->chip));
|
|
|
|
|
|
|
|
|
|
+
|
|
|
qcom_nandc_unalloc(nandc);
|
|
qcom_nandc_unalloc(nandc);
|
|
|
|
|
|
|
|
clk_disable_unprepare(nandc->aon_clk);
|
|
clk_disable_unprepare(nandc->aon_clk);
|
|
|
clk_disable_unprepare(nandc->core_clk);
|
|
clk_disable_unprepare(nandc->core_clk);
|
|
|
|
|
|
|
|
|
|
+ dma_unmap_resource(&pdev->dev, nandc->base_dma, resource_size(res),
|
|
|
|
|
+ DMA_BIDIRECTIONAL, 0);
|
|
|
|
|
+
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|