diff options
Diffstat (limited to 'drivers/mtd/nand/raw/fsmc_nand.c')
| -rw-r--r-- | drivers/mtd/nand/raw/fsmc_nand.c | 191 |
1 files changed, 119 insertions, 72 deletions
diff --git a/drivers/mtd/nand/raw/fsmc_nand.c b/drivers/mtd/nand/raw/fsmc_nand.c index c9149a37f8f0..b13b2b0c3f30 100644 --- a/drivers/mtd/nand/raw/fsmc_nand.c +++ b/drivers/mtd/nand/raw/fsmc_nand.c @@ -15,6 +15,7 @@ #include <linux/clk.h> #include <linux/completion.h> +#include <linux/delay.h> #include <linux/dmaengine.h> #include <linux/dma-direction.h> #include <linux/dma-mapping.h> @@ -25,8 +26,8 @@ #include <linux/sched.h> #include <linux/types.h> #include <linux/mtd/mtd.h> +#include <linux/mtd/nand-ecc-sw-hamming.h> #include <linux/mtd/rawnand.h> -#include <linux/mtd/nand_ecc.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/mtd/partitions.h> @@ -93,6 +94,14 @@ #define FSMC_BUSY_WAIT_TIMEOUT (1 * HZ) +/* + * According to SPEAr300 Reference Manual (RM0082) + * TOUDEL = 7ns (Output delay from the flip-flops to the board) + * TINDEL = 5ns (Input delay from the board to the flipflop) + */ +#define TOUTDEL 7000 +#define TINDEL 5000 + struct fsmc_nand_timings { u8 tclr; u8 tar; @@ -277,7 +286,7 @@ static int fsmc_calc_timings(struct fsmc_nand_data *host, { unsigned long hclk = clk_get_rate(host->clk); unsigned long hclkn = NSEC_PER_SEC / hclk; - u32 thiz, thold, twait, tset; + u32 thiz, thold, twait, tset, twait_min; if (sdrt->tRC_min < 30000) return -EOPNOTSUPP; @@ -309,13 +318,6 @@ static int fsmc_calc_timings(struct fsmc_nand_data *host, else if (tims->thold > FSMC_THOLD_MASK) tims->thold = FSMC_THOLD_MASK; - twait = max(sdrt->tRP_min, sdrt->tWP_min); - tims->twait = DIV_ROUND_UP(twait / 1000, hclkn) - 1; - if (tims->twait == 0) - tims->twait = 1; - else if (tims->twait > FSMC_TWAIT_MASK) - tims->twait = FSMC_TWAIT_MASK; - tset = max(sdrt->tCS_min - sdrt->tWP_min, sdrt->tCEA_max - sdrt->tREA_max); tims->tset = DIV_ROUND_UP(tset / 1000, hclkn) - 1; @@ -324,11 +326,26 @@ static int fsmc_calc_timings(struct fsmc_nand_data *host, else if (tims->tset > FSMC_TSET_MASK) tims->tset = FSMC_TSET_MASK; + /* + * According to SPEAr300 Reference Manual (RM0082) which gives more + * information related to FSMSC timings than the SPEAr600 one (RM0305), + * twait >= tCEA - (tset * TCLK) + TOUTDEL + TINDEL + */ + twait_min = sdrt->tCEA_max - ((tims->tset + 1) * hclkn * 1000) + + TOUTDEL + TINDEL; + twait = max3(sdrt->tRP_min, sdrt->tWP_min, twait_min); + + tims->twait = DIV_ROUND_UP(twait / 1000, hclkn) - 1; + if (tims->twait == 0) + tims->twait = 1; + else if (tims->twait > FSMC_TWAIT_MASK) + tims->twait = FSMC_TWAIT_MASK; + return 0; } -static int fsmc_setup_data_interface(struct nand_chip *nand, int csline, - const struct nand_data_interface *conf) +static int fsmc_setup_interface(struct nand_chip *nand, int csline, + const struct nand_interface_config *conf) { struct fsmc_nand_data *host = nand_to_fsmc(nand); struct fsmc_nand_timings tims; @@ -433,6 +450,17 @@ static int fsmc_read_hwecc_ecc1(struct nand_chip *chip, const u8 *data, return 0; } +static int fsmc_correct_ecc1(struct nand_chip *chip, + unsigned char *buf, + unsigned char *read_ecc, + unsigned char *calc_ecc) +{ + bool sm_order = chip->ecc.options & NAND_ECC_SOFT_HAMMING_SM_ORDER; + + return ecc_sw_hamming_correct(buf, read_ecc, calc_ecc, + chip->ecc.size, sm_order); +} + /* Count the number of 0's in buff upto a max of max_bits */ static int count_written_bits(u8 *buff, int size, int max_bits) { @@ -475,6 +503,8 @@ static int dma_xfer(struct fsmc_nand_data *host, void *buffer, int len, dma_dev = chan->device; dma_addr = dma_map_single(dma_dev->dev, buffer, len, direction); + if (dma_mapping_error(dma_dev->dev, dma_addr)) + return -EINVAL; if (direction == DMA_TO_DEVICE) { dma_src = dma_addr; @@ -608,33 +638,28 @@ static int fsmc_exec_op(struct nand_chip *chip, const struct nand_operation *op, unsigned int op_id; int i; + if (check_only) + return 0; + pr_debug("Executing operation [%d instructions]:\n", op->ninstrs); for (op_id = 0; op_id < op->ninstrs; op_id++) { instr = &op->instrs[op_id]; + nand_op_trace(" ", instr); + switch (instr->type) { case NAND_OP_CMD_INSTR: - pr_debug(" ->CMD [0x%02x]\n", - instr->ctx.cmd.opcode); - writeb_relaxed(instr->ctx.cmd.opcode, host->cmd_va); break; case NAND_OP_ADDR_INSTR: - pr_debug(" ->ADDR [%d cyc]", - instr->ctx.addr.naddrs); - for (i = 0; i < instr->ctx.addr.naddrs; i++) writeb_relaxed(instr->ctx.addr.addrs[i], host->addr_va); break; case NAND_OP_DATA_IN_INSTR: - pr_debug(" ->DATA_IN [%d B%s]\n", instr->ctx.data.len, - instr->ctx.data.force_8bit ? - ", force 8-bit" : ""); - if (host->mode == USE_DMA_ACCESS) fsmc_read_buf_dma(host, instr->ctx.data.buf.in, instr->ctx.data.len); @@ -644,10 +669,6 @@ static int fsmc_exec_op(struct nand_chip *chip, const struct nand_operation *op, break; case NAND_OP_DATA_OUT_INSTR: - pr_debug(" ->DATA_OUT [%d B%s]\n", instr->ctx.data.len, - instr->ctx.data.force_8bit ? - ", force 8-bit" : ""); - if (host->mode == USE_DMA_ACCESS) fsmc_write_buf_dma(host, instr->ctx.data.buf.out, @@ -658,13 +679,13 @@ static int fsmc_exec_op(struct nand_chip *chip, const struct nand_operation *op, break; case NAND_OP_WAITRDY_INSTR: - pr_debug(" ->WAITRDY [max %d ms]\n", - instr->ctx.waitrdy.timeout_ms); - ret = nand_soft_waitrdy(chip, instr->ctx.waitrdy.timeout_ms); break; } + + if (instr->delay_ns) + ndelay(instr->delay_ns); } return ret; @@ -706,7 +727,7 @@ static int fsmc_read_page_hwecc(struct nand_chip *chip, u8 *buf, for (i = 0, s = 0; s < eccsteps; s++, i += eccbytes, p += eccsize) { nand_read_page_op(chip, page, s * eccsize, NULL, 0); chip->ecc.hwctl(chip, NAND_ECC_READ); - ret = nand_read_data_op(chip, p, eccsize, false); + ret = nand_read_data_op(chip, p, eccsize, false, false); if (ret) return ret; @@ -824,11 +845,12 @@ static int fsmc_bch8_correct_data(struct nand_chip *chip, u8 *dat, i = 0; while (num_err--) { - change_bit(0, (unsigned long *)&err_idx[i]); - change_bit(1, (unsigned long *)&err_idx[i]); + err_idx[i] ^= 3; if (err_idx[i] < chip->ecc.size * 8) { - change_bit(err_idx[i], (unsigned long *)dat); + int err = err_idx[i]; + + dat[err >> 3] ^= BIT(err & 7); i++; } } @@ -854,13 +876,17 @@ static int fsmc_nand_probe_config_dt(struct platform_device *pdev, if (!of_property_read_u32(np, "bank-width", &val)) { if (val == 2) { nand->options |= NAND_BUSWIDTH_16; - } else if (val != 1) { + } else if (val == 1) { + nand->options |= NAND_BUSWIDTH_AUTO; + } else { dev_err(&pdev->dev, "invalid bank-width %u\n", val); return -EINVAL; } + } else { + nand->options |= NAND_BUSWIDTH_AUTO; } - if (of_get_property(np, "nand-skip-bbtscan", NULL)) + if (of_property_read_bool(np, "nand-skip-bbtscan")) nand->options |= NAND_SKIP_BBTSCAN; host->dev_timings = devm_kzalloc(&pdev->dev, @@ -891,6 +917,20 @@ static int fsmc_nand_attach_chip(struct nand_chip *nand) struct mtd_info *mtd = nand_to_mtd(nand); struct fsmc_nand_data *host = nand_to_fsmc(nand); + if (nand->ecc.engine_type == NAND_ECC_ENGINE_TYPE_INVALID) + nand->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + + if (!nand->ecc.size) + nand->ecc.size = 512; + + if (AMBA_REV_BITS(host->pid) >= 8) { + nand->ecc.read_page = fsmc_read_page_hwecc; + nand->ecc.calculate = fsmc_read_hwecc_ecc4; + nand->ecc.correct = fsmc_bch8_correct_data; + nand->ecc.bytes = 13; + nand->ecc.strength = 8; + } + if (AMBA_REV_BITS(host->pid) >= 8) { switch (mtd->oobsize) { case 16: @@ -911,24 +951,26 @@ static int fsmc_nand_attach_chip(struct nand_chip *nand) return 0; } - switch (nand->ecc.mode) { - case NAND_ECC_HW: + switch (nand->ecc.engine_type) { + case NAND_ECC_ENGINE_TYPE_ON_HOST: dev_info(host->dev, "Using 1-bit HW ECC scheme\n"); nand->ecc.calculate = fsmc_read_hwecc_ecc1; - nand->ecc.correct = nand_correct_data; + nand->ecc.correct = fsmc_correct_ecc1; + nand->ecc.hwctl = fsmc_enable_hwecc; nand->ecc.bytes = 3; nand->ecc.strength = 1; nand->ecc.options |= NAND_ECC_SOFT_HAMMING_SM_ORDER; break; - case NAND_ECC_SOFT: - if (nand->ecc.algo == NAND_ECC_BCH) { + case NAND_ECC_ENGINE_TYPE_SOFT: + if (nand->ecc.algo == NAND_ECC_ALGO_BCH) { dev_info(host->dev, "Using 4-bit SW BCH ECC scheme\n"); break; } + break; - case NAND_ECC_ON_DIE: + case NAND_ECC_ENGINE_TYPE_ON_DIE: break; default: @@ -938,9 +980,9 @@ static int fsmc_nand_attach_chip(struct nand_chip *nand) /* * Don't set layout for BCH4 SW ECC. This will be - * generated later in nand_bch_init() later. + * generated later during BCH initialization. */ - if (nand->ecc.mode == NAND_ECC_HW) { + if (nand->ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) { switch (mtd->oobsize) { case 16: case 64: @@ -962,9 +1004,22 @@ static int fsmc_nand_attach_chip(struct nand_chip *nand) static const struct nand_controller_ops fsmc_nand_controller_ops = { .attach_chip = fsmc_nand_attach_chip, .exec_op = fsmc_exec_op, - .setup_data_interface = fsmc_setup_data_interface, + .setup_interface = fsmc_setup_interface, }; +/** + * fsmc_nand_disable() - Disables the NAND bank + * @host: The instance to disable + */ +static void fsmc_nand_disable(struct fsmc_nand_data *host) +{ + u32 val; + + val = readl(host->regs_va + FSMC_PC); + val &= ~FSMC_ENABLE; + writel(val, host->regs_va + FSMC_PC); +} + /* * fsmc_nand_probe - Probe function * @pdev: platform device structure @@ -1017,16 +1072,12 @@ static int __init fsmc_nand_probe(struct platform_device *pdev) host->regs_va = base + FSMC_NOR_REG_SIZE + (host->bank * FSMC_NAND_BANK_SZ); - host->clk = devm_clk_get(&pdev->dev, NULL); + host->clk = devm_clk_get_enabled(&pdev->dev, NULL); if (IS_ERR(host->clk)) { dev_err(&pdev->dev, "failed to fetch block clock\n"); return PTR_ERR(host->clk); } - ret = clk_prepare_enable(host->clk); - if (ret) - return ret; - /* * This device ID is actually a common AMBA ID as used on the * AMBA PrimeCell bus. However it is not a PrimeCell. @@ -1053,13 +1104,6 @@ static int __init fsmc_nand_probe(struct platform_device *pdev) mtd->dev.parent = &pdev->dev; - /* - * Setup default ECC mode. nand_dt_init() called from nand_scan_ident() - * can overwrite this value if the DT provides a different value. - */ - nand->ecc.mode = NAND_ECC_HW; - nand->ecc.hwctl = fsmc_enable_hwecc; - nand->ecc.size = 512; nand->badblockbits = 7; if (host->mode == USE_DMA_ACCESS) { @@ -1068,11 +1112,13 @@ static int __init fsmc_nand_probe(struct platform_device *pdev) host->read_dma_chan = dma_request_channel(mask, filter, NULL); if (!host->read_dma_chan) { dev_err(&pdev->dev, "Unable to get read dma channel\n"); - goto disable_clk; + ret = -ENODEV; + goto disable_fsmc; } host->write_dma_chan = dma_request_channel(mask, filter, NULL); if (!host->write_dma_chan) { dev_err(&pdev->dev, "Unable to get write dma channel\n"); + ret = -ENODEV; goto release_dma_read_chan; } } @@ -1082,14 +1128,6 @@ static int __init fsmc_nand_probe(struct platform_device *pdev) nand->options |= NAND_KEEP_TIMINGS; } - if (AMBA_REV_BITS(host->pid) >= 8) { - nand->ecc.read_page = fsmc_read_page_hwecc; - nand->ecc.calculate = fsmc_read_hwecc_ecc4; - nand->ecc.correct = fsmc_bch8_correct_data; - nand->ecc.bytes = 13; - nand->ecc.strength = 8; - } - nand_controller_init(&host->base); host->base.ops = &fsmc_nand_controller_ops; nand->controller = &host->base; @@ -1119,8 +1157,8 @@ release_dma_write_chan: release_dma_read_chan: if (host->mode == USE_DMA_ACCESS) dma_release_channel(host->read_dma_chan); -disable_clk: - clk_disable_unprepare(host->clk); +disable_fsmc: + fsmc_nand_disable(host); return ret; } @@ -1128,21 +1166,24 @@ disable_clk: /* * Clean up routine */ -static int fsmc_nand_remove(struct platform_device *pdev) +static void fsmc_nand_remove(struct platform_device *pdev) { struct fsmc_nand_data *host = platform_get_drvdata(pdev); if (host) { - nand_release(&host->nand); + struct nand_chip *chip = &host->nand; + int ret; + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + fsmc_nand_disable(host); if (host->mode == USE_DMA_ACCESS) { dma_release_channel(host->write_dma_chan); dma_release_channel(host->read_dma_chan); } - clk_disable_unprepare(host->clk); } - - return 0; } #ifdef CONFIG_PM_SLEEP @@ -1159,11 +1200,17 @@ static int fsmc_nand_suspend(struct device *dev) static int fsmc_nand_resume(struct device *dev) { struct fsmc_nand_data *host = dev_get_drvdata(dev); + int ret; if (host) { - clk_prepare_enable(host->clk); + ret = clk_prepare_enable(host->clk); + if (ret) { + dev_err(dev, "failed to enable clk\n"); + return ret; + } if (host->dev_timings) fsmc_nand_setup(host, host->dev_timings); + nand_reset(&host->nand, 0); } return 0; |
