diff options
author | Uwe Stuehler <uwe@cvs.openbsd.org> | 2006-11-25 14:32:01 +0000 |
---|---|---|
committer | Uwe Stuehler <uwe@cvs.openbsd.org> | 2006-11-25 14:32:01 +0000 |
commit | 3d181ecda67c9ab9633a36011ba4b76b3d1f2a7c (patch) | |
tree | 466f5b6aa1c21712a8fb87c85506e12bf088e6bc /sys | |
parent | 7f5337a570881bf9a7a29775b9ca02914568e566 (diff) |
Initial NAND flash support for Zaurus, not enabled yet; prodded by many.
Diffstat (limited to 'sys')
-rw-r--r-- | sys/arch/zaurus/conf/GENERIC | 3 | ||||
-rw-r--r-- | sys/arch/zaurus/conf/files.zaurus | 7 | ||||
-rw-r--r-- | sys/arch/zaurus/dev/zaurus_flash.c | 914 | ||||
-rw-r--r-- | sys/conf/files | 3 | ||||
-rw-r--r-- | sys/dev/flash.c | 1099 | ||||
-rw-r--r-- | sys/dev/flashvar.h | 143 |
6 files changed, 2166 insertions, 3 deletions
diff --git a/sys/arch/zaurus/conf/GENERIC b/sys/arch/zaurus/conf/GENERIC index a4a7b1d265f..d182a1c1d8c 100644 --- a/sys/arch/zaurus/conf/GENERIC +++ b/sys/arch/zaurus/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.54 2006/09/27 08:54:44 grange Exp $ +# $OpenBSD: GENERIC,v 1.55 2006/11/25 14:31:59 uwe Exp $ # # For further information on compiling OpenBSD kernels, see the config(8) # man page. @@ -195,6 +195,7 @@ zaudio0 at pxaip? # Zaurus I2S/I2C sound audio* at zaudio? zrc0 at pxaip? # Zaurus remote control wskbd* at zrc? mux 1 +#flash0 at pxaip? # NAND flash memory # 1-Wire devices option ONEWIREVERBOSE diff --git a/sys/arch/zaurus/conf/files.zaurus b/sys/arch/zaurus/conf/files.zaurus index 4097fd2d0d9..29feac6abe4 100644 --- a/sys/arch/zaurus/conf/files.zaurus +++ b/sys/arch/zaurus/conf/files.zaurus @@ -1,4 +1,4 @@ -# $OpenBSD: files.zaurus,v 1.22 2006/09/27 06:33:03 grange Exp $ +# $OpenBSD: files.zaurus,v 1.23 2006/11/25 14:31:59 uwe Exp $ # # First try for arm-specific configuration info # @@ -66,6 +66,11 @@ device zrc: wskbddev attach zrc at pxaip file arch/zaurus/dev/zaurus_remote.c zrc +# NAND flash pseudo-disk device (Xilinx NAND flash controller) +device flash: disk +attach flash at pxaip with flash_pxaip +file arch/zaurus/dev/zaurus_flash.c flash_pxaip + # # Machine-independent ATA drivers # diff --git a/sys/arch/zaurus/dev/zaurus_flash.c b/sys/arch/zaurus/dev/zaurus_flash.c new file mode 100644 index 00000000000..8dc584a5f32 --- /dev/null +++ b/sys/arch/zaurus/dev/zaurus_flash.c @@ -0,0 +1,914 @@ +/* $OpenBSD: zaurus_flash.c,v 1.1 2006/11/25 14:31:59 uwe Exp $ */ + +/* + * Copyright (c) 2005 Uwe Stuehler <uwe@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Samsung NAND flash controlled by some unspecified CPLD device. + */ + +#include <sys/param.h> +#include <sys/buf.h> +#include <sys/device.h> +#include <sys/disk.h> +#include <sys/disklabel.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/systm.h> + +#include <dev/flashvar.h> +#include <dev/rndvar.h> + +#include <machine/zaurus_var.h> + +#include <arch/arm/xscale/pxa2x0var.h> + +#define DEBUG +#ifdef DEBUG +#define DPRINTF(x) printf x +#else +#define DPRINTF(x) +#endif + +/* CPLD register definitions */ +#define CPLD_REG_ECCLPLB 0x00 +#define CPLD_REG_ECCLPUB 0x04 +#define CPLD_REG_ECCCP 0x08 +#define CPLD_REG_ECCCNTR 0x0c +#define CPLD_REG_ECCCLRR 0x10 +#define CPLD_REG_FLASHIO 0x14 +#define CPLD_REG_FLASHCTL 0x18 +#define FLASHCTL_NCE0 (1<<0) +#define FLASHCTL_CLE (1<<1) +#define FLASHCTL_ALE (1<<2) +#define FLASHCTL_NWP (1<<3) +#define FLASHCTL_NCE1 (1<<4) +#define FLASHCTL_RYBY (1<<5) +#define FLASHCTL_NCE (FLASHCTL_NCE0|FLASHCTL_NCE1) + +/* CPLD register accesses */ +#define CPLD_READ(sc, r) \ + bus_space_read_1((sc)->sc_iot, (sc)->sc_ioh, (r)) +#define CPLD_WRITE(sc, r, v) \ + bus_space_write_1((sc)->sc_iot, (sc)->sc_ioh, (r), (v)) +#define CPLD_SET(sc, r, v) \ + CPLD_WRITE((sc), (r), CPLD_READ((sc), (r)) | (v)) +#define CPLD_CLR(sc, r, v) \ + CPLD_WRITE((sc), (r), CPLD_READ((sc), (r)) & ~(v)) +#define CPLD_SETORCLR(sc, r, m, v) \ + ((v) ? CPLD_SET((sc), (r), (m)) : CPLD_CLR((sc), (r), (m))) + +/* Offsets into OOB data. */ +#define OOB_JFFS2_ECC0 0 +#define OOB_JFFS2_ECC1 1 +#define OOB_JFFS2_ECC2 2 +#define OOB_JFFS2_ECC3 3 +#define OOB_JFFS2_ECC4 6 +#define OOB_JFFS2_ECC5 7 +#define OOB_LOGADDR_0_LO 8 +#define OOB_LOGADDR_0_HI 9 +#define OOB_LOGADDR_1_LO 10 +#define OOB_LOGADDR_1_HI 11 +#define OOB_LOGADDR_2_LO 12 +#define OOB_LOGADDR_2_HI 13 + +/* + * Structure for managing logical blocks in a partition; allocated on + * first use of each partition on a "safe" flash device. + */ +struct zflash_safe { + dev_t sp_dev; + u_long sp_pblks; /* physical block count */ + u_long sp_lblks; /* logical block count */ + u_int16_t *sp_phyuse; /* physical block usage */ + u_int *sp_logmap; /* logical to physical */ + u_int sp_pnext; /* next physical block */ +}; + +struct zflash_softc { + struct flash_softc sc_flash; + bus_space_tag_t sc_iot; + bus_space_handle_t sc_ioh; + int sc_ioobbadblk; + int sc_ioobpostbadblk; + struct zflash_safe *sc_safe[MAXPARTITIONS]; +}; + +int zflashmatch(struct device *, void *, void *); +void zflashattach(struct device *, struct device *, void *); +int zflashdetach(struct device *, int); + +u_int8_t zflash_reg8_read(void *, int); +int zflash_regx_read_page(void *, caddr_t, caddr_t); +void zflash_reg8_write(void *, int, u_int8_t); +int zflash_regx_write_page(void *, caddr_t, caddr_t); +void zflash_default_disklabel(void *, dev_t, struct disklabel *, + struct cpu_disklabel *); +int zflash_safe_strategy(void *, struct buf *); + +int zflash_safe_start(struct zflash_softc *, dev_t); +void zflash_safe_stop(struct zflash_softc *, dev_t); + +struct cfattach flash_pxaip_ca = { + sizeof(struct zflash_softc), zflashmatch, zflashattach, + zflashdetach, flashactivate +}; + +struct flash_ctl_tag zflash_ctl_tag = { + zflash_reg8_read, + zflash_regx_read_page, + zflash_reg8_write, + zflash_regx_write_page, + zflash_default_disklabel, + zflash_safe_strategy +}; + +int +zflashmatch(struct device *parent, void *match, void *aux) +{ + /* XXX call flashprobe(), yet to be implemented */ + return ZAURUS_ISC3000; +} + +void +zflashattach(struct device *parent, struct device *self, void *aux) +{ + struct zflash_softc *sc = (struct zflash_softc *)self; + struct pxaip_attach_args *pxa = aux; + bus_addr_t addr = pxa->pxa_addr; + bus_size_t size = pxa->pxa_size; + + sc->sc_iot = pxa->pxa_iot; + + if ((int)addr == -1 || (int)size == 0) { + addr = 0x0c000000; + size = 0x00001000; + } + + if (bus_space_map(sc->sc_iot, addr, size, 0, &sc->sc_ioh) != 0) { + printf(": failed to map controller\n"); + return; + } + + /* Disable and write-protect the chip. */ + CPLD_WRITE(sc, CPLD_REG_FLASHCTL, FLASHCTL_NCE); + + flashattach(&sc->sc_flash, &zflash_ctl_tag, sc); + + switch (sc->sc_flash.sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: /* C3000 */ + sc->sc_ioobpostbadblk = 4; + sc->sc_ioobbadblk = 5; + break; + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: /* C3100 */ + sc->sc_ioobpostbadblk = 4; + sc->sc_ioobbadblk = 0; + break; + } +} + +int +zflashdetach(struct device *self, int flags) +{ + struct zflash_softc *sc = (struct zflash_softc *)self; + int part; + + for (part = 0; part < MAXPARTITIONS; part++) + zflash_safe_stop(sc, part); + + return (flashdetach(self, flags)); +} + +u_int8_t +zflash_reg8_read(void *arg, int reg) +{ + struct zflash_softc *sc = arg; + u_int8_t value; + + switch (reg) { + case FLASH_REG_DATA: + value = CPLD_READ(sc, CPLD_REG_FLASHIO); + break; + case FLASH_REG_READY: + value = (CPLD_READ(sc, CPLD_REG_FLASHCTL) & + FLASHCTL_RYBY) != 0; + break; + default: +#ifdef DIAGNOSTIC + printf("%s: read from pseudo-register %02x\n", + sc->sc_flash.sc_dev.dv_xname, reg); +#endif + value = 0; + break; + } + return value; +} + +void +zflash_reg8_write(void *arg, int reg, u_int8_t value) +{ + struct zflash_softc *sc = arg; + + switch (reg) { + case FLASH_REG_DATA: + case FLASH_REG_COL: + case FLASH_REG_ROW: + case FLASH_REG_CMD: + CPLD_WRITE(sc, CPLD_REG_FLASHIO, value); + break; + case FLASH_REG_ALE: + CPLD_SETORCLR(sc, CPLD_REG_FLASHCTL, FLASHCTL_ALE, value); + break; + case FLASH_REG_CLE: + CPLD_SETORCLR(sc, CPLD_REG_FLASHCTL, FLASHCTL_CLE, value); + break; + case FLASH_REG_CE: + CPLD_SETORCLR(sc, CPLD_REG_FLASHCTL, FLASHCTL_NCE, !value); + break; + case FLASH_REG_WP: + CPLD_SETORCLR(sc, CPLD_REG_FLASHCTL, FLASHCTL_NWP, !value); + break; +#ifdef DIAGNOSTIC + default: + printf("%s: write to pseudo-register %02x\n", + sc->sc_flash.sc_dev.dv_xname, reg); +#endif + } +} + +int +zflash_regx_read_page(void *arg, caddr_t data, caddr_t oob) +{ + struct zflash_softc *sc = arg; + + if (oob == NULL || sc->sc_flash.sc_flashdev->pagesize != 512) { + flash_reg8_read_page(&sc->sc_flash, data, oob); + return 0; + } + + flash_reg8_read_page(&sc->sc_flash, data, oob); + + oob[OOB_JFFS2_ECC0] = 0xff; + oob[OOB_JFFS2_ECC1] = 0xff; + oob[OOB_JFFS2_ECC2] = 0xff; + oob[OOB_JFFS2_ECC3] = 0xff; + oob[OOB_JFFS2_ECC4] = 0xff; + oob[OOB_JFFS2_ECC5] = 0xff; + return 0; +} + +int +zflash_regx_write_page(void *arg, caddr_t data, caddr_t oob) +{ + struct zflash_softc *sc = arg; + int i; + + if (oob == NULL || sc->sc_flash.sc_flashdev->pagesize != 512) { + flash_reg8_write_page(&sc->sc_flash, data, oob); + return 0; + } + + if (oob[OOB_JFFS2_ECC0] != 0xff || oob[OOB_JFFS2_ECC1] != 0xff || + oob[OOB_JFFS2_ECC2] != 0xff || oob[OOB_JFFS2_ECC3] != 0xff || + oob[OOB_JFFS2_ECC4] != 0xff || oob[OOB_JFFS2_ECC5] != 0xff) { +#ifdef DIAGNOSTIC + printf("%s: non-FF ECC bytes in OOB data\n", + sc->sc_flash.sc_dev.dv_xname); +#endif + return EINVAL; + } + + CPLD_WRITE(sc, CPLD_REG_ECCCLRR, 0x00); + for (i = 0; i < sc->sc_flash.sc_flashdev->pagesize / 2; i++) + flash_reg8_write(&sc->sc_flash, FLASH_REG_DATA, data[i]); + + oob[OOB_JFFS2_ECC0] = ~CPLD_READ(sc, CPLD_REG_ECCLPUB); + oob[OOB_JFFS2_ECC1] = ~CPLD_READ(sc, CPLD_REG_ECCLPLB); + oob[OOB_JFFS2_ECC2] = (~CPLD_READ(sc, CPLD_REG_ECCCP) << 2) | 0x03; + + if (CPLD_READ(sc, CPLD_REG_ECCCNTR) != 0) { + printf("%s: ECC failed\n", sc->sc_flash.sc_dev.dv_xname); + oob[OOB_JFFS2_ECC0] = 0xff; + oob[OOB_JFFS2_ECC1] = 0xff; + oob[OOB_JFFS2_ECC2] = 0xff; + return EIO; + } + + CPLD_WRITE(sc, CPLD_REG_ECCCLRR, 0x00); + for (; i < sc->sc_flash.sc_flashdev->pagesize; i++) + flash_reg8_write(&sc->sc_flash, FLASH_REG_DATA, data[i]); + + oob[OOB_JFFS2_ECC3] = ~CPLD_READ(sc, CPLD_REG_ECCLPUB); + oob[OOB_JFFS2_ECC4] = ~CPLD_READ(sc, CPLD_REG_ECCLPLB); + oob[OOB_JFFS2_ECC5] = (~CPLD_READ(sc, CPLD_REG_ECCCP) << 2) | 0x03; + + if (CPLD_READ(sc, CPLD_REG_ECCCNTR) != 0) { + printf("%s: ECC failed\n", sc->sc_flash.sc_dev.dv_xname); + oob[OOB_JFFS2_ECC0] = 0xff; + oob[OOB_JFFS2_ECC1] = 0xff; + oob[OOB_JFFS2_ECC2] = 0xff; + oob[OOB_JFFS2_ECC3] = 0xff; + oob[OOB_JFFS2_ECC4] = 0xff; + oob[OOB_JFFS2_ECC5] = 0xff; + return EIO; + } + + for (i = 0; i < sc->sc_flash.sc_flashdev->oobsize; i++) + flash_reg8_write(&sc->sc_flash, FLASH_REG_DATA, oob[i]); + + oob[OOB_JFFS2_ECC0] = 0xff; + oob[OOB_JFFS2_ECC1] = 0xff; + oob[OOB_JFFS2_ECC2] = 0xff; + oob[OOB_JFFS2_ECC3] = 0xff; + oob[OOB_JFFS2_ECC4] = 0xff; + oob[OOB_JFFS2_ECC5] = 0xff; + return 0; +} + +/* + * A default disklabel with only one RAW_PART spanning the whole + * device is passed to us. We add the partitions besides RAW_PART. + */ +void +zflash_default_disklabel(void *arg, dev_t dev, struct disklabel *lp, + struct cpu_disklabel *clp) +{ + struct zflash_softc *sc = arg; + long bsize = sc->sc_flash.sc_flashdev->pagesize; + + switch (sc->sc_flash.sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + lp->d_partitions[8].p_size = 7*1024*1024 / bsize; + lp->d_partitions[9].p_size = 5*1024*1024 / bsize; + lp->d_partitions[10].p_size = 4*1024*1024 / bsize; + break; + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + lp->d_partitions[8].p_size = 7*1024*1024 / bsize; + lp->d_partitions[9].p_size = 32*1024*1024 / bsize; + lp->d_partitions[10].p_size = 89*1024*1024 / bsize; + break; + default: + return; + } + + /* The "smf" partition uses logical addressing. */ + lp->d_partitions[8].p_offset = 0; + lp->d_partitions[8].p_fstype = FS_OTHER; + + /* The "root" partition uses physical addressing. */ + lp->d_partitions[9].p_offset = lp->d_partitions[8].p_size; + lp->d_partitions[9].p_fstype = FS_OTHER; + + /* The "home" partition uses physical addressing. */ + lp->d_partitions[10].p_offset = lp->d_partitions[9].p_offset + + lp->d_partitions[9].p_size; + lp->d_partitions[10].p_fstype = FS_OTHER; + + lp->d_npartitions = 11; + + /* Re-calculate the checksum. */ + lp->d_checksum = dkcksum(lp); +} + +/* + * Sharp's access strategy for bad blocks management and wear-leveling. + */ + +#define PHYUSE_STATUS(v) ((v) & 0x00ff) +#define P_BADBLOCK 0x0000 +#define P_POSTBADBLOCK 0x00f0 +#define P_NORMALBLOCK 0x00ff +#define PHYUSE_WRITTEN(v) ((v) & 0xff00) +#define P_DUST 0x0000 +#define P_LOGICAL 0x0100 +#define P_JFFS2 0x0300 + +void zflash_write_strategy(struct zflash_softc *, struct buf *, + struct zflash_safe *, u_int, u_int); +u_int zflash_safe_next_block(struct zflash_safe *); + +u_char zflash_oob_status_decode(u_char); +u_int16_t zflash_oob_status(struct zflash_softc *, u_char *); +u_int zflash_oob_logno(struct zflash_softc *, u_char *); +void zflash_oob_set_status(struct zflash_softc *, u_char *, u_int16_t); +void zflash_oob_set_logno(struct zflash_softc *, u_char *, u_int); + +int +zflash_safe_strategy(void *arg, struct buf *bp) +{ + struct zflash_softc *sc = arg; + struct zflash_safe *sp; + u_int logno; + u_int blkofs; + u_int blkno; + int error; + int part; + int i; + + /* Initialize logical blocks management on the fly. */ + /* XXX toss everything when the disklabel has changed. */ + if ((error = zflash_safe_start(sc, bp->b_dev)) != 0) { + bp->b_error = error; + bp->b_flags |= B_ERROR; + return 0; + } + + part = flashpart(bp->b_dev); + sp = sc->sc_safe[part]; + + logno = bp->b_blkno / (sc->sc_flash.sc_flashdev->blkpages * + sc->sc_flash.sc_flashdev->pagesize / DEV_BSIZE); + blkofs = bp->b_blkno % (sc->sc_flash.sc_flashdev->blkpages * + sc->sc_flash.sc_flashdev->pagesize / DEV_BSIZE); + + /* If exactly at end of logical flash, return EOF, else error. */ + if (logno == sp->sp_lblks && blkofs == 0) { + bp->b_resid = bp->b_bcount; + return 0; + } else if (logno >= sp->sp_lblks) { + bp->b_error = EINVAL; + bp->b_flags |= B_ERROR; + return 0; + } + + /* Writing is more complicated, so handle it separately. */ + if ((bp->b_flags & B_READ) == 0) { + flash_chip_enable(&sc->sc_flash); + zflash_write_strategy(sc, bp, sp, logno, blkofs); + flash_chip_disable(&sc->sc_flash); + return 0; + } + + /* Get the physical flash block number for this logical one. */ + blkno = sp->sp_logmap[logno]; + + /* Unused logical blocks read as all 0xff. */ + if ((bp->b_flags & B_READ) != 0 && blkno == UINT_MAX) { + for (i = 0; i < sc->sc_flash.sc_flashdev->pagesize; i++) + ((u_char *)bp->b_data)[i] = 0xff; + bp->b_resid = bp->b_bcount - + sc->sc_flash.sc_flashdev->pagesize; + return 0; + } + + /* Update the block number in the buffer with the physical one. */ + bp->b_blkno = blkno * (sc->sc_flash.sc_flashdev->blkpages * + sc->sc_flash.sc_flashdev->pagesize / DEV_BSIZE) + blkofs; + + /* Process the modified transfer buffer normally. */ + return 1; +} + +void +zflash_write_strategy(struct zflash_softc *sc, struct buf *bp, + struct zflash_safe *sp, u_int logno, u_int logofs) +{ + size_t bufsize; + u_char *buf = NULL; + size_t oobsize; + u_char *oob = NULL; + u_int oblkno; + u_int nblkno; + int error; + + /* Not efficient, but we always transfer one page for now. */ + if (bp->b_bcount < sc->sc_flash.sc_flashdev->pagesize) { + bp->b_error = EINVAL; + goto bad; + } + + /* Allocate a temporary buffer for one flash block. */ + bufsize = sc->sc_flash.sc_flashdev->blkpages * + sc->sc_flash.sc_flashdev->pagesize; + buf = (u_char *)malloc(bufsize, M_DEVBUF, M_NOWAIT); + if (buf == NULL) { + bp->b_error = ENOMEM; + goto bad; + } + + /* Allocate a temporary buffer for one spare area. */ + oobsize = sc->sc_flash.sc_flashdev->oobsize; + oob = (u_char *)malloc(oobsize, M_DEVBUF, M_NOWAIT); + if (oob == NULL) { + bp->b_error = ENOMEM; + goto bad; + } + + /* Read the old logical block into the temporary buffer. */ + oblkno = sp->sp_logmap[logno]; + if (oblkno != UINT_MAX) { + error = flash_chip_read_block(&sc->sc_flash, oblkno, buf); + if (error != 0) { + bp->b_error = error; + goto bad; + } + } else + /* Unused logical blocks read as all 0xff. */ + memset(buf, 0xff, bufsize); + + /* Transfer the page into the logical block buffer. */ + bcopy(bp->b_data, buf + logofs * sc->sc_flash.sc_flashdev->pagesize, + sc->sc_flash.sc_flashdev->pagesize); + + /* Generate OOB data for the spare area of this logical block. */ + memset(oob, 0xff, oobsize); + zflash_oob_set_status(sc, oob, P_NORMALBLOCK); + zflash_oob_set_logno(sc, oob, logno); + + while (1) { + /* Search for a free physical block. */ + nblkno = zflash_safe_next_block(sp); + if (nblkno == UINT_MAX) { + printf("%s: no spare block, giving up on logical" + " block %u\n", sc->sc_flash.sc_dev.dv_xname, + logno); + bp->b_error = ENOSPC; + goto bad; + } + +#if 0 + DPRINTF(("%s: moving logical block %u from physical %u to %u\n", + sc->sc_flash.sc_dev.dv_xname, logno, oblkno, nblkno)); +#endif + + /* Erase the free physical block. */ + if (flash_chip_erase_block(&sc->sc_flash, nblkno) != 0) { + printf("%s: can't erase block %u, retrying\n", + sc->sc_flash.sc_dev.dv_xname, nblkno); + sp->sp_phyuse[nblkno] = P_POSTBADBLOCK | P_DUST; + continue; + } + + /* Write the logical block to the free physical block. */ + if (flash_chip_write_block(&sc->sc_flash, nblkno, buf, oob)) { + printf("%s: can't write block %u, retrying\n", + sc->sc_flash.sc_dev.dv_xname, nblkno); + goto trynext; + } + + /* Yeah, we re-wrote that logical block! */ + break; + trynext: + sp->sp_phyuse[nblkno] = P_POSTBADBLOCK | P_DUST; + (void)flash_chip_erase_block(&sc->sc_flash, nblkno); + } + + /* Map the new physical block. */ + sp->sp_logmap[logno] = nblkno; + sp->sp_phyuse[nblkno] = PHYUSE_STATUS(sp->sp_phyuse[nblkno]) + | P_LOGICAL; + + /* Erase the old physical block. */ + if (oblkno != UINT_MAX) { + sp->sp_phyuse[oblkno] = PHYUSE_STATUS(sp->sp_phyuse[oblkno]) + | P_DUST; + error = flash_chip_erase_block(&sc->sc_flash, oblkno); + if (error != 0) { + printf("%s: can't erase old block %u\n", + sc->sc_flash.sc_dev.dv_xname, oblkno); + bp->b_error = error; + goto bad; + } + } + + bp->b_resid = bp->b_bcount - sc->sc_flash.sc_flashdev->pagesize; + free(oob, M_DEVBUF); + free(buf, M_DEVBUF); + return; +bad: + bp->b_flags |= B_ERROR; + if (oob != NULL) + free(oob, M_DEVBUF); + if (buf != NULL) + free(buf, M_DEVBUF); +} + +int +zflash_safe_start(struct zflash_softc *sc, dev_t dev) +{ + u_char oob[FLASH_MAXOOBSIZE]; + struct disklabel *lp = sc->sc_flash.sc_dk.dk_label; + struct zflash_safe *sp; + u_int16_t *phyuse; + u_int *logmap; + u_int blksect; + u_int blkno; + u_int logno; + u_int unusable; + int part; + + part = flashpart(dev); + if (sc->sc_safe[part] != NULL) + return 0; + + /* We can only handle so much OOB data here. */ + if (sc->sc_flash.sc_flashdev->oobsize > FLASH_MAXOOBSIZE) + return EIO; + + /* Safe partitions must start on a flash block boundary. */ + blksect = (sc->sc_flash.sc_flashdev->blkpages * + sc->sc_flash.sc_flashdev->pagesize) / lp->d_secsize; + if (lp->d_partitions[part].p_offset % blksect) + return EIO; + + MALLOC(sp, struct zflash_safe *, sizeof(struct zflash_safe), + M_DEVBUF, M_NOWAIT); + if (sp == NULL) + return ENOMEM; + + bzero(sp, sizeof(struct zflash_safe)); + sp->sp_dev = dev; + + sp->sp_pblks = lp->d_partitions[part].p_size / blksect; + sp->sp_lblks = sp->sp_pblks; + + /* Try to reserve a number of spare physical blocks. */ + switch (sc->sc_flash.sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + sp->sp_lblks -= 24; /* C3000 */ + break; + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + sp->sp_lblks -= 4; /* C3100 */ + break; + } + + DPRINTF(("pblks %u lblks %u\n", sp->sp_pblks, sp->sp_lblks)); + + /* Next physical block to use; randomize for wear-leveling. */ + sp->sp_pnext = arc4random() % sp->sp_pblks; + + /* Allocate physical block usage map. */ + phyuse = (u_int16_t *)malloc(sp->sp_pblks * sizeof(u_int16_t), + M_DEVBUF, M_NOWAIT); + if (phyuse == NULL) { + FREE(sp, M_DEVBUF); + return ENOMEM; + } + sp->sp_phyuse = phyuse; + + /* Allocate logical to physical block map. */ + logmap = (u_int *)malloc(sp->sp_lblks * sizeof(u_int), + M_DEVBUF, M_NOWAIT); + if (logmap == NULL) { + FREE(phyuse, M_DEVBUF); + FREE(sp, M_DEVBUF); + return ENOMEM; + } + sp->sp_logmap = logmap; + + /* Initialize the physical and logical block maps. */ + for (blkno = 0; blkno < sp->sp_pblks; blkno++) + phyuse[blkno] = P_BADBLOCK | P_DUST; + for (blkno = 0; blkno < sp->sp_lblks; blkno++) + logmap[blkno] = UINT_MAX; + + /* Update physical block usage map with real data. */ + unusable = 0; + flash_chip_enable(&sc->sc_flash); + for (blkno = 0; blkno < sp->sp_pblks; blkno++) { + long pageno; + + pageno = blkno * sc->sc_flash.sc_flashdev->blkpages; + if (flash_chip_read_oob(&sc->sc_flash, pageno, oob) != 0) { + DPRINTF(("blkno %u: can't read oob data\n", blkno)); + phyuse[blkno] = P_POSTBADBLOCK | P_DUST; + unusable++; + continue; + } + + phyuse[blkno] = zflash_oob_status(sc, oob); + if (PHYUSE_STATUS(phyuse[blkno]) != P_NORMALBLOCK) { + DPRINTF(("blkno %u: badblock status %x\n", blkno, + PHYUSE_STATUS(phyuse[blkno]))); + phyuse[blkno] |= P_DUST; + unusable++; + continue; + } + + logno = zflash_oob_logno(sc, oob); + if (logno == UINT_MAX) { + DPRINTF(("blkno %u: can't read logno\n", blkno)); + phyuse[blkno] |= P_JFFS2; + unusable++; + continue; + } + + if (logno == USHRT_MAX) { + phyuse[blkno] |= P_DUST; + /* Block is usable and available. */ + continue; + } + + if (logno >= sp->sp_lblks) { + DPRINTF(("blkno %u: logno %u too big\n", blkno, + logno)); + phyuse[blkno] |= P_JFFS2; + unusable++; + continue; + } + + if (logmap[logno] == UINT_MAX) { + phyuse[blkno] |= P_LOGICAL; + logmap[logno] = blkno; + } else { + /* Duplicate logical block! */ + DPRINTF(("blkno %u: duplicate logno %u\n", blkno, + logno)); + phyuse[blkno] |= P_DUST; + } + } + flash_chip_disable(&sc->sc_flash); + + if (unusable > 0) + printf("%s: %u unusable blocks\n", + sc->sc_flash.sc_dev.dv_xname, unusable); + + sc->sc_safe[part] = sp; + return 0; +} + +void +zflash_safe_stop(struct zflash_softc *sc, dev_t dev) +{ + struct zflash_safe *sp; + int part; + + part = flashpart(dev); + if (sc->sc_safe[part] == NULL) + return; + + sp = sc->sc_safe[part]; + free(sp->sp_phyuse, M_DEVBUF); + free(sp->sp_logmap, M_DEVBUF); + FREE(sp, M_DEVBUF); + sc->sc_safe[part] = NULL; +} + +u_int +zflash_safe_next_block(struct zflash_safe *sp) +{ + u_int blkno; + + for (blkno = sp->sp_pnext; blkno < sp->sp_pblks; blkno++) + if (sp->sp_phyuse[blkno] == (P_NORMALBLOCK|P_DUST)) { + sp->sp_pnext = blkno + 1; + return blkno; + } + + for (blkno = 0; blkno < sp->sp_pnext; blkno++) + if (sp->sp_phyuse[blkno] == (P_NORMALBLOCK|P_DUST)) { + sp->sp_pnext = blkno + 1; + return blkno; + } + + return UINT_MAX; +} + +/* + * Correct single bit errors in the block's status byte. + */ +u_char +zflash_oob_status_decode(u_char status) +{ + u_char bit; + int count; + + /* Speed-up. */ + if (status == 0xff) + return 0xff; + + /* Count the number of bits set in the byte. */ + for (count = 0, bit = 0x01; bit != 0x00; bit <<= 1) + if ((status & bit) != 0) + count++; + + return (count > 6) ? 0xff : 0x00; +} + +/* + * Decode the block's status byte into a value for the phyuse map. + */ +u_int16_t +zflash_oob_status(struct zflash_softc *sc, u_char *oob) +{ + u_char status; + + status = zflash_oob_status_decode(oob[sc->sc_ioobbadblk]); + if (status != 0xff) + return P_BADBLOCK; + + status = zflash_oob_status_decode(oob[sc->sc_ioobpostbadblk]); + if (status != 0xff) + return P_POSTBADBLOCK; + + return P_NORMALBLOCK; +} + +/* + * Extract the 16-bit logical block number corresponding to a physical + * block from the physical block's OOB data. + */ +u_int +zflash_oob_logno(struct zflash_softc *sc, u_char *oob) +{ + int idx_lo, idx_hi; + u_int16_t word; + u_int16_t bit; + int parity; + + /* Find a matching pair of high and low bytes. */ + if (oob[OOB_LOGADDR_0_LO] == oob[OOB_LOGADDR_1_LO] && + oob[OOB_LOGADDR_0_HI] == oob[OOB_LOGADDR_1_HI]) { + idx_lo = OOB_LOGADDR_0_LO; + idx_hi = OOB_LOGADDR_0_HI; + } else if (oob[OOB_LOGADDR_1_LO] == oob[OOB_LOGADDR_2_LO] && + oob[OOB_LOGADDR_1_HI] == oob[OOB_LOGADDR_2_HI]) { + idx_lo = OOB_LOGADDR_1_LO; + idx_hi = OOB_LOGADDR_1_HI; + } else if (oob[OOB_LOGADDR_2_LO] == oob[OOB_LOGADDR_0_LO] && + oob[OOB_LOGADDR_2_HI] == oob[OOB_LOGADDR_0_HI]) { + idx_lo = OOB_LOGADDR_2_LO; + idx_hi = OOB_LOGADDR_2_HI; + } else + /* Block's OOB data may be invalid. */ + return UINT_MAX; + + word = ((u_int16_t)oob[idx_lo] << 0) | + ((u_int16_t)oob[idx_hi] << 8); + + /* Check for parity error in the logical block number. */ + for (parity = 0, bit = 0x0001; bit != 0x0000; bit <<= 1) + if ((word & bit) != 0) + parity++; + if ((parity & 1) != 0) + return UINT_MAX; + + /* No logical block number assigned to this block? */ + if (word == USHRT_MAX) + return word; + + /* Return the validated logical block number. */ + return (word & 0x07fe) >> 1; +} + +void +zflash_oob_set_status(struct zflash_softc *sc, u_char *oob, u_int16_t phyuse) +{ + switch (PHYUSE_STATUS(phyuse)) { + case P_NORMALBLOCK: + oob[sc->sc_ioobbadblk] = 0xff; + oob[sc->sc_ioobpostbadblk] = 0xff; + break; + case P_BADBLOCK: + oob[sc->sc_ioobbadblk] = 0x00; + oob[sc->sc_ioobpostbadblk] = 0x00; + break; + case P_POSTBADBLOCK: + oob[sc->sc_ioobbadblk] = 0xff; + oob[sc->sc_ioobpostbadblk] = 0x00; + break; + } +} + +void +zflash_oob_set_logno(struct zflash_softc *sc, u_char *oob, u_int logno) +{ + u_int16_t word; + u_int16_t bit; + u_char lo; + u_char hi; + int parity; + + /* Why do we set the most significant bit? */ + word = ((logno & 0x03ff) << 1) | 0x1000; + + /* Calculate the parity. */ + for (bit = 0x0001; bit != 0x0000; bit <<= 1) + if ((word & bit) != 0) + parity++; + if ((parity & 1) != 0) + word |= 0x0001; + + lo = word & 0x00ff; + hi = (word & 0xff00) >> 8; + + oob[OOB_LOGADDR_0_LO] = lo; + oob[OOB_LOGADDR_0_HI] = hi; + oob[OOB_LOGADDR_1_LO] = lo; + oob[OOB_LOGADDR_1_HI] = hi; + oob[OOB_LOGADDR_2_LO] = lo; + oob[OOB_LOGADDR_2_HI] = hi; +} diff --git a/sys/conf/files b/sys/conf/files index 25464cf0b65..8367c2dff7e 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1,4 +1,4 @@ -# $OpenBSD: files,v 1.388 2006/11/17 18:58:30 mglocker Exp $ +# $OpenBSD: files,v 1.389 2006/11/25 14:31:59 uwe Exp $ # $NetBSD: files,v 1.87 1996/05/19 17:17:50 jonathan Exp $ # @(#)files.newconf 7.5 (Berkeley) 5/10/93 @@ -518,6 +518,7 @@ file dev/audio.c audio needs-flag file dev/ccd.c ccd needs-flag file dev/cons.c file dev/firmload.c firmload +file dev/flash.c flash needs-flag file dev/ic/ncr5380sbc.c ncr5380sbc file dev/ic/ncr53c7xx.c ncr53c7xx file dev/ic/pdq.c pdq diff --git a/sys/dev/flash.c b/sys/dev/flash.c new file mode 100644 index 00000000000..0fa721463e2 --- /dev/null +++ b/sys/dev/flash.c @@ -0,0 +1,1099 @@ +/* $OpenBSD: flash.c,v 1.1 2006/11/25 14:32:00 uwe Exp $ */ + +/* + * Copyright (c) 2005 Uwe Stuehler <uwe@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> +#include <sys/buf.h> +#include <sys/conf.h> +#include <sys/device.h> +#include <sys/disk.h> +#include <sys/disklabel.h> +#include <sys/dkio.h> +#include <sys/kernel.h> +#include <sys/stat.h> +#include <sys/systm.h> + +#include <dev/flashvar.h> + +#include <ufs/ffs/fs.h> /* XXX */ + +/* Samsung command set */ +#define SAMSUNG_CMD_PTRLO 0x00 +#define SAMSUNG_CMD_PTRHI 0x01 +#define SAMSUNG_CMD_PTROOB 0x50 +#define SAMSUNG_CMD_READ 0x30 +#define SAMSUNG_CMD_SEQIN 0x80 +#define SAMSUNG_CMD_WRITE 0x10 +#define SAMSUNG_CMD_ERASE0 0x60 +#define SAMSUNG_CMD_ERASE1 0xd0 +#define SAMSUNG_CMD_STATUS 0x70 +#define STATUS_FAIL (1<<0) +#define STATUS_READY (1<<6) +#define STATUS_NWP (1<<7) +#define SAMSUNG_CMD_READID 0x90 +#define SAMSUNG_CMD_RESET 0xff + +int flash_wait_ready(struct flash_softc *); +int flash_wait_complete(struct flash_softc *); + +/* XXX: these should go elsewhere */ +cdev_decl(flash); +bdev_decl(flash); + +#define flashlock(sc) disk_lock(&(sc)->sc_dk) +#define flashunlock(sc) disk_unlock(&(sc)->sc_dk) +#define flashlookup(unit) \ + (struct flash_softc *)device_lookup(&flash_cd, (unit)) + +void flashminphys(struct buf *); +void flashstart(struct flash_softc *); +void _flashstart(struct flash_softc *, struct buf *); +void flashdone(void *); + +int flashsafestrategy(struct flash_softc *, struct buf *); +void flashgetdefaultlabel(dev_t, struct flash_softc *, + struct disklabel *); +void flashgetdisklabel(dev_t, struct flash_softc *, struct disklabel *, + struct cpu_disklabel *, int); + +/* + * Driver attachment glue + */ + +struct flashvendor { + u_int8_t vendor; + const char *name; +}; + +static const struct flashvendor flashvendors[] = { + { FLASH_VENDOR_SAMSUNG, "Samsung" } +}; +#define FLASH_NVENDORS (sizeof(flashvendors) / sizeof(flashvendors[0])) + +static const struct flashdev flashdevs[] = { + { FLASH_DEVICE_SAMSUNG_K9F2808U0C, "K9F2808U0C 16Mx8 3.3V", + 512, 16, 32, 32768 }, + { FLASH_DEVICE_SAMSUNG_K9F1G08U0A, "K9F1G08U0A 128Mx8 3.3V", + 2048, 64, 64, 65536 }, +}; +#define FLASH_NDEVS (sizeof(flashdevs) / sizeof(flashdevs[0])) + +struct cfdriver flash_cd = { + NULL, "flash", DV_DISK +}; + +struct dkdriver flashdkdriver = { flashstrategy }; + +void +flashattach(struct flash_softc *sc, struct flash_ctl_tag *tag, + void *cookie) +{ + u_int8_t vendor, device; + u_int16_t id; + int i; + + sc->sc_tag = tag; + sc->sc_cookie = cookie; + + if (sc->sc_maxwaitready <= 0) + sc->sc_maxwaitready = 1000; /* 1ms */ + if (sc->sc_maxwaitcomplete <= 0) + sc->sc_maxwaitcomplete = 200000; /* 200ms */ + + flash_chip_enable(sc); + + /* Identify the flash device. */ + if (flash_chip_identify(sc, &vendor, &device) != 0) { + printf(": identification failed\n"); + flash_chip_disable(sc); + return; + } + id = (vendor << 8) | device; + + /* Look up device characteristics, abort if not recognized. */ + for (i = 0; i < FLASH_NVENDORS; i++) { + if (flashvendors[i].vendor == vendor) { + printf(": %s", flashvendors[i].name); + break; + } + } + if (i == FLASH_NVENDORS) + printf(": vendor 0x%02x", vendor); + for (i = 0; i < FLASH_NDEVS; i++) { + if (flashdevs[i].id == id) { + printf(" %s\n", flashdevs[i].longname); + break; + } + } + if (i == FLASH_NDEVS) { + /* Need to add this device to flashdevs first. */ + printf(" device 0x%02x\n", device); + flash_chip_disable(sc); + return; + } + sc->sc_flashdev = &flashdevs[i]; + + /* Check if the device really works or fail early. */ + if (flash_chip_reset(sc) != 0) { + printf("%s: reset failed\n", sc->sc_dev.dv_xname); + flash_chip_disable(sc); + return; + } + + flash_chip_disable(sc); + + /* + * Initialize and attach the disk structure. + */ + sc->sc_dk.dk_driver = &flashdkdriver; + sc->sc_dk.dk_name = sc->sc_dev.dv_xname; + disk_attach(&sc->sc_dk); + + /* XXX establish shutdown hook to finish any commands. */ +} + +int +flashdetach(struct device *self, int flags) +{ + struct flash_softc *sc = (struct flash_softc *)self; + + /* Detach disk. */ + disk_detach(&sc->sc_dk); + + /* XXX more resources need to be freed here. */ + return 0; +} + +int +flashactivate(struct device *self, enum devact act) +{ + /* XXX anything to be done here? */ + return 0; +} + +/* + * Flash controller and chip functions + */ + +u_int8_t +flash_reg8_read(struct flash_softc *sc, int reg) +{ + return sc->sc_tag->reg8_read(sc->sc_cookie, reg); +} + +void +flash_reg8_read_page(struct flash_softc *sc, caddr_t data, caddr_t oob) +{ + int i; + + for (i = 0; i < sc->sc_flashdev->pagesize; i++) + data[i] = flash_reg8_read(sc, FLASH_REG_DATA); + + if (oob != NULL) + for (i = 0; i < sc->sc_flashdev->oobsize; i++) + oob[i] = flash_reg8_read(sc, FLASH_REG_DATA); +} + +void +flash_reg8_write(struct flash_softc *sc, int reg, u_int8_t value) +{ + sc->sc_tag->reg8_write(sc->sc_cookie, reg, value); +} + +void +flash_reg8_write_page(struct flash_softc *sc, caddr_t data, caddr_t oob) +{ + int i; + + for (i = 0; i < sc->sc_flashdev->pagesize; i++) + flash_reg8_write(sc, FLASH_REG_DATA, data[i]); + + if (oob != NULL) + for (i = 0; i < sc->sc_flashdev->oobsize; i++) + flash_reg8_write(sc, FLASH_REG_DATA, oob[i]); +} + +/* + * Wait for the "Ready/Busy" signal to go high, indicating that the + * device is ready to accept another command. + */ +int +flash_wait_ready(struct flash_softc *sc) +{ + int timo = sc->sc_maxwaitready; + u_int8_t ready; + + ready = flash_reg8_read(sc, FLASH_REG_READY); + while (ready == 0 && timo-- > 0) { + delay(1); + ready = flash_reg8_read(sc, FLASH_REG_READY); + } + return (ready == 0 ? EIO : 0); +} + +/* + * Similar to flash_wait_ready() but looks at IO 6 and IO 0 signals + * besides R/B to decide whether the last operation was successful. + */ +int +flash_wait_complete(struct flash_softc *sc) +{ + int timo = sc->sc_maxwaitcomplete; + u_int8_t status; + + (void)flash_wait_ready(sc); + + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_STATUS); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + + status = flash_reg8_read(sc, FLASH_REG_DATA); + while ((status & STATUS_READY) == 0 && timo-- > 0) { + if (flash_reg8_read(sc, FLASH_REG_READY)) + break; + delay(1); + status = flash_reg8_read(sc, FLASH_REG_DATA); + } + + status = flash_reg8_read(sc, FLASH_REG_DATA); + return ((status & STATUS_FAIL) != 0 ? EIO : 0); +} + +void +flash_chip_enable(struct flash_softc *sc) +{ + /* XXX aquire the lock. */ + flash_reg8_write(sc, FLASH_REG_CE, 1); +} + +void +flash_chip_disable(struct flash_softc *sc) +{ + flash_reg8_write(sc, FLASH_REG_CE, 0); + /* XXX release the lock. */ +} + +int +flash_chip_reset(struct flash_softc *sc) +{ + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_RESET); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + + return flash_wait_ready(sc); +} + +int +flash_chip_identify(struct flash_softc *sc, u_int8_t *vendor, + u_int8_t *device) +{ + int error; + + (void)flash_wait_ready(sc); + + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_READID); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + + error = flash_wait_ready(sc); + if (error == 0) { + *vendor = flash_reg8_read(sc, FLASH_REG_DATA); + *device = flash_reg8_read(sc, FLASH_REG_DATA); + } + return error; +} + +int +flash_chip_erase_block(struct flash_softc *sc, long blkno) +{ + long pageno = blkno * sc->sc_flashdev->blkpages; + int error; + + (void)flash_wait_ready(sc); + + /* Disable write-protection. */ + flash_reg8_write(sc, FLASH_REG_WP, 0); + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_ERASE0); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_ALE, 1); + flash_reg8_write(sc, FLASH_REG_ROW, pageno); + flash_reg8_write(sc, FLASH_REG_ROW, pageno >> 8); + flash_reg8_write(sc, FLASH_REG_ALE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_ERASE1); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + break; + } + + error = flash_wait_complete(sc); + + /* Re-enable write-protection. */ + flash_reg8_write(sc, FLASH_REG_WP, 1); + + return error; +} + +int +flash_chip_read_block(struct flash_softc *sc, long blkno, caddr_t data) +{ + long pageno; + long blkend; + int error; + + pageno = blkno * sc->sc_flashdev->blkpages; + blkend = pageno + sc->sc_flashdev->blkpages; + + while (pageno < blkend) { + error = flash_chip_read_page(sc, pageno, data, NULL); + if (error != 0) + return error; + data += sc->sc_flashdev->pagesize; + pageno++; + } + return 0; +} + +int +flash_chip_read_page(struct flash_softc *sc, long pageno, caddr_t data, + caddr_t oob) +{ + int error; + + (void)flash_wait_ready(sc); + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_PTRLO); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + flash_reg8_write(sc, FLASH_REG_ALE, 1); + flash_reg8_write(sc, FLASH_REG_COL, 0x00); + flash_reg8_write(sc, FLASH_REG_ALE, 0); + break; + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_ALE, 1); + flash_reg8_write(sc, FLASH_REG_COL, 0x00); + flash_reg8_write(sc, FLASH_REG_COL, 0x00); + flash_reg8_write(sc, FLASH_REG_ALE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_ALE, 1); + flash_reg8_write(sc, FLASH_REG_ROW, pageno); + flash_reg8_write(sc, FLASH_REG_ROW, pageno >> 8); + flash_reg8_write(sc, FLASH_REG_ALE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_READ); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + break; + } + + if ((error = flash_wait_ready(sc)) != 0) + return error; + + /* Support hardware ECC calculation. */ + if (sc->sc_tag->regx_read_page) { + error = sc->sc_tag->regx_read_page(sc->sc_cookie, data, + oob); + if (error != 0) + return error; + } else + flash_reg8_read_page(sc, data, oob); + + return 0; +} + +int +flash_chip_read_oob(struct flash_softc *sc, long pageno, caddr_t oob) +{ + u_int8_t *p = (u_int8_t *)oob; + int error; + int i; + + (void)flash_wait_ready(sc); + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_PTROOB); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + break; + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_PTRLO); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + flash_reg8_write(sc, FLASH_REG_ALE, 1); + flash_reg8_write(sc, FLASH_REG_COL, 0x00); + flash_reg8_write(sc, FLASH_REG_ALE, 0); + break; + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_ALE, 1); + flash_reg8_write(sc, FLASH_REG_COL, 0x00); + flash_reg8_write(sc, FLASH_REG_COL, 0x08); + flash_reg8_write(sc, FLASH_REG_ALE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_ALE, 1); + flash_reg8_write(sc, FLASH_REG_ROW, pageno); + flash_reg8_write(sc, FLASH_REG_ROW, pageno >> 8); + flash_reg8_write(sc, FLASH_REG_ALE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_READ); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + break; + } + + if ((error = flash_wait_ready(sc)) != 0) + return error; + + for (i = 0; i < sc->sc_flashdev->oobsize; i++) + p[i] = flash_reg8_read(sc, FLASH_REG_DATA); + + return 0; +} + +int +flash_chip_write_block(struct flash_softc *sc, long blkno, caddr_t data, + caddr_t oob) +{ + long pageno; + long blkend; + caddr_t p; + int error; + + pageno = blkno * sc->sc_flashdev->blkpages; + blkend = pageno + sc->sc_flashdev->blkpages; + + p = data; + while (pageno < blkend) { + error = flash_chip_write_page(sc, pageno, p, oob); + if (error != 0) + return error; + p += sc->sc_flashdev->pagesize; + pageno++; + } + + /* Verify the newly written block. */ + return flash_chip_verify_block(sc, blkno, data, oob); +} + +int +flash_chip_write_page(struct flash_softc *sc, long pageno, caddr_t data, + caddr_t oob) +{ + int error; + + (void)flash_wait_ready(sc); + + /* Disable write-protection. */ + flash_reg8_write(sc, FLASH_REG_WP, 0); + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_PTRLO); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_SEQIN); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + flash_reg8_write(sc, FLASH_REG_ALE, 1); + flash_reg8_write(sc, FLASH_REG_COL, 0x00); + flash_reg8_write(sc, FLASH_REG_ALE, 0); + break; + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_ALE, 1); + flash_reg8_write(sc, FLASH_REG_COL, 0x00); + flash_reg8_write(sc, FLASH_REG_COL, 0x00); + flash_reg8_write(sc, FLASH_REG_ALE, 0); + break; + } + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_ALE, 1); + flash_reg8_write(sc, FLASH_REG_ROW, pageno); + flash_reg8_write(sc, FLASH_REG_ROW, pageno >> 8); + flash_reg8_write(sc, FLASH_REG_ALE, 0); + break; + } + + /* Support hardware ECC calculation. */ + if (sc->sc_tag->regx_write_page) { + error = sc->sc_tag->regx_write_page(sc->sc_cookie, data, + oob); + if (error != 0) + return error; + } else + flash_reg8_write_page(sc, data, oob); + + switch (sc->sc_flashdev->id) { + case FLASH_DEVICE_SAMSUNG_K9F2808U0C: + case FLASH_DEVICE_SAMSUNG_K9F1G08U0A: + flash_reg8_write(sc, FLASH_REG_CLE, 1); + flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_WRITE); + flash_reg8_write(sc, FLASH_REG_CLE, 0); + break; + } + + /* + * Wait for the write operation to complete although this can + * take up to 700 us for the K9F1G08U0A flash type. + */ + error = flash_wait_complete(sc); + + /* Re-enable write-protection. */ + flash_reg8_write(sc, FLASH_REG_WP, 1); + + return error; +} + +int +flash_chip_verify_block(struct flash_softc *sc, long blkno, caddr_t data, + caddr_t oob) +{ + long pageno; + long blkend; + int error; + + pageno = blkno * sc->sc_flashdev->blkpages; + blkend = pageno + sc->sc_flashdev->blkpages; + + while (pageno < blkend) { + error = flash_chip_verify_page(sc, pageno, data, oob); + if (error != 0) { + printf("block %d page %d verify failed\n", + blkno, pageno); + return error; + } + data += sc->sc_flashdev->pagesize; + pageno++; + } + return 0; +} + +int +flash_chip_verify_page(struct flash_softc *sc, long pageno, caddr_t data, + caddr_t oob) +{ + static u_char rbuf[FLASH_MAXPAGESIZE]; + static u_char roob[FLASH_MAXOOBSIZE]; + int error; + + error = flash_chip_read_page(sc, pageno, rbuf, + oob == NULL ? NULL : roob); + if (error != 0) + return error; + + if (memcmp((const void *)&rbuf[0], (const void *)data, + sc->sc_flashdev->pagesize) != 0) + return EIO; + + if (oob != NULL && memcmp((const void *)&roob[0], + (const void *)oob, sc->sc_flashdev->oobsize) != 0) + return EIO; + + return 0; +} + +/* + * Block device functions + */ + +int +flashopen(dev_t dev, int oflags, int devtype, struct proc *p) +{ + struct flash_softc *sc; + int error; + int part; + + sc = flashlookup(flashunit(dev)); + if (sc == NULL) + return ENXIO; + + if ((error = flashlock(sc)) != 0) { + device_unref(&sc->sc_dev); + return error; + } + + /* + * If no partition is open load the partition info if it is + * not already valid. If partitions are already open, allow + * opens only for the same kind of device. + */ + if (sc->sc_dk.dk_openmask == 0) { + if ((sc->sc_flags & FDK_LOADED) == 0 || + ((sc->sc_flags & FDK_SAFE) == 0) != + (flashsafe(dev) == 0)) { + sc->sc_flags &= ~FDK_SAFE; + sc->sc_flags |= FDK_LOADED; + if (flashsafe(dev)) + sc->sc_flags |= FDK_SAFE; + flashgetdisklabel(dev, sc, sc->sc_dk.dk_label, + sc->sc_dk.dk_cpulabel, 0); + } + } else if (((sc->sc_flags & FDK_SAFE) == 0) != + (flashsafe(dev) == 0)) { + flashunlock(sc); + device_unref(&sc->sc_dev); + return EBUSY; + } + + /* Check that the partition exists. */ + part = flashpart(dev); + if (part != RAW_PART && + (part >= sc->sc_dk.dk_label->d_npartitions || + sc->sc_dk.dk_label->d_partitions[part].p_fstype == FS_UNUSED)) { + flashunlock(sc); + device_unref(&sc->sc_dev); + return ENXIO; + } + + /* Prevent our unit from being deconfigured while open. */ + switch (devtype) { + case S_IFCHR: + sc->sc_dk.dk_copenmask |= (1 << part); + break; + case S_IFBLK: + sc->sc_dk.dk_bopenmask |= (1 << part); + break; + } + sc->sc_dk.dk_openmask = + sc->sc_dk.dk_copenmask | sc->sc_dk.dk_bopenmask; + + flashunlock(sc); + device_unref(&sc->sc_dev); + return 0; +} + +int +flashclose(dev_t dev, int fflag, int devtype, struct proc *p) +{ + struct flash_softc *sc; + int error; + int part; + + sc = flashlookup(flashunit(dev)); + if (sc == NULL) + return ENXIO; + + if ((error = flashlock(sc)) != 0) { + device_unref(&sc->sc_dev); + return error; + } + + /* Close one open partition. */ + part = flashpart(dev); + switch (devtype) { + case S_IFCHR: + sc->sc_dk.dk_copenmask &= ~(1 << part); + break; + case S_IFBLK: + sc->sc_dk.dk_bopenmask &= ~(1 << part); + break; + } + sc->sc_dk.dk_openmask = + sc->sc_dk.dk_copenmask | sc->sc_dk.dk_bopenmask; + + if (sc->sc_dk.dk_openmask == 0) { + /* XXX wait for I/O to complete? */ + } + + flashunlock(sc); + device_unref(&sc->sc_dev); + return 0; +} + +/* + * Queue the transfer of one or more flash pages. + */ +void +flashstrategy(struct buf *bp) +{ + struct flash_softc *sc; + int s; + + sc = flashlookup(flashunit(bp->b_dev)); + if (sc == NULL) { + bp->b_error = ENXIO; + goto bad; + } + + /* Transfer only a multiple of the flash page size. */ + if ((bp->b_bcount % sc->sc_flashdev->pagesize) != 0) { + bp->b_error = EINVAL; + goto bad; + } + + /* If the device has been invalidated, error out. */ + if ((sc->sc_flags & FDK_LOADED) == 0) { + bp->b_error = EIO; + goto bad; + } + + /* Translate logical block numbers to physical. */ + if (flashsafe(bp->b_dev) && flashsafestrategy(sc, bp) <= 0) + goto done; + + /* Return immediately if it is a null transfer. */ + if (bp->b_bcount == 0) + goto done; + + /* Do bounds checking on partitions. */ + if (flashpart(bp->b_dev) != RAW_PART && + bounds_check_with_label(bp, sc->sc_dk.dk_label, + sc->sc_dk.dk_cpulabel, 0) <= 0) + goto done; + + /* Queue the transfer. */ + s = splbio(); + disksort(&sc->sc_q, bp); + flashstart(sc); + splx(s); + device_unref(&sc->sc_dev); + return; + +bad: + bp->b_flags |= B_ERROR; +done: + if ((bp->b_flags & B_ERROR) != 0) + bp->b_resid = bp->b_bcount; + s = splbio(); + biodone(bp); + splx(s); + if (sc != NULL) + device_unref(&sc->sc_dev); +} + +int +flashioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, struct proc *p) +{ + struct flash_softc *sc; + int error = 0; + + sc = flashlookup(flashunit(dev)); + if (sc == NULL) + return ENXIO; + + if ((sc->sc_flags & FDK_LOADED) == 0) { + device_unref(&sc->sc_dev); + return EIO; + } + + switch (cmd) { + case DIOCGDINFO: + *(struct disklabel *)data = *sc->sc_dk.dk_label; + break; + default: + error = ENOTTY; + break; + } + + device_unref(&sc->sc_dev); + return error; +} + +int +flashdump(dev_t dev, daddr_t blkno, caddr_t va, size_t size) +{ + printf("flashdump\n"); + return ENODEV; +} + +int +flashsize(dev_t dev) +{ + printf("flashsize\n"); + return ENODEV; +} + +void +flashstart(struct flash_softc *sc) +{ + struct buf *dp, *bp; + + while (1) { + /* Remove the next buffer from the queue or stop. */ + dp = &sc->sc_q; + bp = dp->b_actf; + if (bp == NULL) + return; + dp->b_actf = bp->b_actf; + + /* Transfer this buffer now. */ + _flashstart(sc, bp); + } +} + +void +_flashstart(struct flash_softc *sc, struct buf *bp) +{ + int part; + long offset; + long pgno; + + part = flashpart(bp->b_dev); + if (part != RAW_PART) + offset = sc->sc_dk.dk_label->d_partitions[part].p_offset; + else + offset = 0; + + offset = offset + bp->b_blkno; + pgno = offset / (sc->sc_flashdev->pagesize / DEV_BSIZE); + + /* + * If the requested page is exactly at the end of flash and it + * is an "unsafe" device, return EOF, else error out. + */ + if (!flashsafe(bp->b_dev) && pgno == sc->sc_flashdev->capacity) { + bp->b_resid = bp->b_bcount; + biodone(bp); + return; + } else if (pgno >= sc->sc_flashdev->capacity) { + bp->b_error = EINVAL; + bp->b_flags |= B_ERROR; + biodone(bp); + return; + } + + sc->sc_bp = bp; + + /* Instrumentation. */ + disk_busy(&sc->sc_dk); + + /* XXX this should be done asynchronously. */ + flash_chip_enable(sc); + if ((bp->b_flags & B_READ) != 0) + bp->b_error = flash_chip_read_page(sc, pgno, bp->b_data, + NULL); + else + bp->b_error = flash_chip_write_page(sc, pgno, bp->b_data, + NULL); + if (bp->b_error == 0) + bp->b_resid = bp->b_bcount - sc->sc_flashdev->pagesize; + flash_chip_disable(sc); + flashdone(sc); +} + +void +flashdone(void *v) +{ + struct flash_softc *sc = v; + struct buf *bp = sc->sc_bp; + + /* Instrumentation. */ + disk_unbusy(&sc->sc_dk, bp->b_bcount - bp->b_resid, + (bp->b_flags & B_READ) != 0); + + if (bp->b_error != 0) + bp->b_flags |= B_ERROR; + + biodone(bp); + flashstart(sc); +} + +void +flashgetdefaultlabel(dev_t dev, struct flash_softc *sc, + struct disklabel *lp) +{ + size_t len; + + bzero(lp, sizeof(struct disklabel)); + + lp->d_type = 0; + lp->d_subtype = 0; + strncpy(lp->d_typename, "NAND flash", sizeof(lp->d_typename)); + + /* Use the product name up to the first space. */ + strncpy(lp->d_packname, sc->sc_flashdev->longname, + sizeof(lp->d_packname)); + for (len = 0; len < sizeof(lp->d_packname); len++) + if (lp->d_packname[len] == ' ') { + lp->d_packname[len] = '\0'; + break; + } + + /* Fake the disk geometry. */ + lp->d_ncylinders = 1; + lp->d_ntracks = 16; + lp->d_secsize = sc->sc_flashdev->pagesize; + lp->d_nsectors = sc->sc_flashdev->capacity / lp->d_ntracks + / lp->d_ncylinders; + lp->d_secpercyl = lp->d_ntracks * lp->d_nsectors; + lp->d_secperunit = lp->d_ncylinders * lp->d_secpercyl; + + /* Fake hardware characteristics. */ + lp->d_rpm = 3600; + lp->d_interleave = 1; + + /* XXX these values assume ffs. */ + lp->d_bbsize = BBSIZE; + lp->d_sbsize = SBSIZE; + + /* Fake the partition information. */ + lp->d_npartitions = RAW_PART + 1; + lp->d_partitions[RAW_PART].p_size = lp->d_secperunit; + lp->d_partitions[RAW_PART].p_offset = 0; + lp->d_partitions[RAW_PART].p_fstype = FS_UNUSED; + + /* Wrap it up. */ + lp->d_magic = DISKMAGIC; + lp->d_magic2 = DISKMAGIC; + lp->d_checksum = dkcksum(lp); +} + +void +flashgetdisklabel(dev_t dev, struct flash_softc *sc, + struct disklabel *lp, struct cpu_disklabel *clp, int spoofonly) +{ + char *errstring; + dev_t labeldev; + + flashgetdefaultlabel(dev, sc, lp); + bzero(clp, sizeof(struct cpu_disklabel)); + + if (sc->sc_tag->default_disklabel != NULL) + sc->sc_tag->default_disklabel(sc->sc_cookie, dev, lp, clp); + + /* Call the generic disklabel extraction routine. */ + labeldev = flashlabeldev(dev); + errstring = readdisklabel(labeldev, flashstrategy, lp, clp, + spoofonly); + if (errstring != NULL) + printf("%s: %s\n", sc->sc_dev.dv_xname, errstring); +} + +/* + * Character device functions + */ + +void +flashminphys(struct buf *bp) +{ + struct flash_softc *sc; + + sc = flashlookup(flashunit(bp->b_dev)); + + if (bp->b_bcount > sc->sc_flashdev->pagesize) + bp->b_bcount = sc->sc_flashdev->pagesize; +} + +int +flashread(dev_t dev, struct uio *uio, int ioflag) +{ + return physio(flashstrategy, NULL, dev, B_READ, flashminphys, uio); +} + +int +flashwrite(dev_t dev, struct uio *uio, int ioflag) +{ + return physio(flashstrategy, NULL, dev, B_WRITE, flashminphys, uio); +} + +/* + * Physical access strategy "fixup" routines for transparent bad + * blocks management, wear-leveling, etc. + */ + +/* + * Call the machine-specific routine if there is any or use just a + * default strategy for bad blocks management. + */ +int +flashsafestrategy(struct flash_softc *sc, struct buf *bp) +{ + if (sc->sc_tag->safe_strategy) { + return sc->sc_tag->safe_strategy(sc->sc_cookie, bp); + } + + /* XXX no default bad blocks management strategy yet */ + return 1; +} + +void dumppage(u_char *); +void dumppage(u_char *buf) +{ + int i; + for (i = 0; i < 512; i++) { + if ((i % 16) == 0) + printf("%04x: ", i); + if ((i % 16) == 8) + printf(" "); + printf(" %02x", buf[i]); + if ((i % 16) == 15) + printf("\n"); + } + if ((i % 16) != 0) + printf("\n"); +} diff --git a/sys/dev/flashvar.h b/sys/dev/flashvar.h new file mode 100644 index 00000000000..dccf80d566d --- /dev/null +++ b/sys/dev/flashvar.h @@ -0,0 +1,143 @@ +/* $OpenBSD: flashvar.h,v 1.1 2006/11/25 14:32:00 uwe Exp $ */ + +/* + * Copyright (c) 2005 Uwe Stuehler <uwe@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _FLASHVAR_H_ +#define _FLASHVAR_H_ + +#ifdef _KERNEL + +/* Flash controller descriptor structure */ +struct flash_ctl_tag { + u_int8_t (*reg8_read)(void *, int); + int (*regx_read_page)(void *, caddr_t, caddr_t); + void (*reg8_write)(void *, int, u_int8_t); + int (*regx_write_page)(void *, caddr_t, caddr_t); + void (*default_disklabel)(void *, dev_t, struct disklabel *, + struct cpu_disklabel *); + int (*safe_strategy)(void *, struct buf *); +}; + +/* + * Pseudo-registers for a fictitious flash controller + * + * Note that logical levels are assumed for CE and WP bits. + * Signals corresponding to these bits are usually negated. + */ +#define FLASH_REG_DATA 0x00 +#define FLASH_REG_COL 0x01 +#define FLASH_REG_ROW 0x02 +#define FLASH_REG_CMD 0x03 +#define FLASH_REG_ALE 0x04 +#define FLASH_REG_CLE 0x05 +#define FLASH_REG_CE 0x06 +#define FLASH_REG_WP 0x07 +#define FLASH_REG_READY 0x0f + +/* Flash device descriptor structure */ +struct flashdev { + u_int16_t id; + const char *longname; + u_long pagesize; /* bytes per page */ + u_long oobsize; /* OOB bytes per page */ + u_long blkpages; /* pages per erasable block */ + u_long capacity; /* pages per device */ +}; + +#define FLASH_DEVICE(v,d) ((FLASH_VENDOR_##v << 8) | (d)) + +/* Flash device vendors */ +#define FLASH_VENDOR_SAMSUNG 0xec + +/* Flash devices */ +#define FLASH_DEVICE_SAMSUNG_K9F2808U0C FLASH_DEVICE(SAMSUNG, 0x73) +#define FLASH_DEVICE_SAMSUNG_K9F1G08U0A FLASH_DEVICE(SAMSUNG, 0xf1) + +/* Maximum sizes for all devices */ +#define FLASH_MAXPAGESIZE 2048 +#define FLASH_MAXOOBSIZE 64 + +/* + * Should-be private softc structure for the generic flash driver. + */ +struct flash_softc { + struct device sc_dev; + /* Disk device information */ + struct disk sc_dk; + struct buf sc_q; + struct buf *sc_bp; + int sc_flags; + /* Flash controller tag */ + struct flash_ctl_tag *sc_tag; + void *sc_cookie; + /* Flash device characteristics */ + const struct flashdev *sc_flashdev; + int sc_maxwaitready; + int sc_maxwaitcomplete; +}; + +/* Values for sc_flags */ +#define FDK_LOADED 0x00000001 +#define FDK_SAFE 0x00000002 + +/* + * Similar to vnd(4) devices there are two kinds of flash devices. + * Both device kinds share the same disklabel. + * + * ``Safe'' devices have bit 11 set in the minor number and use the + * out-of-band page data to implement wear-leveling and transparent + * management of bad block information. Block erasing and rewriting + * is also handled transparently; arbitrary pages can be modified. + * + * ``Unsafe'' devices provide raw access to the flash pages. Access + * to OOB page data is possible via ioctl()s only with these devices. + * Erasing the containing flash block may be necessary before a page + * can be writting successfully, but the block erase command is only + * provided as an ioctl(). + */ +#define flashsafe(x) (minor(x) & 0x800) +#define flashunit(x) DISKUNIT(makedev(major(x), minor(x) & 0x7ff)) +#define flashpart(x) DISKPART(makedev(major(x), minor(x) & 0x7ff)) +#define flashlabeldev(x) (MAKEDISKDEV(major(x), flashunit(x), RAW_PART)\ + |flashsafe(x)) + +void flashattach(struct flash_softc *, struct flash_ctl_tag *, void *); +int flashdetach(struct device *, int); +int flashactivate(struct device *, enum devact); + +u_int8_t flash_reg8_read(struct flash_softc *, int); +void flash_reg8_read_page(struct flash_softc *, caddr_t, caddr_t); +void flash_reg8_write(struct flash_softc *, int, u_int8_t); +void flash_reg8_write_page(struct flash_softc *, caddr_t, caddr_t); +void flash_chip_enable(struct flash_softc *); +void flash_chip_disable(struct flash_softc *); +int flash_chip_reset(struct flash_softc *); +int flash_chip_identify(struct flash_softc *, u_int8_t *, u_int8_t *); +int flash_chip_erase_block(struct flash_softc *, long); +int flash_chip_read_block(struct flash_softc *, long, caddr_t); +int flash_chip_read_page(struct flash_softc *, long, caddr_t, caddr_t); +int flash_chip_read_oob(struct flash_softc *, long, caddr_t); +int flash_chip_write_block(struct flash_softc *, long, caddr_t, caddr_t); +int flash_chip_write_page(struct flash_softc *, long, caddr_t, caddr_t); +int flash_chip_verify_block(struct flash_softc *, long, caddr_t, caddr_t); +int flash_chip_verify_page(struct flash_softc *, long, caddr_t, caddr_t); + +#endif /* _KERNEL */ + +/* XXX: define ioctl commands for OOB page data access and block erase. */ + +#endif /* _FLASHVAR_H_ */ |