diff --git a/sys/dev/spi/files.spi b/sys/dev/spi/files.spi index e21e88422f12..9dedfc97f231 100644 --- a/sys/dev/spi/files.spi +++ b/sys/dev/spi/files.spi @@ -1,4 +1,4 @@ -# $NetBSD: files.spi,v 1.1 2006/10/02 07:18:19 gdamore Exp $ +# $NetBSD: files.spi,v 1.2 2006/10/07 07:21:13 gdamore Exp $ define spibus { } @@ -10,6 +10,17 @@ file dev/spi/spi.c spi | spibus # SPI client devices # +# Common SPI flash support +define spiflashbus { } +device spiflash: disk +attach spiflash at spiflashbus +file dev/spi/spiflash.c spiflash | spiflashbus + +# STMicro M25P SPI flash +device m25p: spiflashbus +attach m25p at spi +file dev/spi/m25p.c m25p + # TI TMP121 digital temperature sensor device tmp121temp: sysmon_envsys attach tmp121temp at spi diff --git a/sys/dev/spi/m25p.c b/sys/dev/spi/m25p.c new file mode 100644 index 000000000000..4960c0b4ea34 --- /dev/null +++ b/sys/dev/spi/m25p.c @@ -0,0 +1,206 @@ +/* $NetBSD: m25p.c,v 1.1 2006/10/07 07:21:13 gdamore Exp $ */ + +/*- + * Copyright (c) 2006 Urbana-Champaign Independent Media Center. + * Copyright (c) 2006 Garrett D'Amore. + * All rights reserved. + * + * Portions of this code were written by Garrett D'Amore for the + * Champaign-Urbana Community Wireless Network Project. + * + * 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. + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgements: + * This product includes software developed by the Urbana-Champaign + * Independent Media Center. + * This product includes software developed by Garrett D'Amore. + * 4. Urbana-Champaign Independent Media Center's name and Garrett + * D'Amore's name may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE URBANA-CHAMPAIGN INDEPENDENT + * MEDIA CENTER AND GARRETT D'AMORE ``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 URBANA-CHAMPAIGN INDEPENDENT + * MEDIA CENTER OR GARRETT D'AMORE 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 +__KERNEL_RCSID(0, "$NetBSD: m25p.c,v 1.1 2006/10/07 07:21:13 gdamore Exp $"); + +#include +#include +#include +#include + +#include + +#include +#include + +/* + * Device driver for STMicroelectronics M25P Family SPI Flash Devices + */ + +static int m25p_match(struct device *, struct cfdata *, void *); +static void m25p_attach(struct device *, struct device *, void *); +static const char *m25p_getname(void *); +static struct spi_handle *m25p_gethandle(void *); +static int m25p_getflags(void *); +static int m25p_getsize(void *, int); + +struct m25p_softc { + struct device sc_dev; + struct spi_handle *sc_sh; + const char *sc_name; + int sc_sizes[SPIFLASH_SIZE_COUNT]; + int sc_flags; +}; + +CFATTACH_DECL(m25p, sizeof(struct m25p_softc), + m25p_match, m25p_attach, NULL, NULL); + +static const struct spiflash_hw_if m25p_hw_if = { + .sf_getname = m25p_getname, + .sf_gethandle = m25p_gethandle, + .sf_getsize = m25p_getsize, + .sf_getflags = m25p_getflags, +}; + +static const struct m25p_info { + uint8_t sig; + uint8_t mfgid; + uint16_t devid; + const char *name; + uint16_t size; /* in KB */ + uint16_t sector; /* in KB */ + uint16_t mhz; +} m25p_infos[] = { + { 0x16, 0x20, 0x2017, "STMicro M25P64", 8192, 64 }, /* 64Mbit */ + { 0x12, 0x20, 0x2013, "STMicro M25P40", 512, 64 }, /* 4Mbit */ + { 0 } +}; + +static int +m25p_match(struct device *parent, struct cfdata *cf, void *aux) +{ + struct spi_attach_args *sa = aux; + + /* configure for 20MHz, which is the max for normal reads */ + if (spi_configure(sa->sa_handle, SPI_MODE_0, 20000000)) + return 0; + + return 1; +} + +static void +m25p_attach(struct device *parent, struct device *self, void *aux) +{ + struct m25p_softc *sc = device_private(self); + struct spi_attach_args *sa = aux; + const struct m25p_info *info; + uint8_t buf[4]; + uint8_t cmd; + uint8_t mfgid; + uint8_t sig; + uint16_t devid; + + sc->sc_sh = sa->sa_handle; + + /* first we try JEDEC ID read */ + + cmd = SPIFLASH_CMD_RDJI; + if (spi_send_recv(sa->sa_handle, 1, &cmd, 3, buf)) { + aprint_error(": failed to get JEDEC identification\n"); + return; + } + mfgid = buf[0]; + devid = ((uint16_t)buf[1] << 8) | buf[2]; + + if ((mfgid == 0xff) || (mfgid == 0)) { + cmd = SPIFLASH_CMD_RDID; + if (spi_send_recv(sa->sa_handle, 1, &cmd, 4, buf)) { + aprint_error(": failed to get legacy signature\n"); + return; + } + sig = buf[3]; + } else { + sig = 0; + } + + /* okay did it match */ + for (info = m25p_infos; info->name; info++) { + if ((info->mfgid == mfgid) && (info->devid == devid)) + break; + if (sig == info->sig) + break; + } + + if (info->name == NULL) { + aprint_error(": unknown or unsupported device\n"); + return; + } + + sc->sc_name = info->name; + sc->sc_sh = sa->sa_handle; + sc->sc_sizes[SPIFLASH_SIZE_DEVICE] = info->size * 1024; + sc->sc_sizes[SPIFLASH_SIZE_ERASE] = info->sector * 1024; + sc->sc_sizes[SPIFLASH_SIZE_WRITE] = 256; + sc->sc_sizes[SPIFLASH_SIZE_READ] = -1; + + sc->sc_flags = SPIFLASH_FLAG_FAST_READ; + + printf("\n"); + + spiflash_attach_mi(&m25p_hw_if, sc, self); +} + +const char * +m25p_getname(void *cookie) +{ + struct m25p_softc *sc = cookie; + + return (sc->sc_name); +} + +struct spi_handle * +m25p_gethandle(void *cookie) +{ + struct m25p_softc *sc = cookie; + + return (sc->sc_sh); +} + +int +m25p_getflags(void *cookie) +{ + struct m25p_softc *sc = cookie; + + return (sc->sc_flags); +} + +int +m25p_getsize(void *cookie, int idx) +{ + struct m25p_softc *sc = cookie; + + if ((idx < 0) || (idx >= SPIFLASH_SIZE_COUNT)) + return -1; + return (sc->sc_sizes[idx]); +} diff --git a/sys/dev/spi/spi.c b/sys/dev/spi/spi.c index edaa1318675d..85edaa857111 100644 --- a/sys/dev/spi/spi.c +++ b/sys/dev/spi/spi.c @@ -1,4 +1,4 @@ -/* $NetBSD: spi.c,v 1.1 2006/10/02 07:18:19 gdamore Exp $ */ +/* $NetBSD: spi.c,v 1.2 2006/10/07 07:21:13 gdamore Exp $ */ /*- * Copyright (c) 2006 Urbana-Champaign Independent Media Center. @@ -42,7 +42,7 @@ */ #include -__KERNEL_RCSID(0, "$NetBSD: spi.c,v 1.1 2006/10/02 07:18:19 gdamore Exp $"); +__KERNEL_RCSID(0, "$NetBSD: spi.c,v 1.2 2006/10/07 07:21:13 gdamore Exp $"); #include "locators.h" @@ -149,6 +149,7 @@ spi_attach(struct device *parent, struct device *self, void *aux) M_DEVBUF, M_WAITOK | M_ZERO); sc->sc_speed = 0; + sc->sc_mode = -1; /* * Initialize slave handles diff --git a/sys/dev/spi/spiflash.c b/sys/dev/spi/spiflash.c new file mode 100644 index 000000000000..822b2bcbdca2 --- /dev/null +++ b/sys/dev/spi/spiflash.c @@ -0,0 +1,628 @@ +/* $NetBSD: spiflash.c,v 1.1 2006/10/07 07:21:13 gdamore Exp $ */ + +/*- + * Copyright (c) 2006 Urbana-Champaign Independent Media Center. + * Copyright (c) 2006 Garrett D'Amore. + * All rights reserved. + * + * Portions of this code were written by Garrett D'Amore for the + * Champaign-Urbana Community Wireless Network Project. + * + * 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. + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgements: + * This product includes software developed by the Urbana-Champaign + * Independent Media Center. + * This product includes software developed by Garrett D'Amore. + * 4. Urbana-Champaign Independent Media Center's name and Garrett + * D'Amore's name may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE URBANA-CHAMPAIGN INDEPENDENT + * MEDIA CENTER AND GARRETT D'AMORE ``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 URBANA-CHAMPAIGN INDEPENDENT + * MEDIA CENTER OR GARRETT D'AMORE 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 +__KERNEL_RCSID(0, "$NetBSD: spiflash.c,v 1.1 2006/10/07 07:21:13 gdamore Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * This is an MI block driver for SPI flash devices. It could probably be + * converted to some more generic framework, if someone wanted to create one + * for NOR flashes. Note that some flashes have the ability to handle + * interrupts. + */ + +struct spiflash_softc { + struct device sc_dev; + struct disk sc_dk; + + struct spiflash_hw_if sc_hw; + void *sc_cookie; + + const char *sc_name; + struct spi_handle *sc_handle; + int sc_device_size; + int sc_write_size; + int sc_erase_size; + int sc_read_size; + int sc_device_blks; + + struct bufq_state *sc_bufq; + struct proc *sc_thread; +}; + +#define sc_getname sc_hw.sf_getname +#define sc_gethandle sc_hw.sf_gethandle +#define sc_getsize sc_hw.sf_getsize +#define sc_getflags sc_hw.sf_getflags +#define sc_erase sc_hw.sf_erase +#define sc_write sc_hw.sf_write +#define sc_read sc_hw.sf_read +#define sc_getstatus sc_hw.sf_getstatus +#define sc_setstatus sc_hw.sf_setstatus + +struct spiflash_attach_args { + const struct spiflash_hw_if *hw; + void *cookie; +}; + +#define STATIC +STATIC int spiflash_match(struct device *, struct cfdata *, void *); +STATIC void spiflash_attach(struct device *, struct device *, void *); +STATIC int spiflash_print(void *, const char *); +STATIC int spiflash_common_erase(spiflash_handle_t, size_t, size_t); +STATIC int spiflash_common_write(spiflash_handle_t, size_t, size_t, + const uint8_t *); +STATIC int spiflash_common_read(spiflash_handle_t, size_t, size_t, uint8_t *); +STATIC void spiflash_process(spiflash_handle_t, struct buf *); +STATIC void spiflash_thread(void *); +STATIC void spiflash_thread_create(void *); + +CFATTACH_DECL(spiflash, sizeof(struct spiflash_softc), + spiflash_match, spiflash_attach, NULL, NULL); + +extern struct cfdriver spiflash_cd; + +dev_type_open(spiflash_open); +dev_type_close(spiflash_close); +dev_type_read(spiflash_read); +dev_type_write(spiflash_write); +dev_type_ioctl(spiflash_ioctl); +dev_type_strategy(spiflash_strategy); + +const struct bdevsw spiflash_bdevsw = { + .d_open = spiflash_open, + .d_close = spiflash_close, + .d_strategy = spiflash_strategy, + .d_ioctl = spiflash_ioctl, + .d_dump = nodump, + .d_psize = nosize, + .d_type = D_DISK, +}; + +const struct cdevsw spiflash_cdevsw = { + .d_open = spiflash_open, + .d_close = spiflash_close, + .d_read = spiflash_read, + .d_write = spiflash_write, + .d_ioctl = spiflash_ioctl, + .d_stop = nostop, + .d_tty = notty, + .d_poll = nopoll, + .d_mmap = nommap, + .d_kqfilter = nokqfilter, + .d_type = D_DISK, +}; + +static struct dkdriver spiflash_dkdriver = { spiflash_strategy, NULL }; + +spiflash_handle_t +spiflash_attach_mi(const struct spiflash_hw_if *hw, void *cookie, + struct device *dev) +{ + struct spiflash_attach_args sfa; + sfa.hw = hw; + sfa.cookie = cookie; + + return (spiflash_handle_t)config_found(dev, &sfa, spiflash_print); +} + +int +spiflash_print(void *aux, const char *pnp) +{ + if (pnp != NULL) + printf("spiflash at %s\n", pnp); + + return UNCONF; +} + +int +spiflash_match(struct device *parent, struct cfdata *cf, void *aux) +{ + + return 1; +} + +void +spiflash_attach(struct device *parent, struct device *self, void *aux) +{ + struct spiflash_softc *sc = device_private(self); + struct spiflash_attach_args *sfa = aux; + void *cookie = sfa->cookie; + + sc->sc_hw = *sfa->hw; + sc->sc_cookie = cookie; + sc->sc_name = sc->sc_getname(cookie); + sc->sc_handle = sc->sc_gethandle(cookie); + sc->sc_device_size = sc->sc_getsize(cookie, SPIFLASH_SIZE_DEVICE); + sc->sc_erase_size = sc->sc_getsize(cookie, SPIFLASH_SIZE_ERASE); + sc->sc_write_size = sc->sc_getsize(cookie, SPIFLASH_SIZE_WRITE); + sc->sc_read_size = sc->sc_getsize(cookie, SPIFLASH_SIZE_READ); + sc->sc_device_blks = sc->sc_device_size / DEV_BSIZE; + + if (sc->sc_read == NULL) + sc->sc_read = spiflash_common_read; + if (sc->sc_write == NULL) + sc->sc_write = spiflash_common_write; + if (sc->sc_erase == NULL) + sc->sc_erase = spiflash_common_erase; + + aprint_naive(": SPI flash\n"); + aprint_normal(": %s SPI flash\n", sc->sc_name); + /* XXX: note that this has to change for boot-sectored flash */ + aprint_normal("%s: %d KB, %d sectors of %d KB each\n", + sc->sc_dev.dv_xname, sc->sc_device_size / 1024, + sc->sc_device_size / sc->sc_erase_size, + sc->sc_erase_size / 1024); + + /* first-come first-served strategy works best for us */ + bufq_alloc(&sc->sc_bufq, "fcfs", BUFQ_SORT_RAWBLOCK); + + /* arrange to allocate the kthread */ + kthread_create(spiflash_thread_create, sc); + + sc->sc_dk.dk_driver = &spiflash_dkdriver; + sc->sc_dk.dk_name = sc->sc_dev.dv_xname; + + disk_attach(&sc->sc_dk); +} + +int +spiflash_open(dev_t dev, int flags, int mode, struct lwp *l) +{ + spiflash_handle_t sc; + + if ((sc = device_lookup(&spiflash_cd, DISKUNIT(dev))) == NULL) + return ENXIO; + + /* + * XXX: We need to handle partitions here. The problem is + * that it isn't entirely clear to me how to deal with this. + * There are devices that could be used "in the raw" with a + * NetBSD label, but then you get into devices that have other + * kinds of data on them -- some have VxWorks data, some have + * RedBoot data, and some have other contraints -- for example + * some devices might have a portion that is read-only, + * whereas others might have a portion that is read-write. + * + * For now we just permit access to the entire device. + */ + return 0; +} + +int +spiflash_close(dev_t dev, int flags, int mode, struct lwp *l) +{ + spiflash_handle_t sc; + + if ((sc = device_lookup(&spiflash_cd, DISKUNIT(dev))) == NULL) + return ENXIO; + + return 0; +} + +int +spiflash_read(dev_t dev, struct uio *uio, int ioflag) +{ + + return physio(spiflash_strategy, NULL, dev, B_READ, minphys, uio); +} + +int +spiflash_write(dev_t dev, struct uio *uio, int ioflag) +{ + + return physio(spiflash_strategy, NULL, dev, B_WRITE, minphys, uio); +} + +int +spiflash_ioctl(dev_t dev, u_long cmd, caddr_t data, int flags, struct lwp *l) +{ + spiflash_handle_t sc; + + if ((sc = device_lookup(&spiflash_cd, DISKUNIT(dev))) == NULL) + return ENXIO; + + return EINVAL; +} + +void +spiflash_strategy(struct buf *bp) +{ + spiflash_handle_t sc; + int sz; + int s; + + sc = device_lookup(&spiflash_cd, DISKUNIT(bp->b_dev)); + if (sc == NULL) { + bp->b_error = ENXIO; + bp->b_flags |= B_ERROR; + biodone(bp); + return; + } + + if ((bp->b_bcount % sc->sc_write_size) || + (bp->b_blkno < 0)) { + bp->b_error = EINVAL; + bp->b_flags |= B_ERROR; + biodone(bp); + return; + } + + /* no work? */ + if (bp->b_bcount == 0) { + biodone(bp); + return; + } + + sz = bp->b_bcount / DEV_BSIZE; + + if ((bp->b_blkno + sz) > sc->sc_device_blks) { + sz = sc->sc_device_blks - bp->b_blkno; + /* exactly at end of media? return EOF */ + if (sz == 0) { + biodone(bp); + return; + } + if (sz < 0) { + /* past end of disk */ + bp->b_error = EINVAL; + bp->b_flags |= B_ERROR; + biodone(bp); + } + /* otherwise truncate it */ + bp->b_bcount = sz << DEV_BSHIFT; + } + + bp->b_resid = bp->b_bcount; + + /* all ready, hand off to thread for async processing */ + s = splbio(); + BUFQ_PUT(sc->sc_bufq, bp); + wakeup(&sc->sc_thread); + splx(s); +} + +void +spiflash_process(spiflash_handle_t sc, struct buf *bp) +{ + int cnt; + size_t addr; + uint8_t *data; + + addr = bp->b_blkno * DEV_BSIZE; + data = bp->b_data; + + while (bp->b_resid > 0) { + cnt = max(sc->sc_write_size, DEV_BSIZE); + if (bp->b_flags & B_READ) { + bp->b_error = sc->sc_read(sc, addr, cnt, data); + } else { + bp->b_error = sc->sc_write(sc, addr, cnt, data); + } + if (bp->b_error) { + bp->b_flags |= B_ERROR; + biodone(bp); + return; + } + bp->b_resid -= cnt; + data += cnt; + addr += cnt; + } + biodone(bp); +} + +void +spiflash_thread(void *arg) +{ + spiflash_handle_t sc = arg; + struct buf *bp; + int s; + + s = splbio(); + for (;;) { + if ((bp = BUFQ_GET(sc->sc_bufq)) == NULL) { + tsleep(&sc->sc_thread, PRIBIO, "spiflash_thread", 0); + continue; + } + + spiflash_process(sc, bp); + } +} + +void +spiflash_thread_create(void *arg) +{ + spiflash_handle_t sc = arg; + + kthread_create1(spiflash_thread, arg, &sc->sc_thread, + "spiflash_thread"); +} + +/* + * SPI flash common implementation. + */ + +/* + * Most devices take on the order of 1 second for each block that they + * delete. + */ +int +spiflash_common_erase(spiflash_handle_t sc, size_t start, size_t size) +{ + int rv; + + if ((start % sc->sc_erase_size) || (size % sc->sc_erase_size)) + return EINVAL; + + /* the second test is to test against wrap */ + if ((start > sc->sc_device_size) || + ((start + size) > sc->sc_device_size)) + return EINVAL; + + /* + * XXX: check protection status? Requires master table mapping + * sectors to status bits, and so forth. + */ + + while (size) { + if ((rv = spiflash_write_enable(sc)) != 0) { + spiflash_write_disable(sc); + return rv; + } + if ((rv = spiflash_cmd(sc, SPIFLASH_CMD_ERASE, 3, start, 0, + NULL, NULL)) != 0) { + spiflash_write_disable(sc); + return rv; + } + + /* + * The devices I have all say typical for sector erase + * is ~1sec. We check ten times that often. (There + * is no way to interrupt on this.) + */ + if ((rv = spiflash_wait(sc, hz / 10)) != 0) + return rv; + + start += sc->sc_erase_size; + size -= sc->sc_erase_size; + + /* NB: according to the docs I have, the write enable + * is automatically cleared upon completion of an erase + * command, so there is no need to explicitly disable it. + */ + } + + return 0; +} + +int +spiflash_common_write(spiflash_handle_t sc, size_t start, size_t size, + const uint8_t *data) +{ + int rv; + + if ((start % sc->sc_write_size) || (size % sc->sc_write_size)) + return EINVAL; + + /* the second test is to test against wrap */ + if ((start > sc->sc_device_size) || + ((start + size) > sc->sc_device_size)) + return EINVAL; + + while (size) { + int cnt; + + if ((rv = spiflash_write_enable(sc)) != 0) { + spiflash_write_disable(sc); + return rv; + } + + cnt = min(size, sc->sc_write_size); + if ((rv = spiflash_cmd(sc, SPIFLASH_CMD_PROGRAM, 3, start, + cnt, data, NULL)) != 0) { + spiflash_write_disable(sc); + return rv; + } + + /* + * It seems that most devices can write bits fairly + * quickly. For example, one part I have access to + * takes ~5msec to process the entire 256 byte page. + * Probably this should be modified to cope with + * device-specific timing, and maybe also take into + * account systems with higher values of HZ (which + * could benefit from sleeping.) + */ + if ((rv = spiflash_wait(sc, 0)) != 0) + return rv; + + start += cnt; + size -= cnt; + } + + return 0; +} + +int +spiflash_common_read(spiflash_handle_t sc, size_t start, size_t size, + uint8_t *data) +{ + int rv; + int align; + + align = sc->sc_write_size; + if (sc->sc_read_size > 0) + align = sc->sc_read_size; + + if ((start % align) || (size % align)) + return EINVAL; + + /* the second test is to test against wrap */ + if ((start > sc->sc_device_size) || + ((start + size) > sc->sc_device_size)) + return EINVAL; + + while (size) { + int cnt; + + if ((rv = spiflash_write_enable(sc)) != 0) { + spiflash_write_disable(sc); + return rv; + } + + if (sc->sc_read_size > 0) + cnt = min(size, sc->sc_read_size); + else + cnt = size; + + if ((rv = spiflash_cmd(sc, SPIFLASH_CMD_READ, 3, start, + cnt, NULL, data)) != 0) { + spiflash_write_disable(sc); + return rv; + } + + start += cnt; + size -= cnt; + } + + return 0; +} + +/* read status register */ +int +spiflash_read_status(spiflash_handle_t sc, uint8_t *sr) +{ + + return spiflash_cmd(sc, SPIFLASH_CMD_RDSR, 0, 0, 1, NULL, sr); +} + +int +spiflash_write_enable(spiflash_handle_t sc) +{ + + return spiflash_cmd(sc, SPIFLASH_CMD_WREN, 0, 0, 0, NULL, NULL); +} + +int +spiflash_write_disable(spiflash_handle_t sc) +{ + + return spiflash_cmd(sc, SPIFLASH_CMD_WRDI, 0, 0, 0, NULL, NULL); +} + +int +spiflash_cmd(spiflash_handle_t sc, uint8_t cmd, + size_t addrlen, uint32_t addr, + size_t cnt, const uint8_t *wdata, uint8_t *rdata) +{ + struct spi_transfer trans; + struct spi_chunk chunk1, chunk2; + char buf[4]; + int i; + + buf[0] = cmd; + + if (addrlen > 3) + return EINVAL; + + for (i = addrlen; i > 0; i--) { + buf[i] = (addr >> ((i - 1) * 8)) & 0xff; + } + spi_transfer_init(&trans); + spi_chunk_init(&chunk1, addrlen + 1, buf, NULL); + spi_transfer_add(&trans, &chunk1); + if (cnt) { + spi_chunk_init(&chunk2, cnt, wdata, rdata); + spi_transfer_add(&trans, &chunk2); + } + + spi_transfer(sc->sc_handle, &trans); + spi_wait(&trans); + + if (trans.st_flags & SPI_F_ERROR) + return trans.st_errno; + return 0; +} + +int +spiflash_wait(spiflash_handle_t sc, int tmo) +{ + int rv; + uint8_t sr; + + for (;;) { + if ((rv = spiflash_read_status(sc, &sr)) != 0) + return rv; + + if ((sr & SPIFLASH_SR_BUSY) == 0) + break; + /* + * The devices I have all say typical for sector + * erase is ~1sec. We check time times that often. + * (There is no way to interrupt on this.) + */ + if (tmo) + tsleep(&sr, PWAIT, "spiflash_wait", tmo); + } + return 0; +} diff --git a/sys/dev/spi/spiflash.h b/sys/dev/spi/spiflash.h new file mode 100644 index 000000000000..d67d64f6e914 --- /dev/null +++ b/sys/dev/spi/spiflash.h @@ -0,0 +1,153 @@ +/* $NetBSD: spiflash.h,v 1.1 2006/10/07 07:21:13 gdamore Exp $ */ + +/*- + * Copyright (c) 2006 Urbana-Champaign Independent Media Center. + * Copyright (c) 2006 Garrett D'Amore. + * All rights reserved. + * + * Portions of this code were written by Garrett D'Amore for the + * Champaign-Urbana Community Wireless Network Project. + * + * 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. + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgements: + * This product includes software developed by the Urbana-Champaign + * Independent Media Center. + * This product includes software developed by Garrett D'Amore. + * 4. Urbana-Champaign Independent Media Center's name and Garrett + * D'Amore's name may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE URBANA-CHAMPAIGN INDEPENDENT + * MEDIA CENTER AND GARRETT D'AMORE ``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 URBANA-CHAMPAIGN INDEPENDENT + * MEDIA CENTER OR GARRETT D'AMORE 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. + */ + +#ifndef _DEV_SPI_SPIFLASH_H_ +#define _DEV_SPI_SPIFLASH_H_ + +#define SPIFLASH_CMD_RDSR 0x05 /* read status register */ +#define SPIFLASH_CMD_WRSR 0x01 /* write status register */ +#define SPIFLASH_CMD_WREN 0x06 /* enable WRSR */ +#define SPIFLASH_CMD_WRDI 0x04 /* disable WRSR */ + +/* + * Different chips offer different ways to read a device ID, although + * newer parts should all offer the standard JEDEC variant. + * Additionally, many parts have a faster read, though how to make use + * of this sometimes requires special hacks. E.g. some parts use an + * extra data pin, and some crank the clock rate up. + */ +#define SPIFLASH_CMD_READ 0x03 /* read data (normal) */ +#define SPIFLASH_CMD_RDID 0xab /* read id */ +#define SPIFLASH_CMD_RDID2 0x90 /* read id (alternate) */ +#define SPIFLASH_CMD_RDJI 0x9f /* read JEDEC id */ +#define SPIFLASH_CMD_READFAST 0x0b /* fast read */ + +/* + * Different chips offer different variations on the sector erase. + * E.g. SST parts offer 4k, 32k, and 64k erase sizes on the same part, + * with just different cmds. However, at least SST, AMD, and Winbond + * all offer at least the main (0xd8) variant. + */ +#define SPIFLASH_CMD_ERASE 0xd8 /* sector erase */ +#define SPIFLASH_CMD_ERASE2 0x52 /* sector erase (alternate) */ +#define SPIFLASH_CMD_ERASE3 0x20 /* sector erase (alternate) */ +#define SPIFLASH_CMD_ERASE4 0x81 /* page erase */ +#define SPIFLASH_CMD_CHIPERASE 0xc7 /* chip erase */ + +/* + * Some parts can stream bytes with the program command, whereas others require + * a seperate command sequence for each byte. + */ +#define SPIFLASH_CMD_PROGRAM 0x02 /* page or byte program */ +#define SPIFLASH_CMD_PROGRAM_AA 0xad /* program (autoincrement) */ + +/* + * Some additional commands. Again, mostly device specific. + */ +#define SPIFLASH_CMD_EBSY 0x70 /* output busy signal (SST) */ +#define SPIFLASH_CMD_DBSY 0x80 /* disable busy signal (SST) */ + +/* + * Status register bits. Not all devices implement all bits. In + * addition, the meanings of the BP bits seem to vary from device to + * device. + */ +#define SPIFLASH_SR_BUSY 0x01 /* program in progress */ +#define SPIFLASH_SR_WEL 0x02 /* write enable latch */ +#define SPIFLASH_SR_BP0 0x04 /* block protect bits */ +#define SPIFLASH_SR_BP1 0x08 +#define SPIFLASH_SR_BP2 0x10 +#define SPIFLASH_SR_BP3 0x20 +#define SPIFLASH_SR_AAI 0x40 /* auto-increment mode */ +#define SPIFLASH_SR_SRP 0x80 /* SR write protected */ + +/* + * This needs to change to accomodate boot-sectored devices. + */ + +typedef struct spiflash_softc *spiflash_handle_t; + +struct spiflash_hw_if { + /* + * Driver MUST provide these. + */ + const char *(*sf_getname)(void *); + struct spi_handle *(*sf_gethandle)(void *); + int (*sf_getflags)(void *); + int (*sf_getsize)(void *, int); + + /* + * SPI framework will provide these if the driver does not. + */ + int (*sf_erase)(spiflash_handle_t, size_t, size_t); + int (*sf_write)(spiflash_handle_t, size_t, size_t, + const uint8_t *); + int (*sf_read)(spiflash_handle_t, size_t, size_t, uint8_t *); + /* + * Not implemented yet. + */ + int (*sf_getstatus)(spiflash_handle_t, int, int); + int (*sf_setstatus)(spiflash_handle_t, int, int, int); +}; + +#define SPIFLASH_SIZE_DEVICE 0 +#define SPIFLASH_SIZE_ERASE 1 +#define SPIFLASH_SIZE_WRITE 2 /* return -1 for unlimited */ +#define SPIFLASH_SIZE_READ 3 /* return -1 for unlimited */ +#define SPIFLASH_SIZE_COUNT 4 + +#define SPIFLASH_FLAG_FAST_READ 0x0004 /* use fast read sequence */ + +spiflash_handle_t spiflash_attach_mi(const struct spiflash_hw_if *, void *, + struct device *); +void spiflash_set_private(spiflash_handle_t, void *); +void *spiflash_get_private(spiflash_handle_t); +int spiflash_read_status(spiflash_handle_t, uint8_t *); +int spiflash_write_disable(spiflash_handle_t); +int spiflash_write_enable(spiflash_handle_t); +int spiflash_cmd(spiflash_handle_t, uint8_t, size_t, uint32_t, size_t, + const uint8_t *, uint8_t *); +int spiflash_wait(spiflash_handle_t, int); + + +#endif /* _DEV_SPI_SPIFLASH_H_ */ diff --git a/sys/dev/spi/spivar.h b/sys/dev/spi/spivar.h index 3b9d4c8af3c0..48cf753e089e 100644 --- a/sys/dev/spi/spivar.h +++ b/sys/dev/spi/spivar.h @@ -1,4 +1,4 @@ -/* $NetBSD: spivar.h,v 1.1 2006/10/02 07:18:19 gdamore Exp $ */ +/* $NetBSD: spivar.h,v 1.2 2006/10/07 07:21:13 gdamore Exp $ */ /*- * Copyright (c) 2006 Urbana-Champaign Independent Media Center. @@ -109,11 +109,15 @@ struct spi_transfer { void *st_private; void (*st_done)(struct spi_transfer *); struct simplelock st_lock; + void *st_busprivate; }; /* declare a list of transfers */ SIMPLEQ_HEAD(spi_transq, spi_transfer); +#define spi_transq_init(q) \ + SIMPLEQ_INIT(q) + #define spi_transq_enqueue(q, trans) \ SIMPLEQ_INSERT_TAIL(q, trans, st_chain)