diff options
author | imp <imp@FreeBSD.org> | 2006-11-29 08:05:55 +0000 |
---|---|---|
committer | imp <imp@FreeBSD.org> | 2006-11-29 08:05:55 +0000 |
commit | f8717ce4d76451eab7cce6b959ba459154922e1a (patch) | |
tree | cf3ff76c5d77f7e1778f48a1b7a0c2caa0d384b8 | |
parent | 85a0be372bb9fac00010df21561c9b263e934882 (diff) | |
download | FreeBSD-src-f8717ce4d76451eab7cce6b959ba459154922e1a.zip FreeBSD-src-f8717ce4d76451eab7cce6b959ba459154922e1a.tar.gz |
MFp4:
Preliminary support for Atmel AT45D series of DataFlash on the
SPI bus (ok, not really a hardware bus, but a logical
connection). This works only for the 8MB version of the part
due to hard coding. Both read and write are supported.
-rw-r--r-- | sys/dev/flash/at45d.c | 284 |
1 files changed, 284 insertions, 0 deletions
diff --git a/sys/dev/flash/at45d.c b/sys/dev/flash/at45d.c new file mode 100644 index 0000000..e9536e3 --- /dev/null +++ b/sys/dev/flash/at45d.c @@ -0,0 +1,284 @@ +/*- + * Copyright (c) 2006 M. Warner Losh. 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 ``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 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/bio.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/gpio.h> +#include <sys/kernel.h> +#include <sys/kthread.h> +#include <sys/lock.h> +#include <sys/mbuf.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/mutex.h> +#include <geom/geom_disk.h> + +#include <dev/spibus/spi.h> +#include "spibus_if.h" + +struct at45d_softc +{ + struct intr_config_hook config_intrhook; + device_t dev; + struct mtx sc_mtx; + struct disk *disk; + struct proc *p; + struct bio_queue_head bio_queue; +}; + +#define AT45D_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) +#define AT45D_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) +#define AT45D_LOCK_INIT(_sc) \ + mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \ + "at45d", MTX_DEF) +#define AT45D_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx); +#define AT45D_ASSERT_LOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED); +#define AT45D_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED); + +static void at45d_delayed_attach(void *xsc); + +/* disk routines */ +static int at45d_open(struct disk *dp); +static int at45d_close(struct disk *dp); +static void at45d_strategy(struct bio *bp); +static void at45d_task(void *arg); + +#define CONTINUOUS_ARRAY_READ 0xE8 +#define CONTINUOUS_ARRAY_READ_HF 0x0B +#define CONTINUOUS_ARRAY_READ_LF 0x03 +#define STATUS_REGISTER_READ 0xD7 +#define PROGRAM_THROUGH_BUFFER 0x82 +#define MANUFACTURER_ID 0x9F + +static uint8_t +at45d_get_status(device_t dev) +{ + uint8_t txBuf[8], rxBuf[8]; + struct spi_command cmd; + int err; + + memset(&cmd, 0, sizeof(cmd)); + memset(txBuf, 0, sizeof(txBuf)); + memset(rxBuf, 0, sizeof(rxBuf)); + + txBuf[0] = STATUS_REGISTER_READ; + cmd.tx_cmd = txBuf; + cmd.rx_cmd = rxBuf; + cmd.rx_cmd_sz = 2; + cmd.tx_cmd_sz = 2; + err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd); + return (rxBuf[1]); +} + +static void +at45d_wait_for_device_ready(device_t dev) +{ + while (!(at45d_get_status(dev) & 0x80)) + continue; +} + +static int +at45d_get_mfg_info(device_t dev, uint8_t *resp) +{ + uint8_t txBuf[8], rxBuf[8]; + struct spi_command cmd; + int err; + + memset(&cmd, 0, sizeof(cmd)); + memset(txBuf, 0, sizeof(txBuf)); + memset(rxBuf, 0, sizeof(rxBuf)); + + txBuf[0] = MANUFACTURER_ID; + cmd.tx_cmd = &txBuf; + cmd.rx_cmd = &rxBuf; + cmd.tx_cmd_sz = 5; + cmd.rx_cmd_sz = 5; + err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd); + if (err) + return (err); + memcpy(resp, rxBuf + 1, 4); + // XXX We really should 'decode' the reply into some kind of + // XXX structure. To be generic (and not just for atmel parts) + // XXX we'd have to loop until we got a full reply. + return (0); +} + +static int +at45d_probe(device_t dev) +{ + device_set_desc(dev, "AT45 Flash Family"); + return (0); +} + +static int +at45d_attach(device_t dev) +{ + struct at45d_softc *sc; + + sc = device_get_softc(dev); + sc->dev = dev; + AT45D_LOCK_INIT(sc); + + /* We'll see what kind of flash we have later... */ + sc->config_intrhook.ich_func = at45d_delayed_attach; + sc->config_intrhook.ich_arg = sc; + if (config_intrhook_establish(&sc->config_intrhook) != 0) + device_printf(dev, "config_intrhook_establish failed\n"); + return (0); +} + +static int +at45d_detach(device_t dev) +{ + return EIO; +} + +static void +at45d_delayed_attach(void *xsc) +{ + struct at45d_softc *sc = xsc; + uint8_t buf[4]; + + at45d_get_mfg_info(sc->dev, buf); + at45d_wait_for_device_ready(sc->dev); + + sc->disk = disk_alloc(); + sc->disk->d_open = at45d_open; + sc->disk->d_close = at45d_close; + sc->disk->d_strategy = at45d_strategy; + sc->disk->d_name = "flash/spi"; + sc->disk->d_drv1 = sc; + sc->disk->d_maxsize = DFLTPHYS; + sc->disk->d_sectorsize = 1056; /* XXX */ + sc->disk->d_mediasize = 8192 * 1056; /* XXX */ + sc->disk->d_unit = device_get_unit(sc->dev); + disk_create(sc->disk, DISK_VERSION); + bioq_init(&sc->bio_queue); + kthread_create(&at45d_task, sc, &sc->p, 0, 0, "task: at45d flash"); + + config_intrhook_disestablish(&sc->config_intrhook); +} + +static int +at45d_open(struct disk *dp) +{ + return 0; +} + +static int +at45d_close(struct disk *dp) +{ + return 0; +} + +static void +at45d_strategy(struct bio *bp) +{ + struct at45d_softc *sc; + + sc = (struct at45d_softc *)bp->bio_disk->d_drv1; + AT45D_LOCK(sc); + bioq_disksort(&sc->bio_queue, bp); + wakeup(sc); + AT45D_UNLOCK(sc); +} + +static void +at45d_task(void *arg) +{ + struct at45d_softc *sc = (struct at45d_softc*)arg; + struct bio *bp; + uint8_t txBuf[8], rxBuf[8]; + struct spi_command cmd; + int sz; + daddr_t block, end; + device_t dev, pdev; + int err; + + for (;;) { + dev = sc->dev; + pdev = device_get_parent(dev); + AT45D_LOCK(sc); + do { + bp = bioq_first(&sc->bio_queue); + if (bp == NULL) + msleep(sc, &sc->sc_mtx, PRIBIO, "jobqueue", 0); + } while (bp == NULL); + bioq_remove(&sc->bio_queue, bp); + AT45D_UNLOCK(sc); + sz = sc->disk->d_sectorsize; + end = bp->bio_pblkno + (bp->bio_bcount / sz); + for (block = bp->bio_pblkno; block < end; block++) { + char *vaddr = bp->bio_data + (block - bp->bio_pblkno) * sz; + if (bp->bio_cmd == BIO_READ) { + txBuf[0] = CONTINUOUS_ARRAY_READ_HF; + cmd.tx_cmd_sz = 5; + cmd.rx_cmd_sz = 5; + } else { + txBuf[0] = PROGRAM_THROUGH_BUFFER; + cmd.tx_cmd_sz = 4; + cmd.rx_cmd_sz = 4; + } + // XXX only works on certain devices... Fixme + txBuf[1] = ((block >> 5) & 0xFF); + txBuf[2] = ((block << 3) & 0xF8); + txBuf[3] = 0; + txBuf[4] = 0; + cmd.tx_cmd = txBuf; + cmd.rx_cmd = rxBuf; + cmd.tx_data = vaddr; + cmd.tx_data_sz = sz; + cmd.rx_data = vaddr; + cmd.rx_data_sz = sz; + err = SPIBUS_TRANSFER(pdev, dev, &cmd); + // XXX err check? + } + biodone(bp); + } +} + +static devclass_t at45d_devclass; + +static device_method_t at45d_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, at45d_probe), + DEVMETHOD(device_attach, at45d_attach), + DEVMETHOD(device_detach, at45d_detach), + + { 0, 0 } +}; + +static driver_t at45d_driver = { + "at45d", + at45d_methods, + sizeof(struct at45d_softc), +}; + +DRIVER_MODULE(at45d, spibus, at45d_driver, at45d_devclass, 0, 0); |