|  | /* | 
|  | *  drivers/mtd/nand/nomadik_nand.c | 
|  | * | 
|  | *  Overview: | 
|  | *  	Driver for on-board NAND flash on Nomadik Platforms | 
|  | * | 
|  | * Copyright © 2007 STMicroelectronics Pvt. Ltd. | 
|  | * Author: Sachin Verma <sachin.verma@st.com> | 
|  | * | 
|  | * Copyright © 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/mtd/nand_ecc.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/mtd/partitions.h> | 
|  | #include <linux/io.h> | 
|  | #include <linux/slab.h> | 
|  | #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 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(); | 
|  | if (ret < 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; | 
|  | } | 
|  | host->addr_va = ioremap(res->start, resource_size(res)); | 
|  |  | 
|  | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand_data"); | 
|  | if (!res) { | 
|  | ret = -EIO; | 
|  | goto err_unmap; | 
|  | } | 
|  | host->data_va = ioremap(res->start, resource_size(res)); | 
|  |  | 
|  | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand_cmd"); | 
|  | if (!res) { | 
|  | ret = -EIO; | 
|  | goto err_unmap; | 
|  | } | 
|  | host->cmd_va = ioremap(res->start, resource_size(res)); | 
|  |  | 
|  | 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; | 
|  |  | 
|  | /* | 
|  | * This stanza declares ECC_HW but uses soft routines. It's because | 
|  | * HW claims to make the calculation but not the correction. However, | 
|  | * I haven't managed to get the desired data out of it until now. | 
|  | */ | 
|  | nand->ecc.mode = NAND_ECC_SOFT; | 
|  | nand->ecc.layout = &nomadik_ecc_layout; | 
|  | nand->ecc.hwctl = nomadik_ecc_control; | 
|  | nand->ecc.size = 512; | 
|  | nand->ecc.bytes = 3; | 
|  |  | 
|  | nand->options = pdata->options; | 
|  |  | 
|  | /* | 
|  | * Scan to find existence of the device | 
|  | */ | 
|  | if (nand_scan(&host->mtd, 1)) { | 
|  | ret = -ENXIO; | 
|  | goto err_unmap; | 
|  | } | 
|  |  | 
|  | mtd_device_register(&host->mtd, pdata->parts, pdata->nparts); | 
|  |  | 
|  | 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) { | 
|  | nand_release(&host->mtd); | 
|  | iounmap(host->cmd_va); | 
|  | iounmap(host->data_va); | 
|  | iounmap(host->addr_va); | 
|  | kfree(host); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int nomadik_nand_suspend(struct device *dev) | 
|  | { | 
|  | struct nomadik_nand_host *host = dev_get_drvdata(dev); | 
|  | int ret = 0; | 
|  | if (host) | 
|  | ret = host->mtd.suspend(&host->mtd); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int nomadik_nand_resume(struct device *dev) | 
|  | { | 
|  | struct nomadik_nand_host *host = dev_get_drvdata(dev); | 
|  | if (host) | 
|  | host->mtd.resume(&host->mtd); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct dev_pm_ops nomadik_nand_pm_ops = { | 
|  | .suspend = nomadik_nand_suspend, | 
|  | .resume = nomadik_nand_resume, | 
|  | }; | 
|  |  | 
|  | static struct platform_driver nomadik_nand_driver = { | 
|  | .probe = nomadik_nand_probe, | 
|  | .remove = nomadik_nand_remove, | 
|  | .driver = { | 
|  | .owner = THIS_MODULE, | 
|  | .name = "nomadik_nand", | 
|  | .pm = &nomadik_nand_pm_ops, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | 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@st.com)"); | 
|  | MODULE_DESCRIPTION("NAND driver for Nomadik Platform"); |