| /***************************************************************** |
| ** |
| ** Copyright (C) 2012 Amlogic,Inc. All rights reserved |
| ** |
| ** Filename : phydev.c |
| ** Revision : 1.001 |
| ** Author: Benjamin Zhao |
| ** Description: |
| ** 1) Phydev basic operation based on phydev |
| ** contains read/write/erase/block_isbad/block_markbad. |
| ** 2) Get/release chip function ensure only one entry to nand chip; |
| ** |
| *****************************************************************/ |
| #include "../include/phynand.h" |
| |
| #ifdef AML_NAND_UBOOT |
| //extern struct amlnf_partition amlnf_partitions; |
| extern struct amlnf_partition * amlnf_partitions; |
| #endif |
| |
| #define DBG_WRITE_VERIFY (0) |
| |
| #if (DBG_WRITE_VERIFY) |
| u8 * glb_verify_buffer = NULL; |
| #endif |
| |
| enum chip_state_t get_chip_state(struct amlnand_chip *aml_chip) |
| { |
| return aml_chip->state; |
| } |
| |
| |
| void set_chip_state(struct amlnand_chip *aml_chip, enum chip_state_t state) |
| { |
| aml_chip->state = state; |
| } |
| |
| static u32 amlnand_slc_addr_trs(struct amlnand_phydev *phydev) |
| { |
| struct amlnand_chip *aml_chip = (struct amlnand_chip *)phydev->priv; |
| struct nand_flash *flash = &(aml_chip->flash); |
| struct phydev_ops *devops = &(phydev->ops); |
| struct hw_controller *controller = &(aml_chip->controller); |
| struct en_slc_info *slc_info = &(controller->slc_info); |
| int real_page_per_blk, page_per_blk, blk_num, blk_num_in_dev, page_num; |
| /* u64 addr, readlen = 0; */ |
| u32 page_addr = 0; |
| u32 real_erase_size, real_erase_shift, w_size_shift, e_size_shift; |
| int total_pages_in_dev; |
| |
| real_erase_size = (phydev->erasesize << 1); |
| real_erase_shift = ffs(real_erase_size) - 1; |
| w_size_shift = phydev->writesize_shift; |
| e_size_shift = phydev->erasesize_shift; |
| |
| real_page_per_blk = (1 << ((ffs(real_erase_size) - 1) - w_size_shift)); |
| page_per_blk = (1 << (e_size_shift - w_size_shift)); |
| |
| blk_num = phydev->offset >> real_erase_shift; |
| blk_num_in_dev = devops->addr >> e_size_shift; |
| blk_num += blk_num_in_dev; |
| |
| total_pages_in_dev = blk_num_in_dev * page_per_blk; |
| page_num = ((devops->addr >> w_size_shift) - total_pages_in_dev); |
| |
| if ((flash->new_type > 0) && (flash->new_type < 10)) |
| page_addr = blk_num * real_page_per_blk + |
| slc_info->pagelist[page_num]; |
| else if (flash->new_type == SANDISK_19NM) |
| page_addr = blk_num * real_page_per_blk + (page_num << 1); |
| else { /* not surpport slc nand */ |
| page_addr = |
| (int)((phydev->offset + devops->addr) >> w_size_shift); |
| aml_nand_msg("nand not surpport slc "); |
| } |
| |
| #if 0 |
| aml_nand_dbg(" devops->addr =%llx %d", devops->addr, devops->addr); |
| aml_nand_dbg("real_erase_size =%x", real_erase_size); |
| aml_nand_dbg("real_page_per_blk =%d", real_page_per_blk); |
| aml_nand_dbg("page_per_blk =%d", page_per_blk); |
| aml_nand_dbg("blk_num =%d", blk_num); |
| aml_nand_dbg("blk_num_in_dev =%d", blk_num_in_dev); |
| aml_nand_dbg("page_num =%d", page_num); |
| aml_nand_dbg("page_addr =%d", page_addr); |
| #endif |
| |
| return page_addr; |
| } |
| |
| /* |
| * fixme, pls use DBG_WRITE_VERIFY instead(20150918) before fix! yyh. |
| */ |
| static void nand_write_verify(struct amlnand_phydev *phydev) |
| { |
| struct amlnand_chip *aml_chip = (struct amlnand_chip *)phydev->priv; |
| struct phydev_ops *devops = &(phydev->ops); |
| /* struct hw_controller *controller = &(aml_chip->controller); */ |
| struct chip_operation *operation = &(aml_chip->operation); |
| struct chip_ops_para *ops_para = &(aml_chip->ops_para); |
| int ret = 0; |
| u8 *verify_buf = NULL; |
| |
| verify_buf = aml_nand_malloc(2 * phydev->writesize); |
| if (!verify_buf) { |
| aml_nand_msg("malloc failed for nand_read_verify"); |
| return; |
| } |
| |
| ops_para->data_buf = verify_buf; |
| ret = operation->read_page(aml_chip); |
| if ((ops_para->ecc_err) || (ret < 0)) |
| aml_nand_msg("nand phy read failed at devops->addr : %llx", |
| devops->addr); |
| |
| if (memcmp(verify_buf, devops->datbuf, phydev->writesize)) |
| aml_nand_msg("nand write verify failed"); |
| |
| kfree(verify_buf); |
| |
| return; |
| } |
| |
| /* |
| *read data case: |
| *only data read function, if none ecc mode, buf should be data+oob |
| * operation type as below: oob_mode data_buf oob_buf ooblen |
| *1) read oob hw ecc mode 0 NULL available available |
| *2) read data and oob hw ecc mode 0 available available available |
| *3) read data and oob sw ecc mode 1 available available 0 |
| */ |
| static int nand_read(struct amlnand_phydev *phydev) |
| { |
| struct amlnand_chip *aml_chip = (struct amlnand_chip *)phydev->priv; |
| struct phydev_ops *devops = &(phydev->ops); |
| struct hw_controller *controller = &(aml_chip->controller); |
| struct chip_operation *operation = &(aml_chip->operation); |
| struct chip_ops_para *ops_para = &(aml_chip->ops_para); |
| u64 addr, readlen = 0, len = 0; |
| int ret = 0; |
| |
| if ((devops->addr + devops->len) > phydev->size) { |
| aml_nand_msg("out of space and addr:"); |
| aml_nand_msg("%llx len:%llx pdev->offset:%llx pdev->size:%llx", |
| devops->addr, |
| devops->len, |
| phydev->offset, |
| phydev->size); |
| return -NAND_ARGUMENT_FAILURE; |
| } |
| |
| if ((devops->len == 0) && (devops->ooblen == 0)) { |
| aml_nand_msg("len equal zero here"); |
| return NAND_SUCCESS; |
| } |
| |
| amlnand_get_device(aml_chip, CHIP_READING); |
| |
| /* clear ops_para here */ |
| memset(ops_para, 0, sizeof(struct chip_ops_para)); |
| |
| if (devops->len == 0) { |
| len = phydev->writesize; |
| ops_para->ooblen = devops->ooblen; |
| } else { |
| len = devops->len; |
| ops_para->ooblen = devops->ooblen; |
| } |
| |
| addr = phydev->offset + devops->addr; |
| ops_para->data_buf = devops->datbuf; |
| ops_para->option = phydev->option; |
| ops_para->oob_buf = devops->oobbuf; |
| |
| /* aml_nand_dbg("len =%llx",len); */ |
| if (devops->mode == NAND_SOFT_ECC) |
| ops_para->option |= DEV_ECC_SOFT_MODE; |
| |
| while (1) { |
| if (ops_para->option & DEV_SERIAL_CHIP_MODE) { |
| ops_para->chipnr = |
| (addr >> phydev->erasesize) |
| % controller->chip_num; |
| controller->select_chip(controller, ops_para->chipnr); |
| aml_nand_dbg("ops_para->chipnr =%d", ops_para->chipnr); |
| aml_nand_dbg("DEV_SERIAL_CHIP_MODE"); |
| } |
| |
| if (ops_para->option & DEV_SLC_MODE) |
| ops_para->page_addr = amlnand_slc_addr_trs(phydev); |
| else |
| ops_para->page_addr = |
| (int)(addr >> phydev->writesize_shift); |
| |
| //printf("%s() page %x\n", __func__, ops_para->page_addr); |
| ret = operation->read_page(aml_chip); |
| if ((ops_para->ecc_err) || (ret < 0)) { |
| aml_nand_msg("phy read failed at devops->addr: %llx", |
| devops->addr); |
| break; |
| } |
| |
| addr += phydev->writesize; |
| |
| ops_para->data_buf += phydev->writesize; |
| readlen += phydev->writesize; |
| |
| /* aml_nand_dbg("readlen =%llx",readlen); */ |
| if (readlen >= len) |
| break; |
| } |
| |
| devops->retlen = readlen; |
| |
| amlnand_release_device(aml_chip); |
| |
| if (!ret) { |
| if (ops_para->ecc_err) |
| ret = NAND_ECC_FAILURE; |
| else if (ops_para->bit_flip) |
| ret = -EUCLEAN; /* 117 */ |
| } |
| |
| return ret; |
| } |
| |
| #if (DBG_WRITE_VERIFY) |
| static int _nand_write(struct amlnand_phydev *phydev) |
| #else |
| static int nand_write(struct amlnand_phydev *phydev) |
| #endif |
| { |
| struct amlnand_chip *aml_chip = (struct amlnand_chip *)phydev->priv; |
| struct phydev_ops *devops = &(phydev->ops); |
| struct hw_controller *controller = &(aml_chip->controller); |
| struct chip_operation *operation = &(aml_chip->operation); |
| struct chip_ops_para *ops_para = &(aml_chip->ops_para); |
| u64 addr, writelen = 0, len = 0; |
| int ret = 0; |
| |
| PHYDEV_LINE |
| if ((devops->addr + devops->len) > phydev->size) { |
| aml_nand_msg("out of space and addr:"); |
| aml_nand_msg("%llx len:%llx pdev->offset:%llx pdev->size:%llx", |
| devops->addr, |
| devops->len, |
| phydev->offset, |
| phydev->size); |
| return -NAND_ARGUMENT_FAILURE; |
| } |
| |
| if ((devops->len == 0) && (devops->ooblen == 0)) { |
| aml_nand_msg("len equal zero here"); |
| return NAND_SUCCESS; |
| } |
| |
| #ifndef AML_NAND_UBOOT |
| if (phydev->option & NAND_SHUT_DOWN) { |
| aml_nand_msg("nand is in shut dowm protect mod"); |
| return NAND_SUCCESS; |
| } |
| #endif |
| |
| amlnand_get_device(aml_chip, CHIP_WRITING); |
| |
| len = devops->len; |
| /* clear ops_para here */ |
| memset(ops_para, 0, sizeof(struct chip_ops_para)); |
| addr = phydev->offset + devops->addr; |
| ops_para->option = phydev->option; //multi-plane is set |
| ops_para->data_buf = devops->datbuf; |
| ops_para->oob_buf = devops->oobbuf; |
| ops_para->ooblen = devops->ooblen; |
| if (devops->mode == NAND_SOFT_ECC) |
| ops_para->option |= DEV_ECC_SOFT_MODE; |
| |
| while (1) { |
| if (ops_para->option & DEV_SERIAL_CHIP_MODE) { |
| ops_para->chipnr = |
| (addr>>phydev->erasesize_shift) % |
| controller->chip_num; |
| controller->select_chip(controller, ops_para->chipnr); |
| aml_nand_dbg("DEV_SERIAL_CHIP_MODE"); |
| } |
| |
| if (ops_para->option & DEV_SLC_MODE) |
| ops_para->page_addr = amlnand_slc_addr_trs(phydev); |
| else |
| ops_para->page_addr = |
| (int)(addr >> phydev->writesize_shift); |
| //printf("%s() page %x\n", __func__, ops_para->page_addr); |
| ret = operation->write_page(aml_chip); |
| if (ret < 0) { |
| aml_nand_msg("Write fail devops->addr:%llx,addr:%llx", |
| devops->addr, |
| addr); |
| break; |
| } |
| |
| addr += phydev->writesize; |
| ops_para->data_buf += phydev->writesize; |
| writelen += phydev->writesize; |
| if (writelen >= len) |
| break; |
| } |
| |
| devops->retlen = writelen; |
| |
| if (aml_chip->debug_flag & NAND_WRITE_VERIFY) |
| nand_write_verify(phydev); |
| |
| amlnand_release_device(aml_chip); |
| |
| return ret; |
| } |
| |
| #if (DBG_WRITE_VERIFY) |
| static int nand_write(struct amlnand_phydev *phydev) |
| { |
| int ret; |
| u8 * tmp; |
| struct phydev_ops *devops = &(phydev->ops); |
| |
| ret = _nand_write(phydev); |
| tmp = devops->datbuf; |
| devops->datbuf = glb_verify_buffer; |
| nand_read(phydev); |
| |
| devops->datbuf = tmp; |
| printf("."); |
| if (memcmp(tmp, glb_verify_buffer, phydev->writesize)) { |
| aml_nand_msg("nand write verify failed"); |
| while (1) ; |
| } |
| return ret; |
| } |
| #endif |
| |
| int nand_erase(struct amlnand_phydev *phydev) |
| { |
| struct amlnand_chip *aml_chip = (struct amlnand_chip *)phydev->priv; |
| struct phydev_ops *devops = &(phydev->ops); |
| struct hw_controller *controller = &(aml_chip->controller); |
| struct chip_operation *operation = &(aml_chip->operation); |
| struct chip_ops_para *ops_para = &(aml_chip->ops_para); |
| u64 addr = 0, eraselen = 0; |
| int ret = 0; |
| |
| if ((devops->addr + devops->len) > phydev->size) { |
| aml_nand_msg("out of space and addr:"); |
| aml_nand_msg("%llx len:%llx pdev->offset:%llx pdev->size:%llx", |
| devops->addr, |
| devops->len, |
| phydev->offset, |
| phydev->size); |
| return -NAND_ARGUMENT_FAILURE; |
| } |
| |
| if (devops->len == 0) { |
| aml_nand_msg("len equal zero here"); |
| return NAND_SUCCESS; |
| } |
| |
| if (phydev->option & NAND_SHUT_DOWN) { |
| aml_nand_msg("nand is in shut dowm protect mod"); |
| return NAND_SUCCESS; |
| } |
| |
| amlnand_get_device(aml_chip, CHIP_ERASING); |
| |
| /* clear ops_para here */ |
| memset(ops_para, 0, sizeof(struct chip_ops_para)); |
| addr = phydev->offset + devops->addr; |
| ops_para->option = phydev->option; |
| |
| while (1) { |
| if (ops_para->option & DEV_SLC_MODE) |
| ops_para->page_addr = amlnand_slc_addr_trs(phydev); |
| else |
| ops_para->page_addr = |
| (int)(addr >> phydev->writesize_shift); |
| |
| if (ops_para->option & DEV_SERIAL_CHIP_MODE) { |
| ops_para->chipnr = |
| (addr>>phydev->erasesize)%controller->chip_num; |
| controller->select_chip(controller, ops_para->chipnr); |
| } |
| |
| ret = operation->erase_block(aml_chip); |
| if (ret < 0) { |
| aml_nand_msg("nand erase fail at addr :%x ", |
| ops_para->page_addr); |
| break; |
| } |
| |
| addr += phydev->erasesize; |
| eraselen += phydev->erasesize; |
| |
| if (eraselen >= devops->len) |
| break; |
| } |
| |
| devops->retlen = eraselen; |
| |
| amlnand_release_device(aml_chip); |
| |
| return ret; |
| } |
| |
| static int nand_block_isbad(struct amlnand_phydev *phydev) |
| { |
| struct amlnand_chip *aml_chip = (struct amlnand_chip *)phydev->priv; |
| /* struct nand_flash *flash = &(aml_chip->flash); */ |
| struct phydev_ops *devops = &(phydev->ops); |
| struct hw_controller *controller = &(aml_chip->controller); |
| struct chip_operation *operation = &(aml_chip->operation); |
| struct chip_ops_para *ops_para = &(aml_chip->ops_para); |
| u64 addr = 0; |
| int ret = 0; |
| |
| if (devops->addr > phydev->size) { |
| aml_nand_msg("out of space and addr:"); |
| aml_nand_msg("%llx phydev->offset:%llx phydev->size:%llx", |
| devops->addr, phydev->offset, phydev->size); |
| return -NAND_ARGUMENT_FAILURE; |
| } |
| #ifndef AML_NAND_UBOOT |
| if (phydev->option & NAND_SHUT_DOWN) { |
| aml_nand_msg("nand is in shut dowm protect mod"); |
| return NAND_SUCCESS; |
| } |
| #endif |
| amlnand_get_device(aml_chip, CHIP_READING); |
| |
| /* clear ops_para here */ |
| memset(ops_para, 0, sizeof(struct chip_ops_para)); |
| addr = phydev->offset + devops->addr; |
| ops_para->option = phydev->option; |
| if (ops_para->option & DEV_SLC_MODE) |
| ops_para->page_addr = amlnand_slc_addr_trs(phydev); |
| else |
| ops_para->page_addr = (int)(addr >> phydev->writesize_shift); |
| |
| if ((ops_para->option & DEV_SERIAL_CHIP_MODE)) |
| ops_para->chipnr = |
| (addr >> phydev->erasesize_shift) |
| % controller->chip_num; |
| |
| ret = operation->block_isbad(aml_chip); |
| if (ret < 0) |
| aml_nand_msg("fail page_addr:ret=%d, %x len:%llx", |
| ret, |
| ops_para->page_addr, |
| devops->len); |
| |
| amlnand_release_device(aml_chip); |
| |
| return ret; |
| } |
| |
| static int nand_block_markbad(struct amlnand_phydev *phydev) |
| { |
| struct amlnand_chip *aml_chip = (struct amlnand_chip *)phydev->priv; |
| struct phydev_ops *devops = &(phydev->ops); |
| struct hw_controller *controller = &(aml_chip->controller); |
| struct chip_operation *operation = &(aml_chip->operation); |
| struct chip_ops_para *ops_para = &(aml_chip->ops_para); |
| u64 addr = 0; |
| int ret = 0; |
| |
| if (devops->addr > phydev->size) { |
| aml_nand_msg("out of space and addr:"); |
| aml_nand_msg("%llx phydev->offset:%llx phydev->size:%llx", |
| devops->addr, |
| phydev->offset, |
| phydev->size); |
| return -NAND_ARGUMENT_FAILURE; |
| } |
| #ifndef AML_NAND_UBOOT |
| if (phydev->option & NAND_SHUT_DOWN) { |
| aml_nand_msg("nand is in shut dowm protect mod"); |
| return NAND_SUCCESS; |
| } |
| #endif |
| amlnand_get_device(aml_chip, CHIP_READING); |
| |
| /* clear ops_para here */ |
| memset(ops_para, 0, sizeof(struct chip_ops_para)); |
| addr = phydev->offset + devops->addr; |
| ops_para->option = phydev->option; |
| /* ops_para->page_addr = (int)(addr >> phydev->writesize_shift); */ |
| if (ops_para->option & DEV_SLC_MODE) |
| ops_para->page_addr = amlnand_slc_addr_trs(phydev); |
| else |
| ops_para->page_addr = (int)(addr >> phydev->writesize_shift); |
| |
| if (ops_para->option & DEV_SERIAL_CHIP_MODE) { |
| ops_para->chipnr = |
| (addr>>phydev->erasesize)%controller->chip_num; |
| controller->select_chip(controller, ops_para->chipnr); |
| } |
| |
| ret = operation->block_markbad(aml_chip); |
| if (ret < 0) |
| aml_nand_msg("nand mark bad failed at page %d", |
| ops_para->page_addr); |
| |
| amlnand_release_device(aml_chip); |
| |
| return ret; |
| } |
| static int block_modifybbt(struct amlnand_phydev *phydev, int value) |
| { |
| struct amlnand_chip *aml_chip = (struct amlnand_chip *)phydev->priv; |
| struct phydev_ops *devops = &(phydev->ops); |
| struct hw_controller *controller = &(aml_chip->controller); |
| struct chip_operation *operation = &(aml_chip->operation); |
| struct chip_ops_para *ops_para = &(aml_chip->ops_para); |
| u64 addr = 0; |
| int ret = 0; |
| |
| if (devops->addr > phydev->size) { |
| aml_nand_msg("out of space and addr:"); |
| aml_nand_msg("%llx phydev->offset:%llx phydev->size:%llx", |
| devops->addr, |
| phydev->offset, |
| phydev->size); |
| return -NAND_ARGUMENT_FAILURE; |
| } |
| |
| amlnand_get_device(aml_chip, CHIP_READING); |
| |
| /* clear ops_para here */ |
| memset(ops_para, 0, sizeof(struct chip_ops_para)); |
| addr = phydev->offset + devops->addr; |
| aml_nand_msg("addr = %lld", addr); |
| ops_para->option = phydev->option; |
| /* ops_para->page_addr = (int)(addr >> phydev->writesize_shift); */ |
| if (ops_para->option & DEV_SLC_MODE) |
| ops_para->page_addr = amlnand_slc_addr_trs(phydev); |
| else |
| ops_para->page_addr = (int)(addr >> phydev->writesize_shift); |
| |
| if (ops_para->option & DEV_SERIAL_CHIP_MODE) { |
| ops_para->chipnr = |
| (addr>>phydev->erasesize)%controller->chip_num; |
| controller->select_chip(controller, ops_para->chipnr); |
| } |
| |
| ret = operation->blk_modify_bbt_chip_op(aml_chip, value); |
| if (ret < 0) |
| aml_nand_msg("nand mark bad failed at page %d", |
| ops_para->page_addr); |
| |
| amlnand_release_device(aml_chip); |
| |
| return ret; |
| } |
| |
| static int update_bbt(struct amlnand_phydev *phydev) |
| { |
| struct amlnand_chip *aml_chip = (struct amlnand_chip *)phydev->priv; |
| struct phydev_ops *devops = &(phydev->ops); |
| struct hw_controller *controller = &(aml_chip->controller); |
| struct chip_operation *operation = &(aml_chip->operation); |
| struct chip_ops_para *ops_para = &(aml_chip->ops_para); |
| u64 addr = 0; |
| int ret = 0; |
| |
| if (devops->addr > phydev->size) { |
| aml_nand_msg("out of space and addr:"); |
| aml_nand_msg("%llx phydev->offset:%llx phydev->size:%llx", |
| devops->addr, |
| phydev->offset, |
| phydev->size); |
| return -NAND_ARGUMENT_FAILURE; |
| } |
| |
| amlnand_get_device(aml_chip, CHIP_READING); |
| |
| /* clear ops_para here */ |
| memset(ops_para, 0, sizeof(struct chip_ops_para)); |
| addr = phydev->offset + devops->addr; |
| ops_para->option = phydev->option; |
| /* ops_para->page_addr = (int)(addr >> phydev->writesize_shift); */ |
| if (ops_para->option & DEV_SLC_MODE) |
| ops_para->page_addr = amlnand_slc_addr_trs(phydev); |
| else |
| ops_para->page_addr = (int)(addr >> phydev->writesize_shift); |
| |
| if (ops_para->option & DEV_SERIAL_CHIP_MODE) { |
| ops_para->chipnr = |
| (addr>>phydev->erasesize)%controller->chip_num; |
| controller->select_chip(controller, ops_para->chipnr); |
| } |
| |
| ret = operation->update_bbt_chip_op(aml_chip); |
| if (ret < 0) |
| aml_nand_msg("nand mark bad failed at page %d", |
| ops_para->page_addr); |
| |
| amlnand_release_device(aml_chip); |
| |
| return ret; |
| } |
| |
| static int nand_test_block(struct amlnand_phydev *phydev) |
| { |
| struct amlnand_chip *aml_chip = (struct amlnand_chip *)phydev->priv; |
| struct phydev_ops *devops = &(phydev->ops); |
| struct hw_controller *controller = &(aml_chip->controller); |
| struct chip_operation *operation = &(aml_chip->operation); |
| struct chip_ops_para *ops_para = &(aml_chip->ops_para); |
| u64 addr = 0; |
| int ret = 0; |
| u32 tmp_addr = 0; |
| |
| if (devops->addr > phydev->size) { |
| aml_nand_msg("out of space and addr:"); |
| aml_nand_msg("%llx phydev->offset:%llx phydev->size:%llx", |
| devops->addr, |
| phydev->offset, |
| phydev->size); |
| return -NAND_ARGUMENT_FAILURE; |
| } |
| |
| if (phydev->option & NAND_SHUT_DOWN) { |
| aml_nand_msg("nand is in shut dowm protect mod"); |
| return NAND_SUCCESS; |
| } |
| |
| amlnand_get_device(aml_chip, CHIP_READING); |
| |
| /* clear ops_para here */ |
| memset(ops_para, 0, sizeof(struct chip_ops_para)); |
| addr = phydev->offset + devops->addr; |
| aml_nand_msg("addr = %lld", addr); |
| ops_para->option = phydev->option; |
| /* ops_para->page_addr = (int)(addr >> phydev->writesize_shift); */ |
| if (ops_para->option & DEV_SLC_MODE) |
| ops_para->page_addr = amlnand_slc_addr_trs(phydev); |
| else |
| ops_para->page_addr = (int)(addr >> phydev->writesize_shift); |
| |
| if (ops_para->option & DEV_SERIAL_CHIP_MODE) { |
| ops_para->chipnr = |
| (addr>>phydev->erasesize)%controller->chip_num; |
| controller->select_chip(controller, ops_para->chipnr); |
| } |
| |
| tmp_addr = ops_para->page_addr; |
| ops_para->data_buf = devops->datbuf; |
| ret = operation->erase_block(aml_chip); |
| if (ret < 0) { |
| aml_nand_msg("nand erase_block failed at page %d", |
| ops_para->page_addr); |
| goto exit; |
| } |
| |
| ops_para->page_addr = tmp_addr; |
| do { |
| ret = operation->write_page(aml_chip); |
| if (ret < 0) { |
| aml_nand_msg("nand erase_block failed at page %d", |
| ops_para->page_addr); |
| goto exit; |
| } |
| ops_para->page_addr += phydev->writesize; |
| } while (ops_para->page_addr >= devops->len); |
| |
| ops_para->page_addr = tmp_addr; |
| do { |
| ret = operation->read_page(aml_chip); |
| if ((ops_para->ecc_err) || (ret < 0)) { |
| aml_nand_msg("phy read failed at devops->addr: %llx", |
| devops->addr); |
| goto exit; |
| } |
| ops_para->page_addr += phydev->writesize; |
| } while (ops_para->page_addr >= devops->len); |
| |
| ops_para->page_addr = tmp_addr; |
| ret = operation->erase_block(aml_chip); |
| if (ret < 0) { |
| aml_nand_msg("nand erase_block failed at page %d", |
| ops_para->page_addr); |
| goto exit; |
| } |
| aml_nand_msg("nand test OK "); |
| |
| exit: |
| amlnand_release_device(aml_chip); |
| return ret; |
| |
| } |
| |
| |
| |
| int phydev_init_erase(struct amlnand_chip *aml_chip) |
| { |
| int ret =0, percent=0,percent_complete = -1; |
| //uint64_t addr, off, size, chipsize, erase_addr, erase_len, erase_off; |
| uint64_t erase_addr, erase_len, erase_off; |
| struct amlnand_phydev *phydev = NULL; |
| struct phydev_ops *devops = NULL; |
| if (aml_chip->init_flag == NAND_BOOT_ERASE_PROTECT_CACHE) { |
| |
| list_for_each_entry(phydev,&nphy_dev_list,list){ |
| |
| if ((!strncmp((char*)phydev->name, NAND_CODE_NAME, strlen((const char*)NAND_CODE_NAME))) \ |
| ||(!strncmp((char*)phydev->name, NAND_DATA_NAME, strlen((const char*)NAND_DATA_NAME)))){ |
| devops = &phydev->ops; |
| erase_addr = 0; |
| erase_off = erase_addr; |
| erase_len = phydev->size; |
| percent=0;percent_complete = -1; |
| |
| for (; erase_addr <erase_off + erase_len; erase_addr += phydev->erasesize) { |
| |
| memset(devops, 0x0, sizeof(struct phydev_ops)); |
| devops->addr = erase_addr; |
| devops->len = phydev->erasesize; |
| devops->mode = NAND_HW_ECC; |
| |
| ret = phydev->block_isbad(phydev); |
| if (ret > 0) { |
| printf("\rSkipping bad block at 0x%08llx\n", erase_addr); |
| continue; |
| |
| } else if (ret < 0) { |
| printf("\n:AMLNAND get bad block failed: ret=%d at addr=%llx\n",ret, erase_addr); |
| return -1; |
| } |
| |
| ret = nand_erase(phydev); |
| if (ret < 0) { |
| printf("\nAMLNAND Erase failure: %d %llx\n", ret, erase_addr); |
| ret = phydev->block_markbad(phydev); |
| if (ret < 0) |
| printf("AMLNAND bad block mark failed: %llx\n", erase_addr); |
| continue; |
| } |
| |
| percent = (erase_addr * 100) / (erase_off + erase_len); |
| if ((percent != percent_complete) && ((percent %10) == 0)) { |
| percent_complete = percent; |
| aml_nand_msg("nand erasing %d %% --%d %% complete",percent,percent+10); |
| } |
| } |
| } |
| } |
| } |
| |
| return ret; |
| } |
| |
| void amldev_dumpinfo(struct amlnand_phydev *phydev) |
| { |
| /* flash info */ |
| aml_nand_msg("device info"); |
| aml_nand_msg("name:%s, offset:%llx, size:%llx, option:%x", |
| phydev->name, |
| phydev->offset, |
| phydev->size, |
| phydev->option); |
| aml_nand_msg("esize:%x,wsize:%x,oob:%x,esize_shift:%x,wsize_shift:%d", |
| phydev->erasesize, |
| phydev->writesize, |
| phydev->oobavail, |
| phydev->erasesize_shift, |
| phydev->writesize_shift); |
| } |
| |
| #ifndef AML_NAND_UBOOT |
| ssize_t nand_page_read(struct class *class, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct amlnand_phydev *phydev = NULL; |
| struct phydev_ops *devops = NULL; |
| |
| u8 *datbuf, *p; |
| u64 offset , write_len; |
| loff_t off; |
| size_t ret; |
| int i; |
| |
| phydev = container_of(class, struct amlnand_phydev, cls); |
| devops = &(phydev->ops); |
| |
| ret = sscanf(buf, "%llx", &off); |
| |
| datbuf = kmalloc(2*phydev->writesize, GFP_KERNEL); |
| if (!datbuf) { |
| aml_nand_msg("No memory for page buffer"); |
| goto exit_erro; |
| } |
| p = datbuf; |
| |
| memset(datbuf, 0x0, 2*phydev->writesize); |
| aml_nand_dbg("phydev->name =%s", phydev->name); |
| aml_nand_dbg("read page"); |
| |
| offset = 0; |
| write_len = phydev->writesize; |
| |
| memset(devops, 0x0, sizeof(struct phydev_ops)); |
| devops->addr = offset; |
| devops->len = phydev->writesize; |
| devops->datbuf = datbuf; |
| devops->oobbuf = NULL; |
| devops->mode = NAND_HW_ECC; |
| do { |
| if ((((u32)devops->addr % phydev->erasesize)) == 0) { |
| aml_nand_dbg("devops->addr = %llx", devops->addr); |
| ret = phydev->block_isbad(phydev); |
| if (ret > 0) { |
| aml_nand_dbg("\rSkipping bad block at %llx\n", |
| devops->addr); |
| devops->addr += phydev->erasesize; |
| continue; |
| } else if (ret < 0) { |
| aml_nand_dbg("get bad blk fail:"); |
| aml_nand_dbg("ret=%d addr=%llx\n", |
| ret, |
| devops->addr); |
| return -1; |
| } |
| } |
| |
| ret = phydev->read(phydev); |
| if (ret < 0) |
| aml_nand_dbg("nand read failed at %llx", devops->addr); |
| |
| devops->addr += phydev->writesize; |
| datbuf += phydev->writesize; |
| } while (devops->addr < (offset + write_len)); |
| |
| i = 512; |
| aml_nand_dbg("read page"); |
| while (i--) { |
| aml_nand_msg("\t%02x %02x %02x %02x %02x %02x %02x %02x", |
| p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); |
| aml_nand_msg("\t%02x %02x %02x %02x %02x %02x %02x %02x", |
| p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); |
| p += 16; |
| } |
| |
| aml_nand_dbg("read page complete"); |
| |
| exit_erro: |
| kfree(datbuf); |
| |
| return count; |
| } |
| |
| void amlchip_resume(struct amlnand_phydev *phydev) |
| { |
| struct amlnand_chip *aml_chip = (struct amlnand_chip *)phydev->priv; |
| struct hw_controller *controller = &(aml_chip->controller); |
| struct chip_operation *operation = &(aml_chip->operation); |
| u8 onfi_features[4] = {0}; |
| int i = 0, ret = 0; |
| |
| if (!strncmp((char *)phydev->name, |
| NAND_CODE_NAME, |
| strlen((const char *)NAND_CODE_NAME))) { |
| nand_get_chip(aml_chip); |
| for (i = 0; i < controller->chip_num; i++) { |
| ret = controller->select_chip(controller, i); |
| if (ret < 0) |
| aml_nand_msg("select chip %d failed", i); |
| |
| ret = nand_reset(aml_chip, i); |
| if (ret < 0) |
| aml_nand_dbg("reset failed %d", i); |
| } |
| |
| if (controller->onfi_mode) { |
| operation->set_onfi_para(aml_chip, |
| (u8 *)&(controller->onfi_mode), |
| ONFI_TIMING_ADDR); |
| operation->get_onfi_para(aml_chip, |
| onfi_features, |
| ONFI_TIMING_ADDR); |
| if (onfi_features[0] != controller->onfi_mode) { |
| aml_chip->flash.T_REA = DEFAULT_T_REA; |
| aml_chip->flash.T_RHOH = DEFAULT_T_RHOH; |
| } |
| } |
| |
| /* if (aml_chip->state == CHIP_PM_SUSPENDED) */ |
| amlnand_release_device(aml_chip); |
| aml_nand_dbg("nand resume entered\n"); |
| } |
| return; |
| } |
| |
| ssize_t nand_page_write(struct class *class, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct amlnand_phydev *phydev = NULL; |
| struct phydev_ops *devops = NULL; |
| u_char *datbuf; |
| u64 offset , write_len; |
| loff_t off; |
| size_t ret; |
| |
| phydev = container_of(class, struct amlnand_phydev, cls); |
| devops = &(phydev->ops); |
| |
| aml_nand_dbg("phydev->name =%s", phydev->name); |
| aml_nand_dbg("write page"); |
| ret = sscanf(buf, "%llx", &off); |
| datbuf = kmalloc(2*phydev->writesize, GFP_KERNEL); |
| if (!datbuf) { |
| aml_nand_msg("No memory for page buffer"); |
| goto exit_erro; |
| } |
| memset(datbuf, 0xa5, 2*phydev->writesize); |
| |
| offset = 0; |
| write_len = phydev->writesize; |
| |
| memset(devops, 0x0, sizeof(struct phydev_ops)); |
| devops->addr = offset; |
| devops->len = phydev->writesize; |
| devops->datbuf = datbuf; |
| devops->oobbuf = NULL; |
| devops->mode = NAND_HW_ECC; |
| do { |
| if ((((u32)devops->addr % phydev->erasesize)) == 0) { |
| aml_nand_dbg("devops->addr = %llx", devops->addr); |
| ret = phydev->block_isbad(phydev); |
| if (ret > 0) { |
| aml_nand_msg("\rSkipping bad block at %llx\n", |
| devops->addr); |
| devops->addr += phydev->erasesize; |
| continue; |
| } else if (ret < 0) { |
| aml_nand_msg("get bad blk fail:"); |
| aml_nand_msg("ret=%d addr=%llx\n", |
| ret, |
| devops->addr); |
| return -1; |
| } |
| } |
| ret = phydev->write(phydev); |
| if (ret < 0) |
| aml_nand_msg("nand read failed at %llx", devops->addr); |
| |
| devops->addr += phydev->writesize; |
| datbuf += phydev->writesize; |
| } while (devops->addr < (offset + write_len)); |
| |
| aml_nand_dbg("write page complete"); |
| exit_erro: |
| kfree(datbuf); |
| return count; |
| } |
| |
| /**** |
| *verify_nand_page: |
| *read data immediately when write data, then compare; |
| * |
| *enbale this function: set the first bit of aml_chip->debug_flag to 1; |
| *****/ |
| ssize_t verify_nand_page(struct class *class, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct amlnand_phydev *phydev = NULL; |
| struct amlnand_chip *aml_chip = NULL; |
| u8 off; |
| size_t ret; |
| |
| phydev = container_of(class, struct amlnand_phydev, cls); |
| aml_chip = (struct amlnand_chip *)phydev->priv; |
| |
| ret = sscanf(buf, "%d", (int *)&off); |
| aml_chip->debug_flag = off; |
| |
| aml_nand_msg("nand set aml_chip->debug_flag to %d", |
| aml_chip->debug_flag); |
| |
| return count; |
| } |
| |
| ssize_t dump_nand_page(struct class *class, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct amlnand_phydev *phydev = NULL; |
| struct phydev_ops *devops = NULL; |
| u8 *datbuf, *p; |
| u64 offset , write_len; |
| u64 off; |
| size_t ret; |
| int i; |
| |
| phydev = container_of(class, struct amlnand_phydev, cls); |
| devops = &(phydev->ops); |
| |
| ret = sscanf(buf, "%llx", &off); |
| |
| aml_nand_msg("dump page %llx", off); |
| datbuf = kmalloc(2*phydev->writesize, GFP_KERNEL); |
| if (!datbuf) { |
| aml_nand_msg("No memory for page buffer"); |
| goto exit_erro; |
| } |
| p = datbuf; |
| memset(datbuf, 0x0, 2*phydev->writesize); |
| aml_nand_msg("phydev->name =%s", phydev->name); |
| |
| offset = off; |
| write_len = phydev->writesize; |
| aml_nand_msg("offset %llx", offset); |
| |
| memset(devops, 0x0, sizeof(struct phydev_ops)); |
| devops->addr = offset; |
| devops->len = phydev->writesize; |
| devops->datbuf = datbuf; |
| devops->oobbuf = NULL; |
| devops->mode = NAND_SOFT_ECC; |
| do { |
| if ((((u32)devops->addr % phydev->erasesize)) == 0) { |
| aml_nand_dbg("devops->addr = %llx", devops->addr); |
| ret = phydev->block_isbad(phydev); |
| if (ret > 0) { |
| aml_nand_dbg("\rSkipping bad block at %llx\n", |
| devops->addr); |
| devops->addr += phydev->erasesize; |
| continue; |
| } else if (ret < 0) { |
| aml_nand_dbg("get bad blk fail:"); |
| aml_nand_dbg("ret=%d addr=%llx\n", |
| ret, |
| devops->addr); |
| return -1; |
| } |
| } |
| ret = phydev->read(phydev); |
| if (ret < 0) |
| aml_nand_dbg("nand read failed at %llx", devops->addr); |
| |
| devops->addr += phydev->writesize; |
| datbuf += phydev->writesize; |
| } while (devops->addr < (offset + write_len)); |
| |
| i = 512; |
| aml_nand_dbg("read page"); |
| while (i--) { |
| aml_nand_msg("\t%02x %02x %02x %02x %02x %02x %02x %02x", |
| p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); |
| aml_nand_msg("\t%02x %02x %02x %02x %02x %02x %02x %02x", |
| p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); |
| p += 16; |
| } |
| |
| aml_nand_dbg("read page complete"); |
| exit_erro: |
| kfree(datbuf); |
| return count; |
| } |
| |
| ssize_t show_nand_info(struct class *class, |
| struct class_attribute *attr, |
| char *buf) |
| { |
| struct amlnand_phydev *phydev = NULL; |
| aml_nand_dbg("phydev info:"); |
| |
| phydev = container_of(class, struct amlnand_phydev, cls); |
| |
| amldev_dumpinfo(phydev); |
| return 0; |
| } |
| |
| ssize_t show_bbt_table(struct class *class, |
| struct class_attribute *attr, |
| const char *buf, |
| size_t count) |
| { |
| struct amlnand_phydev *phydev = NULL; |
| struct amlnand_chip *aml_chip = NULL; |
| struct hw_controller *controller = NULL; |
| u16 *tmp_arr; |
| int start_block, chipnr, total_block; |
| int ret; |
| |
| phydev = container_of(class, struct amlnand_phydev, cls); |
| aml_chip = (struct amlnand_chip *)phydev->priv; |
| controller = &(aml_chip->controller); |
| |
| aml_nand_msg("show the block status !!!!"); |
| |
| ret = sscanf(buf, "%d", &total_block); |
| if (ret != 1) |
| return -EINVAL; |
| aml_nand_msg("set total_block %d", total_block); |
| |
| for (chipnr = 0; chipnr < controller->chip_num; chipnr++) { |
| tmp_arr = &aml_chip->block_status->blk_status[chipnr][0]; |
| for (start_block = total_block; |
| start_block < (total_block + 50); |
| start_block++) |
| aml_nand_msg(" aml_chip->block_status[%d][%d]=%d", |
| chipnr, |
| start_block, |
| tmp_arr[start_block]); |
| } |
| return count; |
| } |
| |
| ssize_t change_test_sync_flag(struct class *class, |
| struct class_attribute *attr, |
| const char *buf, |
| size_t count) |
| { |
| int num; |
| int ret; |
| |
| ret = sscanf(buf, "%x", &num); |
| if (ret != 1) |
| return -EINVAL; |
| aml_nand_msg("---------------test_flag=%d", num); |
| test_flag = num; |
| |
| return count; |
| } |
| |
| ssize_t show_amlnf_version_info(struct class *class, |
| struct class_attribute *attr, |
| char *buf) |
| { |
| aml_nand_dbg("show_nand_version_info v0.01"); |
| return 0; |
| } |
| |
| #endif /* AML_NAND_UBOOT */ |
| |
| static void show_phydev_info(void) |
| { |
| struct amlnand_phydev *phydev = NULL; |
| //struct amlnf_partition *partition = NULL; |
| //int i = 0; |
| char *config1, *config2; |
| |
| list_for_each_entry(phydev, &nphy_dev_list, list) { |
| if (phydev == NULL) |
| break; |
| #if 0 |
| for (i = 0; i < phydev->nr_partitions; i++) { |
| partition = &phydev->partitions[i]; |
| aml_nand_msg("%s: name=%s,size=%llx", |
| phydev->name, |
| partition->name, |
| partition->size); |
| } |
| #endif |
| if (phydev->option & DEV_MULTI_CHIP_MODE) |
| config1 = "multi_chip"; |
| else |
| config1 = "single_chip"; |
| if (phydev->option & DEV_MULTI_PLANE_MODE) |
| config2 = "multi_plane"; |
| else |
| config2 = "single_plane"; |
| |
| aml_nand_msg("%-10s: 0x%012llx-0x%012llx :partn=%d:%s %s", |
| phydev->name, |
| phydev->offset, |
| phydev->size, |
| phydev->nr_partitions, |
| config1, |
| config2); |
| } |
| } |
| |
| |
| /**********/ |
| /* check list by name 1st, avoiding reinit */ |
| int phydev_add_list(struct amlnand_phydev *new) |
| { |
| int ret = 0; |
| struct amlnand_phydev *dev; |
| list_for_each_entry(dev, &nphy_dev_list, list) { |
| //if (!(memcmp(new->name, dev->name, sizeof(new->name)))) { |
| if (!strncmp((char *)new->name, |
| (char *) dev->name, |
| strlen((char *)new->name))) { |
| ret = -1; |
| goto _out; |
| } |
| } |
| list_add_tail(&new->list, &nphy_dev_list); |
| |
| _out: |
| return ret; |
| } |
| |
| void show_phydev_list(void) |
| { |
| int i = 0; |
| struct amlnand_phydev *dev; |
| printk("%s\n", __func__); |
| list_for_each_entry(dev, &nphy_dev_list, list) { |
| printk("%d: %s\n", i , dev->name); |
| i++; |
| } |
| return; |
| } |
| |
| static int init_phydev_ops(struct amlnand_phydev *phydev) |
| { |
| int ret = 0; |
| phydev->write = nand_write; |
| phydev->read = nand_read; |
| phydev->erase = nand_erase; |
| |
| phydev->block_isbad = nand_block_isbad; |
| phydev->block_markbad = nand_block_markbad; |
| |
| phydev->block_modifybbt = block_modifybbt; |
| phydev->update_bbt = update_bbt; |
| // phydev->phydev_test_block = nand_test_block; |
| |
| return ret; |
| } |
| |
| /*only init dev for u-boot*/ |
| int boot_dev_init(struct amlnand_chip *aml_chip) |
| { |
| int ret = 0; |
| struct amlnand_phydev *phydev = NULL; |
| struct nand_flash *flash = &(aml_chip->flash); |
| struct hw_controller *controller = &(aml_chip->controller); |
| |
| PHYDEV_LINE |
| phydev = aml_nand_malloc(sizeof(struct amlnand_phydev)); |
| if (phydev == NULL) { |
| aml_nand_msg("malloc failed need %x here", |
| (u32)(sizeof(struct amlnand_phydev))); |
| ret = -NAND_MALLOC_FAILURE; |
| goto _out; |
| } |
| memset(phydev, 0, sizeof(struct amlnand_phydev)); |
| phydev->priv = aml_chip; |
| PHYDEV_LINE |
| phydev->writesize = flash->pagesize; |
| phydev->erasesize = flash->blocksize; |
| phydev->oobavail = controller->oobavail; |
| |
| PHYDEV_LINE |
| //fixme, phy name... |
| memcpy((char *)phydev->name, NAND_BOOT_NAME, strlen(NAND_BOOT_NAME)); |
| PHYDEV_LINE |
| phydev->offset = 0; |
| phydev->size = (BOOT_COPY_NUM*BOOT_PAGES_PER_COPY); |
| phydev->size *= flash->pagesize; |
| /* phydev->size *= chip_num; */ |
| PHYDEV_LINE |
| phydev->writesize_shift = ffs(phydev->writesize) - 1; |
| phydev->erasesize_shift = ffs(phydev->erasesize) - 1; |
| phydev->writesize_mask = |
| (phydev->size>>phydev->writesize_shift) - 1; |
| PHYDEV_LINE |
| init_phydev_ops(phydev); |
| PHYDEV_LINE |
| if ((flash->option & NAND_CHIP_SLC_MODE) |
| && (!(phydev->option & DEV_MULTI_PLANE_MODE))) { |
| |
| phydev->option |= DEV_SLC_MODE; |
| phydev->erasesize >>= 1; |
| phydev->erasesize_shift = ffs(phydev->erasesize) - 1; |
| phydev->size >>= 1; |
| aml_nand_msg(" DEV %s enable SLC mode", phydev->name); |
| } |
| PHYDEV_LINE |
| ret = phydev_add_list(phydev); |
| if (ret) { |
| kfree(phydev); |
| } |
| |
| show_phydev_list(); |
| _out: |
| |
| return ret; |
| } |
| |
| |
| /****** |
| *nand chip usage |
| * all dev size should |
| * uboot(nand boot) reserved code data |
| */ |
| extern struct list_head nphy_dev_list; |
| |
| int aml_alloc_phydev(struct amlnand_phydev **phydev_pp, |
| struct amlnand_chip *aml_chip, |
| struct dev_para **dev_para, |
| int dev_idx) |
| { |
| int ret = 0; |
| struct hw_controller *controller = &(aml_chip->controller); |
| struct nand_flash *flash = &(aml_chip->flash); |
| struct amlnand_phydev* phydev_p = NULL; |
| |
| *phydev_pp = aml_nand_malloc(sizeof(struct amlnand_phydev)); |
| if (*phydev_pp == NULL) { |
| aml_nand_msg("malloc failed need %lx here", |
| sizeof(struct amlnand_phydev)); |
| ret = -NAND_MALLOC_FAILURE; |
| return ret; |
| } |
| phydev_p = *phydev_pp; |
| memset(phydev_p, 0, sizeof(struct amlnand_phydev)); |
| phydev_p->priv = aml_chip; |
| |
| *dev_para = &aml_chip->config_ptr->dev_para[dev_idx]; |
| memcpy(&phydev_p->name, &(*dev_para)->name, MAX_DEVICE_NAME_LEN*sizeof(char)); |
| /*set default parameter*/ |
| phydev_p->writesize = flash->pagesize; |
| phydev_p->erasesize = flash->blocksize; |
| phydev_p->oobavail = controller->oobavail; |
| phydev_p->writesize_shift = ffs(phydev_p->writesize) - 1; |
| phydev_p->erasesize_shift = ffs(phydev_p->erasesize) - 1; |
| phydev_p->write = nand_write; |
| phydev_p->read = nand_read; |
| phydev_p->erase = nand_erase; |
| phydev_p->block_isbad = nand_block_isbad; |
| phydev_p->block_markbad = nand_block_markbad; |
| phydev_p->block_modifybbt = block_modifybbt; |
| phydev_p->update_bbt = update_bbt; |
| phydev_p->phydev_test_block = nand_test_block; |
| |
| return ret; |
| } |
| |
| int amlnand_phydev_init(struct amlnand_chip *aml_chip) |
| { |
| struct amlnand_phydev *phydev = NULL, *phydev_pre = NULL; |
| struct nand_flash *flash = &(aml_chip->flash); |
| struct hw_controller *controller = &(aml_chip->controller); |
| struct chip_operation *operation = &(aml_chip->operation); |
| struct chip_ops_para *ops_para = &(aml_chip->ops_para); |
| struct nand_config *config = aml_chip->config_ptr; |
| struct nand_arg_info phy_part = aml_chip->nand_phy_partition; |
| struct dev_para *dev_para = NULL; |
| struct amlnf_partition *partition = NULL; |
| u64 offset = 0, dev_size = 0, chip_size = 0, phydev_pre_size = 0; |
| u32 start_blk, total_blk, tmp_write_shift, tmp_erase_shift; |
| u32 tmp_offset = 0, tmp_blk = 0, pages_per_blk; |
| u8 boot_flag = 0, plane_num = 1, chip_num; |
| int i, j, k, ret = 0; |
| u32 adjust_blk = 0, cpu_type; |
| u64 tmp_value; |
| #if 0 |
| /*when opened , need be free bad_blk; */ |
| u64 *bad_blk = NULL; |
| bad_blk = aml_nand_malloc(128*sizeof(u64)); |
| if (bad_blk == NULL) { |
| aml_nand_msg("malloc failed"); |
| ret = -NAND_MALLOC_FAILURE; |
| goto exit_error0; |
| } |
| memset(bad_blk, 0, 128*sizeof(u64)); |
| #endif |
| |
| if (flash->option & NAND_MULTI_PLANE_MODE) |
| plane_num = 2; |
| else |
| plane_num = 1; |
| |
| /* #ifndef AML_NAND_UBOOT */ |
| /* INIT_LIST_HEAD (&nphy_dev_list); */ |
| /* #endif */ |
| |
| chip_num = controller->chip_num; |
| |
| chip_size = (flash->chipsize*chip_num); |
| chip_size = chip_size << 20; |
| if (config->dev_num == 0) { |
| aml_nand_msg("config get unvalid: config->dev_num =%d", |
| config->dev_num); |
| ret = -NAND_FAILED; |
| goto exit_error0; |
| } |
| PHYDEV_LINE |
| /* if phy-partition saved is valid, |
| no need to calc and care about driver version. |
| */ |
| if (phy_part.arg_valid == 1) { |
| for (i = 0; i < config->dev_num; i++) { |
| ret = aml_alloc_phydev(&phydev, aml_chip, &dev_para, i); |
| phydev->offset = |
| aml_chip->phy_part_ptr->partition[i].phy_off; |
| phydev->size = |
| aml_chip->phy_part_ptr->partition[i].phy_len; |
| phydev->nr_partitions = dev_para->nr_partitions; |
| phydev->partitions = dev_para->partitions; |
| if ((dev_para->option & DEV_MULTI_CHIP_MODE) |
| && (controller->chip_num > 1)) { |
| phydev->option |= DEV_MULTI_CHIP_MODE; |
| phydev->writesize *= controller->chip_num; |
| phydev->erasesize *= controller->chip_num; |
| phydev->oobavail *= controller->chip_num; |
| } |
| |
| if ((dev_para->option & DEV_MULTI_PLANE_MODE) |
| && (flash->option & NAND_MULTI_PLANE_MODE)) { |
| phydev->option |= DEV_MULTI_PLANE_MODE; |
| phydev->writesize <<= 1; |
| phydev->erasesize <<= 1; |
| phydev->oobavail <<= 1; |
| } |
| phydev->writesize_shift = ffs(phydev->writesize) - 1; |
| phydev->erasesize_shift = ffs(phydev->erasesize) - 1; |
| printk("%s,%d,phydev->offset=%llx,phydev->size=%llx\n", |
| __func__, |
| __LINE__, |
| phydev->offset,phydev->size); |
| phydev_add_list(phydev); |
| } |
| } else { |
| for (i = 0; i < config->dev_num; i++) { |
| ret = aml_alloc_phydev(&phydev, aml_chip, &dev_para, i); |
| dev_size = 0; |
| |
| tmp_write_shift = ffs(flash->pagesize) - 1; |
| tmp_erase_shift = ffs(flash->blocksize) - 1; |
| pages_per_blk = (1 << (tmp_erase_shift - tmp_write_shift)); |
| |
| /* set partitions and caulate dev size */ |
| if (dev_para->nr_partitions) { |
| phydev->nr_partitions = dev_para->nr_partitions; |
| phydev->partitions = dev_para->partitions; |
| for (k = 0; k < dev_para->nr_partitions; k++) { |
| partition = &(dev_para->partitions[k]); |
| aml_nand_dbg("partition[%d]-name:%s,size:%llx", |
| k, |
| partition->name, |
| partition->size); |
| } |
| PHYDEV_LINE |
| if (i != (config->dev_num - 1)) { |
| for (j = 0; j < dev_para->nr_partitions; j++) { |
| partition = &(dev_para->partitions[j]); |
| dev_size += partition->size; |
| } |
| if (!is_phydev_off_adjust()) { |
| int adjust_shift = |
| ffs(ADJUST_SIZE_NFTL) - 1; |
| /* |
| aml_nand_msg("not adjust, |
| adjust_shift : %d", |
| adjust_shift); |
| */ |
| dev_size = dev_size |
| + (dev_size >> adjust_shift); |
| } |
| /* |
| adjust dev_size for nftl |
| dev_size = dev_size \ |
| + ((u64)(dev_size)/(u64)(ADJUST_SIZE_NFTL)); |
| */ |
| } else { |
| if ((phydev_pre->option & DEV_SLC_MODE) |
| && (flash->option & NAND_CHIP_SLC_MODE) |
| && (!(phydev->option & DEV_MULTI_PLANE_MODE))) |
| phydev_pre_size = phydev_pre->size << 1; |
| else |
| phydev_pre_size = phydev_pre->size; |
| |
| dev_size = chip_size - phydev_pre->offset |
| - phydev_pre_size; |
| } |
| } else { |
| dev_size = dev_para->size; |
| } |
| |
| if ((dev_para->option & DEV_SLC_MODE) && |
| (flash->option & NAND_CHIP_SLC_MODE) && |
| (!(dev_para->option & DEV_MULTI_PLANE_MODE))) { |
| dev_size <<= 1; |
| aml_nand_msg("DEV enable SLC mode"); |
| } |
| |
| if (!strncmp((char *)phydev->name, |
| NAND_BOOT_NAME, |
| strlen((const char *)NAND_BOOT_NAME))) { |
| boot_flag = 1; |
| |
| phydev->offset = 0; |
| phydev->size = (BOOT_COPY_NUM * BOOT_PAGES_PER_COPY); |
| //printk("----------%llx\n", phydev->size); |
| phydev->size *= flash->pagesize; |
| //printk("----------%llx\n", phydev->size); |
| /* phydev->size *= chip_num; */ |
| |
| phydev->writesize_shift = ffs(phydev->writesize) - 1; |
| phydev->erasesize_shift = ffs(phydev->erasesize) - 1; |
| phydev->writesize_mask = |
| (phydev->size>>phydev->writesize_shift) - 1; |
| |
| tmp_blk = phydev->size >> phydev->erasesize_shift; |
| } else { /* not boot phydev[nftl] */ |
| if ((dev_para->option & DEV_MULTI_CHIP_MODE) |
| && (chip_num > 1)) { |
| phydev->option |= DEV_MULTI_CHIP_MODE; |
| phydev->writesize *= chip_num; |
| phydev->erasesize *= chip_num; |
| phydev->oobavail *= chip_num; |
| } |
| |
| if ((dev_para->option & DEV_MULTI_PLANE_MODE) |
| && (flash->option & NAND_MULTI_PLANE_MODE)) { |
| phydev->option |= DEV_MULTI_PLANE_MODE; |
| phydev->writesize <<= 1; |
| phydev->erasesize <<= 1; |
| phydev->oobavail <<= 1; |
| } |
| #if (DBG_WRITE_VERIFY) |
| /*debug code */ |
| if ( NULL == glb_verify_buffer ) { |
| glb_verify_buffer = aml_nand_malloc(phydev->writesize); |
| if (glb_verify_buffer == NULL) { |
| aml_nand_msg("%s() %d, malloc glb_verify_buffer failed!\n", |
| __func__, __LINE__); |
| |
| } |
| |
| } |
| #endif |
| phydev->writesize_shift = ffs(phydev->writesize) - 1; |
| phydev->erasesize_shift = ffs(phydev->erasesize) - 1; |
| |
| if (((boot_flag == 1) && (i == 1)) |
| || ((boot_flag == 0) && (i == 0))) { |
| offset = start_blk = 0; |
| total_blk = RESERVED_BLOCK_CNT; |
| memset(ops_para, 0, |
| sizeof(struct chip_ops_para)); |
| do { |
| tmp_value = offset>>tmp_erase_shift; |
| ops_para->chipnr = |
| ((u32)tmp_value) % chip_num; |
| tmp_value += tmp_blk; |
| tmp_value /= chip_num; |
| tmp_value += tmp_blk - tmp_blk/chip_num; |
| tmp_value *= pages_per_blk; |
| ops_para->page_addr = (u32)tmp_value; |
| ret = operation->block_isbad(aml_chip); |
| if (ret == NAND_BLOCK_FACTORY_BAD) { |
| offset += flash->blocksize; |
| continue; |
| } |
| start_blk++; |
| offset += flash->blocksize; |
| } while (start_blk < total_blk); |
| tmp_value = (offset>>tmp_erase_shift) - 1; |
| tmp_value /= chip_num*plane_num; |
| tmp_value += 1; |
| total_blk = tmp_value * chip_num * plane_num; |
| aml_nand_dbg("total_blk =%d", total_blk); |
| aml_nand_dbg(" phydev_pre->size =%llx", |
| phydev_pre->size); |
| if (phydev_pre == NULL) |
| phydev->offset = |
| total_blk * flash->blocksize; |
| else |
| phydev->offset = |
| total_blk * flash->blocksize + |
| phydev_pre->size; |
| aml_nand_dbg("phydev->offset =%llx", |
| phydev->offset); |
| } else { |
| if ((!(phydev->option & DEV_MULTI_PLANE_MODE) |
| && (flash->option & NAND_CHIP_SLC_MODE) |
| && (phydev_pre->option & DEV_SLC_MODE))) |
| phydev_pre_size = phydev_pre->size << 1; |
| else |
| phydev_pre_size = phydev_pre->size; |
| |
| phydev->offset = |
| phydev_pre->offset + phydev_pre_size; |
| } |
| |
| if (i != (config->dev_num - 1)) { |
| start_blk = 0; |
| if (((boot_flag == 1) && (i == 1)) |
| || ((boot_flag == 0) && (i == 0))) |
| offset = phydev->offset; |
| else |
| offset = phydev_pre->offset + |
| phydev_pre->size; |
| tmp_offset = offset; |
| |
| aml_nand_dbg("offset = %llx, %llx", |
| offset, |
| tmp_erase_shift); |
| /* |
| if (!is_phydev_off_adjust()) |
| total_blk = dev_size >> tmp_erase_shift; |
| else { |
| */ |
| aml_nand_dbg("adjust phy offset: %d", |
| ADJUST_BLOCK_NUM); |
| total_blk = |
| dev_size >> phydev->erasesize_shift; |
| |
| cpu_type = get_cpu_type(); |
| /* for compatible with history. */ |
| if ((cpu_type == MESON_CPU_MAJOR_ID_M8) |
| && (phydev->erasesize < 0x400000)) |
| adjust_blk = 0; |
| else |
| adjust_blk = ADJUST_PART_SIZE - 1; |
| |
| tmp_value = total_blk; |
| tmp_value += adjust_blk; |
| tmp_value /= ADJUST_PART_SIZE; |
| tmp_value += total_blk + ADJUST_BLOCK_NUM; |
| total_blk = tmp_value; |
| |
| memset(ops_para, |
| 0, |
| sizeof(struct chip_ops_para)); |
| ops_para->option = phydev->option; |
| /* |
| if (!is_phydev_off_adjust()) { |
| do { |
| ops_para->page_addr = |
| ((((u32)((((u32)(offset >> tmp_erase_shift)))) / |
| chip_num) + tmp_blk - tmp_blk / chip_num) * pages_per_blk); |
| ops_para->chipnr = |
| ((u32) (offset >> tmp_erase_shift))%chip_num; |
| ret = operation->block_isbad(aml_chip); |
| if (ret == NAND_BLOCK_FACTORY_BAD) { |
| offset += flash->blocksize; |
| continue; |
| } |
| start_blk++; |
| offset += flash->blocksize; |
| } while (start_blk < total_blk); |
| |
| total_blk = |
| ((((u32) (offset >> phydev->erasesize_shift)) - 1) / \ |
| chip_num*plane_num) + 1) * (chip_num*plane_num); |
| aml_nand_dbg("total_blk =%d", total_blk); |
| phydev->size = ((u64)total_blk*(u64)phydev->erasesize); |
| } else { |
| */ |
| do { |
| tmp_value = |
| offset>>phydev->writesize_shift; |
| ops_para->page_addr = (u32)(tmp_value); |
| /* |
| ops_para->chipnr = |
| ((u32)(offset>>phydev->erasesize_shift))%chip_num; |
| */ |
| ret = operation->block_isbad(aml_chip); |
| if (ret == NAND_BLOCK_FACTORY_BAD) { |
| offset += phydev->erasesize; |
| continue; |
| } |
| start_blk++; |
| offset += phydev->erasesize; |
| } while (start_blk < total_blk); |
| |
| tmp_value = offset - tmp_offset; |
| tmp_value = |
| (tmp_value >> phydev->erasesize_shift); |
| tmp_value -= 1; |
| tmp_value /= chip_num * plane_num; |
| tmp_value += 1; |
| total_blk = tmp_value * chip_num * plane_num; |
| //printk("%s() %d, total_blk 0x%x, erase_size 0x%x\n", __func__, __LINE__, total_blk, phydev->erasesize); |
| phydev->size = ((u64)total_blk * |
| (u64)phydev->erasesize); |
| /* |
| } |
| */ |
| } else |
| phydev->size = dev_size; |
| |
| if ((phydev->offset + phydev->size) > chip_size) { |
| aml_nand_msg("nand dev size is out of space"); |
| ret = -NAND_FAILED; |
| goto exit_error0; |
| } |
| #ifndef AML_NAND_UBOOT |
| phydev->suspend = phydev_suspend; |
| phydev->resume = phydev_resume; |
| #endif |
| } |
| |
| phydev_pre = phydev; |
| |
| if ((dev_para->option & DEV_SLC_MODE) |
| && (flash->option & NAND_CHIP_SLC_MODE) |
| && (!(phydev->option & DEV_MULTI_PLANE_MODE))) { |
| phydev->option |= DEV_SLC_MODE; |
| phydev->erasesize >>= 1; |
| phydev->erasesize_shift = ffs(phydev->erasesize) - 1; |
| phydev->size >>= 1; |
| aml_nand_msg(" DEV %s enable SLC mode", phydev->name); |
| } |
| |
| aml_chip->phy_part_ptr->partition[i].phy_off = phydev->offset; |
| aml_chip->phy_part_ptr->partition[i].phy_len = phydev->size; |
| if (dev_para->nr_partitions) { |
| for (k = 0; k < dev_para->nr_partitions; k++) { |
| partition = &(dev_para->partitions[k]); |
| aml_chip->phy_part_ptr->partition[i].logic_len += |
| partition->size; |
| } |
| } else |
| aml_chip->phy_part_ptr->partition[i].logic_len = dev_para->size; |
| |
| /* fixme, add new physic device */ |
| //list_add_tail(&phydev->list, &nphy_dev_list); |
| phydev_add_list(phydev); |
| |
| #if (AML_NAND_DBG) |
| amldev_dumpinfo(phydev); |
| #endif |
| aml_nand_dbg("####: %s :phydev->offset = %llx", |
| phydev_pre->name, phydev_pre->offset); |
| aml_nand_dbg("####: %s :phydev->size = %llx", |
| phydev_pre->name, phydev_pre->size); |
| aml_nand_dbg("####: %s :phydev->writesize = %x", |
| phydev_pre->name, phydev_pre->writesize); |
| aml_nand_dbg("####: %s :phydev->erasesize = %x", |
| phydev_pre->name, phydev_pre->erasesize); |
| |
| } |
| /*save phy partitions to nand*/ |
| aml_chip->phy_part_ptr->dev_num = config->dev_num; |
| aml_chip->phy_part_ptr->crc = |
| aml_info_checksum((u8 *)aml_chip->phy_part_ptr->partition, |
| (MAX_DEVICE_NUM * sizeof(struct _phy_partition))); |
| ret = amlnand_save_info_by_name(aml_chip, |
| (u8 *)&(aml_chip->nand_phy_partition), |
| (u8 *)aml_chip->phy_part_ptr, |
| (u8 *)PHY_PARTITION_HEAD_MAGIC, |
| sizeof(struct phy_partition_info)); |
| if (ret < 0) { |
| aml_nand_msg("save nand phy partition failed and ret:%d",ret); |
| goto exit_error0; |
| } |
| } |
| |
| show_phydev_info(); |
| PHYDEV_LINE |
| #if 0 |
| phydev = NULL; |
| list_for_each_entry(phydev, &nphy_dev_list, list) { |
| if (phydev == NULL) |
| break; |
| |
| aml_nand_dbg("-----------------------------\n"); |
| aml_nand_dbg("name:%s,offset:%llx,size:%llx,option:%x", |
| phydev->name, |
| phydev->offset, |
| phydev->size, |
| phydev->option); |
| aml_nand_dbg("es:%x,ws:%x,oob:%x,eshift:%x,wshift:%d", |
| phydev->erasesize, |
| phydev->writesize, |
| phydev->oobavail, |
| phydev->erasesize_shift, |
| phydev->writesize_shift); |
| aml_nand_dbg(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); |
| relative_offset = 0; |
| bad_blk_cnt = 0; |
| devops = &(phydev->ops); |
| memset(bad_blk, 0, 128*sizeof(u64)); |
| do { |
| memset(devops, 0x0, sizeof(struct phydev_ops)); |
| memset(devops, 0x0, sizeof(struct phydev_ops)); |
| devops->addr = relative_offset; |
| devops->len = phydev->erasesize; |
| devops->datbuf = NULL; |
| devops->oobbuf = NULL; |
| devops->mode = NAND_HW_ECC; |
| ret = nand_block_isbad(phydev); |
| if (ret == NAND_BLOCK_USED_BAD) { |
| if (bad_blk_cnt < 128) { |
| bad_blk[bad_blk_cnt] = relative_offset; |
| bad_blk_cnt++; |
| } |
| } |
| relative_offset += phydev->erasesize; |
| } while (relative_offset < phydev->size); |
| |
| aml_nand_msg("(%s) bad blks %d", phydev->name, bad_blk_cnt); |
| |
| if ((bad_blk_cnt * 32 > |
| (phydev->size >> phydev->erasesize_shift)) || |
| (bad_blk_cnt > 10)) { |
| aml_nand_dbg("Too many new bad blks,try to repair..\n"); |
| /* |
| ret = aml_repair_bbt(phydev,bad_blk,bad_blk_cnt); |
| */ |
| } |
| } |
| kfree(bad_blk); |
| #endif |
| #ifdef AML_NAND_UBOOT |
| if (aml_chip->init_flag == NAND_BOOT_ERASE_PROTECT_CACHE) { |
| ret = phydev_init_erase(aml_chip); |
| if (ret < 0) { |
| aml_nand_msg("amlnand_phydev_init : phydev_init_erase failed"); |
| } |
| } |
| #endif /* AML_NAND_UBOOT */ |
| return NAND_SUCCESS; |
| |
| exit_error0: |
| return ret; |
| } |
| |
| #ifdef AML_NAND_UBOOT |
| void amlnf_phy_exit(void) |
| { |
| |
| struct amlnand_phydev *phydev = NULL; |
| struct amlnand_chip *aml_chip = NULL; |
| //struct list_head *entry; |
| int time= 0; |
| list_for_each_entry(phydev,&nphy_dev_list,list){ |
| |
| if (phydev) { |
| if (time == 0) { |
| aml_chip = (struct amlnand_chip *)phydev->priv; |
| if (aml_chip) { |
| if (aml_chip->block_status) { |
| kfree(aml_chip->block_status); |
| aml_chip->block_status = NULL; |
| } |
| if (aml_chip->user_page_buf) { |
| kfree(aml_chip->user_page_buf); |
| aml_chip->user_page_buf = NULL; |
| } |
| if (aml_chip->user_oob_buf) { |
| kfree(aml_chip->user_oob_buf); |
| aml_chip->user_oob_buf = NULL; |
| } |
| if (aml_chip->shipped_bbt_ptr) { |
| kfree(aml_chip->shipped_bbt_ptr); |
| aml_chip->shipped_bbt_ptr = NULL; |
| } |
| if (aml_chip->config_ptr) { |
| kfree(aml_chip->config_ptr); |
| aml_chip->config_ptr = NULL; |
| } |
| } |
| time++; |
| } |
| } |
| } |
| |
| list_del_init(&nphy_dev_list); |
| |
| } |
| #endif |
| |