/* * (C) Copyright 2005 * 2N Telekomunikace, a.s. * Ladislav Michl * * See file CREDITS for list of people who contributed to this * project. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include static ssize_t nand_read(struct cdev *cdev, void* buf, size_t count, ulong offset, ulong flags) { struct mtd_info *info = cdev->priv; size_t retlen; int ret; debug("nand_read: 0x%08lx 0x%08x\n", offset, count); ret = info->read(info, offset, count, &retlen, buf); if(ret) { printf("err %d\n", ret); return ret; } return retlen; } #define NOTALIGNED(x) (x & (info->writesize - 1)) != 0 #ifdef CONFIG_NAND_WRITE static int all_ff(const void *buf, int len) { int i; const uint8_t *p = buf; for (i = 0; i < len; i++) if (p[i] != 0xFF) return 0; return 1; } static ssize_t nand_write(struct cdev* cdev, const void *buf, size_t _count, ulong offset, ulong flags) { struct mtd_info *info = cdev->priv; size_t retlen, now; int ret = 0; void *wrbuf = NULL; size_t count = _count; if (NOTALIGNED(offset)) { printf("offset 0x%0lx not page aligned\n", offset); return -EINVAL; } debug("write: 0x%08lx 0x%08x\n", offset, count); while (count) { now = count > info->writesize ? info->writesize : count; if (NOTALIGNED(now)) { debug("not aligned: %d %ld\n", info->writesize, (offset % info->writesize)); wrbuf = xmalloc(info->writesize); memset(wrbuf, 0xff, info->writesize); memcpy(wrbuf + (offset % info->writesize), buf, now); if (!all_ff(wrbuf, info->writesize)) ret = info->write(info, offset & ~(info->writesize - 1), info->writesize, &retlen, wrbuf); free(wrbuf); } else { if (!all_ff(buf, info->writesize)) ret = info->write(info, offset, now, &retlen, buf); debug("offset: 0x%08lx now: 0x%08x retlen: 0x%08x\n", offset, now, retlen); } if (ret) goto out; offset += now; count -= now; buf += now; } out: return ret ? ret : _count; } #endif static int nand_ioctl(struct cdev *cdev, int request, void *buf) { struct mtd_info *info = cdev->priv; struct mtd_info_user *user = buf; switch (request) { case MEMGETBADBLOCK: debug("MEMGETBADBLOCK: 0x%08lx\n", (off_t)buf); return info->block_isbad(info, (off_t)buf); #ifdef CONFIG_NAND_WRITE case MEMSETBADBLOCK: debug("MEMSETBADBLOCK: 0x%08lx\n", (off_t)buf); return info->block_markbad(info, (off_t)buf); #endif case MEMGETINFO: user->type = info->type; user->flags = info->flags; user->size = info->size; user->erasesize = info->erasesize; user->oobsize = info->oobsize; user->mtd = info; /* The below fields are obsolete */ user->ecctype = -1; user->eccsize = 0; return 0; } return 0; } #ifdef CONFIG_NAND_WRITE static ssize_t nand_erase(struct cdev *cdev, size_t count, unsigned long offset) { struct mtd_info *info = cdev->priv; struct erase_info erase; int ret; memset(&erase, 0, sizeof(erase)); erase.mtd = info; erase.addr = offset; erase.len = info->erasesize; while (count > 0) { debug("erase %d %d\n", erase.addr, erase.len); ret = info->block_isbad(info, erase.addr); if (ret > 0) { printf("Skipping bad block at 0x%08x\n", erase.addr); } else { ret = info->erase(info, &erase); if (ret) return ret; } erase.addr += info->erasesize; count -= count > info->erasesize ? info->erasesize : count; } return 0; } #endif #if 0 static char* mtd_get_size(struct device_d *, struct param_d *param) { static char } #endif static struct file_operations nand_ops = { .read = nand_read, #ifdef CONFIG_NAND_WRITE .write = nand_write, .erase = nand_erase, #endif .ioctl = nand_ioctl, .lseek = dev_lseek_default, }; #ifdef CONFIG_NAND_OOB_DEVICE static ssize_t nand_read_oob(struct cdev *cdev, void *buf, size_t count, ulong offset, ulong flags) { struct mtd_info *info = cdev->priv; struct nand_chip *chip = info->priv; struct mtd_oob_ops ops; int ret; if (count < info->oobsize) return -EINVAL; ops.mode = MTD_OOB_RAW; ops.ooboffs = 0; ops.ooblen = info->oobsize; ops.oobbuf = buf; ops.datbuf = NULL; ops.len = info->oobsize; offset /= info->oobsize; ret = info->read_oob(info, offset << chip->page_shift, &ops); if (ret) return ret; return info->oobsize; } static struct file_operations nand_ops_oob = { .read = nand_read_oob, .ioctl = nand_ioctl, .lseek = dev_lseek_default, }; static int nand_init_oob_cdev(struct mtd_info *mtd) { struct nand_chip *chip = mtd->priv; mtd->cdev_oob.ops = &nand_ops_oob; mtd->cdev_oob.size = (mtd->size >> chip->page_shift) * mtd->oobsize; mtd->cdev_oob.name = asprintf("nand_oob%d", mtd->class_dev.id); mtd->cdev_oob.priv = mtd; mtd->cdev_oob.dev = &mtd->class_dev; devfs_create(&mtd->cdev_oob); return 0; } static void nand_exit_oob_cdev(struct mtd_info *mtd) { free(mtd->cdev_oob.name); } #else static int nand_init_oob_cdev(struct mtd_info *mtd) { return 0; } static void nand_exit_oob_cdev(struct mtd_info *mtd) { return; } #endif int add_mtd_device(struct mtd_info *mtd) { char str[16]; strcpy(mtd->class_dev.name, "nand"); register_device(&mtd->class_dev); mtd->cdev.ops = &nand_ops; mtd->cdev.size = mtd->size; mtd->cdev.name = asprintf("nand%d", mtd->class_dev.id); mtd->cdev.priv = mtd; mtd->cdev.dev = &mtd->class_dev; mtd->cdev.mtd = mtd; sprintf(str, "%u", mtd->size); dev_add_param_fixed(&mtd->class_dev, "size", str); sprintf(str, "%u", mtd->erasesize); dev_add_param_fixed(&mtd->class_dev, "erasesize", str); sprintf(str, "%u", mtd->writesize); dev_add_param_fixed(&mtd->class_dev, "writesize", str); sprintf(str, "%u", mtd->oobsize); dev_add_param_fixed(&mtd->class_dev, "oobsize", str); devfs_create(&mtd->cdev); nand_init_oob_cdev(mtd); return 0; } int del_mtd_device (struct mtd_info *mtd) { unregister_device(&mtd->class_dev); nand_exit_oob_cdev(mtd); free(mtd->param_size.value); free(mtd->cdev.name); return 0; }