diff options
Diffstat (limited to 'drivers/mtd/nand/spi/core.c')
-rw-r--r-- | drivers/mtd/nand/spi/core.c | 222 |
1 files changed, 166 insertions, 56 deletions
diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 9ca012d481..0c88310e28 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -51,9 +51,11 @@ static void spinand_cache_op_adjust_colum(struct spinand_device *spinand, static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val) { - struct spi_mem_op op = SPINAND_GET_FEATURE_OP(reg, - spinand->scratchbuf); int ret; + struct spi_mem_op op = spinand->ctrl_ops->ops.get_feature; + + op.data.buf.out = spinand->scratchbuf; + memset(&op.addr.val, reg, op.addr.nbytes); ret = spi_mem_exec_op(spinand->slave, &op); if (ret) @@ -65,10 +67,11 @@ static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val) static int spinand_write_reg_op(struct spinand_device *spinand, u8 reg, u8 val) { - struct spi_mem_op op = SPINAND_SET_FEATURE_OP(reg, - spinand->scratchbuf); + struct spi_mem_op op = spinand->ctrl_ops->ops.set_feature; - *spinand->scratchbuf = val; + op.data.buf.out = spinand->scratchbuf; + memset(&op.addr.val, reg, op.addr.nbytes); + memset(spinand->scratchbuf, val, op.data.nbytes); return spi_mem_exec_op(spinand->slave, &op); } @@ -206,9 +209,9 @@ static int spinand_init_quad_enable(struct spinand_device *spinand) if (!(spinand->flags & SPINAND_HAS_QE_BIT)) return 0; - if (spinand->op_templates.read_cache->data.buswidth == 4 || - spinand->op_templates.write_cache->data.buswidth == 4 || - spinand->op_templates.update_cache->data.buswidth == 4) + if (spinand->data_ops.read_cache->data.buswidth == 4 || + spinand->data_ops.write_cache->data.buswidth == 4 || + spinand->data_ops.update_cache->data.buswidth == 4) enable = true; return spinand_upd_cfg(spinand, CFG_QUAD_ENABLE, @@ -222,9 +225,9 @@ static int spinand_ecc_enable(struct spinand_device *spinand, enable ? CFG_ECC_ENABLE : 0); } -static int spinand_write_enable_op(struct spinand_device *spinand) +int spinand_write_enable_op(struct spinand_device *spinand) { - struct spi_mem_op op = SPINAND_WR_EN_DIS_OP(true); + struct spi_mem_op op = spinand->ctrl_ops->ops.write_enable; return spi_mem_exec_op(spinand->slave, &op); } @@ -234,15 +237,16 @@ static int spinand_load_page_op(struct spinand_device *spinand, { struct nand_device *nand = spinand_to_nand(spinand); unsigned int row = nanddev_pos_to_row(nand, &req->pos); - struct spi_mem_op op = SPINAND_PAGE_READ_OP(row); + struct spi_mem_op op = spinand->ctrl_ops->ops.page_read; + op.addr.val = row; return spi_mem_exec_op(spinand->slave, &op); } static int spinand_read_from_cache_op(struct spinand_device *spinand, const struct nand_page_io_req *req) { - struct spi_mem_op op = *spinand->op_templates.read_cache; + struct spi_mem_op op = *spinand->data_ops.read_cache; struct nand_device *nand = spinand_to_nand(spinand); struct mtd_info *mtd = nanddev_to_mtd(nand); struct nand_page_io_req adjreq = *req; @@ -315,28 +319,34 @@ static int spinand_read_from_cache_op(struct spinand_device *spinand, static int spinand_write_to_cache_op(struct spinand_device *spinand, const struct nand_page_io_req *req) { - struct spi_mem_op op = *spinand->op_templates.write_cache; + struct spi_mem_op op = *spinand->data_ops.write_cache; struct nand_device *nand = spinand_to_nand(spinand); struct mtd_info *mtd = nanddev_to_mtd(nand); struct nand_page_io_req adjreq = *req; - unsigned int nbytes = 0; - void *buf = NULL; + void *buf = spinand->databuf; + unsigned int nbytes; u16 column = 0; int ret; - memset(spinand->databuf, 0xff, - nanddev_page_size(nand) + - nanddev_per_page_oobsize(nand)); + /* + * Looks like PROGRAM LOAD (AKA write cache) does not necessarily reset + * the cache content to 0xFF (depends on vendor implementation), so we + * must fill the page cache entirely even if we only want to program + * the data portion of the page, otherwise we might corrupt the BBM or + * user data previously programmed in OOB area. + */ + nbytes = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand); + memset(spinand->databuf, 0xff, nbytes); + adjreq.dataoffs = 0; + adjreq.datalen = nanddev_page_size(nand); + adjreq.databuf.out = spinand->databuf; + adjreq.ooblen = nanddev_per_page_oobsize(nand); + adjreq.ooboffs = 0; + adjreq.oobbuf.out = spinand->oobbuf; - if (req->datalen) { + if (req->datalen) memcpy(spinand->databuf + req->dataoffs, req->databuf.out, req->datalen); - adjreq.dataoffs = 0; - adjreq.datalen = nanddev_page_size(nand); - adjreq.databuf.out = spinand->databuf; - nbytes = adjreq.datalen; - buf = spinand->databuf; - } if (req->ooblen) { if (req->mode == MTD_OPS_AUTO_OOB) @@ -347,19 +357,11 @@ static int spinand_write_to_cache_op(struct spinand_device *spinand, else memcpy(spinand->oobbuf + req->ooboffs, req->oobbuf.out, req->ooblen); - - adjreq.ooblen = nanddev_per_page_oobsize(nand); - adjreq.ooboffs = 0; - nbytes += nanddev_per_page_oobsize(nand); - if (!buf) { - buf = spinand->oobbuf; - column = nanddev_page_size(nand); - } } spinand_cache_op_adjust_colum(spinand, &adjreq, &column); - op = *spinand->op_templates.update_cache; + op = *spinand->data_ops.update_cache; op.addr.val = column; /* @@ -385,12 +387,12 @@ static int spinand_write_to_cache_op(struct spinand_device *spinand, /* * We need to use the RANDOM LOAD CACHE operation if there's - * more than one iteration, because the LOAD operation resets - * the cache to 0xff. + * more than one iteration, because the LOAD operation might + * reset the cache to 0xff. */ if (nbytes) { column = op.addr.val; - op = *spinand->op_templates.update_cache; + op = *spinand->data_ops.update_cache; op.addr.val = column; } } @@ -403,8 +405,9 @@ static int spinand_program_op(struct spinand_device *spinand, { struct nand_device *nand = spinand_to_nand(spinand); unsigned int row = nanddev_pos_to_row(nand, &req->pos); - struct spi_mem_op op = SPINAND_PROG_EXEC_OP(row); + struct spi_mem_op op = spinand->ctrl_ops->ops.program_execute; + op.addr.val = row; return spi_mem_exec_op(spinand->slave, &op); } @@ -413,8 +416,9 @@ static int spinand_erase_op(struct spinand_device *spinand, { struct nand_device *nand = &spinand->base; unsigned int row = nanddev_pos_to_row(nand, pos); - struct spi_mem_op op = SPINAND_BLK_ERASE_OP(row); + struct spi_mem_op op = spinand->ctrl_ops->ops.block_erase; + op.addr.val = row; return spi_mem_exec_op(spinand->slave, &op); } @@ -465,7 +469,7 @@ static int spinand_read_id_op(struct spinand_device *spinand, u8 *buf) static int spinand_reset_op(struct spinand_device *spinand) { - struct spi_mem_op op = SPINAND_RESET_OP; + struct spi_mem_op op = spinand->ctrl_ops->ops.reset; int ret; ret = spi_mem_exec_op(spinand->slave, &op); @@ -833,6 +837,16 @@ static const struct spinand_manufacturer *spinand_manufacturers[] = { &winbond_spinand_manufacturer, }; +static const struct spinand_ctrl_ops spinand_default_ctrl_ops = + SPINAND_CTRL_OPS(SPINAND_1S, + SPINAND_RESET_OP, + SPINAND_GET_FEATURE_OP(0, NULL), + SPINAND_SET_FEATURE_OP(0, NULL), + SPINAND_WR_EN_DIS_OP(true), + SPINAND_BLK_ERASE_OP(0), + SPINAND_PAGE_READ_OP(0), + SPINAND_PROG_EXEC_OP(0)); + static int spinand_manufacturer_detect(struct spinand_device *spinand) { unsigned int i; @@ -867,8 +881,8 @@ static void spinand_manufacturer_cleanup(struct spinand_device *spinand) } static const struct spi_mem_op * -spinand_select_op_variant(struct spinand_device *spinand, - const struct spinand_op_variants *variants) +spinand_select_data_op_variant(struct spinand_device *spinand, + const struct spinand_op_variants *variants) { struct nand_device *nand = spinand_to_nand(spinand); unsigned int i; @@ -900,6 +914,93 @@ spinand_select_op_variant(struct spinand_device *spinand, return NULL; } +static const struct spinand_ctrl_ops * +spinand_select_ctrl_ops_variant(struct spinand_device *spinand, + const struct spinand_ctrl_ops_variants *variants, + const enum spinand_protocol protocol) +{ + unsigned int i; + + for (i = 0; i < variants->nvariants; i++) { + const struct spinand_ctrl_ops *ctrl_ops = + &variants->ctrl_ops_list[i]; + + if (ctrl_ops->protocol != protocol) + continue; + + if (!spi_mem_supports_op(spinand->slave, + &ctrl_ops->ops.reset) || + !spi_mem_supports_op(spinand->slave, + &ctrl_ops->ops.get_feature) || + !spi_mem_supports_op(spinand->slave, + &ctrl_ops->ops.set_feature) || + !spi_mem_supports_op(spinand->slave, + &ctrl_ops->ops.write_enable) || + !spi_mem_supports_op(spinand->slave, + &ctrl_ops->ops.block_erase) || + !spi_mem_supports_op(spinand->slave, + &ctrl_ops->ops.page_read) || + !spi_mem_supports_op(spinand->slave, + &ctrl_ops->ops.program_execute)) + continue; + + return ctrl_ops; + } + + return NULL; +} + +static bool spinand_op_is_octal_dtr(const struct spi_mem_op *op) +{ + return op->cmd.buswidth == 8 && op->cmd.dtr && + op->addr.buswidth == 8 && op->addr.dtr && + op->data.buswidth == 8 && op->data.dtr; +} + +static int spinand_init_octal_dtr_enable(struct spinand_device *spinand) +{ + struct udevice *dev = spinand->slave->dev; + const struct spinand_ctrl_ops *octal_dtr_ctrl_ops; + int ret; + + if (!(spinand->flags & SPINAND_HAS_OCTAL_DTR_BIT)) + return 0; + + if (!(spinand_op_is_octal_dtr(spinand->data_ops.read_cache) && + spinand_op_is_octal_dtr(spinand->data_ops.write_cache) && + spinand_op_is_octal_dtr(spinand->data_ops.update_cache))) + return 0; + + octal_dtr_ctrl_ops = spinand_select_ctrl_ops_variant(spinand, + spinand->desc_entry->ctrl_ops_variants, + SPINAND_8D); + + if (!octal_dtr_ctrl_ops) + return 0; + + if (!spinand->manufacturer->ops->change_protocol) { + dev_info(dev, + "Missing ->change_mode(), unable to switch mode\n"); + return -EOPNOTSUPP; + } + + ret = spinand->manufacturer->ops->change_protocol(spinand, SPINAND_8D); + if (ret) { + dev_err(dev, + "Failed to enable Octal DTR SPI mode (err = %d)\n", + ret); + return ret; + } + + spinand->protocol = SPINAND_8D; + spinand->ctrl_ops = octal_dtr_ctrl_ops; + + dev_dbg(dev, + "%s SPI NAND switched to Octal DTR SPI (8D-8D-8D) mode\n", + spinand->manufacturer->name); + return 0; +} + /** * spinand_match_and_init() - Try to find a match between a device ID and an * entry in a spinand_info table @@ -934,24 +1035,25 @@ int spinand_match_and_init(struct spinand_device *spinand, spinand->eccinfo = table[i].eccinfo; spinand->flags = table[i].flags; spinand->select_target = table[i].select_target; + spinand->desc_entry = &table[i]; - op = spinand_select_op_variant(spinand, - info->op_variants.read_cache); + op = spinand_select_data_op_variant(spinand, + info->data_ops_variants.read_cache); if (!op) return -ENOTSUPP; - spinand->op_templates.read_cache = op; + spinand->data_ops.read_cache = op; - op = spinand_select_op_variant(spinand, - info->op_variants.write_cache); + op = spinand_select_data_op_variant(spinand, + info->data_ops_variants.write_cache); if (!op) return -ENOTSUPP; - spinand->op_templates.write_cache = op; + spinand->data_ops.write_cache = op; - op = spinand_select_op_variant(spinand, - info->op_variants.update_cache); - spinand->op_templates.update_cache = op; + op = spinand_select_data_op_variant(spinand, + info->data_ops_variants.update_cache); + spinand->data_ops.update_cache = op; return 0; } @@ -1035,6 +1137,9 @@ static int spinand_init(struct spinand_device *spinand) if (!spinand->scratchbuf) return -ENOMEM; + spinand->protocol = SPINAND_1S; + spinand->ctrl_ops = &spinand_default_ctrl_ops; + ret = spinand_detect(spinand); if (ret) goto err_free_bufs; @@ -1085,6 +1190,10 @@ static int spinand_init(struct spinand_device *spinand) goto err_free_bufs; } + ret = spinand_init_octal_dtr_enable(spinand); + if (ret) + goto err_manuf_cleanup; + ret = nanddev_init(nand, &spinand_ops, THIS_MODULE); if (ret) goto err_manuf_cleanup; @@ -1188,26 +1297,25 @@ err_spinand_cleanup: return ret; } -#ifndef __UBOOT__ static int spinand_remove(struct udevice *slave) { - struct spinand_device *spinand; + struct spinand_device *spinand = dev_get_priv(slave); struct mtd_info *mtd; - int ret; - spinand = spi_mem_get_drvdata(slave); mtd = spinand_to_mtd(spinand); free(mtd->name); +#ifndef __UBOOT__ ret = mtd_device_unregister(mtd); if (ret) return ret; - +#endif spinand_cleanup(spinand); return 0; } +#ifndef __UBOOT__ static const struct spi_device_id spinand_ids[] = { { .name = "spi-nand" }, { /* sentinel */ }, @@ -1249,4 +1357,6 @@ U_BOOT_DRIVER(spinand) = { .of_match = spinand_ids, .priv_auto_alloc_size = sizeof(struct spinand_device), .probe = spinand_probe, + .remove = spinand_remove, + .flags = DM_FLAG_OS_PREPARE, }; |