| /* |
| * Copyright (c) 2008, Google Inc. |
| * All rights reserved. |
| * Copyright (c) 2009-2018, The Linux Foundation. All rights reserved. |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
| * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
| * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
| * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
| * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| #include <common.h> |
| #include <malloc.h> |
| |
| #include <asm/io.h> |
| #include <asm/errno.h> |
| #include <nand.h> |
| #include <linux/mtd/nand.h> |
| #include <asm/arch-qca-common/bam.h> |
| #include <asm/arch-qca-common/qpic_nand.h> |
| #include <asm/arch-qca-common/gpio.h> |
| #include <fdtdec.h> |
| #include <dm.h> |
| |
| DECLARE_GLOBAL_DATA_PTR; |
| #ifdef READ_ONFI_PAGE_PARA |
| struct nand_onfi_para_page onfi_para; |
| #endif |
| |
| typedef unsigned long addr_t; |
| |
| static uint32_t hw_ver; |
| |
| #ifdef CONFIG_QPIC_SERIAL |
| static struct qpic_serial_nand_params qpic_serial_nand_tbl[] = { |
| { |
| .id = { 0xc8, 0xc1 }, |
| .page_size = 2048, |
| .erase_blk_size = 0x00020000, |
| .pgs_per_blk = 64, |
| .no_of_blocks = 1024, |
| .spare_size = 128, |
| .density = 0x08000000, |
| .otp_region = 0x2000, |
| .no_of_addr_cycle = 0x3, |
| .num_bits_ecc_correctability = 8, |
| .timing_mode_support = 0, |
| .quad_mode = true, |
| .check_quad_config = true, |
| .name = "GD5F1GQ4RE9IG", |
| }, |
| { |
| .id = { 0xc8, 0xc9 }, |
| .page_size = 2048, |
| .erase_blk_size = 0x00020000, |
| .pgs_per_blk = 64, |
| .no_of_blocks = 1024, |
| .spare_size = 64, |
| .density = 0x08000000, |
| .otp_region = 0x2000, |
| .no_of_addr_cycle = 0x3, |
| .num_bits_ecc_correctability = 4, |
| .timing_mode_support = 0, |
| .quad_mode = true, |
| .check_quad_config = true, |
| .name = "GD5F1GQ4RE9IH", |
| }, |
| { |
| .id = { 0xc8, 0x22 }, |
| .page_size = 2048, |
| .erase_blk_size = 0x00020000, |
| .pgs_per_blk = 64, |
| .no_of_blocks = 2048, |
| .spare_size = 64, |
| .density = 0x10000000, |
| .otp_region = 0x2000, |
| .no_of_addr_cycle = 0x3, |
| .num_bits_ecc_correctability = 4, |
| .timing_mode_support = 0, |
| .quad_mode = true, |
| .check_quad_config = true, |
| .name = "GD5F2GQ5REYIH", |
| }, |
| { |
| .id = { 0x2c, 0x15 }, |
| .page_size = 2048, |
| .erase_blk_size = 0x00020000, |
| .pgs_per_blk = 64, |
| .no_of_blocks = 1024, |
| .spare_size = 128, |
| .density = 0x08000000, |
| .otp_region = 0x5000, |
| .no_of_addr_cycle = 0x3, |
| .num_bits_ecc_correctability = 8, |
| .timing_mode_support = 0, |
| .quad_mode = true, |
| .check_quad_config = false, |
| .name = "MT29F1G01ABBFDWB-IT", |
| }, |
| { |
| .id = { 0xef, 0xbc }, |
| .page_size = 2048, |
| .erase_blk_size = 0x00020000, |
| .pgs_per_blk = 64, |
| .no_of_blocks = 1024, |
| .spare_size = 64, |
| .density = 0x08000000, |
| .otp_region = 0x5000, |
| .no_of_addr_cycle = 0x3, |
| .num_bits_ecc_correctability = 4, |
| .timing_mode_support = 0, |
| .quad_mode = true, |
| .check_quad_config = true, |
| .name = "W25N01JW", |
| }, |
| { |
| .id = { 0xc8, 0x11 }, |
| .page_size = 2048, |
| .erase_blk_size = 0x00020000, |
| .pgs_per_blk = 64, |
| .no_of_blocks = 1024, |
| .spare_size = 64, |
| .density = 0x08000000, |
| .otp_region = 0x2000, |
| .no_of_addr_cycle = 0x3, |
| .num_bits_ecc_correctability = 4, |
| .timing_mode_support = 0, |
| .quad_mode = true, |
| .check_quad_config = false, |
| .name = "F50D1G41LB(2M)", |
| }, |
| { |
| .id = { 0xc8, 0x41 }, |
| .page_size = 2048, |
| .erase_blk_size = 0x00020000, |
| .pgs_per_blk = 64, |
| .no_of_blocks = 2048, |
| .spare_size = 128, |
| .density = 0x08000000, |
| .otp_region = 0x2000, |
| .no_of_addr_cycle = 0x3, |
| .num_bits_ecc_correctability = 8, |
| .timing_mode_support = 0, |
| .quad_mode = true, |
| .check_quad_config = true, |
| .name = "GD5F1GQ5REYIG", |
| }, |
| { |
| .id = { 0xc8, 0x21 }, |
| .page_size = 2048, |
| .erase_blk_size = 0x00020000, |
| .pgs_per_blk = 64, |
| .no_of_blocks = 1024, |
| .spare_size = 64, |
| .density = 0x08000000, |
| .otp_region = 0x2000, |
| .no_of_addr_cycle = 0x3, |
| .num_bits_ecc_correctability = 4, |
| .timing_mode_support = 0, |
| .quad_mode = true, |
| .check_quad_config = true, |
| .name = "GD5F1GQ5REYIH", |
| }, |
| { |
| .id = { 0xef, 0xbf }, |
| .page_size = 2048, |
| .erase_blk_size = 0x00020000, |
| .pgs_per_blk = 64, |
| .no_of_blocks = 2048, |
| .spare_size = 64, |
| .density = 0x10000000, |
| .otp_region = 0x2000, |
| .no_of_addr_cycle = 0x3, |
| .num_bits_ecc_correctability = 4, |
| .timing_mode_support = 0, |
| .quad_mode = true, |
| .check_quad_config = true, |
| .name = "W25N02JWZEIF", |
| }, |
| { |
| .id = { 0xc2, 0x92 }, |
| .page_size = 2048, |
| .erase_blk_size = 0x00020000, |
| .pgs_per_blk = 64, |
| .no_of_blocks = 1024, |
| .spare_size = 64, |
| .density = 0x08000000, |
| .otp_region = 0x2000, |
| .no_of_addr_cycle = 0x3, |
| .num_bits_ecc_correctability = 4, |
| .timing_mode_support = 0, |
| .quad_mode = true, |
| .check_quad_config = true, |
| .name = "MX35UF1GE4AC", |
| }, |
| }; |
| struct qpic_serial_nand_params *serial_params; |
| #define MICRON_DEVICE_ID 0x152c152c |
| #define WINBOND_DEVICE_ID 0x0021bcef |
| #define CMD3_MASK 0xfff0ffff |
| /* |
| * An array holding the fixed pattern to compare with |
| * training pattern. |
| */ |
| static const unsigned int training_block_64[] = { |
| 0x0F0F0F0F, 0x0F0F0F0F, 0x0F0F0F0F, 0x0F0F0F0F, |
| 0x0F0F0F0F, 0x0F0F0F0F, 0x0F0F0F0F, 0x0F0F0F0F, |
| 0x0F0F0F0F, 0x0F0F0F0F, 0x0F0F0F0F, 0x0F0F0F0F, |
| 0x0F0F0F0F, 0x0F0F0F0F, 0x0F0F0F0F, 0x0F0F0F0F, |
| }; |
| #define TRAINING_PART_OFFSET 0x3c00000 |
| #define MAXIMUM_ALLOCATED_TRAINING_BLOCK 4 |
| #define TOTAL_NUM_PHASE 7 |
| #endif |
| |
| struct cmd_element ce_array[100] |
| __attribute__ ((aligned(CONFIG_SYS_CACHELINE_SIZE))); |
| struct cmd_element ce_read_array[20] |
| __attribute__ ((aligned(CONFIG_SYS_CACHELINE_SIZE))); |
| static struct qpic_nand_dev qpic_nand_dev; |
| struct bam_desc qpic_cmd_desc_fifo[QPIC_BAM_CMD_FIFO_SIZE] |
| __attribute__ ((aligned(CONFIG_SYS_CACHELINE_SIZE))); |
| struct bam_desc qpic_data_desc_fifo[QPIC_BAM_DATA_FIFO_SIZE] |
| __attribute__ ((aligned(CONFIG_SYS_CACHELINE_SIZE))); |
| struct bam_desc qpic_status_desc_fifo[QPIC_BAM_STATUS_FIFO_SIZE] |
| __attribute__ ((aligned(CONFIG_SYS_CACHELINE_SIZE))); |
| static struct bam_instance bam; |
| struct nand_ecclayout fake_ecc_layout; |
| |
| uint32_t ret_val __attribute__ ((aligned(CONFIG_SYS_CACHELINE_SIZE))); |
| uint8_t read_bytes[4] |
| __attribute__ ((aligned(CONFIG_SYS_CACHELINE_SIZE))); |
| uint32_t flash_sts[QPIC_NAND_MAX_CWS_IN_PAGE] |
| __attribute__ ((aligned(CONFIG_SYS_CACHELINE_SIZE))); |
| uint32_t buffer_sts[QPIC_NAND_MAX_CWS_IN_PAGE] |
| __attribute__ ((aligned(CONFIG_SYS_CACHELINE_SIZE))); |
| uint32_t status_write[QPIC_NAND_MAX_CWS_IN_PAGE] |
| __attribute__ ((aligned(CONFIG_SYS_CACHELINE_SIZE))); |
| |
| static int |
| qpic_nand_read_page(struct mtd_info *mtd, uint32_t page, |
| enum nand_cfg_value cfg_mode, struct mtd_oob_ops *ops); |
| |
| static const struct udevice_id qpic_ver_ids[] = { |
| { .compatible = "qcom,qpic-nand.1.4.20", .data = QCA_QPIC_V1_4_20 }, |
| { .compatible = "qcom,qpic-nand.1.5.20", .data = QCA_QPIC_V1_5_20 }, |
| { .compatible = "qcom,qpic-nand-v2.1.1", .data = QCA_QPIC_V2_1_1}, |
| { }, |
| }; |
| |
| static uint32_t |
| qpic_onfi_mode_to_xfer_steps[QPIC_MAX_ONFI_MODES][QPIC_NUM_XFER_STEPS] = { |
| #ifdef CONFIG_QPIC_SERIAL |
| /* Mode 0 */ |
| { |
| 0x00e00080, 0x49f04998, 0x8de08d80, 0xc000c000, |
| 0xc000c000, 0xc000c000, 0xc000c000, |
| }, |
| #else |
| { |
| 0x04e00480, 0x59f05998, 0x89e08980, 0xd000d000, |
| 0xc000c000, 0xc000c000, 0xc000c000, |
| }, |
| #endif |
| /* Mode 1 */ |
| { |
| 0x00e00080, 0x49f04d99, 0x85e08580, 0xd000d000, |
| 0xc000c000, 0xc000c000, 0xc000c000, |
| }, |
| /* Mode 2 */ |
| { |
| 0x00e00080, 0x45f0459a, 0x85e08580, 0xd000d000, |
| 0xc000c000, 0xc000c000, 0xc000c000, |
| }, |
| /* Mode 3 */ |
| { |
| 0x00e00080, 0x45f04599, 0x81e08180, 0xd000d000, |
| 0xc000c000, 0xc000c000, 0xc000c000, |
| }, |
| }; |
| |
| |
| static void |
| qpic_nand_wait_for_cmd_exec(uint32_t num_desc) |
| { |
| /* Create a read/write event to notify the periperal of the added desc. */ |
| bam_sys_gen_event(&bam, CMD_PIPE_INDEX, num_desc); |
| |
| /* Wait for the descriptors to be processed */ |
| bam_wait_for_interrupt(&bam, CMD_PIPE_INDEX, P_PRCSD_DESC_EN_MASK); |
| |
| /* Read offset update for the circular FIFO */ |
| bam_read_offset_update(&bam, CMD_PIPE_INDEX); |
| } |
| |
| static void |
| qpic_nand_wait_for_data(uint32_t pipe_num) |
| { |
| /* Wait for the descriptors to be processed */ |
| bam_wait_for_interrupt(&bam, pipe_num, P_PRCSD_DESC_EN_MASK); |
| |
| /* Read offset update for the circular FIFO */ |
| bam_read_offset_update(&bam, pipe_num); |
| } |
| |
| static uint32_t |
| qpic_nand_read_reg(uint32_t reg_addr, |
| uint8_t flags) |
| { |
| struct cmd_element *cmd_list_read_ptr = ce_read_array; |
| |
| bam_add_cmd_element(cmd_list_read_ptr, reg_addr, |
| (uint32_t)((addr_t)&ret_val), CE_READ_TYPE); |
| |
| /* Enqueue the desc for the above command */ |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)((addr_t)cmd_list_read_ptr), |
| BAM_CE_SIZE, |
| BAM_DESC_CMD_FLAG| BAM_DESC_INT_FLAG | flags); |
| |
| qpic_nand_wait_for_cmd_exec(1); |
| #if !defined(CONFIG_SYS_DCACHE_OFF) |
| flush_dcache_range((unsigned long)&ret_val, |
| (unsigned long)&ret_val + sizeof(ret_val)); |
| #endif |
| return ret_val; |
| } |
| |
| #ifdef CONFIG_PAGE_SCOPE_MULTI_PAGE_READ |
| static void reset_multi_page_cmd_reg(struct cmd_element *cmd_list_ptr, uint8_t flags) |
| { |
| bam_add_cmd_element(cmd_list_ptr, NAND_MULTI_PAGE_CMD, (uint32_t)0, |
| CE_WRITE_TYPE); |
| |
| /* Enqueue the desc for the above command */ |
| bam_add_one_desc(&bam, CMD_PIPE_INDEX, (unsigned char*)cmd_list_ptr, |
| BAM_CE_SIZE, BAM_DESC_CMD_FLAG | BAM_DESC_INT_FLAG); |
| |
| qpic_nand_wait_for_cmd_exec(1); |
| } |
| |
| static void reset_addr_reg(struct cmd_element *cmd_list_ptr, uint8_t flags) |
| { |
| bam_add_cmd_element(cmd_list_ptr, NAND_ADDR0, (uint32_t)0, |
| CE_WRITE_TYPE); |
| |
| /* Enqueue the desc for the above command */ |
| bam_add_one_desc(&bam, CMD_PIPE_INDEX, (unsigned char*)cmd_list_ptr, |
| BAM_CE_SIZE, BAM_DESC_CMD_FLAG | BAM_DESC_INT_FLAG); |
| |
| qpic_nand_wait_for_cmd_exec(1); |
| } |
| #endif |
| |
| /* Assume the BAM is in a locked state. */ |
| void |
| qpic_nand_erased_status_reset(struct cmd_element *cmd_list_ptr, uint8_t flags) |
| { |
| uint32_t val = 0; |
| |
| /* Reset the Erased Codeword/Page detection controller. */ |
| val = NAND_ERASED_CW_DETECT_CFG_RESET_CTRL; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_ERASED_CW_DETECT_CFG, val, |
| CE_WRITE_TYPE); |
| |
| /* Enqueue the desc for the above command */ |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)cmd_list_ptr, |
| BAM_CE_SIZE, |
| BAM_DESC_CMD_FLAG | BAM_DESC_INT_FLAG | flags); |
| |
| qpic_nand_wait_for_cmd_exec(1); |
| |
| /* Enable the Erased Codeword/Page detection |
| * controller to check the data as it arrives. |
| * Also disable ECC reporting for an erased CW. |
| */ |
| val = NAND_ERASED_CW_DETECT_CFG_ACTIVATE_CTRL | |
| NAND_ERASED_CW_DETECT_ERASED_CW_ECC_MASK; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_ERASED_CW_DETECT_CFG, val, |
| CE_WRITE_TYPE); |
| |
| /* Enqueue the desc for the above command */ |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)cmd_list_ptr, |
| BAM_CE_SIZE, |
| BAM_DESC_CMD_FLAG | BAM_DESC_INT_FLAG | |
| BAM_DESC_UNLOCK_FLAG); |
| |
| qpic_nand_wait_for_cmd_exec(1); |
| } |
| |
| static int |
| qpic_nand_check_read_status(struct mtd_info *mtd, struct read_stats *stats) |
| { |
| uint32_t status = stats->flash_sts; |
| |
| /* Check for errors */ |
| if (!(status & NAND_FLASH_ERR)) { |
| uint32_t corrected = stats->buffer_sts & NUM_ERRORS_MASK; |
| mtd->ecc_stats.corrected += corrected; |
| return corrected; |
| } |
| |
| if (status & NAND_FLASH_MPU_ERR) |
| return -EPERM; |
| |
| if (status & NAND_FLASH_TIMEOUT_ERR) |
| return -ETIMEDOUT; |
| |
| if (stats->buffer_sts & NAND_BUFFER_UNCORRECTABLE) { |
| /* Check if this is an ECC error on an erased page. */ |
| if ((stats->erased_cw_sts & NAND_CW_ERASED) != NAND_CW_ERASED) |
| return -EBADMSG; |
| |
| return 0; |
| } |
| |
| return -EIO; |
| } |
| |
| static int |
| qpic_nand_check_status(struct mtd_info *mtd, uint32_t status) |
| { |
| /* Check for errors */ |
| if (status & NAND_FLASH_ERR) { |
| printf("Nand Flash error. Status = %d\n", status); |
| |
| if (status & NAND_FLASH_MPU_ERR) |
| return -EPERM; |
| |
| if (status & NAND_FLASH_TIMEOUT_ERR) |
| return -ETIMEDOUT; |
| |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static uint32_t |
| qpic_nand_fetch_id(struct mtd_info *mtd) |
| { |
| struct cmd_element *cmd_list_ptr = ce_array; |
| struct cmd_element *cmd_list_ptr_start = ce_array; |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| int num_desc = 0; |
| uint32_t status; |
| uint32_t id; |
| uint32_t flash_cmd = NAND_CMD_FETCH_ID; |
| uint32_t exec_cmd = 1; |
| int nand_ret = NANDC_RESULT_SUCCESS; |
| uint32_t vld = NAND_CMD_VALID_BASE; |
| uint32_t cmd_vld = NAND_DEV_CMD_VLD_V1_4_20; |
| |
| #ifdef CONFIG_QPIC_SERIAL |
| flash_cmd |= QPIC_SPI_WP_SET | QPIC_SPI_HOLD_SET | |
| QPIC_SPI_TRANSFER_MODE_X1; |
| vld = FLASH_DEV_CMD_VLD; |
| #endif |
| /* Issue the Fetch id command to the NANDc */ |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_CMD, (uint32_t)flash_cmd, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| if (hw_ver == QCA_QPIC_V1_5_20 || hw_ver == QCA_QPIC_V2_1_1) |
| cmd_vld = NAND_DEV_CMD_VLD_V1_5_20; |
| |
| bam_add_cmd_element(cmd_list_ptr, cmd_vld, (uint32_t)vld, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| /* Execute the cmd */ |
| bam_add_cmd_element(cmd_list_ptr, NAND_EXEC_CMD, (uint32_t)exec_cmd, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| /* Prepare the cmd desc for the above commands */ |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)cmd_list_ptr_start, |
| ((uint32_t)cmd_list_ptr - (uint32_t)cmd_list_ptr_start), |
| BAM_DESC_LOCK_FLAG | BAM_DESC_INT_FLAG | |
| BAM_DESC_NWD_FLAG | BAM_DESC_CMD_FLAG); |
| |
| /* Keep track of the number of desc added. */ |
| num_desc++; |
| qpic_nand_wait_for_cmd_exec(num_desc); |
| |
| /* Read the status register */ |
| status = qpic_nand_read_reg(NAND_FLASH_STATUS, 0); |
| |
| /* Check for errors */ |
| nand_ret = qpic_nand_check_status(mtd, status); |
| if (nand_ret) { |
| printf("Read ID cmd status failed\n"); |
| goto qpic_nand_fetch_id_err; |
| } |
| |
| /* Read the id */ |
| id = qpic_nand_read_reg(NAND_READ_ID, BAM_DESC_UNLOCK_FLAG); |
| |
| dev->id = id; |
| dev->vendor = id & 0xff; |
| dev->device = (id >> 8) & 0xff; |
| dev->dev_cfg = (id >> 24) & 0xFF; |
| dev->widebus = 0; |
| dev->widebus &= (id >> 24) & 0xFF; |
| dev->widebus = dev->widebus? 1: 0; |
| |
| printf("ID = %x\n", dev->id); |
| printf("Vendor = %x\n", dev->vendor); |
| printf("Device = %x\n", dev->device); |
| |
| qpic_nand_fetch_id_err: |
| return nand_ret; |
| } |
| |
| static int |
| qpic_bam_init(struct qpic_nand_init_config *config) |
| { |
| uint32_t bam_ret = NANDC_RESULT_SUCCESS; |
| |
| bam.base = config->bam_base; |
| /* Set Read pipe params. */ |
| bam.pipe[DATA_PRODUCER_PIPE_INDEX].pipe_num = config->pipes.read_pipe; |
| /* System consumer */ |
| bam.pipe[DATA_PRODUCER_PIPE_INDEX].trans_type = BAM2SYS; |
| bam.pipe[DATA_PRODUCER_PIPE_INDEX].fifo.size = QPIC_BAM_DATA_FIFO_SIZE; |
| bam.pipe[DATA_PRODUCER_PIPE_INDEX].fifo.head = qpic_data_desc_fifo; |
| bam.pipe[DATA_PRODUCER_PIPE_INDEX].lock_grp = config->pipes.read_pipe_grp; |
| |
| /* Set Write pipe params. */ |
| bam.pipe[DATA_CONSUMER_PIPE_INDEX].pipe_num = config->pipes.write_pipe; |
| /* System producer */ |
| bam.pipe[DATA_CONSUMER_PIPE_INDEX].trans_type = SYS2BAM; |
| bam.pipe[DATA_CONSUMER_PIPE_INDEX].fifo.size = QPIC_BAM_DATA_FIFO_SIZE; |
| bam.pipe[DATA_CONSUMER_PIPE_INDEX].fifo.head = qpic_data_desc_fifo; |
| bam.pipe[DATA_CONSUMER_PIPE_INDEX].lock_grp = config->pipes.write_pipe_grp; |
| |
| /* Set Cmd pipe params. */ |
| bam.pipe[CMD_PIPE_INDEX].pipe_num = config->pipes.cmd_pipe; |
| /* System consumer */ |
| bam.pipe[CMD_PIPE_INDEX].trans_type = SYS2BAM; |
| bam.pipe[CMD_PIPE_INDEX].fifo.size = QPIC_BAM_CMD_FIFO_SIZE; |
| bam.pipe[CMD_PIPE_INDEX].fifo.head = qpic_cmd_desc_fifo; |
| bam.pipe[CMD_PIPE_INDEX].lock_grp = config->pipes.cmd_pipe_grp; |
| #ifdef CONFIG_PAGE_SCOPE_MULTI_PAGE_READ |
| /* Set Status pipe params. */ |
| bam.pipe[BAM_STATUS_PIPE_INDEX].pipe_num = config->pipes.status_pipe; |
| /* System consumer */ |
| bam.pipe[BAM_STATUS_PIPE_INDEX].trans_type = BAM2SYS; |
| bam.pipe[BAM_STATUS_PIPE_INDEX].fifo.size = QPIC_BAM_STATUS_FIFO_SIZE; |
| bam.pipe[BAM_STATUS_PIPE_INDEX].fifo.head = qpic_status_desc_fifo; |
| bam.pipe[BAM_STATUS_PIPE_INDEX].lock_grp = config->pipes.status_pipe_grp; |
| #endif |
| /* Programs the threshold for BAM transfer |
| * When this threshold is reached, BAM signals the peripheral via the |
| * pipe_bytes_available interface. |
| * The peripheral is signalled with this notification in the following cases: |
| * a. It has accumulated all the descriptors. |
| * b. It has accumulated more than threshold bytes. |
| * c. It has reached EOT (End Of Transfer). |
| * Note: this value needs to be set by the h/w folks and is specific |
| * for each peripheral. |
| */ |
| bam.threshold = 32; |
| |
| /* Set the EE. */ |
| bam.ee = config->ee; |
| |
| /* Set the max desc length for this BAM. */ |
| bam.max_desc_len = config->max_desc_len; |
| |
| /* BAM Init. */ |
| bam_init(&bam); |
| |
| /* Initialize BAM QPIC read pipe */ |
| bam_sys_pipe_init(&bam, DATA_PRODUCER_PIPE_INDEX); |
| |
| /* Init read fifo */ |
| bam_ret = bam_pipe_fifo_init(&bam, |
| bam.pipe[DATA_PRODUCER_PIPE_INDEX].pipe_num); |
| |
| if (bam_ret) { |
| printf("QPIC:NANDc BAM Read FIFO init error\n"); |
| bam_ret = NANDC_RESULT_FAILURE; |
| goto qpic_nand_bam_init_error; |
| } |
| |
| /* Initialize BAM QPIC write pipe */ |
| bam_sys_pipe_init(&bam, DATA_CONSUMER_PIPE_INDEX); |
| |
| /* Init write fifo. Use the same fifo as read fifo. */ |
| bam_ret = bam_pipe_fifo_init(&bam, |
| bam.pipe[DATA_CONSUMER_PIPE_INDEX].pipe_num); |
| |
| if (bam_ret) { |
| printf("QPIC: NANDc: BAM Write FIFO init error\n"); |
| bam_ret = NANDC_RESULT_FAILURE; |
| goto qpic_nand_bam_init_error; |
| } |
| |
| /* Initialize BAM QPIC cmd pipe */ |
| bam_sys_pipe_init(&bam, CMD_PIPE_INDEX); |
| |
| /* Init cmd fifo */ |
| bam_ret = bam_pipe_fifo_init(&bam, bam.pipe[CMD_PIPE_INDEX].pipe_num); |
| |
| if (bam_ret) { |
| printf("QPIC:NANDc BAM CMD FIFO init error\n"); |
| bam_ret = NANDC_RESULT_FAILURE; |
| goto qpic_nand_bam_init_error; |
| } |
| #ifdef CONFIG_PAGE_SCOPE_MULTI_PAGE_READ |
| /* Initialize BAM QPIC status pipe */ |
| bam_sys_pipe_init(&bam, BAM_STATUS_PIPE_INDEX); |
| |
| /* Init status fifo */ |
| bam_ret = bam_pipe_fifo_init(&bam, bam.pipe[BAM_STATUS_PIPE_INDEX].pipe_num); |
| |
| if (bam_ret) { |
| printf("QPIC:NANDc BAM STATUS FIFO init error\n"); |
| bam_ret = NANDC_RESULT_FAILURE; |
| goto qpic_nand_bam_init_error; |
| } |
| #endif |
| /* |
| * Once BAM_MODE_EN bit is set then QPIC_NAND_CTRL register |
| * should be written with BAM instead of writel. |
| * Check if BAM_MODE_EN is already set by bootloader and write only |
| * if this bit is not set. |
| */ |
| if (!(readl(QPIC_NAND_CTRL) & BAM_MODE_EN)) { |
| #ifdef CONFIG_QPIC_SERIAL |
| writel(BAM_MODE_EN | NANDC_READ_DELAY_COUNTER_VAL, QPIC_NAND_CTRL); |
| #else |
| writel(BAM_MODE_EN , QPIC_NAND_CTRL); |
| #endif |
| } |
| |
| qpic_nand_bam_init_error: |
| return bam_ret; |
| } |
| |
| /* Adds command elements for addr and cfg register writes. |
| * cfg: Defines the configuration for the flash cmd. |
| * start: Address where the command elements are added. |
| * |
| * Returns the address where the next cmd element can be added. |
| */ |
| struct cmd_element* |
| qpic_nand_add_addr_n_cfg_ce(struct cfg_params *cfg, |
| struct cmd_element *start) |
| { |
| struct cmd_element *cmd_list_ptr = start; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_ADDR0, (uint32_t)cfg->addr0, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| bam_add_cmd_element(cmd_list_ptr, NAND_ADDR1, (uint32_t)cfg->addr1, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| bam_add_cmd_element(cmd_list_ptr, NAND_DEV0_CFG0, (uint32_t)cfg->cfg0, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| bam_add_cmd_element(cmd_list_ptr, NAND_DEV0_CFG1, (uint32_t)cfg->cfg1, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| return cmd_list_ptr; |
| } |
| |
| |
| static struct cmd_element* |
| qpic_nand_add_onfi_probe_ce(struct onfi_probe_params *params, |
| struct cmd_element *start) |
| { |
| struct cmd_element *cmd_list_ptr = start; |
| uint32_t cmd_vld = NAND_DEV_CMD_VLD_V1_4_20; |
| uint32_t dev_cmd1 = NAND_DEV_CMD1_V1_4_20; |
| |
| cmd_list_ptr = qpic_nand_add_addr_n_cfg_ce(¶ms->cfg, cmd_list_ptr); |
| |
| if (hw_ver == QCA_QPIC_V1_5_20) { |
| cmd_vld = NAND_DEV_CMD_VLD_V1_5_20; |
| dev_cmd1 = NAND_DEV_CMD1_V1_5_20; |
| } |
| bam_add_cmd_element(cmd_list_ptr, dev_cmd1, |
| (uint32_t)params->dev_cmd1, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| bam_add_cmd_element(cmd_list_ptr, cmd_vld, |
| (uint32_t)params->vld, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| bam_add_cmd_element(cmd_list_ptr, NAND_READ_LOCATION_n(0), |
| (uint32_t)params->cfg.addr_loc_0, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_CMD, |
| (uint32_t)params->cfg.cmd, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| bam_add_cmd_element(cmd_list_ptr, NAND_EXEC_CMD, |
| (uint32_t)params->cfg.exec, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| return cmd_list_ptr; |
| } |
| |
| static int |
| onfi_probe_cmd_exec(struct mtd_info *mtd, |
| struct onfi_probe_params *params, |
| unsigned char* data_ptr, |
| int data_len) |
| { |
| struct cmd_element *cmd_list_ptr = ce_array; |
| struct cmd_element *cmd_list_ptr_start = ce_array; |
| int num_desc = 0; |
| uint32_t status = 0; |
| int nand_ret = NANDC_RESULT_SUCCESS; |
| uint8_t desc_flags = BAM_DESC_NWD_FLAG | BAM_DESC_CMD_FLAG |
| | BAM_DESC_LOCK_FLAG | BAM_DESC_INT_FLAG; |
| |
| params->cfg.addr_loc_0 = 0; |
| params->cfg.addr_loc_0 |= NAND_RD_LOC_LAST_BIT(1); |
| params->cfg.addr_loc_0 |= NAND_RD_LOC_OFFSET(0); |
| params->cfg.addr_loc_0 |= NAND_RD_LOC_SIZE(data_len); |
| |
| cmd_list_ptr = qpic_nand_add_onfi_probe_ce(params, cmd_list_ptr); |
| |
| /* Enqueue the desc for the above commands */ |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)cmd_list_ptr_start, |
| ((addr_t)(uint32_t)cmd_list_ptr - |
| (uint32_t)cmd_list_ptr_start), |
| desc_flags); |
| |
| cmd_list_ptr_start = cmd_list_ptr; |
| num_desc++; |
| |
| /* Add Data desc */ |
| bam_add_desc(&bam, |
| DATA_PRODUCER_PIPE_INDEX, |
| (unsigned char *)((addr_t)data_ptr), |
| data_len, |
| BAM_DESC_INT_FLAG); |
| |
| /* Wait for the commands to be executed */ |
| qpic_nand_wait_for_cmd_exec(num_desc); |
| |
| /* Read buffer status and check for errors. */ |
| status = qpic_nand_read_reg(NAND_FLASH_STATUS, 0); |
| |
| nand_ret = qpic_nand_check_status(mtd, status); |
| if (nand_ret) { |
| goto onfi_probe_exec_err; |
| } |
| |
| /* Wait for data to be available */ |
| qpic_nand_wait_for_data(DATA_PRODUCER_PIPE_INDEX); |
| #if !defined(CONFIG_SYS_DCACHE_OFF) |
| flush_dcache_range((unsigned long)data_ptr, |
| (unsigned long)data_ptr + data_len); |
| #endif |
| |
| /* Check for errors */ |
| nand_ret = qpic_nand_check_status(mtd, status); |
| |
| onfi_probe_exec_err: |
| return nand_ret; |
| } |
| |
| /* TODO: check why both vld and cmd need to be written. */ |
| void |
| qpic_nand_onfi_probe_cleanup(uint32_t vld, uint32_t dev_cmd1) |
| { |
| struct cmd_element *cmd_list_ptr = ce_array; |
| struct cmd_element *cmd_list_ptr_start = ce_array; |
| uint32_t cmd_vld = NAND_DEV_CMD_VLD_V1_4_20; |
| uint32_t dev_cmd1_reg = NAND_DEV_CMD1_V1_4_20; |
| |
| if (hw_ver == QCA_QPIC_V1_5_20) { |
| cmd_vld = NAND_DEV_CMD_VLD_V1_5_20; |
| dev_cmd1_reg = NAND_DEV_CMD1_V1_5_20; |
| } |
| bam_add_cmd_element(cmd_list_ptr, dev_cmd1_reg, dev_cmd1, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| bam_add_cmd_element(cmd_list_ptr, cmd_vld, vld, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| /* Enqueue the desc for the above commands */ |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)cmd_list_ptr_start, |
| ((uint32_t)cmd_list_ptr - |
| (uint32_t)cmd_list_ptr_start), |
| BAM_DESC_UNLOCK_FLAG | BAM_DESC_CMD_FLAG| |
| BAM_DESC_INT_FLAG); |
| |
| qpic_nand_wait_for_cmd_exec(1); |
| } |
| |
| #ifndef CONFIG_QPIC_SERIAL |
| static void |
| qpic_config_timing_parameters(struct mtd_info *mtd) |
| { |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| uint32_t xfer_start; |
| uint32_t i, timing_mode; |
| |
| timing_mode = dev->timing_mode_support & |
| GENMASK(QPIC_MAX_ONFI_MODES - 1, 0); |
| |
| /* If ONFI mode is not valid then use the default register values */ |
| if (!timing_mode) |
| return; |
| |
| timing_mode = fls(timing_mode) - 1; |
| |
| if (hw_ver == QCA_QPIC_V1_5_20) |
| xfer_start = NAND_XFR_STEPS_V1_5_20; |
| else |
| xfer_start = NAND_XFR_STEP1; |
| |
| for (i = 0; i < QPIC_NUM_XFER_STEPS; i++) |
| writel(qpic_onfi_mode_to_xfer_steps[timing_mode][i], |
| xfer_start + 4 * i); |
| } |
| #endif |
| |
| #ifdef CONFIG_QPIC_SERIAL |
| static void qpic_serial_update_dev_params(struct mtd_info *mtd) |
| { |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| uint32_t ecc_bits; |
| |
| dev->page_size = serial_params->page_size; |
| mtd->writesize = dev->page_size; |
| dev->block_size = serial_params->pgs_per_blk * (dev->page_size); |
| mtd->erasesize = dev->block_size; |
| dev->num_blocks = serial_params->no_of_blocks; |
| dev->widebus = 0x0; |
| dev->density = serial_params->no_of_blocks * (dev->block_size); |
| mtd->size = dev->density; |
| dev->spare_size = serial_params->spare_size; |
| mtd->oobsize = dev->spare_size; |
| ecc_bits = serial_params->num_bits_ecc_correctability; |
| dev->num_pages_per_blk = serial_params->pgs_per_blk; |
| dev->num_pages_per_blk_mask = serial_params->pgs_per_blk - 1; |
| dev->timing_mode_support = serial_params->timing_mode_support; |
| dev->quad_mode = serial_params->quad_mode; |
| dev->check_quad_config = serial_params->check_quad_config; |
| |
| if (ecc_bits >= 8) |
| mtd->ecc_strength = 8; |
| else |
| mtd->ecc_strength = 4; |
| printf("Serial NAND device Manufacturer:%s\n",serial_params->name); |
| printf("Device Size:%d MiB, Page size:%d, Spare Size:%d, ECC:%d-bit\n", |
| (int)(dev->density >> 20), dev->page_size, mtd->oobsize, mtd->ecc_strength); |
| } |
| #endif |
| |
| static int |
| qpic_nand_onfi_save_params(struct mtd_info *mtd, |
| struct onfi_param_page *param_page) |
| { |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| int onfi_ret = NANDC_RESULT_SUCCESS; |
| uint32_t ecc_bits; |
| |
| onfi_ret = qpic_nand_fetch_id(mtd); |
| |
| if (onfi_ret) { |
| printf("Fetch ID cmd failed\n"); |
| goto onfi_save_params_err; |
| } |
| |
| dev->page_size = param_page->data_per_pg; |
| mtd->writesize = dev->page_size; |
| dev->block_size = param_page->pgs_per_blk * (dev->page_size); |
| mtd->erasesize = dev->block_size; |
| dev->num_blocks = param_page->blks_per_LUN; |
| dev->widebus = param_page->feature_supported & 0x1; |
| dev->density = param_page->blks_per_LUN * (dev->block_size); |
| mtd->size = dev->density * param_page->num_LUN; |
| dev->spare_size = param_page->spare_per_pg; |
| mtd->oobsize = dev->spare_size; |
| ecc_bits = param_page->num_bits_ecc_correctability; |
| dev->num_pages_per_blk = param_page->pgs_per_blk; |
| dev->num_pages_per_blk_mask = param_page->pgs_per_blk - 1; |
| dev->timing_mode_support = param_page->timing_mode_support; |
| |
| if (ecc_bits >= 8) |
| mtd->ecc_strength = 8; |
| else |
| mtd->ecc_strength = 4; |
| |
| onfi_save_params_err: |
| return onfi_ret; |
| } |
| |
| static int |
| qpic_nand_save_config(struct mtd_info *mtd) |
| { |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| struct nand_chip *chip = MTD_NAND_CHIP(mtd); |
| uint32_t qpic_oob_size; |
| uint32_t no_of_address_cycle = 5; |
| uint32_t disable_status_after_write = 0; |
| uint32_t recovery_cycle = 7; |
| uint32_t wr_rd_busy_gap = 2; |
| |
| /* Save Configurations */ |
| dev->cws_per_page = dev->page_size >> NAND_CW_DIV_RIGHT_SHIFT; |
| |
| /* Verify that we have enough buffer to handle all the cws in a page. */ |
| if (!(dev->cws_per_page <= QPIC_NAND_MAX_CWS_IN_PAGE)) { |
| printf("Not enough buffer to handle CW\n"); |
| return -EINVAL; |
| } |
| |
| /* Codeword Size = UD_SIZE_BYTES + ECC_PARITY_SIZE_BYTES |
| * + SPARE_SIZE_BYTES + Bad Block size |
| */ |
| if (mtd->ecc_strength == 8) { |
| dev->cw_size = NAND_CW_SIZE_8_BIT_ECC; |
| /* Use 8-bit ecc */ |
| dev->ecc_bch_cfg |= (1 << NAND_DEV0_ECC_MODE_SHIFT); |
| |
| if (dev->widebus) { |
| dev->ecc_bytes_hw = 14; |
| dev->spare_bytes = 0; |
| dev->bbm_size = 2; |
| } else { |
| dev->ecc_bytes_hw = 13; |
| dev->spare_bytes = 2; |
| dev->bbm_size = 1; |
| } |
| } else { |
| dev->cw_size = NAND_CW_SIZE_4_BIT_ECC; |
| if (dev->widebus) { |
| dev->ecc_bytes_hw = 8; |
| dev->spare_bytes = 2; |
| dev->bbm_size = 2; |
| } else { |
| dev->ecc_bytes_hw = 7; |
| dev->spare_bytes = 4; |
| dev->bbm_size = 1; |
| } |
| } |
| |
| /* spare size bytes in each CW */ |
| dev->cfg0 |= dev->spare_bytes << NAND_DEV0_CFG0_SPARE_SZ_BYTES_SHIFT; |
| /* parity bytes in each CW */ |
| dev->ecc_bch_cfg |= |
| dev->ecc_bytes_hw << NAND_DEV0_ECC_PARITY_SZ_BYTES_SHIFT; |
| |
| qpic_oob_size = dev->cw_size * dev->cws_per_page - mtd->writesize; |
| |
| if (mtd->oobsize < qpic_oob_size) { |
| printf("qpic_nand: ecc data doesn't fit in available OOB area\n"); |
| return -EINVAL; |
| } |
| |
| if (mtd->oobsize > qpic_oob_size) |
| printf("qpic_nand: changing oobsize to %d from %d bytes\n", |
| qpic_oob_size, mtd->oobsize); |
| |
| /* Make the device OOB size as QPIC supported OOB size. */ |
| mtd->oobsize = qpic_oob_size; |
| mtd->oobavail = (DATA_BYTES_IN_IMG_PER_CW - USER_DATA_BYTES_PER_CW) * |
| dev->cws_per_page; |
| dev->oob_per_page = mtd->oobavail; |
| mtd->writebufsize = mtd->writesize; |
| /* BAD_BLOCK_BYTE_NUM = Page Size - (CW_PER_PAGE * Codeword Size) + 1 |
| * Note: Set CW_PER_PAGE to 1 less than the actual number. |
| */ |
| dev->bad_blk_loc = dev->page_size - dev->cw_size * |
| (dev->cws_per_page - 1) + 1; |
| #ifdef CONFIG_QPIC_SERIAL |
| no_of_address_cycle = 3; |
| disable_status_after_write = 1; |
| recovery_cycle = 0; |
| wr_rd_busy_gap = 20; |
| #endif |
| dev->cfg0 |= ((dev->cws_per_page - 1) << NAND_DEV0_CFG0_CW_PER_PAGE_SHIFT) /* 4/8 cw/pg for 2/4k */ |
| |(DATA_BYTES_IN_IMG_PER_CW << NAND_DEV0_CFG0_UD_SIZE_BYTES_SHIFT) /* 516 user data bytes */ |
| |(no_of_address_cycle << NAND_DEV0_CFG0_ADDR_CYCLE_SHIFT) /* 5 address cycles */ |
| |(disable_status_after_write << NAND_DEV0_CFG0_DIS_STS_AFTER_WR_SHIFT);/* Send read status cmd after each write. */ |
| |
| dev->cfg1 |= (recovery_cycle << NAND_DEV0_CFG1_RECOVERY_CYCLES_SHIFT) /* 8 recovery cycles */ |
| |(0 << NAND_DEV0_CFG1_CS_ACTIVE_BSY_SHIFT) /* Allow CS deassertion */ |
| |(dev->bad_blk_loc << NAND_DEV0_CFG1_BAD_BLK_BYTE_NUM_SHIFT)/* Bad block marker location */ |
| |(0 << NAND_DEV0_CFG1_BAD_BLK_IN_SPARE_SHIFT) /* Bad block in user data area */ |
| |(wr_rd_busy_gap << NAND_DEV0_CFG1_WR_RD_BSY_GAP_SHIFT) /* 8 cycle tWB/tRB */ |
| |(dev->widebus << NAND_DEV0_CFG1_WIDE_BUS_SHIFT); /* preserve wide flash flag */ |
| |
| dev->cfg0_raw = ((dev->cws_per_page- 1) << NAND_DEV0_CFG0_CW_PER_PAGE_SHIFT) |
| |(no_of_address_cycle << NAND_DEV0_CFG0_ADDR_CYCLE_SHIFT) |
| |(dev->cw_size << NAND_DEV0_CFG0_UD_SIZE_BYTES_SHIFT) //figure out the size of cw |
| | (disable_status_after_write << NAND_DEV0_CFG0_DIS_STS_AFTER_WR_SHIFT); |
| |
| dev->cfg1_raw = (recovery_cycle << NAND_DEV0_CFG1_RECOVERY_CYCLES_SHIFT) |
| | (0 << NAND_DEV0_CFG1_CS_ACTIVE_BSY_SHIFT) |
| | (17 << NAND_DEV0_CFG1_BAD_BLK_BYTE_NUM_SHIFT) |
| | (1 << NAND_DEV0_CFG1_BAD_BLK_IN_SPARE_SHIFT) |
| | (wr_rd_busy_gap << NAND_DEV0_CFG1_WR_RD_BSY_GAP_SHIFT) |
| | (dev->widebus << NAND_DEV0_CFG1_WIDE_BUS_SHIFT) |
| | 1 ; /* to disable reed solomon ecc..this feild is now read only. */ |
| |
| dev->ecc_bch_cfg |= (0 << NAND_DEV0_ECC_DISABLE_SHIFT) /* Enable ECC */ |
| | (0 << NAND_DEV0_ECC_SW_RESET_SHIFT) /* Put ECC core in op mode */ |
| | (DATA_BYTES_IN_IMG_PER_CW << NAND_DEV0_ECC_NUM_DATA_BYTES) |
| | (1 << NAND_DEV0_ECC_FORCE_CLK_OPEN_SHIFT); /* Enable all clocks */ |
| |
| /* |
| * Safe to use a single instance global variable, |
| * fake_ecc_layout, since we will be called only once for the |
| * lifetime of the driver. We can be called more than once, |
| * but the previous instance of the driver would have been |
| * deinit before the next one is inited. |
| */ |
| memset(&fake_ecc_layout, 0, sizeof(fake_ecc_layout)); |
| chip->ecc.layout = &fake_ecc_layout; |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_QPIC_SERIAL |
| static int qpic_serial_get_feature(struct mtd_info *mtd, uint32_t ftr_addr) |
| { |
| struct cmd_element *cmd_list_ptr = ce_array; |
| struct cmd_element *cmd_list_ptr_start = ce_array; |
| uint8_t num_desc = 0; |
| uint32_t status, nand_ret; |
| uint32_t exec_cmd = 1; |
| |
| uint32_t cmd_val = (QPIC_SPI_TRANSFER_MODE_X1 | QPIC_SPI_HOLD_SET | |
| QPIC_SPI_WP_SET | NAND_CMD_ACC_FEATURE); |
| |
| /* Set the feature address to NAND_ADDR0 register */ |
| bam_add_cmd_element(cmd_list_ptr, NAND_ADDR0, ftr_addr, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| /* Set the value 0x0 to NAND_ADDR1 register */ |
| bam_add_cmd_element(cmd_list_ptr, NAND_ADDR1, 0, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| /* First Clear the feature register to get the fresh feature value */ |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_FEATURES, 0, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| /* cmd_val = 0x3800000E |
| * bit-31 is clear means set feature |
| * bit-30-29 means x1 mode |
| * bit-28 is set , this is for wp pin |
| * wp# pin should be set to high then only we can get the feature |
| * bit-27 SPI_HOLD : this pin also should be high |
| */ |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_CMD, cmd_val, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| /* Execute the cmd */ |
| bam_add_cmd_element(cmd_list_ptr, NAND_EXEC_CMD, exec_cmd, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| /* Prepare the cmd desc for the above commands */ |
| bam_add_one_desc(&bam, CMD_PIPE_INDEX, |
| (unsigned char *)cmd_list_ptr_start, |
| ((uint32_t)cmd_list_ptr - (uint32_t)cmd_list_ptr_start), |
| BAM_DESC_NWD_FLAG | BAM_DESC_CMD_FLAG | |
| BAM_DESC_INT_FLAG); |
| |
| /* Keep track of the number of desc added. */ |
| num_desc++; |
| |
| qpic_nand_wait_for_cmd_exec(num_desc); |
| |
| status = qpic_nand_read_reg(NAND_FLASH_STATUS, 0); |
| |
| /* Check for errors */ |
| nand_ret = qpic_nand_check_status(mtd, status); |
| if (nand_ret) { |
| printf("%s : CMD status failed\n", __func__); |
| goto err; |
| } |
| /* read the feature register value and update in feature |
| * Feature value will get updated in [15:8] |
| */ |
| nand_ret = qpic_nand_read_reg(NAND_FLASH_FEATURES, 0); |
| |
| qspi_debug("NAND Feature Register Addr:0x%02x and Val:0x%08x\n", |
| ftr_addr,nand_ret); |
| err: |
| return nand_ret; |
| |
| } |
| |
| static int qpic_serial_set_feature(struct mtd_info *mtd, uint32_t ftr_addr, |
| uint32_t ftr_val) |
| { |
| struct cmd_element *cmd_list_ptr = ce_array; |
| struct cmd_element *cmd_list_ptr_start = ce_array; |
| uint8_t num_desc = 0; |
| uint32_t status, nand_ret; |
| |
| uint32_t cmd_val = (QPIC_SPI_SET_FEATURE | QPIC_SPI_WP_SET | |
| QPIC_SPI_HOLD_SET | QPIC_SPI_TRANSFER_MODE_X1 | |
| NAND_CMD_ACC_FEATURE); |
| |
| uint32_t exec_cmd = 1; |
| |
| /* set the feature value to NAND_FLASH_FEATURES feature register */ |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_FEATURES, ftr_val, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| /* Set the feature address to NAND_ADDR0 register */ |
| bam_add_cmd_element(cmd_list_ptr, NAND_ADDR0, ftr_addr, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| /* Set the value 0x0 to NAND_ADDR1 register */ |
| bam_add_cmd_element(cmd_list_ptr, NAND_ADDR1, 0, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| /* cmd_val = 0xB800000E |
| * bit-31 is set means set feature |
| * bit-30-29 means x1 mode |
| * bit-28 is set , this is for wp pin |
| * wp# pin should be set to high then only we can set the feature |
| * bit-27 SPI_HOLD : this pin also should be high |
| */ |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_CMD, cmd_val, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| /* Execute the cmd */ |
| bam_add_cmd_element(cmd_list_ptr, NAND_EXEC_CMD, exec_cmd, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| /* Prepare the cmd desc for the above commands */ |
| bam_add_one_desc(&bam, CMD_PIPE_INDEX, |
| (unsigned char *)cmd_list_ptr_start, |
| ((uint32_t)cmd_list_ptr - (uint32_t)cmd_list_ptr_start), |
| BAM_DESC_NWD_FLAG | BAM_DESC_CMD_FLAG | |
| BAM_DESC_INT_FLAG); |
| |
| /* Keep track of the number of desc added. */ |
| num_desc++; |
| |
| qpic_nand_wait_for_cmd_exec(num_desc); |
| |
| status = qpic_nand_read_reg(NAND_FLASH_STATUS, 0); |
| |
| /* Check for errors */ |
| nand_ret = qpic_nand_check_status(mtd, status); |
| if (nand_ret) { |
| printf("%s : CMD status failed\n", __func__); |
| goto err; |
| } |
| err: |
| return nand_ret; |
| } |
| |
| static int qpic_nand_read_id_serial(struct mtd_info *mtd) |
| { |
| uint32_t nand_ret; |
| uint32_t serial_dev_id[2] = {0x0}; |
| int i; |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| |
| nand_ret = qpic_nand_fetch_id(mtd); |
| if (!nand_ret) { |
| |
| serial_dev_id[0] = dev->id & 0x000000ff; |
| serial_dev_id[1] = (dev->id >> 8) & 0x000000ff; |
| |
| for (i = 0; i < ARRAY_SIZE(qpic_serial_nand_tbl); i++) { |
| serial_params = &qpic_serial_nand_tbl[i]; |
| if ((serial_params->id[0] == serial_dev_id[0]) && |
| (serial_params->id[1] == serial_dev_id[1])) { |
| printf("Serial Nand Device Found With ID : 0x%02x 0x%02x\n", |
| serial_dev_id[0],serial_dev_id[1]); |
| |
| /* Upadate device paramter as per device table */ |
| qpic_serial_update_dev_params(mtd); |
| |
| return nand_ret; |
| } |
| } |
| if (i == ARRAY_SIZE(qpic_serial_nand_tbl)) { |
| printf("%s : No serial Nand device found in table.\n", |
| __func__); |
| return -QPIC_SERIAL_ERROR; |
| } |
| } else { |
| printf("%s : Error in featching id from device\n",__func__); |
| goto id_err; |
| } |
| id_err: |
| return nand_ret; |
| } |
| |
| int qpic_spi_nand_config(struct mtd_info *mtd) |
| { |
| uint32_t status = 0x0; |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| uint32_t cmd3_val = NAND_FLASH_DEV_CMD3_VAL; |
| |
| /* For micron device the READ_CACHE_SEQ command is different than |
| * Giga device. for Giga 0x31 and for Micron 0x30. |
| * so based on id update the command configuration register |
| * CMD3. |
| */ |
| if (dev->id == MICRON_DEVICE_ID) { |
| cmd3_val = (NAND_FLASH_DEV_CMD3_VAL & CMD3_MASK); |
| writel(cmd3_val, SPI_NAND_DEV_CMD3); |
| } |
| |
| /* Get the block protection status*/ |
| status = qpic_serial_get_feature(mtd, FLASH_SPI_NAND_BLK_PROCT_ADDR); |
| if (status < 0) { |
| printf("%s : Error in getting feature.\n",__func__); |
| return status; |
| } |
| |
| if ((status >> 8) & FLASH_SPI_NAND_BLK_PROCT_ENABLE) { |
| qspi_debug("%s: Block protection is enabled\n",__func__); |
| qspi_debug("%s: Issuing set feature command to disable it.\n",__func__); |
| |
| status = qpic_serial_set_feature(mtd, FLASH_SPI_NAND_BLK_PROCT_ADDR, |
| FLASH_SPI_NAND_BLK_PROCT_DISABLE); |
| if (status < 0) { |
| printf("%s : Error in disabling block protection.\n",__func__); |
| return status; |
| } |
| /* After disabling the block protection again read the status |
| * i.e again call the get feature command to get the status |
| */ |
| status = qpic_serial_get_feature(mtd, FLASH_SPI_NAND_BLK_PROCT_ADDR); |
| if (status < 0) { |
| printf("%s : Error in getting feature.\n",__func__); |
| return status; |
| } |
| if ((status >> 8) & FLASH_SPI_NAND_BLK_PROCT_ENABLE) { |
| printf("%s : block protection still enabled.We can't erase a block\n", |
| __func__); |
| return -QPIC_SERIAL_ERROR; |
| } else |
| qspi_debug("%s : Block protection Disabled.\n",__func__); |
| } else |
| qspi_debug("%s: Block protection Disabled on Power on.\n",__func__); |
| |
| /* Get Internal ECC status */ |
| status = qpic_serial_get_feature(mtd, FLASH_SPI_NAND_FR_ADDR); |
| if (status < 0) { |
| printf("%s : Error in getting feature.\n",__func__); |
| return status; |
| } |
| |
| if ((status >> 8) & FLASH_SPI_NAND_FR_ECC_ENABLE) { |
| qspi_debug("%s : Internal ECC enabled, disabling internal ECC\n",__func__); |
| |
| status &= ~(FLASH_SPI_NAND_FR_ECC_ENABLE); |
| status = qpic_serial_set_feature(mtd, FLASH_SPI_NAND_FR_ADDR, |
| status); |
| |
| if (status < 0) { |
| printf("%s : Error in disabling internal ECC.\n",__func__); |
| return status; |
| } |
| /* again check internal ECC is disabled or not using get feature |
| * command |
| */ |
| status = qpic_serial_get_feature(mtd, FLASH_SPI_NAND_FR_ADDR); |
| if (status < 0) { |
| printf("%s : Error in getting feature.\n",__func__); |
| return status; |
| } |
| |
| if ((status >> 8) & FLASH_SPI_NAND_FR_ECC_ENABLE) { |
| printf("%s: Failed to disabled device internal ECC\n", |
| __func__); |
| return -QPIC_SERIAL_ERROR; |
| } else |
| qspi_debug("%s : Internal ECC disabled.\n",__func__); |
| } else |
| qspi_debug("%s : Internal ECC disabled on power on.\n",__func__); |
| |
| /* Enable QUAD mode if device supported. Check this condition only |
| * if dev->quad_mode = true , means device will support Quad mode |
| * else no need to check for Quad mode. |
| * For Micron device there is no quad config bit so no need to check |
| * quad config bit. |
| */ |
| /* Get QUAD bit status */ |
| if (!dev->check_quad_config) { |
| dev->quad_mode = true; |
| return 0; |
| } |
| |
| if (dev->quad_mode) { |
| |
| status = qpic_serial_get_feature(mtd, FLASH_SPI_NAND_FR_ADDR); |
| if (status < 0) { |
| printf("%s : Error in getting feature.\n",__func__); |
| return status; |
| } |
| |
| if (!((status >> 8) & FLASH_SPI_NAND_FR_QUAD_ENABLE)) { |
| qspi_debug("%s : Quad bit not enabled.\n",__func__); |
| qspi_debug("%s : Issuning set feature command to enable it.\n", |
| __func__); |
| /* Enable quad bit */ |
| status = qpic_serial_set_feature(mtd, FLASH_SPI_NAND_FR_ADDR, |
| FLASH_SPI_NAND_FR_QUAD_ENABLE); |
| if (status < 0) { |
| printf("%s : Error in enabling Quad bit.\n",__func__); |
| return status; |
| } |
| |
| /* Read status again to know wether Quad bit enabled or not */ |
| status = qpic_serial_get_feature(mtd, FLASH_SPI_NAND_FR_ADDR); |
| if (status < 0) { |
| printf("%s : Error in getting feature.\n",__func__); |
| return status; |
| } |
| |
| if (!((status >> 8) & FLASH_SPI_NAND_FR_QUAD_ENABLE)) { |
| qspi_debug("%s:Quad mode not enabled,so use x1 Mode.\n", |
| __func__); |
| dev->quad_mode = false; |
| } else { |
| qspi_debug("%s: Quad mode enabled. using X4 mode\n",__func__); |
| } |
| } else { |
| qspi_debug("%s: Quad mode enabled on Opwer on.\n",__func__); |
| } |
| } |
| |
| if (dev->id == WINBOND_DEVICE_ID) { |
| status = qpic_serial_get_feature(mtd, FLASH_SPI_NAND_FR_ADDR); |
| if (status < 0) { |
| printf("%s : Error in getting feature.\n",__func__); |
| return status; |
| } |
| |
| if (!((status >> 8) & FLASH_SPI_NAND_FR_BUFF_ENABLE)) { |
| qspi_debug("%s :continous buffer mode disabled\n", |
| __func__); |
| qspi_debug("%s : Issuing set feature command to enable it\n", |
| __func__); |
| status = qpic_serial_set_feature(mtd, FLASH_SPI_NAND_FR_ADDR, |
| (FLASH_SPI_NAND_FR_BUFF_ENABLE | (status >> 8))); |
| if (status < 0) { |
| printf("%s : Error in disabling continous buffer bit.\n", |
| __func__); |
| return status; |
| } |
| } else { |
| qspi_debug("%s : continous buffer mode enabled on power on\n", |
| __func__); |
| } |
| } |
| |
| return 0; |
| } |
| #endif |
| |
| /* Onfi probe should issue the following commands to the flash device: |
| * 1. Read ID - with addr ONFI_READ_ID_ADDR. |
| * This returns the ONFI ASCII string indicating support for ONFI. |
| * 2. Read Prameter Page - with addr ONFI_READ_PARAM_PAGE_ADDR. |
| * This returns the params for the device. |
| * Each command inturn issues commands- ADDR0, ADDR1, chip_select, |
| * cfg0, cfg1, cmd_vld, dev_cmd1, read_loc0, flash, exec. |
| */ |
| static int |
| qpic_nand_onfi_probe(struct mtd_info *mtd) |
| { |
| struct onfi_probe_params params; |
| uint32_t vld; |
| uint32_t dev_cmd1; |
| uint32_t cmd_vld = NAND_DEV_CMD_VLD_V1_4_20; |
| uint32_t dev_cmd1_reg = NAND_DEV_CMD1_V1_4_20; |
| unsigned char *buffer; |
| unsigned char *onfi_str = read_bytes; |
| uint32_t *id; |
| struct onfi_param_page *param_page; |
| int onfi_ret = NANDC_RESULT_SUCCESS; |
| |
| #ifdef CONFIG_QPIC_SERIAL |
| uint32_t nand_ret; |
| nand_ret = qpic_nand_read_id_serial(mtd); |
| return nand_ret; |
| #endif |
| |
| /* Allocate memory required to read the onfi param page */ |
| buffer = (unsigned char*) malloc(ONFI_READ_PARAM_PAGE_BUFFER_SIZE); |
| if (buffer == NULL) { |
| printf("Buffer Alloc Failed \n"); |
| return -ENOMEM; |
| } |
| |
| if (hw_ver == QCA_QPIC_V1_5_20) { |
| cmd_vld = NAND_DEV_CMD_VLD_V1_5_20; |
| dev_cmd1_reg = NAND_DEV_CMD1_V1_5_20; |
| } |
| /* Read the vld and dev_cmd1 registers before modifying */ |
| vld = qpic_nand_read_reg(cmd_vld, 0); |
| dev_cmd1 = qpic_nand_read_reg(dev_cmd1_reg, 0); |
| |
| /* Initialize flash cmd */ |
| params.cfg.cmd = NAND_CMD_PAGE_READ; |
| params.cfg.exec = 1; |
| |
| /* Execute Read ID cmd */ |
| |
| /* Initialize the config */ |
| params.cfg.cfg0 = NAND_CFG0_RAW_ONFI_ID; |
| params.cfg.cfg1 = NAND_CFG1_RAW_ONFI_ID; |
| |
| /* Initialize the cmd and vld */ |
| params.dev_cmd1 = (dev_cmd1 & 0xFFFFFF00) | ONFI_READ_ID_CMD; |
| params.vld = vld & 0xFFFFFFFE; |
| |
| /* Initialize the address |
| * addr1 is not used bcos of the cfg. |
| */ |
| params.cfg.addr0 = ONFI_READ_ID_ADDR; |
| params.cfg.addr1 = 0; |
| |
| /* Lock the pipe and execute the cmd. */ |
| onfi_ret = onfi_probe_cmd_exec(mtd, ¶ms, onfi_str, |
| ONFI_READ_ID_BUFFER_SIZE); |
| if (onfi_ret) { |
| printf("ONFI Read id cmd failed\n"); |
| goto qpic_nand_onfi_probe_err; |
| } |
| |
| /* Write back vld and cmd and unlock the pipe. */ |
| qpic_nand_onfi_probe_cleanup(vld, dev_cmd1); |
| |
| /* Check for onfi string */ |
| id = (uint32_t*)onfi_str; |
| if (*id != ONFI_SIGNATURE) { |
| printf("Not an ONFI device\n"); |
| /* Not an onfi device. Return error. */ |
| onfi_ret = NANDC_RESULT_DEV_NOT_SUPPORTED; |
| goto qpic_nand_onfi_probe_err; |
| } |
| |
| printf("ONFI device found\n"); |
| /* Now read the param page */ |
| /* Initialize the config */ |
| params.cfg.cfg0 = NAND_CFG0_RAW_ONFI_PARAM_PAGE; |
| params.cfg.cfg1 = NAND_CFG1_RAW_ONFI_PARAM_PAGE; |
| |
| /* Initialize the cmd and vld */ |
| params.dev_cmd1 = (dev_cmd1 & 0xFFFFFF00) | ONFI_READ_PARAM_PAGE_CMD; |
| params.vld = vld & 0xFFFFFFFE; |
| |
| /* Initialize the address |
| * addr1 is not used bcos of the cfg. |
| */ |
| params.cfg.addr0 = ONFI_READ_PARAM_PAGE_ADDR; |
| params.cfg.addr1 = 0; |
| |
| /* Lock the pipe and execute the cmd. */ |
| onfi_ret = onfi_probe_cmd_exec(mtd, ¶ms, buffer, |
| ONFI_READ_PARAM_PAGE_BUFFER_SIZE); |
| if (onfi_ret) { |
| printf("ONFI Read param page failed\n"); |
| goto qpic_nand_onfi_probe_err; |
| } |
| |
| #ifdef READ_ONFI_PAGE_PARA |
| memmove(onfi_para.buffer, buffer, ONFI_READ_PARAM_PAGE_BUFFER_SIZE); |
| onfi_para.size = ONFI_READ_PARAM_PAGE_BUFFER_SIZE; |
| #endif |
| |
| /* Write back vld and cmd and unlock the pipe. */ |
| qpic_nand_onfi_probe_cleanup(vld, dev_cmd1); |
| |
| /* Verify the integrity of the returned page */ |
| param_page = (struct onfi_param_page*)buffer; |
| |
| /* TODO: Add CRC check to validate the param page. */ |
| |
| /* Save the parameter values */ |
| onfi_ret = qpic_nand_onfi_save_params(mtd, param_page); |
| |
| qpic_nand_onfi_probe_err: |
| if (onfi_ret) |
| printf("ONFI probe failed\n"); |
| |
| if (buffer) |
| free(buffer); |
| |
| return onfi_ret; |
| } |
| |
| #ifdef CONFIG_QPIC_SERIAL |
| static void qpic_spi_init(struct mtd_info *mtd) |
| { |
| uint32_t xfer_start = NAND_XFR_STEPS_V1_5_20; |
| int i; |
| unsigned int default_clk_rate; |
| |
| int num_desc = 0; |
| struct cmd_element *cmd_list_ptr = ce_array; |
| struct cmd_element *cmd_list_ptr_start = ce_array; |
| |
| unsigned int val; |
| |
| val = readl(NAND_QSPI_MSTR_CONFIG); |
| |
| #if defined(QSPI_IO_MACRO_DEFAULT_CLK_320MHZ) && !defined(CONFIG_QSPI_SERIAL_TRAINING) |
| default_clk_rate = IO_MACRO_CLK_320_MHZ; |
| val &= ~FB_CLK_BIT; |
| #else |
| default_clk_rate = IO_MACRO_CLK_200_MHZ; |
| val |= FB_CLK_BIT; |
| #endif |
| if ((readl(QPIC_NAND_CTRL) & BAM_MODE_EN)) { |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_QSPI_MSTR_CONFIG, |
| (uint32_t)val, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_SPI_CFG, |
| (uint32_t)0, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_SPI_CFG, |
| (uint32_t)SPI_CFG_VAL, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| val = SPI_CFG_VAL & ~SPI_LOAD_CLK_CNTR_INIT_EN; |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_SPI_CFG, |
| (uint32_t)val, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)cmd_list_ptr_start, |
| ((uint32_t)cmd_list_ptr - (uint32_t)cmd_list_ptr_start), |
| BAM_DESC_CMD_FLAG); |
| num_desc++; |
| |
| /* Notify BAM HW about the newly added descriptors */ |
| bam_sys_gen_event(&bam, CMD_PIPE_INDEX, num_desc); |
| } else { |
| writel(val, NAND_QSPI_MSTR_CONFIG); |
| writel(0x0, NAND_FLASH_SPI_CFG); |
| writel(SPI_CFG_VAL, NAND_FLASH_SPI_CFG); |
| val = SPI_CFG_VAL & ~SPI_LOAD_CLK_CNTR_INIT_EN; |
| writel(val, NAND_FLASH_SPI_CFG); |
| } |
| |
| num_desc = 0; |
| |
| /* set the FB_CLK_BIT of register QPIC_QSPI_MSTR_CONFIG |
| * to by pass the serial training. if this FB_CLK_BIT |
| * bit enabled then , we can apply upto maximum 200MHz |
| * input to IO_MACRO_BLOCK. |
| */ |
| qpic_set_clk_rate(default_clk_rate, QPIC_IO_MACRO_CLK, |
| GPLL0_CLK_SRC); |
| |
| /*qpic_set_clk_rate(IO_MACRO_CLK_200_MHZ, QPIC_IO_MACRO_CLK, |
| XO_CLK_SRC);*/ |
| |
| /* According to HPG Setting Xfer steps and spi_num_addr_cycles |
| * is part of initialization flow before reset.However these |
| * values differ from NAND part to part.sitting in QPIC layer |
| * we won't know which NAND we don't know which NAND is connected. |
| * So we are not following HPG init sequence.Instead we reset and |
| * read id of NAND,then based on NAND ID we get Xfer steps |
| * and spi_num_addr_cycles and configure them in this function.Since |
| * Xfer steps and spi_num_addr_cycles are required for read/write/erase |
| * functionality. |
| * |
| * NOTE: For now address cycle is same for Giga devices & Micron devices |
| * so we can configure no of addess cycle here only |
| * The NAND_FLASH_XFR_STEP register also fixed for both the devices so we |
| * can configure this register here only . later change this logic as per |
| * device |
| * |
| * NOTE: The XFER register value is now fixed as HPG. |
| * |
| */ |
| for (i = 0; i < QPIC_NUM_XFER_STEPS; i++) |
| writel(qpic_onfi_mode_to_xfer_steps[0][i], |
| xfer_start + 4 * i); |
| |
| writel(NAND_FLASH_DEV_CMD0_VAL, SPI_NAND_DEV_CMD0); |
| writel(NAND_FLASH_DEV_CMD1_VAL, SPI_NAND_DEV_CMD1); |
| writel(NAND_FLASH_DEV_CMD2_VAL, SPI_NAND_DEV_CMD2); |
| writel(NAND_FLASH_DEV_CMD3_VAL, SPI_NAND_DEV_CMD3); |
| writel(NAND_FLASH_DEV_CMD7_VAL, SPI_NAND_DEV_CMD7); |
| |
| /* NAND_DEV_CMD8 & NAND_DEV_CMD9 default value will be used for |
| * QSPI |
| */ |
| writel(FLASH_DEV_CMD_VLD, NAND_FLASH_DEV_CMD_VLD); |
| |
| /* No of address cycle is same for Giga device & Micron so |
| * configure no of address cycle now. |
| */ |
| if ((readl(QPIC_NAND_CTRL) & BAM_MODE_EN)) { |
| cmd_list_ptr = ce_array; |
| bam_add_cmd_element(cmd_list_ptr, NAND_SPI_NUM_ADDR_CYCLES, |
| (uint32_t)SPI_NUM_ADDR_CYCLES, CE_WRITE_TYPE); |
| |
| cmd_list_ptr++; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_SPI_BUSY_CHECK_WAIT_CNT, |
| (uint32_t)SPI_BUSY_CHECK_WAIT_CNT, CE_WRITE_TYPE); |
| |
| cmd_list_ptr++; |
| |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)cmd_list_ptr_start, |
| ((uint32_t)cmd_list_ptr - (uint32_t)cmd_list_ptr_start), |
| BAM_DESC_CMD_FLAG); |
| num_desc++; |
| |
| /* Notify BAM HW about the newly added descriptors */ |
| bam_sys_gen_event(&bam, CMD_PIPE_INDEX, num_desc); |
| } else { |
| writel(SPI_NUM_ADDR_CYCLES, NAND_SPI_NUM_ADDR_CYCLES); |
| writel(SPI_BUSY_CHECK_WAIT_CNT, NAND_SPI_BUSY_CHECK_WAIT_CNT); |
| } |
| } |
| #endif |
| static int qpic_nand_reset(struct mtd_info *mtd) |
| { |
| struct cmd_element *cmd_list_ptr = ce_array; |
| struct cmd_element *cmd_list_ptr_start = ce_array; |
| uint8_t num_desc = 0; |
| uint32_t status, nand_ret; |
| uint32_t exec_cmd = 1; |
| uint32_t flash_cmd = NAND_CMD_RESET_DEVICE; |
| |
| #ifdef CONFIG_QPIC_SERIAL |
| flash_cmd |= (QPIC_SPI_WP_SET | QPIC_SPI_HOLD_SET | QPIC_SPI_TRANSFER_MODE_X1); |
| uint32_t cfg0 = SPI_NAND_DEVn_CFG0 & 0xff00f0; |
| uint32_t cfg1 = SPI_NAND_DEVn_CFG1_RESET; |
| uint32_t ecc_cfg = ((SPI_NAND_DEVn_ECC_CFG & 0x0f000002) | (1 << 0)) |
| & ~(1 << 1); |
| /* As per HPG the reset sequence as follow |
| * NAND_DEV0_CFG0 0x005400D0 or 0x00540010 |
| * NAND_DEVn_CFG1 0x087476B1 |
| * NAND_DEV0_ECC_CFG 0x02000001 |
| * NAND_FLASH_CMD 0x3800000D |
| * NAND_EXEC_CMD 0x00000001 |
| */ |
| /* write the reset sequence as per HPG */ |
| bam_add_cmd_element(cmd_list_ptr, NAND_DEV0_CFG0, (uint32_t)cfg0, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_DEV0_CFG1, (uint32_t)cfg1, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_DEV0_ECC_CFG, (uint32_t)ecc_cfg, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| #endif |
| /* Issue the Reset device command to the NANDc */ |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_CMD, (uint32_t)flash_cmd, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| /* Execute the cmd */ |
| bam_add_cmd_element(cmd_list_ptr, NAND_EXEC_CMD, (uint32_t)exec_cmd, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| /* Prepare the cmd desc for the above commands */ |
| bam_add_one_desc(&bam, CMD_PIPE_INDEX, |
| (unsigned char *)cmd_list_ptr_start, |
| ((uint32_t)cmd_list_ptr - (uint32_t)cmd_list_ptr_start), |
| BAM_DESC_NWD_FLAG | BAM_DESC_CMD_FLAG | |
| BAM_DESC_INT_FLAG); |
| /* Keep track of the number of desc added. */ |
| num_desc++; |
| qpic_nand_wait_for_cmd_exec(num_desc); |
| status = qpic_nand_read_reg(NAND_FLASH_STATUS, 0); |
| |
| /* Check for errors */ |
| nand_ret = qpic_nand_check_status(mtd, status); |
| if (nand_ret) { |
| printf("Reset cmd status failed\n"); |
| return nand_ret; |
| } |
| |
| return nand_ret; |
| } |
| |
| /* Enquues a desc for a flash cmd with NWD flag set: |
| * cfg: Defines the configuration for the flash cmd. |
| * start: Address where the command elements are added. |
| * |
| * Returns the address where the next cmd element can be added. |
| */ |
| struct cmd_element* |
| qpic_nand_add_cmd_ce(struct cfg_params *cfg, |
| struct cmd_element *start) |
| { |
| struct cmd_element *cmd_list_ptr; |
| |
| cmd_list_ptr = qpic_nand_add_addr_n_cfg_ce(cfg, start); |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_CMD, (uint32_t)cfg->cmd, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_EXEC_CMD, (uint32_t)cfg->exec, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| return cmd_list_ptr; |
| } |
| |
| /* Reads nand_flash_status */ |
| struct cmd_element* |
| qpic_nand_add_read_ce(struct cmd_element *start, uint32_t *flash_status_read) |
| { |
| struct cmd_element *cmd_list_ptr = start; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_STATUS, |
| (uint32_t)((addr_t)flash_status_read), CE_READ_TYPE); |
| cmd_list_ptr++; |
| |
| return cmd_list_ptr; |
| } |
| |
| /* Resets nand_flash_status and nand_read_status */ |
| struct cmd_element* |
| qpic_nand_reset_status_ce(struct cmd_element *start, uint32_t read_status) |
| { |
| struct cmd_element *cmd_list_ptr = start; |
| uint32_t flash_status_reset; |
| uint32_t read_status_reset; |
| |
| /* Read and reset the status registers. */ |
| flash_status_reset = NAND_FLASH_STATUS_RESET; |
| read_status_reset = NAND_READ_STATUS_RESET; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_STATUS, |
| (uint32_t)flash_status_reset, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| if (read_status) { |
| bam_add_cmd_element(cmd_list_ptr, NAND_READ_STATUS, |
| (uint32_t)read_status_reset, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| } |
| |
| return cmd_list_ptr; |
| } |
| |
| struct cmd_element* |
| qpic_nand_add_isbad_cmd_ce(struct cfg_params *cfg, |
| struct cmd_element *start) |
| { |
| struct cmd_element *cmd_list_ptr = start; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_DEV0_ECC_CFG, |
| (uint32_t)cfg->ecc_cfg, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| #ifdef CONFIG_QPIC_SERIAL |
| bam_add_cmd_element(cmd_list_ptr, NAND_READ_LOCATION_LAST_CW_n(0), |
| (uint32_t)cfg->addr_loc_0, CE_WRITE_TYPE); |
| #else |
| bam_add_cmd_element(cmd_list_ptr, NAND_READ_LOCATION_n(0), |
| (uint32_t)cfg->addr_loc_0, CE_WRITE_TYPE); |
| #endif |
| cmd_list_ptr++; |
| |
| cmd_list_ptr = qpic_nand_add_cmd_ce(cfg, cmd_list_ptr); |
| |
| return cmd_list_ptr; |
| } |
| |
| static int |
| qpic_nand_block_isbad_exec(struct mtd_info *mtd, |
| struct cfg_params *params, |
| uint8_t *bad_block) |
| { |
| |
| struct cmd_element *cmd_list_ptr = ce_array; |
| struct cmd_element *cmd_list_ptr_start = ce_array; |
| uint8_t desc_flags = BAM_DESC_NWD_FLAG | BAM_DESC_CMD_FLAG |
| | BAM_DESC_LOCK_FLAG | BAM_DESC_INT_FLAG; |
| int num_desc = 0; |
| uint32_t status = 0; |
| int nand_ret = NANDC_RESULT_SUCCESS; |
| |
| cmd_list_ptr = qpic_nand_add_isbad_cmd_ce(params, cmd_list_ptr); |
| |
| /* Enqueue the desc for the above commands */ |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)cmd_list_ptr_start, |
| ((uint32_t)cmd_list_ptr - (uint32_t)cmd_list_ptr_start), |
| desc_flags); |
| |
| num_desc++; |
| |
| /* Add Data desc */ |
| bam_add_desc(&bam, |
| DATA_PRODUCER_PIPE_INDEX, |
| (unsigned char *)((addr_t)bad_block), |
| 4, |
| BAM_DESC_INT_FLAG); |
| |
| qpic_nand_wait_for_cmd_exec(num_desc); |
| |
| status = qpic_nand_read_reg(NAND_FLASH_STATUS, 0); |
| |
| nand_ret = qpic_nand_check_status(mtd, status); |
| |
| /* Dummy read to unlock pipe. */ |
| status = qpic_nand_read_reg(NAND_FLASH_STATUS, BAM_DESC_UNLOCK_FLAG); |
| |
| if (nand_ret) |
| return nand_ret; |
| |
| qpic_nand_wait_for_data(DATA_PRODUCER_PIPE_INDEX); |
| |
| return nand_ret; |
| } |
| |
| /** |
| * qpic_nand_block_isbad() - Checks is given block is bad |
| * @page - number of page the block starts at |
| * |
| * Returns nand_result_t |
| */ |
| static int qpic_nand_block_isbad(struct mtd_info *mtd, loff_t offs) |
| { |
| unsigned cwperpage; |
| struct cfg_params params; |
| unsigned nand_ret = NANDC_RESULT_SUCCESS; |
| uint32_t page; |
| struct nand_chip *chip = MTD_NAND_CHIP(mtd); |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| uint8_t *bad_block = read_bytes; |
| |
| /* Check for invalid offset */ |
| if (offs > mtd->size) |
| return -EINVAL; |
| |
| if (offs & (mtd->erasesize - 1)) |
| return -EINVAL; |
| |
| page = offs >> chip->page_shift; |
| |
| /* Read the bad block value from the flash. |
| * Bad block value is stored in the first page of the block. |
| */ |
| /* Read the first page in the block. */ |
| cwperpage = (dev->cws_per_page); |
| |
| params.cmd = NAND_CMD_PAGE_READ_ECC; |
| |
| /* Read page cmd */ |
| #ifdef CONFIG_QPIC_SERIAL |
| params.cmd = NAND_CMD_PAGE_READ; |
| if (dev->quad_mode) |
| params.cmd |= QPIC_SPI_TRANSFER_MODE_X4; |
| else |
| params.cmd |= QPIC_SPI_TRANSFER_MODE_X1; |
| |
| params.cmd |= (QPIC_SPI_WP_SET | QPIC_SPI_HOLD_SET); |
| #endif |
| /* Clear the CW per page bits */ |
| params.cfg0 = dev->cfg0_raw & ~(7U << |
| NAND_DEV0_CFG0_CW_PER_PAGE_SHIFT); |
| params.cfg1 = dev->cfg1_raw; |
| |
| /* addr0 - Write column addr + few bits in row addr upto 32 bits. */ |
| params.addr0 = ((page << 16) | (USER_DATA_BYTES_PER_CW * |
| (cwperpage))); |
| |
| /* addr1 - Write rest of row addr. |
| * This will be all 0s. |
| */ |
| params.addr1 = (page >> 16) & 0xff; |
| params.addr_loc_0 = NAND_RD_LOC_OFFSET(0); |
| params.addr_loc_0 |= NAND_RD_LOC_LAST_BIT(1); |
| params.addr_loc_0 |= NAND_RD_LOC_SIZE(4); /* Read 4 bytes */ |
| params.ecc_cfg = 0x1; /* Disable ECC */ |
| params.exec = 1; |
| |
| if (qpic_nand_block_isbad_exec(mtd, ¶ms, bad_block)) { |
| printf("Could not read bad block value\n"); |
| return NANDC_RESULT_FAILURE; |
| } |
| |
| #if !defined(CONFIG_SYS_DCACHE_OFF) |
| flush_dcache_range((unsigned long)bad_block, |
| (unsigned long)bad_block + sizeof(bad_block)); |
| #endif |
| |
| if (dev->widebus) { |
| if (bad_block[0] != 0xFF && bad_block[1] != 0xFF) { |
| nand_ret = NANDC_RESULT_BAD_BLOCK; |
| } |
| } else if (bad_block[0] != 0xFF) { |
| nand_ret = NANDC_RESULT_BAD_BLOCK; |
| } |
| return nand_ret; |
| } |
| |
| /* Return num of desc added. */ |
| static void |
| qpic_nand_add_wr_page_cws_cmd_desc(struct mtd_info *mtd, struct cfg_params *cfg, |
| uint32_t status[], |
| enum nand_cfg_value cfg_mode) |
| { |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| struct cmd_element *cmd_list_ptr = ce_array; |
| struct cmd_element *cmd_list_read_ptr = ce_read_array; |
| struct cmd_element *cmd_list_ptr_start = ce_array; |
| struct cmd_element *cmd_list_read_ptr_start = ce_read_array; |
| uint32_t ecc; |
| int num_desc = 0; |
| int int_flag = 0; |
| unsigned int i; |
| #ifdef CONFIG_QPIC_SERIAL |
| /* For Serial NAND devices the page program sequence as |
| * 02H (PROGRAM LOAD)/32H (PROGRAM LOAD x4) |
| * 06H (WRITE ENABLE) |
| * 10H (PROGRAM EXECUTE) |
| * 0FH (GET FEATURE command to read the status) |
| * No need to 0x02 & 0x32 command manually, controller |
| * automatically send this command to device. we have already mapped |
| * these command in QPIC_FLASH_DEV_CMD9 register, similar for command |
| * 0x06 & 0x0F, controller will take care internally |
| * |
| * NOTE: While initializing we have already enabeld quad bit i.e QE-bit |
| * and disable write protection so no need to check here. |
| */ |
| if (dev->quad_mode) |
| cfg->cmd |= QPIC_SPI_TRANSFER_MODE_X4; |
| else |
| cfg->cmd |= QPIC_SPI_TRANSFER_MODE_X1; |
| |
| cfg->cmd |= (QPIC_SPI_WP_SET | QPIC_SPI_HOLD_SET); |
| #endif |
| |
| if (cfg_mode == NAND_CFG) { |
| ecc = dev->ecc_bch_cfg; |
| } else { |
| ecc = 0x1; /* Disable ECC */ |
| } |
| /* Add ECC configuration */ |
| bam_add_cmd_element(cmd_list_ptr, NAND_DEV0_ECC_CFG, |
| (uint32_t)ecc, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| cmd_list_ptr = qpic_nand_add_addr_n_cfg_ce(cfg, cmd_list_ptr); |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_CMD, |
| (uint32_t)cfg->cmd, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| /* Enqueue the desc for the above commands */ |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)cmd_list_ptr_start, |
| ((uint32_t)cmd_list_ptr - (uint32_t)cmd_list_ptr_start), |
| BAM_DESC_CMD_FLAG | BAM_DESC_LOCK_FLAG); |
| |
| num_desc++; |
| |
| /* Add CE for all the CWs */ |
| for (i = 0; i < (dev->cws_per_page); i++) { |
| cmd_list_ptr_start = cmd_list_ptr; |
| int_flag = BAM_DESC_INT_FLAG; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_EXEC_CMD, (uint32_t)cfg->exec, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| /* Enqueue the desc for the above commands */ |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)cmd_list_ptr_start, |
| ((uint32_t)cmd_list_ptr - (uint32_t)cmd_list_ptr_start), |
| BAM_DESC_NWD_FLAG | BAM_DESC_CMD_FLAG); |
| |
| num_desc++; |
| cmd_list_ptr_start = cmd_list_ptr; |
| cmd_list_read_ptr_start = cmd_list_read_ptr; |
| |
| cmd_list_read_ptr = qpic_nand_add_read_ce(cmd_list_read_ptr_start, |
| &status[i]); |
| /* Enqueue the desc for the NAND_FLASH_STATUS read command */ |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)cmd_list_read_ptr_start, |
| ((uint32_t)cmd_list_read_ptr - |
| (uint32_t)cmd_list_read_ptr_start), |
| BAM_DESC_CMD_FLAG); |
| |
| /* Set interrupt bit only for the last CW */ |
| if (i == (dev->cws_per_page) - 1) |
| cmd_list_ptr = qpic_nand_reset_status_ce(cmd_list_ptr, |
| 1); |
| else |
| cmd_list_ptr = qpic_nand_reset_status_ce(cmd_list_ptr, |
| 0); |
| |
| /* Enqueue the desc for NAND_FLASH_STATUS and NAND_READ_STATUS |
| * write commands */ |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)cmd_list_ptr_start, |
| ((uint32_t)cmd_list_ptr - |
| (uint32_t)cmd_list_ptr_start), |
| int_flag | BAM_DESC_CMD_FLAG | BAM_DESC_UNLOCK_FLAG); |
| num_desc += 2; |
| |
| qpic_nand_wait_for_cmd_exec(num_desc); |
| |
| #if !defined(CONFIG_SYS_DCACHE_OFF) |
| flush_dcache_range((unsigned long)status, |
| (unsigned long)status + |
| sizeof(status_write));/*caluclating the size*/ |
| #endif |
| |
| status[i] = qpic_nand_check_status(mtd, status[i]); |
| |
| num_desc = 0; |
| } |
| return; |
| } |
| |
| void |
| qpic_add_wr_page_cws_data_desc(struct mtd_info *mtd, const void *buffer, |
| enum nand_cfg_value cfg_mode, |
| const void *spareaddr) |
| { |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| int len; |
| int flags; |
| uint32_t start; |
| unsigned num_desc = 0; |
| unsigned i; |
| |
| for( i = 0; i < (dev->cws_per_page); i++) { |
| flags = 0; |
| |
| /* Set the interrupt flag on the last CW write for the page. */ |
| if( i == (dev->cws_per_page) - 1) |
| flags |= BAM_DESC_INT_FLAG; |
| |
| if (cfg_mode != NAND_CFG_RAW) { |
| start = (uint32_t)buffer + i * DATA_BYTES_IN_IMG_PER_CW; |
| if (i < ((dev->cws_per_page) - 1)) { |
| len = DATA_BYTES_IN_IMG_PER_CW; |
| flags |= BAM_DESC_EOT_FLAG; |
| } else { |
| /* Allow space for spare bytes in the last page */ |
| len = USER_DATA_BYTES_PER_CW - |
| (((dev->cws_per_page) - 1) << 2); |
| flags = 0; |
| } |
| } else { |
| start = (uint32_t)buffer + i * (dev->cw_size); |
| if (i < ((dev->cws_per_page) - 1)) { |
| len = (dev->cw_size); |
| flags |= BAM_DESC_EOT_FLAG; |
| } |
| else { |
| len = (dev->cw_size - mtd->oobsize); |
| flags = 0; |
| } |
| } |
| bam_add_one_desc(&bam, DATA_CONSUMER_PIPE_INDEX, (unsigned char*)(start), |
| len, flags); |
| num_desc++; |
| |
| if ((i == ((dev->cws_per_page) - 1))) { |
| /* write extra data */ |
| start = (uint32_t)spareaddr; |
| if (cfg_mode == NAND_CFG) |
| len = ((dev->cws_per_page) << 2); |
| else |
| len = mtd->oobsize; |
| flags = BAM_DESC_EOT_FLAG | BAM_DESC_INT_FLAG; |
| bam_add_one_desc(&bam, DATA_CONSUMER_PIPE_INDEX, |
| (unsigned char*)(start), len, flags); |
| num_desc++; |
| } |
| } |
| |
| bam_sys_gen_event(&bam, DATA_CONSUMER_PIPE_INDEX, num_desc); |
| } |
| |
| static nand_result_t |
| qpic_nand_write_page(struct mtd_info *mtd, uint32_t pg_addr, |
| enum nand_cfg_value cfg_mode, |
| struct mtd_oob_ops *ops) |
| { |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| struct cfg_params cfg; |
| int nand_ret = NANDC_RESULT_SUCCESS; |
| unsigned i; |
| |
| if (cfg_mode == NAND_CFG_RAW) { |
| cfg.cfg0 = dev->cfg0_raw; |
| cfg.cfg1 = dev->cfg1_raw; |
| } else { |
| cfg.cfg0 = dev->cfg0; |
| cfg.cfg1 = dev->cfg1; |
| } |
| |
| cfg.cmd = NAND_CMD_PRG_PAGE; |
| cfg.exec = 1; |
| |
| cfg.addr0 = pg_addr << 16; |
| cfg.addr1 = (pg_addr >> 16) & 0xff; |
| |
| qpic_add_wr_page_cws_data_desc(mtd, ops->datbuf, cfg_mode, ops->oobbuf); |
| |
| qpic_nand_add_wr_page_cws_cmd_desc(mtd, &cfg, status_write, cfg_mode); |
| |
| /* Check for errors */ |
| for(i = 0; i < (dev->cws_per_page); i++) { |
| nand_ret = qpic_nand_check_status(mtd, status_write[i]); |
| if (nand_ret) { |
| printf( |
| "Failed to write CW %d for page: %d\n", |
| i, pg_addr); |
| break; |
| } |
| } |
| |
| /* Wait for data to be available */ |
| qpic_nand_wait_for_data(DATA_CONSUMER_PIPE_INDEX); |
| |
| ops->retlen += mtd->writesize; |
| ops->datbuf += mtd->writesize; |
| |
| if (ops->oobbuf != NULL) { |
| ops->oobretlen += dev->oob_per_page; |
| ops->oobbuf += dev->oob_per_page; |
| } |
| |
| return nand_ret; |
| } |
| |
| static int |
| qpic_nand_mark_badblock(struct mtd_info *mtd, loff_t offs) |
| { |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| struct nand_chip *chip = MTD_NAND_CHIP(mtd); |
| uint32_t page; |
| int nand_ret = NANDC_RESULT_SUCCESS; |
| struct mtd_oob_ops ops; |
| |
| /* Check for invalid offset */ |
| if (offs > mtd->size) |
| return -EINVAL; |
| |
| if (offs & (mtd->erasesize - 1)) |
| return -EINVAL; |
| |
| page = offs >> chip->page_shift; |
| |
| ops.mode = MTD_OPS_RAW; |
| ops.len = mtd->writesize; |
| ops.retlen = 0; |
| ops.ooblen = mtd->oobsize; |
| ops.oobretlen = 0; |
| ops.ooboffs = 0; |
| ops.datbuf = dev->zero_page; |
| ops.oobbuf = dev->zero_oob; |
| |
| /* Going to first page of the block */ |
| if (page & (dev->num_pages_per_blk_mask)) |
| page = page - (page & (dev->num_pages_per_blk_mask)); |
| |
| nand_ret = qpic_nand_write_page(mtd, page, NAND_CFG_RAW, &ops); |
| if (!nand_ret) |
| mtd->ecc_stats.badblocks++; |
| return nand_ret; |
| |
| } |
| |
| /* |
| * Populate flash parameters from the look up table. |
| */ |
| static void qpic_nand_get_info_flash_dev(struct mtd_info *mtd, |
| const struct nand_flash_dev *flash_dev) |
| { |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| mtd->writesize = dev->page_size = flash_dev->pagesize; |
| mtd->erasesize = dev->block_size = flash_dev->erasesize; |
| mtd->oobsize = dev->spare_size = flash_dev->oobsize ? |
| flash_dev->oobsize : (flash_dev->pagesize >> 5); |
| } |
| |
| |
| /* |
| * Populate flash parameters from the configuration byte. |
| */ |
| static void qpic_nand_get_info_cfg(struct mtd_info *mtd, uint8_t cfg_id) |
| { |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| u_int cfg_page_size; |
| u_int cfg_block_size; |
| u_int cfg_spare_size; |
| u_int chunks; |
| u_int spare_per_chunk; |
| |
| /* writesize = 1KB * (2 ^ cfg_page_size) */ |
| cfg_page_size = NAND_CFG_PAGE_SIZE(cfg_id); |
| mtd->writesize = dev->page_size = (1024 << cfg_page_size); |
| |
| /* erasesize = 64KB * (2 ^ cfg_block_size) */ |
| cfg_block_size = NAND_CFG_BLOCK_SIZE(cfg_id); |
| mtd->erasesize = dev->block_size = ((64 * 1024) << cfg_block_size); |
| |
| /* Spare per 512B = 8 * (2 ^ cfg_spare_size) */ |
| cfg_spare_size = NAND_CFG_SPARE_SIZE(cfg_id); |
| chunks = (mtd->writesize / CHUNK_SIZE); |
| spare_per_chunk = (8 << cfg_spare_size); |
| mtd->oobsize = dev->spare_size = (spare_per_chunk * chunks); |
| } |
| |
| /* |
| * Retreive the flash info entry using the device ID. |
| */ |
| static const struct nand_flash_dev *flash_get_dev(uint8_t dev_id) |
| { |
| int i; |
| |
| for (i = 0; nand_flash_ids[i].id; i++) { |
| if (nand_flash_ids[i].dev_id == dev_id) |
| return &nand_flash_ids[i]; |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * Retreive the manuf. info entry using manufacturer ID. |
| */ |
| static const struct nand_manufacturers *flash_get_man(uint8_t man_id) |
| { |
| int i; |
| |
| for (i = 0; nand_manuf_ids[i].id; i++) { |
| if (nand_manuf_ids[i].id == man_id) |
| return &nand_manuf_ids[i]; |
| } |
| |
| return NULL; |
| } |
| |
| |
| /* |
| * Populate flash parameters for non-ONFI devices. |
| */ |
| static int qpic_nand_get_info(struct mtd_info *mtd, uint32_t flash_id) |
| { |
| uint8_t man_id; |
| uint8_t dev_id; |
| uint8_t cfg_id; |
| const struct nand_manufacturers *flash_man; |
| const struct nand_flash_dev *flash_dev; |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| uint32_t min_oobsize_8bit_ecc; |
| |
| man_id = NAND_ID_MAN(flash_id); |
| dev_id = NAND_ID_DEV(flash_id); |
| cfg_id = NAND_ID_CFG(flash_id); |
| |
| debug("Manufacturer ID: %x\n", man_id); |
| debug("Device ID: %x\n", dev_id); |
| debug("Config. Byte: %x\n", cfg_id); |
| |
| flash_man = flash_get_man(man_id); |
| flash_dev = flash_get_dev(dev_id); |
| |
| if (flash_man == NULL || flash_dev == NULL) { |
| printf("qpic_nand: unknown NAND device manufacturer: %x" |
| " device: %x\n", man_id, dev_id); |
| return -ENOENT; |
| } |
| |
| mtd->size = MB_TO_BYTES(flash_dev->chipsize); |
| /* |
| * For older NAND flash, we obtained the flash information |
| * from the flash_dev table. For newer flashes the information |
| * is available in the cfg byte, in the NAND ID sequence. |
| */ |
| if (!flash_dev->pagesize) |
| qpic_nand_get_info_cfg(mtd, cfg_id); |
| else |
| qpic_nand_get_info_flash_dev(mtd, flash_dev); |
| |
| min_oobsize_8bit_ecc = |
| (mtd->writesize / CHUNK_SIZE) * NAND_CW_SPARE_SIZE_8_BIT_ECC; |
| |
| /* |
| * Calculate the minimum required oobsize for using 8 bit ecc and use |
| * 8 bit ecc if this chip oobsize equal or more than that. |
| */ |
| mtd->ecc_strength = mtd->oobsize >= min_oobsize_8bit_ecc ? 8 : 4; |
| |
| dev->num_blocks = mtd->size; |
| dev->num_blocks /= (dev->block_size); |
| dev->num_pages_per_blk = dev->block_size / dev->page_size; |
| dev->num_pages_per_blk_mask = dev->num_pages_per_blk - 1; |
| |
| return 0; |
| } |
| |
| static void |
| qpic_nand_non_onfi_probe(struct mtd_info *mtd) |
| { |
| struct qpic_nand_dev *dev= MTD_QPIC_NAND_DEV(mtd); |
| |
| /* Read the nand id. */ |
| qpic_nand_fetch_id(mtd); |
| |
| qpic_nand_get_info(mtd, dev->id); |
| |
| return; |
| } |
| |
| static void qpic_nand_sync(struct mtd_info *mtd) |
| { |
| /* Nop */ |
| } |
| |
| static int qpic_nand_scan_bbt_nop(struct mtd_info *mtd) |
| { |
| return 0; |
| } |
| |
| /* |
| * Estimate the no. of pages to read, based on the data length and oob |
| * length. |
| */ |
| static u_long qpic_get_read_page_count(struct mtd_info *mtd, |
| struct mtd_oob_ops *ops, loff_t to) |
| { |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| struct nand_chip *chip = MTD_NAND_CHIP(mtd); |
| uint32_t start_page, end_page; |
| |
| if (ops->datbuf != NULL) { |
| /* |
| * Determine start page (can be non page aligned) and end page |
| * and calculate number of pages. |
| */ |
| start_page = to >> chip->page_shift; |
| end_page = (to + ops->len - 1) >> chip->page_shift; |
| return end_page - start_page + 1; |
| } else { |
| if (dev->oob_per_page == 0) |
| return 0; |
| |
| return (ops->ooblen + dev->oob_per_page - 1) / dev->oob_per_page; |
| } |
| } |
| |
| /* |
| * Return the buffer where the next OOB data should be stored. If the |
| * user buffer is insufficient to hold one page worth of OOB data, |
| * return an internal buffer, to hold the data temporarily. |
| */ |
| static uint8_t *qpic_nand_read_oobbuf(struct mtd_info *mtd, |
| struct mtd_oob_ops *ops) |
| { |
| size_t read_ooblen; |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| |
| if (ops->oobbuf == NULL) |
| return NULL; |
| |
| read_ooblen = ops->ooblen - ops->oobretlen; |
| if (read_ooblen < dev->oob_per_page) |
| return dev->pad_oob; |
| |
| return ops->oobbuf + ops->oobretlen; |
| } |
| /* |
| * Return the buffer where the next in-band data should be stored. If |
| * the user buffer is insufficient to hold one page worth of in-band |
| * data, return an internal buffer, to hold the data temporarily. |
| */ |
| static uint8_t *qpic_nand_read_datbuf(struct mtd_info *mtd, |
| struct mtd_oob_ops *ops, uint32_t col) |
| { |
| size_t read_datlen; |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| |
| if (ops->datbuf == NULL) |
| return NULL; |
| |
| read_datlen = ops->len - ops->retlen; |
| if (read_datlen < mtd->writesize || col) |
| return dev->pad_dat; |
| |
| return ops->datbuf + ops->retlen; |
| } |
| |
| /* |
| * Copy the OOB data from the internal buffer, to the user buffer, if |
| * the internal buffer was used for the read. |
| */ |
| static void qpic_nand_read_oobcopy(struct mtd_info *mtd, |
| struct mtd_oob_ops *ops) |
| { |
| size_t ooblen; |
| size_t read_ooblen; |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| |
| if (ops->oobbuf == NULL) |
| return; |
| |
| read_ooblen = ops->ooblen - ops->oobretlen; |
| ooblen = (read_ooblen < dev->oob_per_page ? read_ooblen : dev->oob_per_page); |
| if (read_ooblen < dev->oob_per_page) |
| memcpy(ops->oobbuf + ops->oobretlen, dev->pad_oob, ooblen); |
| |
| ops->oobretlen += ooblen; |
| } |
| |
| /* |
| * Copy the in-band data from the internal buffer, to the user buffer, |
| * if the internal buffer was used for the read. |
| */ |
| static void |
| qpic_nand_read_datcopy(struct mtd_info *mtd, |
| struct mtd_oob_ops *ops, uint32_t col) |
| { |
| size_t datlen; |
| size_t read_datlen; |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| |
| if (ops->datbuf == NULL) |
| return; |
| |
| read_datlen = ops->len - ops->retlen; |
| #ifdef CONFIG_PAGE_SCOPE_MULTI_PAGE_READ |
| if (dev->multi_page_copy) { |
| datlen = (mtd->writesize * dev->multi_page_req_len); |
| dev->multi_page_copy = false; |
| } else if (col == 0 && read_datlen >= mtd->writesize) { |
| datlen = mtd->writesize; |
| } else { |
| datlen = min(read_datlen, mtd->writesize - col); |
| memcpy(ops->datbuf + ops->retlen, dev->pad_dat + col, datlen); |
| } |
| #else |
| if (col == 0 && read_datlen >= mtd->writesize) { |
| datlen = mtd->writesize; |
| } else { |
| datlen = min(read_datlen, mtd->writesize - col); |
| memcpy(ops->datbuf + ops->retlen, dev->pad_dat + col, datlen); |
| } |
| #endif |
| ops->retlen += datlen; |
| } |
| |
| static int |
| qpic_nand_check_erased_buf(unsigned char *buf, int len, int bitflips_threshold) |
| { |
| int bitflips = 0; |
| |
| for (; len > 0; len--, buf++) { |
| bitflips += 8 - hweight8(*buf); |
| if (unlikely(bitflips > bitflips_threshold)) |
| return -EBADMSG; |
| } |
| |
| return bitflips; |
| } |
| |
| /* |
| * Now following logic is being added to identify the erased codeword |
| * bitflips. |
| * 1. Maintain the bitmasks for the codewords which generated uncorrectable |
| * error. |
| * 2. Read the raw data again in temp buffer and count the number of zeros. |
| * Since spare bytes are unused in ECC layout and won’t affect ECC |
| * correctability so no need to count number of zero in spare bytes. |
| * 3. If the number of zero is below ECC correctability then it can be |
| * treated as erased CW. In this case, make all the data/oob of actual user |
| * buffers as 0xff. |
| */ |
| static int |
| qpic_nand_check_erased_page(struct mtd_info *mtd, uint32_t page, |
| unsigned char *datbuf, |
| unsigned char *oobbuf, |
| unsigned int uncorrectable_err_cws, |
| unsigned int *max_bitflips) |
| { |
| struct mtd_oob_ops raw_page_ops; |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| unsigned char *tmp_datbuf; |
| unsigned int tmp_datasize, datasize, oobsize; |
| int i, start_cw, last_cw, ret, data_bitflips; |
| |
| raw_page_ops.mode = MTD_OPS_RAW; |
| raw_page_ops.len = mtd->writesize; |
| raw_page_ops.ooblen = mtd->oobsize; |
| raw_page_ops.datbuf = dev->tmp_datbuf; |
| raw_page_ops.oobbuf = dev->tmp_oobbuf; |
| raw_page_ops.retlen = 0; |
| raw_page_ops.oobretlen = 0; |
| |
| ret = qpic_nand_read_page(mtd, page, NAND_CFG_RAW, &raw_page_ops); |
| if (ret) |
| return ret; |
| |
| start_cw = ffs(uncorrectable_err_cws) - 1; |
| last_cw = fls(uncorrectable_err_cws); |
| |
| tmp_datbuf = dev->tmp_datbuf + start_cw * dev->cw_size; |
| tmp_datasize = dev->cw_size - dev->spare_bytes; |
| datasize = DATA_BYTES_IN_IMG_PER_CW; |
| datbuf += start_cw * datasize; |
| |
| for (i = start_cw; i < last_cw; |
| i++, datbuf += datasize, tmp_datbuf += dev->cw_size) { |
| if (!(BIT(i) & uncorrectable_err_cws)) |
| continue; |
| |
| data_bitflips = |
| qpic_nand_check_erased_buf(tmp_datbuf, tmp_datasize, |
| mtd->ecc_strength); |
| if (data_bitflips < 0) { |
| mtd->ecc_stats.failed++; |
| continue; |
| } |
| |
| *max_bitflips = |
| max_t(unsigned int, *max_bitflips, data_bitflips); |
| |
| if (i == dev->cws_per_page - 1) { |
| oobsize = dev->cws_per_page << 2; |
| datasize = DATA_BYTES_IN_IMG_PER_CW - oobsize; |
| if (oobbuf) |
| memset(oobbuf, 0xff, oobsize); |
| } |
| |
| if (datbuf) |
| memset(datbuf, 0xff, datasize); |
| } |
| |
| return 0; |
| } |
| |
| static int |
| qpic_nand_read_page(struct mtd_info *mtd, uint32_t page, |
| enum nand_cfg_value cfg_mode, |
| struct mtd_oob_ops *ops) |
| { |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| struct cfg_params params; |
| uint32_t ecc; |
| struct read_stats *stats = dev->stats; |
| uint32_t addr_loc_0; |
| uint32_t addr_loc_1; |
| struct cmd_element *cmd_list_ptr = ce_array; |
| struct cmd_element *cmd_list_ptr_start = ce_array; |
| uint32_t num_cmd_desc = 0; |
| uint32_t num_data_desc = 0; |
| uint32_t i; |
| int nand_ret = NANDC_RESULT_SUCCESS; |
| uint8_t flags = 0; |
| uint32_t *cmd_list_temp = NULL; |
| uint16_t data_bytes; |
| uint16_t ud_bytes_in_last_cw; |
| uint16_t oob_bytes; |
| unsigned char *buffer, *ops_datbuf = ops->datbuf; |
| unsigned char *spareaddr, *ops_oobbuf = ops->oobbuf; |
| unsigned char *buffer_st, *spareaddr_st; |
| unsigned int max_bitflips = 0, uncorrectable_err_cws = 0; |
| |
| /* Check This address for serial NAND later on if any issue |
| * Because as per HPG Page Read 0x13 NAND_ADDR1[7:0] |
| * NAND_ADDR0[31:24] NAND_ADDR0[23:16] |
| */ |
| params.addr0 = page << 16; |
| params.addr1 = (page >> 16) & 0xff; |
| |
| if (cfg_mode == NAND_CFG_RAW) { |
| params.cfg0 = dev->cfg0_raw; |
| params.cfg1 = dev->cfg1_raw; |
| params.cmd = NAND_CMD_PAGE_READ; |
| ecc = 0x1; /* Disable ECC */ |
| #ifdef CONFIG_QPIC_SERIAL |
| if (dev->quad_mode) |
| params.cmd |= QPIC_SPI_TRANSFER_MODE_X4; |
| else |
| params.cmd |= QPIC_SPI_TRANSFER_MODE_X1; |
| |
| params.cmd |= (QPIC_SPI_WP_SET | QPIC_SPI_HOLD_SET); |
| #endif |
| data_bytes = dev->cw_size; |
| oob_bytes = mtd->oobsize; |
| ud_bytes_in_last_cw = (dev->cw_size - mtd->oobsize); |
| } else { |
| params.cfg0 = dev->cfg0; |
| params.cfg1 = dev->cfg1; |
| params.cmd = NAND_CMD_PAGE_READ_ALL; |
| #ifdef CONFIG_QPIC_SERIAL |
| if (dev->quad_mode) |
| params.cmd |= QPIC_SPI_TRANSFER_MODE_X4; |
| else |
| params.cmd |= QPIC_SPI_TRANSFER_MODE_X1; |
| |
| params.cmd |= (QPIC_SPI_WP_SET | QPIC_SPI_HOLD_SET); |
| #endif |
| ecc = (dev->ecc_bch_cfg); |
| data_bytes = DATA_BYTES_IN_IMG_PER_CW; |
| ud_bytes_in_last_cw = USER_DATA_BYTES_PER_CW - |
| (((dev->cws_per_page) - 1) << 2); |
| oob_bytes = DATA_BYTES_IN_IMG_PER_CW - ud_bytes_in_last_cw; |
| } |
| |
| params.exec = 1; |
| /* Read all the Data bytes in the first 3 CWs. */ |
| addr_loc_0 = NAND_RD_LOC_OFFSET(0); |
| addr_loc_0 |= NAND_RD_LOC_SIZE(data_bytes);; |
| addr_loc_0 |= NAND_RD_LOC_LAST_BIT(1); |
| |
| addr_loc_1 = NAND_RD_LOC_OFFSET(ud_bytes_in_last_cw); |
| addr_loc_1 |= NAND_RD_LOC_SIZE(oob_bytes); |
| addr_loc_1 |= NAND_RD_LOC_LAST_BIT(1); |
| |
| /* Reset and Configure erased CW/page detection controller */ |
| qpic_nand_erased_status_reset(ce_array, BAM_DESC_LOCK_FLAG); |
| |
| if (ops->datbuf == NULL) { |
| buffer = dev->pad_dat; |
| } else { |
| buffer = ops->datbuf; |
| } |
| |
| if (ops->oobbuf == NULL) { |
| spareaddr = dev->pad_oob; |
| } else { |
| spareaddr = ops->oobbuf; |
| } |
| |
| buffer_st = buffer; |
| spareaddr_st = spareaddr; |
| |
| /* Queue up the command and data descriptors for all the codewords in a page |
| * and do a single bam transfer at the end.*/ |
| for (i = 0; i < (dev->cws_per_page); i++) { |
| num_cmd_desc = 0; |
| num_data_desc = 0; |
| |
| if (i == 0) { |
| cmd_list_ptr = qpic_nand_add_addr_n_cfg_ce(¶ms, cmd_list_ptr); |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_DEV0_ECC_CFG,(uint32_t)ecc, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| } else |
| cmd_list_ptr_start = cmd_list_ptr; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_CMD, (uint32_t)params.cmd, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| if (i == (dev->cws_per_page) - 1) { |
| /* Write addr loc 1 only for the last CW. */ |
| addr_loc_0 = NAND_RD_LOC_OFFSET(0); |
| addr_loc_0 |= NAND_RD_LOC_SIZE(ud_bytes_in_last_cw); |
| addr_loc_0 |= NAND_RD_LOC_LAST_BIT(0); |
| #ifdef CONFIG_QPIC_SERIAL |
| /*To read only spare bytes 80 0r 16*/ |
| bam_add_cmd_element(cmd_list_ptr, NAND_READ_LOCATION_LAST_CW_n(1), |
| (uint32_t)addr_loc_1, CE_WRITE_TYPE); |
| #else |
| bam_add_cmd_element(cmd_list_ptr, NAND_READ_LOCATION_n(1), |
| (uint32_t)addr_loc_1, CE_WRITE_TYPE); |
| #endif |
| |
| cmd_list_ptr++; |
| flags = 0; |
| /* Add Data desc */ |
| bam_add_one_desc(&bam, DATA_PRODUCER_PIPE_INDEX, |
| (unsigned char *)((addr_t)(buffer)), |
| ud_bytes_in_last_cw, |
| flags); |
| num_data_desc++; |
| bam_add_one_desc(&bam, |
| DATA_PRODUCER_PIPE_INDEX, |
| (unsigned char *)((addr_t)(spareaddr)), |
| oob_bytes, |
| BAM_DESC_INT_FLAG); |
| num_data_desc++; |
| bam_sys_gen_event(&bam, DATA_PRODUCER_PIPE_INDEX, |
| num_data_desc); |
| } else { |
| /* Add Data desc */ |
| bam_add_one_desc(&bam, |
| DATA_PRODUCER_PIPE_INDEX, |
| (unsigned char *)((addr_t)buffer), |
| data_bytes, |
| 0); |
| num_data_desc++; |
| bam_sys_gen_event(&bam, DATA_PRODUCER_PIPE_INDEX, |
| num_data_desc); |
| } |
| #ifdef CONFIG_QPIC_SERIAL |
| if (i == (dev->cws_per_page) - 1) |
| bam_add_cmd_element(cmd_list_ptr, |
| NAND_READ_LOCATION_LAST_CW_n(0), |
| (uint32_t)addr_loc_0, |
| CE_WRITE_TYPE); |
| else |
| bam_add_cmd_element(cmd_list_ptr, |
| NAND_READ_LOCATION_n(0), |
| (uint32_t)addr_loc_0, |
| CE_WRITE_TYPE); |
| #else |
| bam_add_cmd_element(cmd_list_ptr, |
| NAND_READ_LOCATION_n(0), |
| (uint32_t)addr_loc_0, |
| CE_WRITE_TYPE); |
| #endif |
| cmd_list_ptr++; |
| |
| bam_add_cmd_element(cmd_list_ptr, |
| NAND_EXEC_CMD, |
| (uint32_t)params.exec, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| /* Enqueue the desc for the above commands */ |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)cmd_list_ptr_start, |
| ((uint32_t)cmd_list_ptr - |
| (uint32_t)cmd_list_ptr_start), |
| BAM_DESC_NWD_FLAG | BAM_DESC_CMD_FLAG); |
| num_cmd_desc++; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_STATUS, |
| (uint32_t)((addr_t)&(stats[i].flash_sts)), |
| CE_READ_TYPE); |
| |
| cmd_list_temp = (uint32_t *)cmd_list_ptr; |
| |
| cmd_list_ptr++; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_BUFFER_STATUS, |
| (uint32_t)((addr_t)&(stats[i].buffer_sts)), |
| CE_READ_TYPE); |
| cmd_list_ptr++; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_ERASED_CW_DETECT_STATUS, |
| (uint32_t)((addr_t)&(stats[i].erased_cw_sts)), |
| CE_READ_TYPE); |
| cmd_list_ptr++; |
| |
| if (i == (dev->cws_per_page) - 1) { |
| flags = BAM_DESC_CMD_FLAG | BAM_DESC_UNLOCK_FLAG; |
| } else |
| flags = BAM_DESC_CMD_FLAG; |
| |
| /* Enqueue the desc for the above command */ |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)((addr_t)cmd_list_temp), |
| ((uint32_t)cmd_list_ptr - (uint32_t)cmd_list_temp), |
| flags); |
| num_cmd_desc++; |
| |
| if (ops->datbuf != NULL) { |
| if (i == (dev->cws_per_page - 1)) { |
| buffer += ud_bytes_in_last_cw; |
| ops->datbuf += ud_bytes_in_last_cw; |
| ops->retlen += ud_bytes_in_last_cw; |
| } else { |
| buffer = ops->datbuf + data_bytes; |
| ops->datbuf += data_bytes; |
| ops->retlen += data_bytes; |
| } |
| } |
| else { |
| if (i == (dev->cws_per_page - 1)) { |
| buffer += ud_bytes_in_last_cw; |
| } else { |
| buffer += data_bytes; |
| } |
| } |
| if ((i == (dev->cws_per_page) - 1)) { |
| if (ops->oobbuf != NULL) { |
| spareaddr += oob_bytes; |
| ops->oobretlen += oob_bytes; |
| ops->oobbuf += oob_bytes; |
| } else |
| spareaddr += oob_bytes; |
| } |
| /* Notify BAM HW about the newly added descriptors */ |
| bam_sys_gen_event(&bam, CMD_PIPE_INDEX, num_cmd_desc); |
| } |
| |
| qpic_nand_wait_for_data(DATA_PRODUCER_PIPE_INDEX); |
| |
| #if !defined(CONFIG_SYS_DCACHE_OFF) |
| flush_dcache_range((unsigned long)flash_sts, |
| (unsigned long)flash_sts + sizeof(flash_sts)); |
| flush_dcache_range((unsigned long)buffer_sts, |
| (unsigned long)buffer_sts + sizeof(buffer_sts)); |
| flush_dcache_range((unsigned long)buffer_st, |
| (unsigned long)buffer); |
| flush_dcache_range((unsigned long)spareaddr_st, |
| (unsigned long)spareaddr); |
| #endif |
| |
| /* Check status */ |
| for (i = 0; i < (dev->cws_per_page) ; i ++) { |
| if (cfg_mode == NAND_CFG_RAW) |
| nand_ret = qpic_nand_check_status(mtd, |
| stats[i].flash_sts); |
| else |
| nand_ret = qpic_nand_check_read_status(mtd, &stats[i]); |
| |
| if (nand_ret < 0) { |
| if (nand_ret == -EBADMSG) { |
| uncorrectable_err_cws |= BIT(i); |
| continue; |
| } |
| |
| goto qpic_nand_read_page_error; |
| } |
| |
| max_bitflips = max_t(unsigned int, max_bitflips, nand_ret); |
| } |
| |
| if (uncorrectable_err_cws) { |
| nand_ret = qpic_nand_check_erased_page(mtd, page, ops_datbuf, |
| ops_oobbuf, |
| uncorrectable_err_cws, |
| &max_bitflips); |
| if (nand_ret < 0) |
| goto qpic_nand_read_page_error; |
| } |
| |
| return max_bitflips; |
| |
| qpic_nand_read_page_error: |
| printf("NAND page read failed. page: %x status %x\n", |
| page, nand_ret); |
| return nand_ret; |
| } |
| |
| #ifdef CONFIG_PAGE_SCOPE_MULTI_PAGE_READ |
| static int qpic_nand_multi_page_read(struct mtd_info *mtd, uint32_t page, |
| enum nand_cfg_value cfg_mode, struct mtd_oob_ops *ops, |
| uint32_t num_pages) |
| { |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| struct cfg_params params; |
| struct cmd_element *cmd_list_ptr = ce_array; |
| struct cmd_element *cmd_list_ptr_start = ce_array; |
| struct read_stats *stats = dev->stats; |
| uint32_t auto_status = QPIC_SPI_NAND_AUTO_STATUS_VAL; |
| unsigned char *buffer, *ops_datbuf = ops->datbuf; |
| unsigned char *spareaddr, *ops_oobbuf = ops->oobbuf; |
| unsigned char *buffer_st, *spareaddr_st; |
| unsigned char *auto_status_buffer = NULL; |
| unsigned char *tmp_status_buffer = NULL; |
| uint16_t data_bytes; |
| uint16_t ud_bytes_in_last_cw; |
| uint16_t oob_bytes; |
| uint32_t addr_loc_0, addr_loc_1, addr_loc_last, ecc; |
| uint32_t num_data_desc = 0; |
| uint32_t num_status_desc = 0; |
| uint32_t i, j; |
| uint8_t flags = 0; |
| int nand_ret = NANDC_RESULT_SUCCESS; |
| unsigned int max_bitflips = 0, uncorrectable_err_cws = 0; |
| |
| params.addr0 = page << 16; |
| params.addr1 = (page >> 16) & 0xff; |
| |
| memset(dev->status_buff, 0, dev->status_buf_size); |
| auto_status_buffer = dev->status_buff; |
| tmp_status_buffer = dev->status_buff; |
| |
| dev->multi_page_copy = true; |
| |
| #ifdef CONFIG_QPIC_SERIAL |
| params.cmd = (QPIC_SPI_WP_SET | QPIC_SPI_HOLD_SET); |
| if (dev->quad_mode) |
| params.cmd |= QPIC_SPI_TRANSFER_MODE_X4; |
| else |
| params.cmd |= QPIC_SPI_TRANSFER_MODE_X1; |
| #endif |
| if (cfg_mode == NAND_CFG_RAW) { |
| params.cfg0 = dev->cfg0_raw; |
| params.cfg1 = dev->cfg1_raw; |
| params.cmd |= (NAND_CMD_PAGE_READ | QPIC_MULTIPAGE_CMD_EN); |
| ecc = 0x1; /* Disable ECC */ |
| |
| data_bytes = dev->cw_size; |
| oob_bytes = mtd->oobsize; |
| ud_bytes_in_last_cw = (dev->cw_size - mtd->oobsize); |
| } else { |
| params.cfg0 = dev->cfg0; |
| params.cfg1 = dev->cfg1; |
| params.cmd |= (NAND_CMD_PAGE_READ_ALL | QPIC_MULTIPAGE_CMD_EN); |
| |
| ecc = (dev->ecc_bch_cfg); |
| data_bytes = DATA_BYTES_IN_IMG_PER_CW; |
| ud_bytes_in_last_cw = USER_DATA_BYTES_PER_CW - |
| (((dev->cws_per_page) - 1) << 2); |
| oob_bytes = DATA_BYTES_IN_IMG_PER_CW - ud_bytes_in_last_cw; |
| } |
| |
| params.exec = 1; |
| |
| addr_loc_0 = NAND_RD_LOC_OFFSET(0); |
| addr_loc_0 |= NAND_RD_LOC_SIZE(data_bytes);; |
| addr_loc_0 |= NAND_RD_LOC_LAST_BIT(1); |
| |
| addr_loc_1 = NAND_RD_LOC_OFFSET(ud_bytes_in_last_cw); |
| addr_loc_1 |= NAND_RD_LOC_SIZE(oob_bytes); |
| addr_loc_1 |= NAND_RD_LOC_LAST_BIT(1); |
| |
| addr_loc_last = NAND_RD_LOC_OFFSET(0); |
| addr_loc_last |= NAND_RD_LOC_SIZE(ud_bytes_in_last_cw); |
| addr_loc_last |= NAND_RD_LOC_LAST_BIT(0); |
| |
| /* reset address reg before executing for |
| * next multi page read |
| */ |
| reset_addr_reg(ce_array, 0); |
| |
| /* reset multi_page_cmd_reg */ |
| reset_multi_page_cmd_reg(ce_array, 0); |
| |
| /* Reset and Configure erased CW/page detection controller */ |
| qpic_nand_erased_status_reset(ce_array, BAM_DESC_LOCK_FLAG); |
| |
| if (ops->datbuf == NULL) { |
| buffer = dev->pad_dat; |
| } else { |
| buffer = ops->datbuf; |
| } |
| |
| if (ops->oobbuf == NULL) { |
| spareaddr = dev->pad_oob; |
| } else { |
| spareaddr = ops->oobbuf; |
| } |
| |
| buffer_st = buffer; |
| spareaddr_st = spareaddr; |
| |
| cmd_list_ptr = qpic_nand_add_addr_n_cfg_ce(¶ms, cmd_list_ptr); |
| bam_add_cmd_element(cmd_list_ptr, NAND_DEV0_ECC_CFG, (uint32_t)ecc, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| bam_add_cmd_element(cmd_list_ptr, NAND_AUTO_STATUS_EN, (uint32_t)auto_status, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| bam_add_cmd_element(cmd_list_ptr, NAND_MULTI_PAGE_CMD, (uint32_t)num_pages - 1, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| /* Enqueue the desc for the above commands */ |
| bam_add_one_desc(&bam, CMD_PIPE_INDEX, (unsigned char*)cmd_list_ptr_start, |
| ((uint32_t)cmd_list_ptr - (uint32_t)cmd_list_ptr_start), |
| BAM_DESC_CMD_FLAG); |
| |
| bam_sys_gen_event(&bam, CMD_PIPE_INDEX, 1); |
| |
| /* Queue up the command and data descriptors for all the requested page |
| * and do a single bam transfer at the end.*/ |
| for (j = 0; j < num_pages; j++) { |
| |
| for (i = 0; i < (dev->cws_per_page); i++) { |
| num_data_desc = 0; |
| num_status_desc = 0; |
| |
| if (i == (dev->cws_per_page) - 1) { |
| |
| if ( j == num_pages - 1) { |
| flags = BAM_DESC_INT_FLAG; |
| } else { |
| flags = 0; |
| } |
| /* Add Data desc */ |
| bam_add_one_desc(&bam, DATA_PRODUCER_PIPE_INDEX, |
| (unsigned char *)((addr_t)(buffer)), |
| ud_bytes_in_last_cw, |
| 0); |
| num_data_desc++; |
| |
| bam_add_one_desc(&bam, |
| DATA_PRODUCER_PIPE_INDEX, |
| (unsigned char *)((addr_t)(spareaddr)), |
| oob_bytes, |
| flags); |
| num_data_desc++; |
| |
| /* add data descriptor to read status */ |
| bam_add_one_desc(&bam, |
| BAM_STATUS_PIPE_INDEX, |
| (unsigned char *)(addr_t) |
| (auto_status_buffer), |
| QPIC_AUTO_STATUS_DES_SIZE, |
| flags); |
| num_status_desc++; |
| |
| bam_sys_gen_event(&bam, DATA_PRODUCER_PIPE_INDEX, |
| num_data_desc); |
| |
| bam_sys_gen_event(&bam, BAM_STATUS_PIPE_INDEX, |
| num_status_desc); |
| } else { |
| /* Add Data desc */ |
| bam_add_one_desc(&bam, |
| DATA_PRODUCER_PIPE_INDEX, |
| (unsigned char *)((addr_t)buffer), |
| data_bytes, |
| 0); |
| num_data_desc++; |
| |
| /* add data descriptor to read status */ |
| bam_add_one_desc(&bam, |
| BAM_STATUS_PIPE_INDEX, |
| (unsigned char *)(addr_t) |
| (auto_status_buffer), |
| QPIC_AUTO_STATUS_DES_SIZE, |
| 0); |
| num_status_desc++; |
| |
| bam_sys_gen_event(&bam, DATA_PRODUCER_PIPE_INDEX, |
| num_data_desc); |
| |
| bam_sys_gen_event(&bam, BAM_STATUS_PIPE_INDEX, |
| num_status_desc); |
| } |
| |
| if (ops->datbuf != NULL) { |
| if (i == (dev->cws_per_page - 1)) { |
| buffer += ud_bytes_in_last_cw; |
| ops->datbuf += ud_bytes_in_last_cw; |
| ops->retlen += ud_bytes_in_last_cw; |
| } else { |
| buffer = ops->datbuf + data_bytes; |
| ops->datbuf += data_bytes; |
| ops->retlen += data_bytes; |
| } |
| } |
| else { |
| if (i == (dev->cws_per_page - 1)) { |
| buffer += ud_bytes_in_last_cw; |
| } else { |
| buffer += data_bytes; |
| } |
| } |
| if ((i == (dev->cws_per_page) - 1)) { |
| if (ops->oobbuf != NULL) { |
| spareaddr += oob_bytes; |
| ops->oobretlen += oob_bytes; |
| ops->oobbuf += oob_bytes; |
| } else |
| spareaddr += oob_bytes; |
| } |
| |
| auto_status_buffer += QPIC_AUTO_STATUS_DES_SIZE; |
| } |
| } |
| |
| cmd_list_ptr = cmd_list_ptr_start; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_READ_LOCATION_n(0), (uint32_t)addr_loc_0, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_READ_LOCATION_LAST_CW_n(0), |
| (uint32_t)addr_loc_last, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| /*To read only spare bytes 80 0r 16*/ |
| bam_add_cmd_element(cmd_list_ptr, NAND_READ_LOCATION_LAST_CW_n(1), |
| (uint32_t)addr_loc_1, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_CMD, (uint32_t)params.cmd, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| bam_add_cmd_element(cmd_list_ptr, NAND_EXEC_CMD, (uint32_t)params.exec, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| /* Enqueue the desc for the above commands */ |
| bam_add_one_desc(&bam, CMD_PIPE_INDEX, (unsigned char*)cmd_list_ptr_start, |
| ((uint32_t)cmd_list_ptr - (uint32_t)cmd_list_ptr_start), |
| BAM_DESC_CMD_FLAG | BAM_DESC_NWD_FLAG); |
| |
| /* Notify BAM HW about the newly added descriptors */ |
| bam_sys_gen_event(&bam, CMD_PIPE_INDEX, 1); |
| |
| qpic_nand_wait_for_data(DATA_PRODUCER_PIPE_INDEX); |
| qpic_nand_wait_for_data(BAM_STATUS_PIPE_INDEX); |
| |
| #if !defined(CONFIG_SYS_DCACHE_OFF) |
| flush_dcache_range((unsigned long)dev->status_buff, |
| (unsigned long)dev->status_buff + |
| dev->status_buf_size); |
| flush_dcache_range((unsigned long)buffer_st, |
| (unsigned long)buffer); |
| flush_dcache_range((unsigned long)spareaddr_st, |
| (unsigned long)spareaddr); |
| #endif |
| /* Update the auto status structure */ |
| for (j =0; j < num_pages; j++) { |
| |
| for (i = 0; i < (dev->cws_per_page); i++) { |
| |
| memscpy(&stats[i].flash_sts, 4, tmp_status_buffer, 4); |
| memscpy(&stats[i].buffer_sts, 4, tmp_status_buffer + 4, 4); |
| memscpy(&stats[i].erased_cw_sts, 4, tmp_status_buffer + 8, 4); |
| |
| /* Check status */ |
| if (cfg_mode == NAND_CFG_RAW) |
| nand_ret = qpic_nand_check_status(mtd, stats[i].flash_sts); |
| else |
| nand_ret = qpic_nand_check_read_status(mtd, &stats[i]); |
| |
| if (nand_ret < 0) { |
| if (nand_ret == -EBADMSG) { |
| uncorrectable_err_cws |= BIT(i); |
| continue; |
| } |
| |
| goto qpic_nand_read_page_error; |
| } |
| |
| max_bitflips = max_t(unsigned int, max_bitflips, nand_ret); |
| tmp_status_buffer += QPIC_SPI_MAX_STATUS_REG; |
| } |
| |
| if (uncorrectable_err_cws) { |
| nand_ret = qpic_nand_check_erased_page(mtd, page + j, (ops_datbuf + (j * mtd->writesize)), |
| ops_oobbuf, |
| uncorrectable_err_cws, |
| &max_bitflips); |
| if (nand_ret < 0) |
| goto qpic_nand_read_page_error; |
| } |
| } |
| |
| return max_bitflips; |
| |
| qpic_nand_read_page_error: |
| |
| printf("NAND page read failed. page: %x status %x\n", |
| page, nand_ret); |
| |
| return nand_ret; |
| } |
| |
| static int qpic_nand_page_scope_read(struct mtd_info *mtd, uint32_t page, |
| enum nand_cfg_value cfg_mode, struct mtd_oob_ops *ops) |
| { |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| struct cfg_params params; |
| struct cmd_element *cmd_list_ptr = ce_array; |
| struct cmd_element *cmd_list_ptr_start = ce_array; |
| struct read_stats *stats = dev->stats; |
| uint32_t auto_status = QPIC_SPI_NAND_AUTO_STATUS_VAL; |
| unsigned char *buffer, *ops_datbuf = ops->datbuf; |
| unsigned char *spareaddr, *ops_oobbuf = ops->oobbuf; |
| unsigned char *buffer_st, *spareaddr_st; |
| unsigned char *auto_status_buffer = NULL; |
| uint16_t data_bytes; |
| uint16_t ud_bytes_in_last_cw; |
| uint16_t oob_bytes; |
| uint32_t addr_loc_0, addr_loc_1, ecc; |
| uint32_t num_cmd_desc = 0; |
| uint32_t num_data_desc = 0; |
| uint32_t num_status_desc = 0; |
| uint32_t i; |
| uint32_t parse_size = 0x0; |
| uint8_t flags = 0; |
| int nand_ret = NANDC_RESULT_SUCCESS; |
| unsigned int max_bitflips = 0, uncorrectable_err_cws = 0; |
| |
| params.addr0 = page << 16; |
| params.addr1 = (page >> 16) & 0xff; |
| |
| memset(dev->status_buff, 0, dev->status_buf_size); |
| auto_status_buffer = dev->status_buff; |
| |
| #ifdef CONFIG_QPIC_SERIAL |
| params.cmd = (QPIC_SPI_WP_SET | QPIC_SPI_HOLD_SET); |
| if (dev->quad_mode) |
| params.cmd |= QPIC_SPI_TRANSFER_MODE_X4; |
| else |
| params.cmd |= QPIC_SPI_TRANSFER_MODE_X1; |
| #endif |
| if (cfg_mode == NAND_CFG_RAW) { |
| params.cfg0 = dev->cfg0_raw; |
| params.cfg1 = dev->cfg1_raw; |
| params.cmd |= (NAND_CMD_PAGE_READ | QPIC_PAGE_SCOPE_CMD_EN); |
| ecc = 0x1; /* Disable ECC */ |
| |
| data_bytes = dev->cw_size; |
| oob_bytes = mtd->oobsize; |
| ud_bytes_in_last_cw = (dev->cw_size - mtd->oobsize); |
| } else { |
| params.cfg0 = dev->cfg0; |
| params.cfg1 = dev->cfg1; |
| params.cmd |= (NAND_CMD_PAGE_READ_ALL | QPIC_PAGE_SCOPE_CMD_EN); |
| |
| ecc = (dev->ecc_bch_cfg); |
| data_bytes = DATA_BYTES_IN_IMG_PER_CW; |
| ud_bytes_in_last_cw = USER_DATA_BYTES_PER_CW - |
| (((dev->cws_per_page) - 1) << 2); |
| oob_bytes = DATA_BYTES_IN_IMG_PER_CW - ud_bytes_in_last_cw; |
| } |
| |
| params.exec = 1; |
| |
| addr_loc_0 = NAND_RD_LOC_OFFSET(0); |
| addr_loc_0 |= NAND_RD_LOC_SIZE(data_bytes);; |
| addr_loc_0 |= NAND_RD_LOC_LAST_BIT(1); |
| |
| addr_loc_1 = NAND_RD_LOC_OFFSET(ud_bytes_in_last_cw); |
| addr_loc_1 |= NAND_RD_LOC_SIZE(oob_bytes); |
| addr_loc_1 |= NAND_RD_LOC_LAST_BIT(1); |
| |
| /* Reset and Configure erased CW/page detection controller */ |
| qpic_nand_erased_status_reset(ce_array, BAM_DESC_LOCK_FLAG); |
| |
| if (ops->datbuf == NULL) { |
| buffer = dev->pad_dat; |
| } else { |
| buffer = ops->datbuf; |
| } |
| |
| if (ops->oobbuf == NULL) { |
| spareaddr = dev->pad_oob; |
| } else { |
| spareaddr = ops->oobbuf; |
| } |
| |
| buffer_st = buffer; |
| spareaddr_st = spareaddr; |
| |
| /* Queue up the command and data descriptors for all the codewords in a page |
| * and do a single bam transfer at the end.*/ |
| for (i = 0; i < (dev->cws_per_page); i++) { |
| num_cmd_desc = 0; |
| num_data_desc = 0; |
| num_status_desc = 0; |
| if (i == 0) { |
| cmd_list_ptr = qpic_nand_add_addr_n_cfg_ce(¶ms, cmd_list_ptr); |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_DEV0_ECC_CFG,(uint32_t)ecc, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_AUTO_STATUS_EN,(uint32_t)auto_status, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_CMD, (uint32_t)params.cmd, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| } else |
| cmd_list_ptr_start = cmd_list_ptr; |
| |
| if (i == (dev->cws_per_page) - 1) { |
| /* Write addr loc 1 only for the last CW. */ |
| addr_loc_0 = NAND_RD_LOC_OFFSET(0); |
| addr_loc_0 |= NAND_RD_LOC_SIZE(ud_bytes_in_last_cw); |
| addr_loc_0 |= NAND_RD_LOC_LAST_BIT(0); |
| |
| /*To read only spare bytes 80 0r 16*/ |
| bam_add_cmd_element(cmd_list_ptr, NAND_READ_LOCATION_LAST_CW_n(1), |
| (uint32_t)addr_loc_1, CE_WRITE_TYPE); |
| |
| cmd_list_ptr++; |
| |
| /* Add Data desc */ |
| bam_add_one_desc(&bam, DATA_PRODUCER_PIPE_INDEX, |
| (unsigned char *)((addr_t)(buffer)), |
| ud_bytes_in_last_cw, |
| 0); |
| num_data_desc++; |
| |
| bam_add_one_desc(&bam, |
| DATA_PRODUCER_PIPE_INDEX, |
| (unsigned char *)((addr_t)(spareaddr)), |
| oob_bytes, |
| BAM_DESC_INT_FLAG); |
| num_data_desc++; |
| |
| /* add data descriptor to read status */ |
| bam_add_one_desc(&bam, |
| BAM_STATUS_PIPE_INDEX, |
| (unsigned char *)((addr_t)(auto_status_buffer + |
| i * QPIC_SPI_MAX_STATUS_REG)), |
| QPIC_AUTO_STATUS_DES_SIZE, |
| BAM_DESC_INT_FLAG); |
| num_status_desc++; |
| |
| bam_sys_gen_event(&bam, DATA_PRODUCER_PIPE_INDEX, |
| num_data_desc); |
| |
| bam_sys_gen_event(&bam, BAM_STATUS_PIPE_INDEX, |
| num_status_desc); |
| } else { |
| /* Add Data desc */ |
| bam_add_one_desc(&bam, |
| DATA_PRODUCER_PIPE_INDEX, |
| (unsigned char *)((addr_t)buffer), |
| data_bytes, |
| 0); |
| num_data_desc++; |
| |
| /* add data descriptor to read status */ |
| bam_add_one_desc(&bam, |
| BAM_STATUS_PIPE_INDEX, |
| (unsigned char *)((addr_t)(auto_status_buffer + |
| i * QPIC_SPI_MAX_STATUS_REG)), |
| QPIC_AUTO_STATUS_DES_SIZE, |
| 0); |
| num_status_desc++; |
| |
| bam_sys_gen_event(&bam, DATA_PRODUCER_PIPE_INDEX, |
| num_data_desc); |
| |
| bam_sys_gen_event(&bam, BAM_STATUS_PIPE_INDEX, |
| num_status_desc); |
| } |
| |
| if (i == (dev->cws_per_page) - 1) { |
| bam_add_cmd_element(cmd_list_ptr, |
| NAND_READ_LOCATION_LAST_CW_n(0), |
| (uint32_t)addr_loc_0, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_EXEC_CMD, |
| (uint32_t)params.exec, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| } else { |
| bam_add_cmd_element(cmd_list_ptr, |
| NAND_READ_LOCATION_n(0), |
| (uint32_t)addr_loc_0, |
| CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| } |
| |
| if (i == (dev->cws_per_page) - 1) { |
| flags = BAM_DESC_CMD_FLAG | BAM_DESC_NWD_FLAG; |
| } else |
| flags = BAM_DESC_CMD_FLAG; |
| |
| /* Enqueue the desc for the above commands */ |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)cmd_list_ptr_start, |
| ((uint32_t)cmd_list_ptr - |
| (uint32_t)cmd_list_ptr_start), |
| flags); |
| num_cmd_desc++; |
| |
| if (ops->datbuf != NULL) { |
| if (i == (dev->cws_per_page - 1)) { |
| buffer += ud_bytes_in_last_cw; |
| ops->datbuf += ud_bytes_in_last_cw; |
| ops->retlen += ud_bytes_in_last_cw; |
| } else { |
| buffer = ops->datbuf + data_bytes; |
| ops->datbuf += data_bytes; |
| ops->retlen += data_bytes; |
| } |
| } |
| else { |
| if (i == (dev->cws_per_page - 1)) { |
| buffer += ud_bytes_in_last_cw; |
| } else { |
| buffer += data_bytes; |
| } |
| } |
| if ((i == (dev->cws_per_page) - 1)) { |
| if (ops->oobbuf != NULL) { |
| spareaddr += oob_bytes; |
| ops->oobretlen += oob_bytes; |
| ops->oobbuf += oob_bytes; |
| } else |
| spareaddr += oob_bytes; |
| } |
| /* Notify BAM HW about the newly added descriptors */ |
| bam_sys_gen_event(&bam, CMD_PIPE_INDEX, num_cmd_desc); |
| } |
| |
| qpic_nand_wait_for_data(BAM_STATUS_PIPE_INDEX); |
| qpic_nand_wait_for_data(DATA_PRODUCER_PIPE_INDEX); |
| |
| GET_STATUS_BUFF_PARSE_SIZE_PER_PAGE(mtd->writesize, parse_size); |
| #if !defined(CONFIG_SYS_DCACHE_OFF) |
| |
| flush_dcache_range((unsigned long)dev->status_buff, |
| (unsigned long)dev->status_buff + parse_size); |
| flush_dcache_range((unsigned long)buffer_st, |
| (unsigned long)buffer); |
| flush_dcache_range((unsigned long)spareaddr_st, |
| (unsigned long)spareaddr); |
| #endif |
| /* Update the auto status structure */ |
| for (i = 0; i < (dev->cws_per_page); i++) { |
| memscpy(&stats[i].flash_sts, 4, (dev->status_buff + |
| i * QPIC_SPI_MAX_STATUS_REG), |
| sizeof(int)); |
| memscpy(&stats[i].buffer_sts, 4, (dev->status_buff + |
| i * QPIC_SPI_MAX_STATUS_REG) + 4, |
| sizeof(int)); |
| memscpy(&stats[i].erased_cw_sts, 4, (dev->status_buff + |
| i * QPIC_SPI_MAX_STATUS_REG) + 8, |
| sizeof(int)); |
| } |
| |
| /* Check status */ |
| for (i = 0; i < (dev->cws_per_page) ; i ++) { |
| if (cfg_mode == NAND_CFG_RAW) |
| nand_ret = qpic_nand_check_status(mtd, |
| stats[i].flash_sts); |
| else |
| nand_ret = qpic_nand_check_read_status(mtd, &stats[i]); |
| |
| if (nand_ret < 0) { |
| if (nand_ret == -EBADMSG) { |
| uncorrectable_err_cws |= BIT(i); |
| continue; |
| } |
| |
| goto qpic_nand_read_page_error; |
| } |
| |
| max_bitflips = max_t(unsigned int, max_bitflips, nand_ret); |
| } |
| |
| if (uncorrectable_err_cws) { |
| nand_ret = qpic_nand_check_erased_page(mtd, page, ops_datbuf, |
| ops_oobbuf, |
| uncorrectable_err_cws, |
| &max_bitflips); |
| if (nand_ret < 0) |
| goto qpic_nand_read_page_error; |
| } |
| |
| return max_bitflips; |
| |
| qpic_nand_read_page_error: |
| printf("NAND page read failed. page: %x status %x\n", |
| page, nand_ret); |
| return nand_ret; |
| } |
| |
| static int qpic_alloc_status_buff(struct qpic_nand_dev *dev, |
| struct mtd_info *mtd) |
| { |
| GET_STATUS_BUFF_ALLOC_SIZE(mtd->writesize, |
| dev->status_buf_size); |
| dev->status_buff = (unsigned char *)malloc(dev->status_buf_size); |
| if (!dev->status_buff) |
| return -ENOMEM; |
| |
| memset(dev->status_buff, 0, dev->status_buf_size); |
| |
| return 0; |
| } |
| |
| static int qpic_nand_read_page_scope_multi_page(struct mtd_info *mtd, |
| loff_t to, struct mtd_oob_ops *ops) |
| { |
| uint32_t i = 0, ret = 0; |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| struct nand_chip *chip = MTD_NAND_CHIP(mtd); |
| uint32_t start_page; |
| uint32_t num_pages, req_pages = 0x0; |
| uint32_t col; |
| enum nand_cfg_value cfg_mode; |
| unsigned int max_bitflips = 0; |
| unsigned int ecc_failures = mtd->ecc_stats.failed; |
| |
| /* We don't support MTD_OOB_PLACE as of yet. */ |
| if (ops->mode == MTD_OPS_PLACE_OOB) |
| return -ENOSYS; |
| |
| /* Check for reads past end of device */ |
| if (ops->datbuf && (to + ops->len) > mtd->size) |
| return -EINVAL; |
| |
| if (ops->ooboffs != 0) |
| return -EINVAL; |
| |
| if(ops->mode == MTD_OPS_RAW) { |
| cfg_mode = NAND_CFG_RAW; |
| dev->oob_per_page = mtd->oobsize; |
| } else { |
| cfg_mode = NAND_CFG; |
| dev->oob_per_page = mtd->oobavail; |
| } |
| |
| start_page = ((to >> chip->page_shift)); |
| num_pages = qpic_get_read_page_count(mtd, ops, to); |
| while (1) { |
| |
| if (num_pages > MAX_MULTI_PAGE) { |
| |
| req_pages = MAX_MULTI_PAGE; |
| |
| } else if (num_pages > 1 && num_pages <= MAX_MULTI_PAGE) { |
| |
| req_pages = num_pages; |
| |
| } else if (num_pages == 1) { |
| |
| req_pages = num_pages; |
| } |
| |
| struct mtd_oob_ops page_ops; |
| |
| col = i == 0 ? to & (mtd->writesize - 1) : 0; |
| page_ops.mode = ops->mode; |
| page_ops.len = mtd->writesize * req_pages; |
| page_ops.ooblen = dev->oob_per_page; |
| page_ops.datbuf = qpic_nand_read_datbuf(mtd, ops, col); |
| page_ops.oobbuf = qpic_nand_read_oobbuf(mtd, ops); |
| page_ops.retlen = 0; |
| page_ops.oobretlen = 0; |
| dev->multi_page_req_len = req_pages; |
| |
| if (num_pages > 1) |
| ret = qpic_nand_multi_page_read(mtd, start_page, |
| cfg_mode, &page_ops, req_pages); |
| else |
| ret = qpic_nand_page_scope_read(mtd, start_page, |
| cfg_mode, &page_ops); |
| |
| if (ret < 0) { |
| printf("%s: reading page %d failed with %d err\n", |
| __func__, start_page, ret); |
| return ret; |
| } |
| |
| max_bitflips = max_t(unsigned int, max_bitflips, ret); |
| qpic_nand_read_datcopy(mtd, ops, col); |
| qpic_nand_read_oobcopy(mtd, ops); |
| |
| num_pages -= req_pages; |
| i++; |
| |
| if (!num_pages) |
| break; |
| |
| start_page += req_pages; |
| } |
| |
| if (ecc_failures != mtd->ecc_stats.failed) { |
| printf("%s: ecc failure while reading from %llx\n", |
| __func__, to); |
| return -EBADMSG; |
| } |
| |
| return max_bitflips; |
| } |
| #endif |
| |
| static int qpic_nand_read_oob(struct mtd_info *mtd, loff_t to, |
| struct mtd_oob_ops *ops) |
| { |
| uint32_t i = 0, ret = 0; |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| struct nand_chip *chip = MTD_NAND_CHIP(mtd); |
| uint32_t start_page; |
| uint32_t num_pages; |
| uint32_t col; |
| enum nand_cfg_value cfg_mode; |
| unsigned int max_bitflips = 0; |
| unsigned int ecc_failures = mtd->ecc_stats.failed; |
| |
| /* We don't support MTD_OOB_PLACE as of yet. */ |
| if (ops->mode == MTD_OPS_PLACE_OOB) |
| return -ENOSYS; |
| |
| /* Check for reads past end of device */ |
| if (ops->datbuf && (to + ops->len) > mtd->size) |
| return -EINVAL; |
| |
| if (ops->ooboffs != 0) |
| return -EINVAL; |
| |
| if(ops->mode == MTD_OPS_RAW) { |
| cfg_mode = NAND_CFG_RAW; |
| dev->oob_per_page = mtd->oobsize; |
| } else { |
| cfg_mode = NAND_CFG; |
| dev->oob_per_page = mtd->oobavail; |
| } |
| |
| start_page = ((to >> chip->page_shift)); |
| num_pages = qpic_get_read_page_count(mtd, ops, to); |
| |
| for (i = 0; i < num_pages; i++) { |
| struct mtd_oob_ops page_ops; |
| |
| /* |
| * If start address is non page alinged then determine the |
| * column offset |
| */ |
| col = i == 0 ? to & (mtd->writesize - 1) : 0; |
| page_ops.mode = ops->mode; |
| page_ops.len = mtd->writesize; |
| page_ops.ooblen = dev->oob_per_page; |
| page_ops.datbuf = qpic_nand_read_datbuf(mtd, ops, col); |
| page_ops.oobbuf = qpic_nand_read_oobbuf(mtd, ops); |
| page_ops.retlen = 0; |
| page_ops.oobretlen = 0; |
| |
| ret = qpic_nand_read_page(mtd, start_page + i, cfg_mode, |
| &page_ops); |
| |
| if (ret < 0) { |
| printf("%s: reading page %d failed with %d err\n", |
| __func__, start_page + i, ret); |
| return ret; |
| } |
| |
| max_bitflips = max_t(unsigned int, max_bitflips, ret); |
| qpic_nand_read_datcopy(mtd, ops, col); |
| qpic_nand_read_oobcopy(mtd, ops); |
| |
| } |
| |
| if (ecc_failures != mtd->ecc_stats.failed) { |
| printf("%s: ecc failure while reading from %llx\n", |
| __func__, to); |
| return -EBADMSG; |
| } |
| |
| return max_bitflips; |
| } |
| |
| /** |
| * qpic_nand_read() - read data |
| * @start_page: number of page to begin reading from |
| * @num_pages: number of pages to read |
| * @buffer: buffer where to store the read data |
| * @spareaddr: buffer where to store spare data. |
| * If null, spare data wont be read |
| * |
| * This function reads @num_pages starting from @start_page and stores the |
| * read data in buffer. Note that it's in the caller responsibility to make |
| * sure the read pages are all from same partition. |
| * |
| */ |
| static int qpic_nand_read(struct mtd_info *mtd, loff_t from, size_t len, |
| size_t *retlen, u_char *buf) |
| { |
| unsigned ret = 0; |
| struct mtd_oob_ops ops; |
| |
| ops.mode = MTD_OPS_AUTO_OOB; |
| ops.len = len; |
| ops.retlen = 0; |
| ops.ooblen = 0; |
| ops.oobretlen = 0; |
| ops.ooboffs = 0; |
| ops.datbuf = (uint8_t *)buf; |
| ops.oobbuf = NULL; |
| #ifdef CONFIG_PAGE_SCOPE_MULTI_PAGE_READ |
| if (hw_ver >= QCA_QPIC_V2_1_1) { |
| ret = qpic_nand_read_page_scope_multi_page(mtd, from, |
| &ops); |
| } else { |
| printf("QPIC controller not support page scope and multi page read.\n"); |
| return -EIO; |
| } |
| #else |
| ret = qpic_nand_read_oob(mtd, from, &ops); |
| #endif |
| |
| *retlen = ops.retlen; |
| |
| return ret; |
| } |
| |
| /* |
| * Return the buffer containing the in-band data to be written. |
| */ |
| static uint8_t *qpic_nand_write_datbuf(struct mtd_info *mtd, |
| struct mtd_oob_ops *ops) |
| { |
| return ops->datbuf + ops->retlen; |
| } |
| |
| /* |
| * Return the buffer containing the OOB data to be written. If user |
| * buffer does not provide on page worth of OOB data, return a padded |
| * internal buffer with the OOB data copied in. |
| */ |
| static uint8_t *qpic_nand_write_oobbuf(struct mtd_info *mtd, |
| struct mtd_oob_ops *ops) |
| { |
| size_t write_ooblen; |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| |
| memset(dev->pad_oob, 0xFF, dev->oob_per_page); |
| |
| if (ops->oobbuf == NULL) |
| return dev->pad_oob; |
| |
| write_ooblen = ops->ooblen - ops->oobretlen; |
| |
| if (write_ooblen < dev->oob_per_page) { |
| memcpy(dev->pad_oob, ops->oobbuf + ops->oobretlen, write_ooblen); |
| return dev->pad_oob; |
| } |
| |
| return ops->oobbuf + ops->oobretlen; |
| } |
| |
| /* |
| * Increment the counters to indicate the no. of in-band bytes |
| * written. |
| */ |
| static void qpic_nand_write_datinc(struct mtd_info *mtd, |
| struct mtd_oob_ops *ops) |
| { |
| ops->retlen += mtd->writesize; |
| } |
| |
| /* |
| * Increment the counters to indicate the no. of OOB bytes written. |
| */ |
| static void qpic_nand_write_oobinc(struct mtd_info *mtd, |
| struct mtd_oob_ops *ops) |
| { |
| size_t write_ooblen; |
| size_t ooblen; |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| |
| if (ops->oobbuf == NULL) |
| return; |
| |
| write_ooblen = ops->ooblen - ops->oobretlen; |
| ooblen = (write_ooblen < dev->oob_per_page ? write_ooblen : dev->oob_per_page); |
| |
| ops->oobretlen += ooblen; |
| } |
| |
| static int qpic_nand_write_oob(struct mtd_info *mtd, loff_t to, |
| struct mtd_oob_ops *ops) |
| |
| { |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| int i, ret = NANDC_RESULT_SUCCESS; |
| struct nand_chip *chip = MTD_NAND_CHIP(mtd); |
| u_long start_page; |
| u_long num_pages; |
| enum nand_cfg_value cfg_mode; |
| |
| /* We don't support MTD_OOB_PLACE as of yet. */ |
| if (ops->mode == MTD_OPS_PLACE_OOB) |
| return -ENOSYS; |
| |
| /* Check for writes past end of device. */ |
| if ((to + ops->len) > mtd->size) |
| return -EINVAL; |
| |
| if (to & (mtd->writesize - 1)) |
| return -EINVAL; |
| |
| if (ops->len & (mtd->writesize - 1)) |
| return -EINVAL; |
| |
| if (ops->ooboffs != 0) |
| return -EINVAL; |
| |
| if (ops->datbuf == NULL) |
| return -EINVAL; |
| |
| if(ops->mode == MTD_OPS_RAW) { |
| cfg_mode = NAND_CFG_RAW; |
| dev->oob_per_page = mtd->oobsize; |
| } |
| else { |
| cfg_mode = NAND_CFG; |
| dev->oob_per_page = mtd->oobavail; |
| } |
| |
| start_page = (to >> chip->page_shift); |
| num_pages = ((ops->len) >> chip->page_shift); |
| ops->retlen = 0; |
| ops->oobretlen = 0; |
| |
| for (i = 0; i < (int)num_pages; i++) { |
| struct mtd_oob_ops page_ops; |
| |
| page_ops.mode = ops->mode; |
| page_ops.len = mtd->writesize; |
| page_ops.ooblen = dev->oob_per_page; |
| page_ops.datbuf = qpic_nand_write_datbuf(mtd,ops); |
| page_ops.oobbuf = qpic_nand_write_oobbuf(mtd, ops); |
| page_ops.retlen = 0; |
| page_ops.oobretlen = 0; |
| |
| ret = qpic_nand_write_page(mtd, start_page + i, |
| cfg_mode, &page_ops); |
| if (ret) { |
| printf("flash_write: write failure @ page %ld, block %ld\n", |
| start_page + i, |
| (start_page + i) / (dev->num_pages_per_blk)); |
| goto out; |
| } else { |
| qpic_nand_write_datinc(mtd, ops); |
| qpic_nand_write_oobinc(mtd, ops); |
| } |
| } |
| out: |
| return ret; |
| } |
| |
| /** |
| * qpic_nand_write() - read data |
| * @start_page: number of page to begin writing to |
| * @num_pages: number of pages to write |
| * @buffer: buffer to be written |
| * @write_extra_bytes: true if spare data (ox 0xff) to be written |
| * |
| * This function writes @num_pages starting from @start_page. Note that it's |
| * in the caller responsibility to make sure the written pages are all from |
| * same partition. |
| * |
| */ |
| static int qpic_nand_write(struct mtd_info *mtd, loff_t to, size_t len, |
| size_t *retlen, const u_char *buf) |
| { |
| int ret = NANDC_RESULT_SUCCESS; |
| struct mtd_oob_ops ops; |
| |
| if (!buf) { |
| printf("qpic_nand_write: buffer = null\n"); |
| return NANDC_RESULT_PARAM_INVALID; |
| } |
| |
| ops.mode = MTD_OPS_AUTO_OOB; |
| ops.len = len; |
| ops.retlen = 0; |
| ops.ooblen = 0; |
| ops.oobretlen = 0; |
| ops.ooboffs = 0; |
| ops.datbuf = (uint8_t *)buf; |
| ops.oobbuf = NULL; |
| |
| ret = qpic_nand_write_oob(mtd, to, &ops); |
| *retlen = ops.retlen; |
| |
| return ret; |
| } |
| |
| /* Function to erase a block on the nand. |
| * page: Starting page address for the block. |
| */ |
| nand_result_t qpic_nand_blk_erase(struct mtd_info *mtd, uint32_t page) |
| { |
| struct cfg_params cfg; |
| struct cmd_element *cmd_list_ptr = ce_array; |
| struct cmd_element *cmd_list_read_ptr = ce_read_array; |
| struct cmd_element *cmd_list_ptr_start = ce_array; |
| struct cmd_element *cmd_list_read_ptr_start = ce_read_array; |
| uint32_t status; |
| int num_desc = 0; |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| uint32_t blk_addr = page / (dev->num_pages_per_blk); |
| struct nand_chip *chip = MTD_NAND_CHIP(mtd); |
| |
| /* Fill in params for the erase flash cmd */ |
| cfg.addr0 = page; |
| cfg.addr1 = 0; |
| /* Clear CW_PER_PAGE in cfg0 */ |
| cfg.cfg0 = dev->cfg0 & ~(7U << NAND_DEV0_CFG0_CW_PER_PAGE_SHIFT); |
| cfg.cfg1 = dev->cfg1; |
| |
| cfg.cmd = NAND_CMD_BLOCK_ERASE; |
| #ifdef CONFIG_QPIC_SERIAL |
| /* For serial NAND devices the block erase sequence is |
| * Issue 06H (WRITE ENBALE command) |
| * Issue D8H (BLOCK ERASE command) |
| * Issue 0FH (GET FEATURES command to read the status register) |
| * But we have already mapped write enable command in register |
| * QPIC_FLASH_DEV_CMD7 so here no need to send this command manually |
| * once we will send block erase command then controller internally |
| * send write enable command |
| * similar for Get feature command, no neeed to send this command |
| * also manually controller will take care. |
| * |
| * NOTE: Initially we are disabling block protection, so no need |
| * to do it again here. |
| */ |
| cfg.addr0 = page << 16; |
| cfg.addr1 = (page >> 16) & 0xffff; |
| cfg.cmd = 0xA; |
| cfg.cmd |= (QPIC_SPI_WP_SET | QPIC_SPI_HOLD_SET | |
| QPIC_SPI_TRANSFER_MODE_X1); |
| #endif |
| cfg.exec = 1; |
| cmd_list_ptr = qpic_nand_add_cmd_ce(&cfg, cmd_list_ptr); |
| |
| /* Enqueue the desc for the above commands */ |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)cmd_list_ptr_start, |
| ((uint32_t)cmd_list_ptr - (uint32_t)cmd_list_ptr_start), |
| BAM_DESC_NWD_FLAG | BAM_DESC_CMD_FLAG | BAM_DESC_INT_FLAG | |
| BAM_DESC_LOCK_FLAG); |
| |
| cmd_list_ptr_start = cmd_list_ptr; |
| num_desc++; |
| |
| qpic_nand_wait_for_cmd_exec(num_desc); |
| |
| status = qpic_nand_read_reg(NAND_FLASH_STATUS, 0); |
| |
| cmd_list_ptr_start = cmd_list_ptr; |
| cmd_list_read_ptr_start = cmd_list_read_ptr; |
| |
| /* QPIC controller automatically sends |
| * GET_STATUS cmd to the nand card because |
| * of the configuration programmed. |
| * Read the result of GET_STATUS cmd. |
| */ |
| cmd_list_read_ptr = qpic_nand_add_read_ce(cmd_list_read_ptr, &status); |
| |
| /* Enqueue the desc for the NAND_FLASH_STATUS read command */ |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)cmd_list_read_ptr_start, |
| ((uint32_t)cmd_list_read_ptr - (uint32_t)cmd_list_read_ptr_start), |
| BAM_DESC_CMD_FLAG); |
| |
| cmd_list_ptr = qpic_nand_reset_status_ce(cmd_list_ptr, 1); |
| |
| /* Enqueue the desc for NAND_FLASH_STATUS and NAND_READ_STATUS write commands */ |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)cmd_list_ptr_start, |
| ((uint32_t)cmd_list_ptr - (uint32_t)cmd_list_ptr_start), |
| BAM_DESC_INT_FLAG | BAM_DESC_CMD_FLAG); |
| num_desc = 2; |
| qpic_nand_wait_for_cmd_exec(num_desc); |
| |
| #if !defined(CONFIG_SYS_DCACHE_OFF) |
| flush_dcache_range((unsigned long)&status, |
| (unsigned long)&status + sizeof(status)); |
| #endif |
| |
| status = qpic_nand_check_status(mtd, status); |
| |
| /* Dummy read to unlock pipe. */ |
| qpic_nand_read_reg(NAND_FLASH_STATUS, BAM_DESC_UNLOCK_FLAG); |
| |
| /* Check for status errors*/ |
| if (status) { |
| printf("NAND Erase error: Block address belongs to " |
| "bad block: %d\n", blk_addr); |
| qpic_nand_mark_badblock(mtd, (page << chip->page_shift)); |
| return status; |
| } |
| |
| /* Check for PROG_ERASE_OP_RESULT bit for the result of erase operation. */ |
| if (!(status & PROG_ERASE_OP_RESULT)) |
| return NANDC_RESULT_SUCCESS; |
| |
| qpic_nand_mark_badblock(mtd, (page << chip->page_shift)); |
| return status; |
| } |
| |
| |
| int |
| qpic_nand_erase(struct mtd_info *mtd, struct erase_info *instr) |
| { |
| int ret = 0, i; |
| |
| loff_t offs; |
| u_long blocks; |
| u_long start; |
| u_long pageno; |
| struct nand_chip *chip = MTD_NAND_CHIP(mtd); |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| |
| /* Check for erase past end of device. */ |
| if ((instr->addr + instr->len) > mtd->size) |
| return -EINVAL; |
| |
| if (instr->addr & (mtd->erasesize - 1)) |
| return -EINVAL; |
| |
| if (instr->len & (mtd->erasesize - 1)) |
| return -EINVAL; |
| |
| start = instr->addr >> chip->phys_erase_shift; |
| blocks = instr->len >> chip->phys_erase_shift; |
| |
| debug("number of blks to erase: %lu\n", blocks); |
| |
| for (i = start; i < (start + blocks); i++) { |
| offs = i << chip->phys_erase_shift; |
| pageno = offs >> chip->page_shift; |
| |
| /* Erase only if the block is not bad */ |
| if (!instr->scrub && qpic_nand_block_isbad(mtd, offs)) { |
| printf("NAND Erase error: Block address belongs to " |
| "bad block: %ld\n", (pageno / (dev->num_pages_per_blk))); |
| return -EIO; |
| } |
| ret = qpic_nand_blk_erase(mtd, pageno); |
| if (ret) { |
| instr->fail_addr = offs; |
| printf("Erase operation failed \n"); |
| } |
| } |
| return ret; |
| } |
| |
| void |
| qpic_nand_mtd_params(struct mtd_info *mtd) |
| { |
| struct nand_chip *chip = MTD_NAND_CHIP(mtd); |
| mtd->type = MTD_NANDFLASH; |
| mtd->flags = MTD_CAP_NANDFLASH; |
| |
| mtd->_erase = qpic_nand_erase; |
| #ifndef __UBOOT__ |
| mtd->_point = NULL; |
| mtd->_unpoint = NULL; |
| #endif |
| mtd->_read = qpic_nand_read; |
| mtd->_write = qpic_nand_write; |
| mtd->_read_oob = qpic_nand_read_oob; |
| mtd->_write_oob = qpic_nand_write_oob; |
| mtd->_lock = NULL; |
| mtd->_unlock = NULL; |
| mtd->_block_isbad = qpic_nand_block_isbad; |
| mtd->_block_markbad = qpic_nand_mark_badblock; |
| mtd->_sync = qpic_nand_sync; |
| |
| mtd->ecclayout = NULL; |
| mtd->bitflip_threshold = DIV_ROUND_UP(mtd->ecc_strength * 3, 4); |
| |
| chip->page_shift = ffs(mtd->writesize) - 1; |
| chip->phys_erase_shift = ffs(mtd->erasesize) - 1; |
| |
| /* One of the NAND layer functions that the command layer |
| * tries to access directly. |
| */ |
| chip->scan_bbt = qpic_nand_scan_bbt_nop; |
| } |
| |
| #ifdef CONFIG_QSPI_SERIAL_TRAINING |
| static void qpic_reg_write_bam(unsigned int reg, unsigned int val) |
| { |
| int num_desc = 0; |
| struct cmd_element *cmd_list_ptr = ce_array; |
| struct cmd_element *cmd_list_ptr_start = ce_array; |
| |
| bam_add_cmd_element(cmd_list_ptr, reg, |
| (uint32_t)val, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)cmd_list_ptr_start, |
| ((uint32_t)cmd_list_ptr - (uint32_t)cmd_list_ptr_start), |
| BAM_DESC_CMD_FLAG); |
| num_desc++; |
| |
| /* Notify BAM HW about the newly added descriptors */ |
| bam_sys_gen_event(&bam, CMD_PIPE_INDEX, num_desc); |
| } |
| static void qpic_set_phase(int phase) |
| { |
| int spi_flash_cfg_val = 0x0; |
| |
| int num_desc = 0; |
| struct cmd_element *cmd_list_ptr = ce_array; |
| struct cmd_element *cmd_list_ptr_start = ce_array; |
| |
| if (phase < 1 || phase > 7) { |
| printf("%s : wrong phase value\n", __func__); |
| return; |
| } |
| /* get the current value of NAND_FLASH_SPI_CFG register */ |
| spi_flash_cfg_val = readl(NAND_FLASH_SPI_CFG); |
| /* set SPI_LOAD_CLK_CNTR_INIT_EN bit */ |
| spi_flash_cfg_val |= SPI_LOAD_CLK_CNTR_INIT_EN; |
| |
| if ((readl(QPIC_NAND_CTRL) & BAM_MODE_EN)) { |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_SPI_CFG, |
| (uint32_t)spi_flash_cfg_val, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| spi_flash_cfg_val &= 0xf000ffff; |
| /* write the phase value for all the line */ |
| spi_flash_cfg_val |= ((phase << 16) | (phase << 19) | |
| (phase << 22) | (phase << 25)); |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_SPI_CFG, |
| (uint32_t)spi_flash_cfg_val, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| /* clear the SPI_LOAD_CLK_CNTR_INIT_EN bit to load the required |
| * phase value |
| */ |
| spi_flash_cfg_val &= ~SPI_LOAD_CLK_CNTR_INIT_EN; |
| |
| bam_add_cmd_element(cmd_list_ptr, NAND_FLASH_SPI_CFG, |
| (uint32_t)spi_flash_cfg_val, CE_WRITE_TYPE); |
| cmd_list_ptr++; |
| |
| bam_add_one_desc(&bam, |
| CMD_PIPE_INDEX, |
| (unsigned char*)cmd_list_ptr_start, |
| ((uint32_t)cmd_list_ptr - (uint32_t)cmd_list_ptr_start), |
| BAM_DESC_CMD_FLAG); |
| num_desc++; |
| |
| /* Notify BAM HW about the newly added descriptors */ |
| bam_sys_gen_event(&bam, CMD_PIPE_INDEX, num_desc); |
| } else { |
| writel(spi_flash_cfg_val, NAND_FLASH_SPI_CFG); |
| |
| spi_flash_cfg_val &= 0xf000ffff; |
| /* write the phase value for all the line */ |
| spi_flash_cfg_val |= ((phase << 16) | (phase << 19) | |
| (phase << 22) | (phase << 25)); |
| writel(spi_flash_cfg_val, NAND_FLASH_SPI_CFG); |
| |
| /* clear the SPI_LOAD_CLK_CNTR_INIT_EN bit to load the required |
| * phase value |
| */ |
| spi_flash_cfg_val &= ~SPI_LOAD_CLK_CNTR_INIT_EN; |
| writel(spi_flash_cfg_val, NAND_FLASH_SPI_CFG); |
| } |
| } |
| |
| static bool IsEven(int num) |
| { |
| return (!(num & 1)); |
| } |
| |
| static int qpic_find_most_appropriate_phase(u8 *phase_table, int phase_count) |
| { |
| int cnt = 0, i; |
| int phase = 0x0; |
| u8 phase_ranges[TOTAL_NUM_PHASE] = {'\0'}; |
| |
| /*currently we are considering continious 3 phase will |
| * pass and tke the middle one out of passed three phase. |
| * if all 7 phase passed so return middle phase i.e 4 |
| */ |
| phase_count -= 2; |
| for (i = 0; i < phase_count; i++) { |
| if ((phase_table[i] + 1 == phase_table[i + 1]) && |
| (phase_table[i + 1] + 1 == phase_table[i + 2])) { |
| phase_ranges[cnt++] = phase_table[i + 1]; |
| } |
| } |
| |
| /* filter out middle phase |
| * if cnt is odd then one middle phase |
| * if cnt is even then two middile phase |
| * so select lower one |
| */ |
| if (IsEven(cnt)) { |
| phase = phase_ranges[cnt/2 - 1]; |
| } else { |
| phase = phase_ranges[cnt/2]; |
| } |
| |
| return phase; |
| } |
| |
| static int qpic_execute_serial_training(struct mtd_info *mtd) |
| { |
| struct qpic_nand_dev *dev = MTD_QPIC_NAND_DEV(mtd); |
| struct nand_chip *chip = MTD_NAND_CHIP(mtd); |
| |
| unsigned int start, blk_cnt = 0; |
| unsigned int offset, pageno, curr_freq; |
| int i; |
| unsigned int io_macro_freq_tbl[] = {24000000, 100000000, 200000000, 320000000}; |
| |
| unsigned char *data_buff, trained_phase[TOTAL_NUM_PHASE] = {'\0'}; |
| int phase, phase_cnt, clk_src; |
| int training_seq_cnt = 4; |
| int index = 3, ret, phase_failed=0; |
| u32 start_blocks; |
| u32 size_blocks; |
| loff_t training_offset; |
| |
| ret = smem_getpart("0:TRAINING", &start_blocks, &size_blocks); |
| if (ret < 0) { |
| printf("Serial Training part offset not found.\n"); |
| return -EIO; |
| } |
| |
| training_offset = ((loff_t) mtd->erasesize * start_blocks); |
| |
| start = (training_offset >> chip->phys_erase_shift); |
| offset = (start << chip->phys_erase_shift); |
| pageno = (offset >> chip->page_shift); |
| |
| clk_src = GPLL0_CLK_SRC; |
| /* At 50Mhz frequency check the bad blocks, if training |
| * blocks is not bad then only start training else operate |
| * at 50Mhz with bypassing software serial traning. |
| */ |
| while (qpic_nand_block_isbad(mtd, offset)) { |
| /* block is bad skip this block and goto next |
| * block |
| */ |
| training_offset += mtd->erasesize; |
| start = (training_offset >> chip->phys_erase_shift); |
| offset = (start << chip->phys_erase_shift); |
| pageno = (offset >> chip->page_shift); |
| blk_cnt++; |
| if (blk_cnt == MAXIMUM_ALLOCATED_TRAINING_BLOCK) |
| break; |
| } |
| |
| if (blk_cnt == MAXIMUM_ALLOCATED_TRAINING_BLOCK) { |
| printf("All training blocks are bad skipping serial training\n"); |
| ret = -EIO; |
| goto err; |
| } |
| |
| ret = qpic_nand_blk_erase(mtd, pageno); |
| if (ret) { |
| printf("error in erasing training block @%x\n",offset); |
| ret = -EIO; |
| goto err; |
| } |
| |
| data_buff = (unsigned char *)malloc(mtd->writesize); |
| if (!data_buff) { |
| printf("Errorn in allocating memory.\n"); |
| ret = -ENOMEM; |
| goto err; |
| } |
| /* prepare clean buffer */ |
| memset(data_buff, 0xff, mtd->writesize); |
| for (i = 0; i < mtd->writesize; i += sizeof(training_block_64)) |
| memcpy(data_buff + i, training_block_64, sizeof(training_block_64)); |
| |
| /*write training data to flash */ |
| ret = NANDC_RESULT_SUCCESS; |
| struct mtd_oob_ops ops; |
| |
| /* write this dumy byte in spare area to avoid bam |
| * transaction error while writing. |
| */ |
| memset(dev->pad_oob, 0xFF, dev->oob_per_page); |
| |
| ops.mode = MTD_OPS_AUTO_OOB; |
| ops.len = mtd->writesize; |
| ops.retlen = 0; |
| ops.ooblen = dev->oob_per_page; |
| ops.oobretlen = 0; |
| ops.ooboffs = 0; |
| ops.datbuf = (uint8_t *)data_buff; |
| ops.oobbuf = (uint8_t *)dev->pad_oob; |
| |
| /* write should be only once */ |
| ret = qpic_nand_write_page(mtd, pageno, NAND_CFG, &ops); |
| if (ret) { |
| printf("Error in writing training data..\n"); |
| goto free; |
| } |
| /* After write verify the the data with read @ lower frequency |
| * after that only start serial tarining @ higher frequency |
| */ |
| memset(data_buff, 0xff, mtd->writesize); |
| ops.datbuf = (uint8_t *)data_buff; |
| |
| ret = qpic_nand_read_page(mtd, pageno, NAND_CFG, &ops); |
| if (ret) { |
| printf("%s : Read training data failed before training start\n",__func__); |
| goto free; |
| } |
| |
| /* compare original data and read data */ |
| for (i = 0; i < mtd->writesize; i += sizeof(training_block_64)) { |
| if (memcmp(data_buff + i, training_block_64, sizeof(training_block_64))) { |
| printf("Training data read failed @ lower frequency\n"); |
| goto free; |
| } |
| } |
| |
| /* disable feed back clock bit to start serial training */ |
| qpic_reg_write_bam(NAND_QSPI_MSTR_CONFIG, |
| (~FB_CLK_BIT & readl(NAND_QSPI_MSTR_CONFIG))); |
| |
| /* start serial training here */ |
| curr_freq = io_macro_freq_tbl[index]; |
| rettry: |
| phase = 1; |
| phase_cnt = 0; |
| |
| /* set frequency, start from higer frequency */ |
| if (curr_freq == IO_MACRO_CLK_24MHZ) |
| clk_src = XO_CLK_SRC; |
| qpic_set_clk_rate(curr_freq, QPIC_IO_MACRO_CLK, clk_src); |
| |
| do { |
| /* set the phase */ |
| qpic_set_phase(phase); |
| |
| memset(data_buff, 0, mtd->writesize); |
| ops.datbuf = (uint8_t *)data_buff; |
| |
| ret = qpic_nand_read_page(mtd, pageno, NAND_CFG, &ops); |
| if (ret) { |
| printf("%s : Read training data failed.\n",__func__); |
| goto free; |
| } |
| /* compare original data and read data */ |
| for (i = 0; i < mtd->writesize; i += sizeof(training_block_64)) { |
| if (memcmp(data_buff + i, training_block_64, sizeof(training_block_64))) { |
| phase_failed++; |
| break; |
| } |
| } |
| if (i == mtd->writesize) |
| trained_phase[phase_cnt++] = phase; |
| /*printf("%s : Found good phase %d\n",__func__,phase);*/ |
| |
| } while (phase++ < TOTAL_NUM_PHASE); |
| |
| if (phase_cnt) { |
| /* Get the appropriate phase */ |
| phase = qpic_find_most_appropriate_phase(trained_phase, phase_cnt); |
| qpic_set_phase(phase); |
| |
| /* update freq & phase to patch to the kernel */ |
| qpic_frequency = curr_freq; |
| qpic_phase = phase; |
| } else { |
| /* lower the the clock frequency |
| * and try again |
| */ |
| curr_freq = io_macro_freq_tbl[--index]; |
| printf("Retry with lower frequency @:%d\n",curr_freq); |
| if (--training_seq_cnt) |
| goto rettry; |
| |
| /* Training failed */ |
| printf("%s : Serial training failed\n",__func__); |
| ret = -EIO; |
| goto free; |
| } |
| |
| /* if phase_failed == 7 it means serial traing failed |
| * on all the phase. so now we have to go via line by line |
| * i.e first check for MISO_0, with all the phase value i.e |
| * 1-7 and then MISO_1 and so on. |
| * NOTE: But this is the worse case , and it this type of senario |
| * will not come. if it will come then go with this design. |
| * ======To DO===== |
| */ |
| free: |
| free(data_buff); |
| err: |
| return ret; |
| } |
| #endif |
| |
| static struct nand_chip nand_chip[CONFIG_SYS_MAX_NAND_DEVICE]; |
| |
| void qpic_nand_init(qpic_nand_cfg_t *qpic_nand_cfg) |
| { |
| struct mtd_info *mtd; |
| const struct udevice_id *of_match = qpic_ver_ids; |
| struct nand_chip *chip; |
| int ret = 0; |
| struct qpic_nand_dev *dev; |
| size_t alloc_size; |
| unsigned char *buf; |
| struct qpic_nand_init_config config; |
| fdt_addr_t nand_base; |
| int i; |
| |
| while (of_match->compatible) { |
| ret = fdt_node_offset_by_compatible(gd->fdt_blob, 0, |
| of_match->compatible); |
| if (ret < 0) { |
| of_match++; |
| continue; |
| } |
| hw_ver = of_match->data; |
| break; |
| } |
| mtd = &nand_info[CONFIG_QPIC_NAND_NAND_INFO_IDX]; |
| mtd->priv = &nand_chip[0]; |
| |
| chip = mtd->priv; |
| chip->priv = &qpic_nand_dev; |
| |
| if (ret < 0) { |
| printf("Could not find nand-flash in device tree\n"); |
| return; |
| } |
| |
| nand_base = fdtdec_get_addr(gd->fdt_blob, ret, "reg"); |
| |
| if (nand_base == FDT_ADDR_T_NONE) { |
| printf("No valid NAND base address found in device tree\n"); |
| return; |
| } |
| |
| ret = fdt_subnode_offset(gd->fdt_blob, ret, "nand_gpio"); |
| |
| if (ret >= 0) { |
| qca_gpio_init(ret); |
| } else if (qpic_nand_cfg) { |
| printf("Could not find nand_gpio in dts, using defaults\n"); |
| for (i = 0; i < qpic_nand_cfg->gpio_count; i++) |
| gpio_tlmm_config(&qpic_nand_cfg->gpio[i]); |
| } else { |
| printf("Could not find nand_gpio in dts, no defaults defined\n"); |
| return; |
| } |
| |
| #ifdef CONFIG_PAGE_SCOPE_MULTI_PAGE_READ |
| config.pipes.status_pipe = NAND_BAM_STATUS_PIPE; |
| config.pipes.status_pipe_grp = NAND_BAM_STATUS_PIPE_GRP; |
| #endif |
| config.pipes.read_pipe = DATA_PRODUCER_PIPE; |
| config.pipes.write_pipe = DATA_CONSUMER_PIPE; |
| config.pipes.cmd_pipe = CMD_PIPE; |
| |
| config.pipes.read_pipe_grp = DATA_PRODUCER_PIPE_GRP; |
| config.pipes.write_pipe_grp = DATA_CONSUMER_PIPE_GRP; |
| config.pipes.cmd_pipe_grp = CMD_PIPE_GRP; |
| |
| config.bam_base = QPIC_BAM_CTRL_BASE; |
| config.nand_base = nand_base; |
| config.ee = QPIC_NAND_EE; |
| config.max_desc_len = QPIC_NAND_MAX_DESC_LEN; |
| |
| qpic_bam_init(&config); |
| |
| #ifdef CONFIG_QPIC_SERIAL |
| qpic_spi_init(mtd); |
| |
| /* Read the Hardware Version register */ |
| hw_ver = readl(NAND_VERSION); |
| /* Only maintain major number */ |
| hw_ver >>= 28; |
| if (hw_ver >= QCA_QPIC_V2_1_1) { |
| printf("QPIC controller support serial NAND\n"); |
| } else { |
| printf("%s : Qpic controller not support serial NAND\n", |
| __func__); |
| return; |
| } |
| #endif |
| ret = qpic_nand_reset(mtd); |
| if (ret < 0) |
| return; |
| |
| ret = qpic_nand_onfi_probe(mtd); |
| if (ret < 0) |
| return; |
| else if (ret > 0) |
| qpic_nand_non_onfi_probe(mtd); |
| |
| #ifndef CONFIG_QPIC_SERIAL |
| qpic_config_timing_parameters(mtd); |
| #endif |
| /* Save the RAW and read/write configs */ |
| ret = qpic_nand_save_config(mtd); |
| if (ret < 0) |
| return; |
| |
| #ifdef CONFIG_QPIC_SERIAL |
| /* Check all blocks of serial NAND device is unlocked or |
| * not if not then unlock the all the blocks of serial NAND |
| * device also check the internal ecc is enabled or not if internal |
| * ecc is enabled then disable internal ecc using get/set feature |
| * command. |
| */ |
| ret = qpic_spi_nand_config(mtd); |
| if (ret < 0) { |
| printf("%s : Issue with Serial Nand configuration.\n",__func__); |
| return; |
| } |
| #endif |
| dev = MTD_QPIC_NAND_DEV(mtd); |
| qpic_nand_mtd_params(mtd); |
| |
| #ifdef CONFIG_PAGE_SCOPE_MULTI_PAGE_READ |
| /* allocate memory for status buffer. we are doing |
| * this here because we do not know the device page |
| * siz ein advance if nand flash is parallel nand and ONFI |
| * complaint. so status buffer size will vary based on page size |
| * e.g if page size is 2KiB then status buffer size for one page |
| * will be 48-bytes similary for 4KiB page , status buffer size |
| * will be 96-bytes for one page and so on. |
| * QPIC controller support max page isze is 8 KiB now so maximum |
| * status buffer size for one page will be 192-bytes. for multi page |
| * read the status buffer size will be multiple of maximum pages supported |
| * in multipage. |
| */ |
| ret = qpic_alloc_status_buff(dev, mtd); |
| if (ret) { |
| printf("Error in allocating status buffer\n"); |
| return; |
| } |
| #endif |
| |
| /* |
| * allocate buffer for dev->pad_dat, dev->pad_oob, dev->zero_page, |
| * dev->zero_oob, dev->tmp_datbuf, dev->tmp_oobbuf |
| * |
| */ |
| #ifdef CONFIG_PAGE_SCOPE_MULTI_PAGE_READ |
| alloc_size = 3 * (mtd->writesize + (mtd->oobsize * MAX_MULTI_PAGE)); |
| #else |
| alloc_size = 3 * (mtd->writesize + mtd->oobsize); |
| #endif |
| |
| dev->buffers = malloc(alloc_size); |
| if (dev->buffers == NULL) { |
| printf("qpic_nand: failed to allocate memory\n"); |
| ret = -ENOMEM; |
| goto err_buf; |
| } |
| buf = dev->buffers; |
| |
| dev->pad_dat = buf; |
| buf += mtd->writesize; |
| dev->pad_oob = buf; |
| #ifdef CONFIG_PAGE_SCOPE_MULTI_PAGE_READ |
| buf += mtd->oobsize * MAX_MULTI_PAGE; |
| #else |
| buf += mtd->oobsize; |
| #endif |
| |
| dev->zero_page = buf; |
| buf += mtd->writesize; |
| |
| dev->zero_oob = buf; |
| buf += mtd->oobsize; |
| |
| memset(dev->zero_page, 0x0, mtd->writesize); |
| memset(dev->zero_oob, 0x0, mtd->oobsize); |
| |
| dev->tmp_datbuf = buf; |
| buf += mtd->writesize; |
| dev->tmp_oobbuf = buf; |
| buf += mtd->oobsize; |
| |
| #ifdef CONFIG_QSPI_SERIAL_TRAINING |
| /* start serial training here */ |
| ret = qpic_execute_serial_training(mtd); |
| if (ret) { |
| printf("Error in serial training.\n"); |
| printf("switch back to 50MHz with feed back clock bit enabled\n"); |
| if ((readl(QPIC_NAND_CTRL) & BAM_MODE_EN)) { |
| qpic_reg_write_bam(NAND_QSPI_MSTR_CONFIG, |
| (FB_CLK_BIT | readl(NAND_QSPI_MSTR_CONFIG))); |
| qpic_set_clk_rate(IO_MACRO_CLK_200_MHZ, QPIC_IO_MACRO_CLK, |
| GPLL0_CLK_SRC); |
| qpic_reg_write_bam(NAND_FLASH_SPI_CFG, 0x0); |
| qpic_reg_write_bam(NAND_FLASH_SPI_CFG, SPI_CFG_VAL); |
| qpic_reg_write_bam(NAND_FLASH_SPI_CFG, |
| (SPI_CFG_VAL & ~SPI_LOAD_CLK_CNTR_INIT_EN)); |
| |
| } else { |
| writel((FB_CLK_BIT | readl(NAND_QSPI_MSTR_CONFIG)), |
| NAND_QSPI_MSTR_CONFIG); |
| |
| qpic_set_clk_rate(IO_MACRO_CLK_200_MHZ, QPIC_IO_MACRO_CLK, |
| GPLL0_CLK_SRC); |
| |
| writel(0x0, NAND_FLASH_SPI_CFG); |
| writel(SPI_CFG_VAL, NAND_FLASH_SPI_CFG); |
| writel((SPI_CFG_VAL & ~SPI_LOAD_CLK_CNTR_INIT_EN), |
| NAND_FLASH_SPI_CFG); |
| } |
| } |
| #endif |
| /* Register with MTD subsystem. */ |
| ret = nand_register(CONFIG_QPIC_NAND_NAND_INFO_IDX); |
| if (ret < 0) { |
| printf("qpic_nand: failed to register with MTD subsystem\n"); |
| ret = NANDC_RESULT_FAILURE; |
| goto err_reg; |
| } |
| if (ret == 0) |
| return; |
| err_reg: |
| free(dev->buffers); |
| err_buf: |
| return; |
| } |
| |
| #ifdef READ_ONFI_PAGE_PARA |
| void Read_onfi_ParameterPage_DataStructure(unsigned char *ParPage, int size) |
| { |
| unsigned int i, j; |
| unsigned int com_var = 0x00000000; |
| |
| /* extract ONFI page signature */ |
| com_var |= ParPage[i]; |
| com_var <<= 8; |
| com_var |= ParPage[i+1]; |
| com_var <<= 8; |
| com_var |= ParPage[i+2]; |
| com_var <<= 8; |
| com_var |= ParPage[i+3]; |
| printf("ONFI \'O'\, \'N'\, \'F'\, \'I'\ parameter page signature: 0x%08x\n",com_var); |
| |
| /* Extract ONFI version number */ |
| i += 4; |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| /* swap bytes*/ |
| com_var = ((com_var << 8) & (0xff00)) | ((com_var >> 8) & (0x00ff)); |
| if (com_var & (1 << 1)) |
| printf("Revision number : 0x%04x and ONFI 1.0 complaint\n", com_var); |
| else if (com_var & (1 << 2)) |
| printf("Revision number : 0x%04x and ONFI 2.0 complaint\n", com_var); |
| else if (com_var & (1 << 3)) |
| printf("Revision number : 0x%04x and ONFI 2.1 complaint\n", com_var); |
| else if (com_var & (1 << 4)) |
| printf("Revision number : 0x%04x and ONFI 2.2 complaint\n", com_var); |
| else if (com_var & (1 << 5)) |
| printf("Revision number : 0x%04x and ONFI 2.3 complaint\n", com_var); |
| else |
| printf("Unsupported ONFI version:0x%04x\n",com_var); |
| |
| /* extract Features supported by this card */ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| /*swap bytes */ |
| com_var = ((com_var << 8) & (0xff00)) | ((com_var >> 8) & (0x00ff)); |
| printf("Features supported : 0x%04x\n",com_var); |
| if (com_var & (1 << 0)) |
| printf(" supports 16-bit data bus width.\n"); |
| if (com_var & (1 << 1)) |
| printf(" supports multiple LUN operations.\n"); |
| if (com_var & (1 << 2)) |
| printf(" supports non-sequential page programming.\n"); |
| if (com_var & (1 << 3)) |
| printf(" supports interleaved operations.\n"); |
| if (com_var & (1 << 4)) |
| printf(" supports odd to even page Copyback.\n"); |
| |
| /* Extarct Optional command supported */ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| /*swap bytes */ |
| com_var = ((com_var << 8) & (0xff00)) | ((com_var >> 8) & (0x00ff)); |
| printf("Optional command supported:0x%04x\n",com_var); |
| if (com_var & (1 << 0)) |
| printf(" supports Page Cache Program command.\n"); |
| if (com_var & (1 << 1)) |
| printf(" supports Read Cache commands.\n"); |
| if (com_var & (1 << 2)) |
| printf(" supports Get Features and Set Features.\n"); |
| if (com_var & (1 << 3)) |
| printf(" supports Read Status Enhanced.\n"); |
| if (com_var & (1 << 4)) |
| printf(" supports Copyback.\n"); |
| if (com_var & (1 << 5)) |
| printf(" supports Read Unique ID.\n"); |
| |
| /* Bytes 10-31 reserved */ |
| i += 22; |
| /* extract Manufacturer information block */ |
| /* Extact Device manufacturer |
| * 6 space character 0x20 */ |
| unsigned char dev_manf[22] = {'\0'}; |
| memcpy(dev_manf, ParPage + i, 12); |
| printf("Device manufacturer:"); |
| for(j = 0; j < 12; j++) |
| printf("%c",dev_manf[j]); |
| printf("\n"); |
| |
| i += 12; |
| |
| /* extract device model */ |
| memset(dev_manf, '\0', 22); |
| memcpy(dev_manf, ParPage + i, 20); |
| printf("Device model:"); |
| for (j = 0; j < 20; j++) |
| printf("%c",dev_manf[j]); |
| printf("\n"); |
| i += 20; |
| |
| /* Extract JEDEC manufacturer ID */ |
| com_var = 0x0; |
| com_var = ParPage[i++]; |
| printf("JEDEC manufacturer ID:0x%02x\n",com_var); |
| |
| /* Extract Date code */ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| printf("Date code:0x%04x\n",com_var); |
| |
| /*67-79 bytes are reserved */ |
| i += 13; |
| |
| /* Extract memory organization block */ |
| /* Extract number of data bytes per page */ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| /* reverse byte on int |
| * 0x00 08 00 00 |
| * 0x00 00 08 00*/ |
| com_var = ((com_var >> 24) & 0xff) | ((com_var << 8) & 0xff0000) | |
| ((com_var >> 8) & 0xff00) | ((com_var << 24) & 0xff000000); |
| printf("Number of data bytes per page:0x%08x, %d bytes, %dKiB\n",com_var,com_var,com_var/1024); |
| |
| /* Extract Number of spare bytes per page */ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| /* swap bytes */ |
| com_var = ((com_var << 8) & (0xff00)) | ((com_var >> 8) & (0x00ff)); |
| printf("Number of spare bytes per page:0x%04x, %d bytes\n", com_var,com_var); |
| |
| /* extract Number of data bytes per partial page */ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| /*reverse bytes */ |
| com_var = ((com_var >> 24) & 0xff) | ((com_var << 8) & 0xff0000) | |
| ((com_var >> 8) & 0xff00) | ((com_var << 24) & 0xff000000); |
| printf("Number of data bytes per partial page:0x%08x, %d bytes\n",com_var,com_var); |
| |
| /* extarct Number of spare bytes per partial page*/ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| /* swap bytes */ |
| com_var = ((com_var << 8) & (0xff00)) | ((com_var >> 8) & (0x00ff)); |
| printf("Number of spare bytes per partial page:0x%02x, %d bytes\n",com_var,com_var); |
| |
| /* Extract Number of pages per block */ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| /*reverse bytes */ |
| com_var = ((com_var >> 24) & 0xff) | ((com_var << 8) & 0xff0000) | |
| ((com_var >> 8) & 0xff00) | ((com_var << 24) & 0xff000000); |
| printf("Number of pages per block:0x%08x, %d bytes\n",com_var,com_var); |
| |
| /* Extract Number of blocks per unit */ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| /*reverse bytes */ |
| com_var = ((com_var >> 24) & 0xff) | ((com_var << 8) & 0xff0000) | |
| ((com_var >> 8) & 0xff00) | ((com_var << 24) & 0xff000000); |
| printf("Number of blocks per unit:0x%08x, %d bytes\n",com_var,com_var); |
| |
| /* Extarct Number of logical units*/ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| printf("Number of logical units:0x%02x\n",com_var); |
| |
| /* Extarct Number of address cycles */ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| printf("Number of address cycles:0x%02x\n",com_var); |
| printf(" Row address cycles:%d\n",(com_var & 0x0000000f)); |
| printf(" Column address cycles:%d\n",(com_var >> 4) & 0x0000000f); |
| /* Extarct Number of bits per cell*/ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| printf("Number of bits per cell:0x%02x\n",com_var); |
| |
| /* Extract Bad blocks maximum per unit*/ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| com_var = ((com_var << 8) & (0xff00)) | ((com_var >> 8) & (0x00ff)); |
| printf("Bad blocks maximum per unit:0x%04x, %d\n",com_var,com_var); |
| |
| /* Extract Block endurance */ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| com_var = ((com_var << 8) & (0xff00)) | ((com_var >> 8) & (0x00ff)); |
| printf("Block endurance:0x%04x\n",com_var); |
| |
| /* Extract Guaranteed valid blocks at beginning of target*/ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| printf("Guaranteed valid blocks at beginning of target:0x%02x\n",com_var); |
| |
| /* Extract Block endurance for guaranteed valid blocks*/ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| com_var = ((com_var << 8) & (0xff00)) | ((com_var >> 8) & (0x00ff)); |
| printf("Block endurance for guaranteed valid blocks:0x%04x\n",com_var); |
| |
| /*Extract Number of programs per page */ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| printf("Number of programs per page:0x%02x\n",com_var); |
| |
| /*Extract Partial programming attributes*/ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| printf("Partial programming attributes:0x%02x\n",com_var); |
| if (com_var & (1 << 0)) |
| printf(" partial page programming has constraints.\n"); |
| if (com_var & (1 << 4)) |
| printf(" partial page layout is partial page data followed by partial page spare.\n"); |
| |
| /* Extract Number of bits ECC correctability*/ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| printf("Number of bits ECC correctability:0x%02x\n",com_var); |
| |
| /* Extract Number of interleaved address bits*/ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| printf("Number of interleaved address bits:0x%02x\n",com_var); |
| |
| /* Extract Interleaved operation attributes*/ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| printf("Interleaved operation attributes:0x%02x\n",com_var); |
| if (com_var & (1 << 1)) |
| printf(" no block address restrictions.\n"); |
| if (com_var & (1 << 2)) |
| printf(" program cache supported.\n"); |
| |
| /* 115-127 reserved */ |
| i += 13; |
| /* Extract Electrical parameter block*/ |
| /*Extract I/O pin capacitance*/ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| printf("I/O pin capacitance:0x%02x\n",com_var); |
| |
| /*Extract Timing mode support*/ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| com_var = ((com_var << 8) & (0xff00)) | ((com_var >> 8) & (0x00ff)); |
| printf("Timing mode support:0x%04x\n",com_var); |
| if (com_var & (1 << 0)) |
| printf(" supports timing mode 0, shall be 1\n"); |
| if (com_var & (1 << 1)) |
| printf(" supports timing mode 1\n"); |
| if (com_var & (1 << 2)) |
| printf(" supports timing mode 2\n"); |
| if (com_var & (1 << 3)) |
| printf(" supports timing mode 3\n"); |
| if (com_var & (1 << 4)) |
| printf(" supports timing mode 4\n"); |
| if (com_var & (1 << 5)) |
| printf(" supports timing mode 5\n"); |
| |
| /*Extarct Program cache timing mode support*/ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| com_var = ((com_var << 8) & (0xff00)) | ((com_var >> 8) & (0x00ff)); |
| printf("Program cache timing mode support:0x%04x\n",com_var); |
| if (com_var & (1 << 0)) |
| printf(" supports timing mode 0\n"); |
| if (com_var & (1 << 1)) |
| printf(" supports timing mode 1\n"); |
| if (com_var & (1 << 2)) |
| printf(" supports timing mode 2\n"); |
| if (com_var & (1 << 3)) |
| printf(" supports timing mode 3\n"); |
| if (com_var & (1 << 4)) |
| printf(" supports timing mode 4\n"); |
| if (com_var & (1 << 5)) |
| printf(" supports timing mode 5\n"); |
| |
| /*Extract tPROG (MAX) page program time*/ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| com_var = ((com_var << 8) & (0xff00)) | ((com_var >> 8) & (0x00ff)); |
| printf("tPROG Maximum page program time:0x%04x, %d us\n",com_var,com_var); |
| |
| /*Extract tBERS Maximum block erase time*/ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| com_var = ((com_var << 8) & (0xff00)) | ((com_var >> 8) & (0x00ff)); |
| printf("tBERS Maximum block erase time:0x%04x, %d us\n",com_var,com_var); |
| |
| /* Extract tR Maximum page read time*/ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| com_var = ((com_var << 8) & (0xff00)) | ((com_var >> 8) & (0x00ff)); |
| printf("tR Maximum page read time:0x%04x, %d us\n",com_var,com_var); |
| |
| /* Extract tCCS Minimum change column setup time*/ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| com_var = ((com_var << 8) & (0xff00)) | ((com_var >> 8) & (0x00ff)); |
| printf("tCCS Minimum change column setup time:0x%04x, %d ns\n",com_var,com_var); |
| |
| /* 141-163 reserved */ |
| i += 23; |
| |
| /* Extract Vendor block */ |
| /* Extract Vendor specific Revision number*/ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| com_var = ((com_var << 8) & (0xff00)) | ((com_var >> 8) & (0x00ff)); |
| printf("Vendor specific Revision number:0x%04x\n",com_var); |
| |
| /* Extract Vendor specific |
| * 166 - 253 bytes |
| */ |
| printf("Bytes 166-253 are Vendor specific.\n"); |
| i += 88; |
| |
| /* Extract Integrity CRC */ |
| com_var = 0x0; |
| com_var |= ParPage[i++]; |
| com_var <<= 8; |
| com_var |= ParPage[i++]; |
| com_var = ((com_var << 8) & (0xff00)) | ((com_var >> 8) & (0x00ff)); |
| printf("Integrity CRC:0x%04x\n",com_var); |
| |
| /*256-511 bytes are vale of bytes 0-255 |
| *512-767 bytes are value of bytes 0-255*/ |
| printf("256-511 bytes are value of bytes 0-255\n"); |
| printf("512-767 bytes are value of bytes 0-255\n"); |
| } |
| #endif |