aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/goldfish/patches-2.6.30/0125--ARM-goldfish-NAND-Add-nand-driver-for-goldfish.patch
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/goldfish/patches-2.6.30/0125--ARM-goldfish-NAND-Add-nand-driver-for-goldfish.patch')
-rw-r--r--target/linux/goldfish/patches-2.6.30/0125--ARM-goldfish-NAND-Add-nand-driver-for-goldfish.patch521
1 files changed, 521 insertions, 0 deletions
diff --git a/target/linux/goldfish/patches-2.6.30/0125--ARM-goldfish-NAND-Add-nand-driver-for-goldfish.patch b/target/linux/goldfish/patches-2.6.30/0125--ARM-goldfish-NAND-Add-nand-driver-for-goldfish.patch
new file mode 100644
index 000000000..0310888fc
--- /dev/null
+++ b/target/linux/goldfish/patches-2.6.30/0125--ARM-goldfish-NAND-Add-nand-driver-for-goldfish.patch
@@ -0,0 +1,521 @@
+From bed297dad6283a5926962c1c59f95ad641824630 Mon Sep 17 00:00:00 2001
+From: =?utf-8?q?Arve=20Hj=C3=B8nnev=C3=A5g?= <arve@google.com>
+Date: Fri, 29 Jun 2007 22:20:07 -0700
+Subject: [PATCH 125/134] [ARM] goldfish: NAND: Add nand driver for goldfish.
+MIME-Version: 1.0
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Mike A. Chan <mikechan@google.com>
+Signed-off-by: Arve Hjønnevåg <arve@android.com>
+---
+ drivers/mtd/devices/Kconfig | 5 +
+ drivers/mtd/devices/Makefile | 1 +
+ drivers/mtd/devices/goldfish_nand.c | 418 +++++++++++++++++++++++++++++++
+ drivers/mtd/devices/goldfish_nand_reg.h | 58 +++++
+ 4 files changed, 482 insertions(+), 0 deletions(-)
+ create mode 100644 drivers/mtd/devices/goldfish_nand.c
+ create mode 100644 drivers/mtd/devices/goldfish_nand_reg.h
+
+--- a/drivers/mtd/devices/Kconfig
++++ b/drivers/mtd/devices/Kconfig
+@@ -297,5 +297,10 @@ config MTD_DOCPROBE_55AA
+ LinuxBIOS or if you need to recover a DiskOnChip Millennium on which
+ you have managed to wipe the first block.
+
++config MTD_GOLDFISH_NAND
++ tristate "Goldfish NAND device"
++ help
++ none
++
+ endmenu
+
+--- a/drivers/mtd/devices/Makefile
++++ b/drivers/mtd/devices/Makefile
+@@ -16,3 +16,4 @@ obj-$(CONFIG_MTD_LART) += lart.o
+ obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o
+ obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o
+ obj-$(CONFIG_MTD_M25P80) += m25p80.o
++obj-$(CONFIG_MTD_GOLDFISH_NAND) += goldfish_nand.o
+--- /dev/null
++++ b/drivers/mtd/devices/goldfish_nand.c
+@@ -0,0 +1,418 @@
++/* drivers/mtd/devices/goldfish_nand.c
++**
++** Copyright (C) 2007 Google, Inc.
++**
++** This software is licensed under the terms of the GNU General Public
++** License version 2, as published by the Free Software Foundation, and
++** may be copied, distributed, and modified under those terms.
++**
++** 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 <asm/div64.h>
++#include <asm/io.h>
++#include <linux/module.h>
++#include <linux/slab.h>
++#include <linux/ioport.h>
++#include <linux/vmalloc.h>
++#include <linux/init.h>
++#include <linux/mtd/compatmac.h>
++#include <linux/mtd/mtd.h>
++#include <linux/platform_device.h>
++
++#include "goldfish_nand_reg.h"
++
++struct goldfish_nand {
++ spinlock_t lock;
++ unsigned char __iomem *base;
++ size_t mtd_count;
++ struct mtd_info mtd[0];
++};
++
++static uint32_t goldfish_nand_cmd(struct mtd_info *mtd, enum nand_cmd cmd,
++ uint64_t addr, uint32_t len, void *ptr)
++{
++ struct goldfish_nand *nand = mtd->priv;
++ uint32_t rv;
++ unsigned long irq_flags;
++ unsigned char __iomem *base = nand->base;
++
++ spin_lock_irqsave(&nand->lock, irq_flags);
++ writel(mtd - nand->mtd, base + NAND_DEV);
++ writel((uint32_t)(addr >> 32), base + NAND_ADDR_HIGH);
++ writel((uint32_t)addr, base + NAND_ADDR_LOW);
++ writel(len, base + NAND_TRANSFER_SIZE);
++ writel(ptr, base + NAND_DATA);
++ writel(cmd, base + NAND_COMMAND);
++ rv = readl(base + NAND_RESULT);
++ spin_unlock_irqrestore(&nand->lock, irq_flags);
++ return rv;
++}
++
++static int goldfish_nand_erase(struct mtd_info *mtd, struct erase_info *instr)
++{
++ loff_t ofs = instr->addr;
++ uint32_t len = instr->len;
++ uint32_t rem;
++
++ if (ofs + len > mtd->size)
++ goto invalid_arg;
++ rem = do_div(ofs, mtd->writesize);
++ if(rem)
++ goto invalid_arg;
++ ofs *= (mtd->writesize + mtd->oobsize);
++
++ if(len % mtd->writesize)
++ goto invalid_arg;
++ len = len / mtd->writesize * (mtd->writesize + mtd->oobsize);
++
++ if(goldfish_nand_cmd(mtd, NAND_CMD_ERASE, ofs, len, NULL) != len) {
++ printk("goldfish_nand_erase: erase failed, start %llx, len %x, dev_size "
++ "%llx, erase_size %x\n", ofs, len, mtd->size, mtd->erasesize);
++ return -EIO;
++ }
++
++ instr->state = MTD_ERASE_DONE;
++ mtd_erase_callback(instr);
++
++ return 0;
++
++invalid_arg:
++ printk("goldfish_nand_erase: invalid erase, start %llx, len %x, dev_size "
++ "%llx, erase_size %x\n", ofs, len, mtd->size, mtd->erasesize);
++ return -EINVAL;
++}
++
++static int goldfish_nand_read_oob(struct mtd_info *mtd, loff_t ofs,
++ struct mtd_oob_ops *ops)
++{
++ uint32_t rem;
++
++ if(ofs + ops->len > mtd->size)
++ goto invalid_arg;
++ if(ops->datbuf && ops->len && ops->len != mtd->writesize)
++ goto invalid_arg;
++ if(ops->ooblen + ops->ooboffs > mtd->oobsize)
++ goto invalid_arg;
++
++ rem = do_div(ofs, mtd->writesize);
++ if(rem)
++ goto invalid_arg;
++ ofs *= (mtd->writesize + mtd->oobsize);
++
++ if(ops->datbuf)
++ ops->retlen = goldfish_nand_cmd(mtd, NAND_CMD_READ, ofs,
++ ops->len, ops->datbuf);
++ ofs += mtd->writesize + ops->ooboffs;
++ if(ops->oobbuf)
++ ops->oobretlen = goldfish_nand_cmd(mtd, NAND_CMD_READ, ofs,
++ ops->ooblen, ops->oobbuf);
++ return 0;
++
++invalid_arg:
++ printk("goldfish_nand_read_oob: invalid read, start %llx, len %x, "
++ "ooblen %x, dev_size %llx, write_size %x\n",
++ ofs, ops->len, ops->ooblen, mtd->size, mtd->writesize);
++ return -EINVAL;
++}
++
++static int goldfish_nand_write_oob(struct mtd_info *mtd, loff_t ofs,
++ struct mtd_oob_ops *ops)
++{
++ uint32_t rem;
++
++ if(ofs + ops->len > mtd->size)
++ goto invalid_arg;
++ if(ops->len && ops->len != mtd->writesize)
++ goto invalid_arg;
++ if(ops->ooblen + ops->ooboffs > mtd->oobsize)
++ goto invalid_arg;
++
++ rem = do_div(ofs, mtd->writesize);
++ if(rem)
++ goto invalid_arg;
++ ofs *= (mtd->writesize + mtd->oobsize);
++
++ if(ops->datbuf)
++ ops->retlen = goldfish_nand_cmd(mtd, NAND_CMD_WRITE, ofs,
++ ops->len, ops->datbuf);
++ ofs += mtd->writesize + ops->ooboffs;
++ if(ops->oobbuf)
++ ops->oobretlen = goldfish_nand_cmd(mtd, NAND_CMD_WRITE, ofs,
++ ops->ooblen, ops->oobbuf);
++ return 0;
++
++invalid_arg:
++ printk("goldfish_nand_write_oob: invalid write, start %llx, len %x, "
++ "ooblen %x, dev_size %llx, write_size %x\n",
++ ofs, ops->len, ops->ooblen, mtd->size, mtd->writesize);
++ return -EINVAL;
++}
++
++static int goldfish_nand_read(struct mtd_info *mtd, loff_t from, size_t len,
++ size_t *retlen, u_char *buf)
++{
++ uint32_t rem;
++
++ if(from + len > mtd->size)
++ goto invalid_arg;
++ if(len != mtd->writesize)
++ goto invalid_arg;
++
++ rem = do_div(from, mtd->writesize);
++ if(rem)
++ goto invalid_arg;
++ from *= (mtd->writesize + mtd->oobsize);
++
++ *retlen = goldfish_nand_cmd(mtd, NAND_CMD_READ, from, len, buf);
++ return 0;
++
++invalid_arg:
++ printk("goldfish_nand_read: invalid read, start %llx, len %x, dev_size %llx"
++ ", write_size %x\n", from, len, mtd->size, mtd->writesize);
++ return -EINVAL;
++}
++
++static int goldfish_nand_write(struct mtd_info *mtd, loff_t to, size_t len,
++ size_t *retlen, const u_char *buf)
++{
++ uint32_t rem;
++
++ if(to + len > mtd->size)
++ goto invalid_arg;
++ if(len != mtd->writesize)
++ goto invalid_arg;
++
++ rem = do_div(to, mtd->writesize);
++ if(rem)
++ goto invalid_arg;
++ to *= (mtd->writesize + mtd->oobsize);
++
++ *retlen = goldfish_nand_cmd(mtd, NAND_CMD_WRITE, to, len, (void *)buf);
++ return 0;
++
++invalid_arg:
++ printk("goldfish_nand_write: invalid write, start %llx, len %x, dev_size %llx"
++ ", write_size %x\n", to, len, mtd->size, mtd->writesize);
++ return -EINVAL;
++}
++
++static int goldfish_nand_block_isbad(struct mtd_info *mtd, loff_t ofs)
++{
++ uint32_t rem;
++
++ if(ofs >= mtd->size)
++ goto invalid_arg;
++
++ rem = do_div(ofs, mtd->erasesize);
++ if(rem)
++ goto invalid_arg;
++ ofs *= mtd->erasesize / mtd->writesize;
++ ofs *= (mtd->writesize + mtd->oobsize);
++
++ return goldfish_nand_cmd(mtd, NAND_CMD_BLOCK_BAD_GET, ofs, 0, NULL);
++
++invalid_arg:
++ printk("goldfish_nand_block_isbad: invalid arg, ofs %llx, dev_size %llx, "
++ "write_size %x\n", ofs, mtd->size, mtd->writesize);
++ return -EINVAL;
++}
++
++static int goldfish_nand_block_markbad(struct mtd_info *mtd, loff_t ofs)
++{
++ uint32_t rem;
++
++ if(ofs >= mtd->size)
++ goto invalid_arg;
++
++ rem = do_div(ofs, mtd->erasesize);
++ if(rem)
++ goto invalid_arg;
++ ofs *= mtd->erasesize / mtd->writesize;
++ ofs *= (mtd->writesize + mtd->oobsize);
++
++ if(goldfish_nand_cmd(mtd, NAND_CMD_BLOCK_BAD_SET, ofs, 0, NULL) != 1)
++ return -EIO;
++ return 0;
++
++invalid_arg:
++ printk("goldfish_nand_block_markbad: invalid arg, ofs %llx, dev_size %llx, "
++ "write_size %x\n", ofs, mtd->size, mtd->writesize);
++ return -EINVAL;
++}
++
++static int goldfish_nand_init_device(struct goldfish_nand *nand, int id)
++{
++ uint32_t name_len;
++ uint32_t result;
++ uint32_t flags;
++ unsigned long irq_flags;
++ unsigned char __iomem *base = nand->base;
++ struct mtd_info *mtd = &nand->mtd[id];
++ char *name;
++
++ spin_lock_irqsave(&nand->lock, irq_flags);
++ writel(id, base + NAND_DEV);
++ flags = readl(base + NAND_DEV_FLAGS);
++ name_len = readl(base + NAND_DEV_NAME_LEN);
++ mtd->writesize = readl(base + NAND_DEV_PAGE_SIZE);
++ mtd->size = readl(base + NAND_DEV_SIZE_LOW);
++ mtd->size |= (uint64_t)readl(base + NAND_DEV_SIZE_HIGH) << 32;
++ mtd->oobsize = readl(base + NAND_DEV_EXTRA_SIZE);
++ mtd->oobavail = mtd->oobsize;
++ mtd->erasesize = readl(base + NAND_DEV_ERASE_SIZE) /
++ (mtd->writesize + mtd->oobsize) * mtd->writesize;
++ do_div(mtd->size, mtd->writesize + mtd->oobsize);
++ mtd->size *= mtd->writesize;
++ printk("goldfish nand dev%d: size %llx, page %d, extra %d, erase %d\n",
++ id, mtd->size, mtd->writesize, mtd->oobsize, mtd->erasesize);
++ spin_unlock_irqrestore(&nand->lock, irq_flags);
++
++ mtd->priv = nand;
++
++ mtd->name = name = kmalloc(name_len + 1, GFP_KERNEL);
++ if(name == NULL)
++ return -ENOMEM;
++
++ result = goldfish_nand_cmd(mtd, NAND_CMD_GET_DEV_NAME, 0, name_len, name);
++ if(result != name_len) {
++ kfree(mtd->name);
++ mtd->name = NULL;
++ printk("goldfish_nand_init_device failed to get dev name %d != %d\n",
++ result, name_len);
++ return -ENODEV;
++ }
++ ((char *) mtd->name)[name_len] = '\0';
++
++ /* Setup the MTD structure */
++ mtd->type = MTD_NANDFLASH;
++ mtd->flags = MTD_CAP_NANDFLASH;
++ if(flags & NAND_DEV_FLAG_READ_ONLY)
++ mtd->flags &= ~MTD_WRITEABLE;
++
++ mtd->owner = THIS_MODULE;
++ mtd->erase = goldfish_nand_erase;
++ mtd->read = goldfish_nand_read;
++ mtd->write = goldfish_nand_write;
++ mtd->read_oob = goldfish_nand_read_oob;
++ mtd->write_oob = goldfish_nand_write_oob;
++ mtd->block_isbad = goldfish_nand_block_isbad;
++ mtd->block_markbad = goldfish_nand_block_markbad;
++
++ if (add_mtd_device(mtd)) {
++ kfree(mtd->name);
++ mtd->name = NULL;
++ return -EIO;
++ }
++
++ return 0;
++}
++
++static int goldfish_nand_probe(struct platform_device *pdev)
++{
++ uint32_t num_dev;
++ int i;
++ int err;
++ uint32_t num_dev_working;
++ uint32_t version;
++ struct resource *r;
++ struct goldfish_nand *nand;
++ unsigned char __iomem *base;
++
++ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
++ if(r == NULL) {
++ err = -ENODEV;
++ goto err_no_io_base;
++ }
++
++ base = ioremap(r->start, PAGE_SIZE);
++ if(base == NULL) {
++ err = -ENOMEM;
++ goto err_ioremap;
++ }
++ version = readl(base + NAND_VERSION);
++ if(version != NAND_VERSION_CURRENT) {
++ printk("goldfish_nand_init: version mismatch, got %d, expected %d\n",
++ version, NAND_VERSION_CURRENT);
++ err = -ENODEV;
++ goto err_no_dev;
++ }
++ num_dev = readl(base + NAND_NUM_DEV);
++ if(num_dev == 0) {
++ err = -ENODEV;
++ goto err_no_dev;
++ }
++
++ nand = kzalloc(sizeof(*nand) + sizeof(struct mtd_info) * num_dev, GFP_KERNEL);
++ if(nand == NULL) {
++ err = -ENOMEM;
++ goto err_nand_alloc_failed;
++ }
++ spin_lock_init(&nand->lock);
++ nand->base = base;
++ nand->mtd_count = num_dev;
++ platform_set_drvdata(pdev, nand);
++
++ num_dev_working = 0;
++ for(i = 0; i < num_dev; i++) {
++ err = goldfish_nand_init_device(nand, i);
++ if(err == 0)
++ num_dev_working++;
++ }
++ if(num_dev_working == 0) {
++ err = -ENODEV;
++ goto err_no_working_dev;
++ }
++ return 0;
++
++err_no_working_dev:
++ kfree(nand);
++err_nand_alloc_failed:
++err_no_dev:
++ iounmap(base);
++err_ioremap:
++err_no_io_base:
++ return err;
++}
++
++static int goldfish_nand_remove(struct platform_device *pdev)
++{
++ struct goldfish_nand *nand = platform_get_drvdata(pdev);
++ int i;
++ for(i = 0; i < nand->mtd_count; i++) {
++ if(nand->mtd[i].name) {
++ del_mtd_device(&nand->mtd[i]);
++ kfree(nand->mtd[i].name);
++ }
++ }
++ iounmap(nand->base);
++ kfree(nand);
++ return 0;
++}
++
++static struct platform_driver goldfish_nand_driver = {
++ .probe = goldfish_nand_probe,
++ .remove = goldfish_nand_remove,
++ .driver = {
++ .name = "goldfish_nand"
++ }
++};
++
++static int __init goldfish_nand_init(void)
++{
++ return platform_driver_register(&goldfish_nand_driver);
++}
++
++static void __exit goldfish_nand_exit(void)
++{
++ platform_driver_unregister(&goldfish_nand_driver);
++}
++
++
++module_init(goldfish_nand_init);
++module_exit(goldfish_nand_exit);
++
+--- /dev/null
++++ b/drivers/mtd/devices/goldfish_nand_reg.h
+@@ -0,0 +1,58 @@
++/* drivers/mtd/devices/goldfish_nand_reg.h
++**
++** Copyright (C) 2007 Google, Inc.
++**
++** This software is licensed under the terms of the GNU General Public
++** License version 2, as published by the Free Software Foundation, and
++** may be copied, distributed, and modified under those terms.
++**
++** 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.
++**
++*/
++
++#ifndef GOLDFISH_NAND_REG_H
++#define GOLDFISH_NAND_REG_H
++
++enum nand_cmd {
++ NAND_CMD_GET_DEV_NAME, // Write device name for NAND_DEV to NAND_DATA (vaddr)
++ NAND_CMD_READ,
++ NAND_CMD_WRITE,
++ NAND_CMD_ERASE,
++ NAND_CMD_BLOCK_BAD_GET, // NAND_RESULT is 1 if block is bad, 0 if it is not
++ NAND_CMD_BLOCK_BAD_SET
++};
++
++enum nand_dev_flags {
++ NAND_DEV_FLAG_READ_ONLY = 0x00000001
++};
++
++#define NAND_VERSION_CURRENT (1)
++
++enum nand_reg {
++ // Global
++ NAND_VERSION = 0x000,
++ NAND_NUM_DEV = 0x004,
++ NAND_DEV = 0x008,
++
++ // Dev info
++ NAND_DEV_FLAGS = 0x010,
++ NAND_DEV_NAME_LEN = 0x014,
++ NAND_DEV_PAGE_SIZE = 0x018,
++ NAND_DEV_EXTRA_SIZE = 0x01c,
++ NAND_DEV_ERASE_SIZE = 0x020,
++ NAND_DEV_SIZE_LOW = 0x028,
++ NAND_DEV_SIZE_HIGH = 0x02c,
++
++ // Command
++ NAND_RESULT = 0x040,
++ NAND_COMMAND = 0x044,
++ NAND_DATA = 0x048,
++ NAND_TRANSFER_SIZE = 0x04c,
++ NAND_ADDR_LOW = 0x050,
++ NAND_ADDR_HIGH = 0x054,
++};
++
++#endif