aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/nand/nand_cdev.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/nand/nand_cdev.c')
-rw-r--r--sys/dev/nand/nand_cdev.c413
1 files changed, 413 insertions, 0 deletions
diff --git a/sys/dev/nand/nand_cdev.c b/sys/dev/nand/nand_cdev.c
new file mode 100644
index 000000000000..ac27ff361537
--- /dev/null
+++ b/sys/dev/nand/nand_cdev.c
@@ -0,0 +1,413 @@
+/*-
+ * Copyright (C) 2009-2012 Semihalf
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/conf.h>
+#include <sys/bus.h>
+#include <sys/malloc.h>
+#include <sys/uio.h>
+#include <sys/bio.h>
+
+#include <dev/nand/nand.h>
+#include <dev/nand/nandbus.h>
+#include <dev/nand/nand_dev.h>
+#include "nand_if.h"
+#include "nandbus_if.h"
+
+static int nand_page_stat(struct nand_chip *, struct page_stat_io *);
+static int nand_block_stat(struct nand_chip *, struct block_stat_io *);
+
+static d_ioctl_t nand_ioctl;
+static d_open_t nand_open;
+static d_strategy_t nand_strategy;
+
+static struct cdevsw nand_cdevsw = {
+ .d_version = D_VERSION,
+ .d_name = "nand",
+ .d_open = nand_open,
+ .d_read = physread,
+ .d_write = physwrite,
+ .d_ioctl = nand_ioctl,
+ .d_strategy = nand_strategy,
+};
+
+static int
+offset_to_page(struct chip_geom *cg, uint32_t offset)
+{
+
+ return (offset / cg->page_size);
+}
+
+static int
+offset_to_page_off(struct chip_geom *cg, uint32_t offset)
+{
+
+ return (offset % cg->page_size);
+}
+
+int
+nand_make_dev(struct nand_chip *chip)
+{
+ struct nandbus_ivar *ivar;
+ device_t parent, nandbus;
+ int parent_unit, unit;
+ char *name;
+
+ ivar = device_get_ivars(chip->dev);
+ nandbus = device_get_parent(chip->dev);
+
+ if (ivar->chip_cdev_name) {
+ name = ivar->chip_cdev_name;
+
+ /*
+ * If we got distinct name for chip device we can enumarete it
+ * based on contoller number.
+ */
+ parent = device_get_parent(nandbus);
+ } else {
+ name = "nand";
+ parent = nandbus;
+ }
+
+ parent_unit = device_get_unit(parent);
+ unit = parent_unit * 4 + chip->num;
+ chip->cdev = make_dev(&nand_cdevsw, unit, UID_ROOT, GID_WHEEL,
+ 0666, "%s%d.%d", name, parent_unit, chip->num);
+
+ if (chip->cdev == NULL)
+ return (ENXIO);
+
+ if (bootverbose)
+ device_printf(chip->dev, "Created cdev %s%d.%d for chip "
+ "[0x%0x, 0x%0x]\n", name, parent_unit, chip->num,
+ ivar->man_id, ivar->dev_id);
+
+ chip->cdev->si_drv1 = chip;
+
+ return (0);
+}
+
+void
+nand_destroy_dev(struct nand_chip *chip)
+{
+
+ if (chip->cdev)
+ destroy_dev(chip->cdev);
+}
+
+static int
+nand_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
+{
+
+ return (0);
+}
+
+static int
+nand_read(struct nand_chip *chip, uint32_t offset, void *buf, uint32_t len)
+{
+ struct chip_geom *cg;
+ device_t nandbus;
+ int start_page, count, off, err = 0;
+ uint8_t *ptr, *tmp;
+
+ nand_debug(NDBG_CDEV, "Read from chip%d [%p] at %d\n", chip->num,
+ chip, offset);
+
+ nandbus = device_get_parent(chip->dev);
+ NANDBUS_LOCK(nandbus);
+ NANDBUS_SELECT_CS(device_get_parent(chip->dev), chip->num);
+
+ cg = &chip->chip_geom;
+ start_page = offset_to_page(cg, offset);
+ off = offset_to_page_off(cg, offset);
+ count = (len > cg->page_size - off) ? cg->page_size - off : len;
+
+ ptr = (uint8_t *)buf;
+ while (len > 0) {
+ if (len < cg->page_size) {
+ tmp = malloc(cg->page_size, M_NAND, M_WAITOK);
+ if (!tmp) {
+ err = ENOMEM;
+ break;
+ }
+ err = NAND_READ_PAGE(chip->dev, start_page,
+ tmp, cg->page_size, 0);
+ if (err) {
+ free(tmp, M_NAND);
+ break;
+ }
+ bcopy(tmp + off, ptr, count);
+ free(tmp, M_NAND);
+ } else {
+ err = NAND_READ_PAGE(chip->dev, start_page,
+ ptr, cg->page_size, 0);
+ if (err)
+ break;
+ }
+
+ len -= count;
+ start_page++;
+ ptr += count;
+ count = (len > cg->page_size) ? cg->page_size : len;
+ off = 0;
+ }
+
+ NANDBUS_UNLOCK(nandbus);
+ return (err);
+}
+
+static int
+nand_write(struct nand_chip *chip, uint32_t offset, void* buf, uint32_t len)
+{
+ struct chip_geom *cg;
+ device_t nandbus;
+ int off, start_page, err = 0;
+ uint8_t *ptr;
+
+ nand_debug(NDBG_CDEV, "Write to chip %d [%p] at %d\n", chip->num,
+ chip, offset);
+
+ nandbus = device_get_parent(chip->dev);
+ NANDBUS_LOCK(nandbus);
+ NANDBUS_SELECT_CS(device_get_parent(chip->dev), chip->num);
+
+ cg = &chip->chip_geom;
+ start_page = offset_to_page(cg, offset);
+ off = offset_to_page_off(cg, offset);
+
+ if (off != 0 || (len % cg->page_size) != 0) {
+ printf("Not aligned write start [0x%08x] size [0x%08x]\n",
+ off, len);
+ NANDBUS_UNLOCK(nandbus);
+ return (EINVAL);
+ }
+
+ ptr = (uint8_t *)buf;
+ while (len > 0) {
+ err = NAND_PROGRAM_PAGE(chip->dev, start_page, ptr,
+ cg->page_size, 0);
+ if (err)
+ break;
+
+ len -= cg->page_size;
+ start_page++;
+ ptr += cg->page_size;
+ }
+
+ NANDBUS_UNLOCK(nandbus);
+ return (err);
+}
+
+static void
+nand_strategy(struct bio *bp)
+{
+ struct nand_chip *chip;
+ struct cdev *dev;
+ int err = 0;
+
+ dev = bp->bio_dev;
+ chip = dev->si_drv1;
+
+ nand_debug(NDBG_CDEV, "Strategy %s on chip %d [%p]\n",
+ (bp->bio_cmd & BIO_READ) == BIO_READ ? "READ" : "WRITE",
+ chip->num, chip);
+
+ if ((bp->bio_cmd & BIO_READ) == BIO_READ) {
+ err = nand_read(chip,
+ bp->bio_offset & 0xffffffff,
+ bp->bio_data, bp->bio_bcount);
+ } else {
+ err = nand_write(chip,
+ bp->bio_offset & 0xffffffff,
+ bp->bio_data, bp->bio_bcount);
+ }
+
+ if (err == 0)
+ bp->bio_resid = 0;
+ else {
+ bp->bio_error = EIO;
+ bp->bio_flags |= BIO_ERROR;
+ bp->bio_resid = bp->bio_bcount;
+ }
+
+ biodone(bp);
+}
+
+static int
+nand_oob_access(struct nand_chip *chip, uint32_t page, uint32_t offset,
+ uint32_t len, uint8_t *data, uint8_t write)
+{
+ struct chip_geom *cg;
+ uint8_t *buf = NULL;
+ int ret = 0;
+
+ cg = &chip->chip_geom;
+
+ buf = malloc(cg->oob_size, M_NAND, M_WAITOK);
+ if (!buf)
+ return (ENOMEM);
+
+ memset(buf, 0xff, cg->oob_size);
+
+ if (!write) {
+ ret = nand_read_oob(chip, page, buf, cg->oob_size);
+ copyout(buf, data, len);
+ } else {
+ copyin(data, buf, len);
+ ret = nand_prog_oob(chip, page, buf, cg->oob_size);
+ }
+
+ free(buf, M_NAND);
+
+ return (ret);
+}
+
+static int
+nand_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag,
+ struct thread *td)
+{
+ struct nand_chip *chip;
+ struct nand_oob_rw *oob_rw = NULL;
+ struct nand_raw_rw *raw_rw = NULL;
+ device_t nandbus;
+ uint8_t *buf = NULL;
+ int ret = 0;
+ uint8_t status;
+
+ chip = (struct nand_chip *)dev->si_drv1;
+ nandbus = device_get_parent(chip->dev);
+
+ if ((cmd == NAND_IO_RAW_READ) || (cmd == NAND_IO_RAW_PROG)) {
+ raw_rw = (struct nand_raw_rw *)data;
+ buf = malloc(raw_rw->len, M_NAND, M_WAITOK);
+ }
+ switch(cmd) {
+ case NAND_IO_ERASE:
+ ret = nand_erase_blocks(chip, ((off_t *)data)[0],
+ ((off_t *)data)[1]);
+ break;
+
+ case NAND_IO_OOB_READ:
+ oob_rw = (struct nand_oob_rw *)data;
+ ret = nand_oob_access(chip, oob_rw->page, 0,
+ oob_rw->len, oob_rw->data, 0);
+ break;
+
+ case NAND_IO_OOB_PROG:
+ oob_rw = (struct nand_oob_rw *)data;
+ ret = nand_oob_access(chip, oob_rw->page, 0,
+ oob_rw->len, oob_rw->data, 1);
+ break;
+
+ case NAND_IO_GET_STATUS:
+ NANDBUS_LOCK(nandbus);
+ ret = NANDBUS_GET_STATUS(nandbus, &status);
+ if (ret == 0)
+ *(uint8_t *)data = status;
+ NANDBUS_UNLOCK(nandbus);
+ break;
+
+ case NAND_IO_RAW_PROG:
+ ret = copyin(raw_rw->data, buf, raw_rw->len);
+ if (ret)
+ break;
+ ret = nand_prog_pages_raw(chip, raw_rw->off, buf,
+ raw_rw->len);
+ break;
+
+ case NAND_IO_RAW_READ:
+ ret = nand_read_pages_raw(chip, raw_rw->off, buf,
+ raw_rw->len);
+ if (ret)
+ break;
+ ret = copyout(buf, raw_rw->data, raw_rw->len);
+ break;
+
+ case NAND_IO_PAGE_STAT:
+ ret = nand_page_stat(chip, (struct page_stat_io *)data);
+ break;
+
+ case NAND_IO_BLOCK_STAT:
+ ret = nand_block_stat(chip, (struct block_stat_io *)data);
+ break;
+
+ case NAND_IO_GET_CHIP_PARAM:
+ nand_get_chip_param(chip, (struct chip_param_io *)data);
+ break;
+
+ default:
+ printf("Unknown nand_ioctl request \n");
+ ret = EIO;
+ }
+
+ if (buf)
+ free(buf, M_NAND);
+
+ return (ret);
+}
+
+static int
+nand_page_stat(struct nand_chip *chip, struct page_stat_io *page_stat)
+{
+ struct chip_geom *cg;
+ struct page_stat *stat;
+ int num_pages;
+
+ cg = &chip->chip_geom;
+ num_pages = cg->pgs_per_blk * cg->blks_per_lun * cg->luns;
+ if (page_stat->page_num >= num_pages)
+ return (EINVAL);
+
+ stat = &chip->pg_stat[page_stat->page_num];
+ page_stat->page_read = stat->page_read;
+ page_stat->page_written = stat->page_written;
+ page_stat->page_raw_read = stat->page_raw_read;
+ page_stat->page_raw_written = stat->page_raw_written;
+ page_stat->ecc_succeded = stat->ecc_stat.ecc_succeded;
+ page_stat->ecc_corrected = stat->ecc_stat.ecc_corrected;
+ page_stat->ecc_failed = stat->ecc_stat.ecc_failed;
+
+ return (0);
+}
+
+static int
+nand_block_stat(struct nand_chip *chip, struct block_stat_io *block_stat)
+{
+ struct chip_geom *cg;
+ uint32_t block_num = block_stat->block_num;
+
+ cg = &chip->chip_geom;
+ if (block_num >= cg->blks_per_lun * cg->luns)
+ return (EINVAL);
+
+ block_stat->block_erased = chip->blk_stat[block_num].block_erased;
+
+ return (0);
+}