[PATCH V4 1/2] Nand driver for Nomadik 8815 SoC (on NHK8815 board)
vimal singh
vimal.newwork at gmail.com
Thu Jul 23 00:30:37 EDT 2009
On Thu, Jul 23, 2009 at 3:55 AM, Alessandro Rubini<rubini-list at gnudd.com> wrote:
>
> Signed-off-by: Alessandro Rubini <rubini at unipv.it>
> Acked-by: Andrea Gallo <andrea.gallo at stericsson.com>
> ---
> arch/arm/mach-nomadik/board-nhk8815.c | 92 ++++++++
> arch/arm/mach-nomadik/include/mach/fsmc.h | 29 +++
> arch/arm/mach-nomadik/include/mach/nand.h | 16 ++
> drivers/mtd/nand/Kconfig | 6 +
> drivers/mtd/nand/Makefile | 1 +
> drivers/mtd/nand/nomadik_nand.c | 329 +++++++++++++++++++++++++++++
> 6 files changed, 473 insertions(+), 0 deletions(-)
> create mode 100644 arch/arm/mach-nomadik/include/mach/fsmc.h
> create mode 100644 arch/arm/mach-nomadik/include/mach/nand.h
> create mode 100644 drivers/mtd/nand/nomadik_nand.c
--snip--
> diff --git a/drivers/mtd/nand/nomadik_nand.c b/drivers/mtd/nand/nomadik_nand.c
> new file mode 100644
> index 0000000..bbd1925
> --- /dev/null
> +++ b/drivers/mtd/nand/nomadik_nand.c
> @@ -0,0 +1,329 @@
> +/*
> + * drivers/mtd/nand/nomadik_nand.c
> + *
> + * Overview:
> + * Driver for on-board NAND flash on Nomadik Platforms
> + *
> + * Copyright (C) 2007 STMicroelectronics Pvt. Ltd.
> + * Author: Sachin Verma <sachin.verma at st.com>
> + *
> + * Copyright (C) 2009 Alessandro Rubini
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + */
> +
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/types.h>
> +#include <linux/mtd/mtd.h>
> +#include <linux/mtd/nand.h>
> +#include <linux/platform_device.h>
> +#include <linux/mtd/partitions.h>
> +#include <linux/io.h>
I think one black line will be good here.
> +#include <mach/nand.h>
> +#include <mach/fsmc.h>
> +
> +#include <mtd/mtd-abi.h>
> +
> +struct nomadik_nand_host {
> + struct mtd_info mtd;
> + struct nand_chip nand;
> + void __iomem *data_va;
> + void __iomem *cmd_va;
> + void __iomem *addr_va;
> + struct nand_bbt_descr *bbt_desc;
> +};
> +
> +static inline int parity(int b) /* uses low 8 bits: returns 0 or all-1 */
> +{
> + b = b ^ (b >> 4);
> + b = b ^ (b >> 2);
> + return (b ^ (b >> 1)) & 1
> + ? ~0 : 0;
> +}
> +
> +/*
> + * This is the ECC routine used in hardware, according to the manual.
> + * HW claims to make the calculation but not the correction. However,
> + * I haven't managed to get the desired data out of it; so do it in sw.
> + * There is problably some errata involved, but currently miss the info.
> + */
'nand_calculate_ecc' and 'nand_correct_data' (nand_ecc.c) do it in SW.
This need not to be re-do.
> +static int nomadik_ecc512_calc(struct mtd_info *mtd, const u_char *data,
> + u_char *ecc)
> +{
> + int gpar = 0;
> + int i, val, par;
> + int pbits = 0; /* P8, P16, ... P2048 */
> + int pprime = 0; /* P8', P16', ... P2048' */
> + int lowbits; /* P1, P2, P4 and primes */
> +
> + for (i = 0; i < 512; i++) {
> + par = parity((val = data[i]));
> + gpar ^= val;
> + pbits ^= (i & par);
> + }
> + /*
> + * Ok, now gpar is global parity (xor of all bytes)
> + * pbits are all the parity bits (non-prime ones)
> + */
> + par = parity(gpar);
> + pprime = pbits ^ par;
> + /* Put low bits in the right position for ecc[2] (bits 7..2) */
> + lowbits = 0
> + | (parity(gpar & 0xf0) & 0x80) /* P4 */
> + | (parity(gpar & 0x0f) & 0x40) /* P4' */
> + | (parity(gpar & 0xcc) & 0x20) /* P2 */
> + | (parity(gpar & 0x33) & 0x10) /* P2' */
> + | (parity(gpar & 0xaa) & 0x08) /* P1 */
> + | (parity(gpar & 0x55) & 0x04); /* P1' */
> +
> + ecc[2] = ~(lowbits | ((pbits & 0x100) >> 7) | ((pprime & 0x100) >> 8));
> + /* now intermix bits for ecc[1] (P1024..P128') and ecc[0] (P64..P8') */
> + ecc[1] = ~( (pbits & 0x80) >> 0 | ((pprime & 0x80) >> 1)
> + | ((pbits & 0x40) >> 1) | ((pprime & 0x40) >> 2)
> + | ((pbits & 0x20) >> 2) | ((pprime & 0x20) >> 3)
> + | ((pbits & 0x10) >> 3) | ((pprime & 0x10) >> 4));
> +
> + ecc[0] = ~( (pbits & 0x8) << 4 | ((pprime & 0x8) << 3)
> + | ((pbits & 0x4) << 3) | ((pprime & 0x4) << 2)
> + | ((pbits & 0x2) << 2) | ((pprime & 0x2) << 1)
> + | ((pbits & 0x1) << 1) | ((pprime & 0x1) << 0));
> + return 0;
> +}
> +
> +static int nomadik_ecc512_correct(struct mtd_info *mtd, uint8_t *dat,
> + uint8_t *r_ecc, uint8_t *c_ecc)
> +{
> + struct nand_chip *chip = mtd->priv;
> + uint32_t r, c, d, diff; /*read, calculated, xor of them */
> +
> + if (!memcmp(r_ecc, c_ecc, chip->ecc.bytes))
> + return 0;
> +
> + /* Reorder the bytes into ascending-order 24 bits -- see manual */
> + r = r_ecc[2] << 22 | r_ecc[1] << 14 | r_ecc[0] << 6 | r_ecc[2] >> 2;
> + c = c_ecc[2] << 22 | c_ecc[1] << 14 | c_ecc[0] << 6 | c_ecc[2] >> 2;
> + diff = (r ^ c) & ((1 << 24) - 1); /* use 24 bits only */
> +
> + /* If 12 bits are different, one per pair, it's correctable */
> + if (((diff | (diff>>1)) & 0x555555) == 0x555555) {
> + int bit = ((diff & 2) >> 1)
> + | ((diff & 0x8) >> 2) | ((diff & 0x20) >> 3);
> + int byte;
> +
> + d = diff >> 6; /* remove bit-order info */
> + byte = ((d & 2) >> 1)
> + | ((d & 0x8) >> 2) | ((d & 0x20) >> 3)
> + | ((d & 0x80) >> 4) | ((d & 0x200) >> 5)
> + | ((d & 0x800) >> 6) | ((d & 0x2000) >> 7)
> + | ((d & 0x8000) >> 8) | ((d & 0x20000) >> 9);
> + /* correct the single bit */
> + dat[byte] ^= 1 << bit;
> + return 0;
> + }
> + /* If 1 bit only differs, it's one bit error in ECC, ignore */
> + if ((diff ^ (1 << (ffs(diff) - 1))) == 0)
> + return 0;
> + /* Otherwise, uncorrectable */
> + return -1;
> +}
> +
> +static struct nand_ecclayout nomadik_ecc_layout = {
> + .eccbytes = 3 * 4,
> + .eccpos = { /* each subpage has 16 bytes: pos 2,3,4 hosts ECC */
> + 0x02, 0x03, 0x04,
> + 0x12, 0x13, 0x14,
> + 0x22, 0x23, 0x24,
> + 0x32, 0x33, 0x34},
> + /* let's keep bytes 5,6,7 for us, just in case we change ECC algo */
> + .oobfree = { {0x08, 0x08}, {0x18, 0x08}, {0x28, 0x08}, {0x38, 0x08} },
> +};
> +
> +static void nomadik_ecc_control(struct mtd_info *mtd, int mode)
> +{
> + /* No need to enable hw ecc, it's on by default */
> +}
> +
> +static void nomadik_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl)
> +{
> + struct nand_chip *nand = mtd->priv;
> + struct nomadik_nand_host *host = nand->priv;
> +
> + if (cmd == NAND_CMD_NONE)
> + return;
> +
> + if (ctrl & NAND_CLE)
> + writeb(cmd, host->cmd_va);
> + else
> + writeb(cmd, host->addr_va);
> +}
> +
> +static int nomadik_nand_probe(struct platform_device *pdev)
> +{
> + struct nomadik_nand_platform_data *pdata = pdev->dev.platform_data;
> + struct nomadik_nand_host *host;
> + struct mtd_info *mtd;
> + struct nand_chip *nand;
> + struct resource *res;
> + int ret = 0;
> +
> + /* Allocate memory for the device structure (and zero it) */
> + host = kzalloc(sizeof(struct nomadik_nand_host), GFP_KERNEL);
> + if (!host) {
> + dev_err(&pdev->dev, "Failed to allocate device structure.\n");
> + return -ENOMEM;
> + }
> +
> + /* Call the client's init function, if any */
> + if (pdata->init && (ret = pdata->init()) < 0) {
> + dev_err(&pdev->dev, "Init function failed\n");
> + goto err;
> + }
> +
> + /* ioremap three regions */
> + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand_addr");
> + if (!res) {ret = -EIO; goto err_unmap; }
We should not write multiple statements in single line needlessly.
> + host->addr_va = ioremap(res->start, res->end - res->start + 1);
> +
> + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand_data");
> + if (!res) {ret = -EIO; goto err_unmap; }
ditto
> + host->data_va = ioremap(res->start, res->end - res->start + 1);
> +
> + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand_cmd");
> + if (!res) {ret = -EIO; goto err_unmap; }
ditto...
> + host->cmd_va = ioremap(res->start, res->end - res->start + 1);
> +
> + if (!host->addr_va || !host->data_va || !host->cmd_va) {
> + ret = -ENOMEM;
> + goto err_unmap;
> + }
> +
> + /* Link all private pointers */
> + mtd = &host->mtd;
> + nand = &host->nand;
> + mtd->priv = nand;
> + nand->priv = host;
> +
> + host->mtd.owner = THIS_MODULE;
> + nand->IO_ADDR_R = host->data_va;
> + nand->IO_ADDR_W = host->data_va;
> + nand->cmd_ctrl = nomadik_cmd_ctrl;
> +
> + nand->ecc.mode = NAND_ECC_HW;
> + nand->ecc.layout = &nomadik_ecc_layout;
> + nand->ecc.calculate = nomadik_ecc512_calc;
> + nand->ecc.correct = nomadik_ecc512_correct;
> + nand->ecc.hwctl = nomadik_ecc_control;
> + nand->ecc.size = 512;
> + nand->ecc.bytes = 3;
> +
> + nand->options = pdata->options;
> +
> + /*
> + * Scan to find existance of the device
> + */
> + if (nand_scan(&host->mtd, 1)) {
> + ret = -ENXIO;
> + goto err_unmap;
> + }
> +
> +#ifdef CONFIG_MTD_PARTITIONS
> + add_mtd_partitions(&host->mtd, pdata->parts, pdata->nparts);
> +#else
> + pr_info("Registering %s as whole device\n", mtd->name);
> + add_mtd_device(mtd);
> +#endif
> +
> + platform_set_drvdata(pdev, host);
> + return 0;
> +
> + err_unmap:
> + if (host->cmd_va) iounmap(host->cmd_va);
> + if (host->data_va) iounmap(host->data_va);
> + if (host->addr_va) iounmap(host->addr_va);
> + err:
> + kfree(host);
> + return ret;
> +}
> +
> +/*
> + * Clean up routine
> + */
> +static int nomadik_nand_remove(struct platform_device *pdev)
> +{
> + struct nomadik_nand_host *host = platform_get_drvdata(pdev);
> + struct nomadik_nand_platform_data *pdata = pdev->dev.platform_data;
> +
> + if (pdata->exit)
> + pdata->exit();
> +
> + if (host) {
> + iounmap(host->cmd_va);
> + iounmap(host->data_va);
> + iounmap(host->addr_va);
> + kfree(host);
> + }
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int nomadik_nand_suspend(struct platform_device *pdev,
> + pm_message_t state)
> +{
> + struct nomadik_nand_host *host = platform_get_drvdata(pdev);
> + int ret = 0;
> + if (host)
> + ret = host->mtd.suspend(&host->mtd);
> + return ret;
> +}
> +
> +static int nomadik_nand_resume(struct platform_device *pdev)
> +{
> + struct nomadik_nand_host *host = platform_get_drvdata(pdev);
> + if (host)
> + host->mtd.resume(&host->mtd);
> + return 0;
> +}
> +
> +#else
> +#define nomadik_nand_suspend NULL
> +#define nomadik_nand_resume NULL
> +#endif /* CONFIG_PM */
> +
> +static struct platform_driver nomadik_nand_driver = {
> + .probe = nomadik_nand_probe,
> + .remove = nomadik_nand_remove,
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "nomadik_nand",
> + },
> + .suspend = nomadik_nand_suspend,
> + .resume = nomadik_nand_resume,
> +};
> +
> +static int __init nand_nomadik_init(void)
> +{
> + pr_info("Nomadik NAND driver\n");
> + return platform_driver_register(&nomadik_nand_driver);
> +}
> +
> +static void __exit nand_nomadik_exit(void)
> +{
> + platform_driver_unregister(&nomadik_nand_driver);
> +}
> +
> +module_init(nand_nomadik_init);
> +module_exit(nand_nomadik_exit);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("ST Microelectronics (sachin.verma at st.com)");
> +MODULE_DESCRIPTION("NAND driver for Nomadik Platform");
> --
> 1.6.0.2
>
> ______________________________________________________
> Linux MTD discussion mailing list
> http://lists.infradead.org/mailman/listinfo/linux-mtd/
>
--
---
Regards,
\/ | |\/| /-\ |_
____ __o
------ -\<,
----- ( )/ ( )
More information about the linux-mtd
mailing list