/*-
* 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.
*/
/*
* 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 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;
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_dev(self, "%d KB, %d sectors of %d KB each\n",
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_waitq, "fcfs", BUFQ_SORT_RAWBLOCK);
bufq_alloc(&sc->sc_workq, "fcfs", BUFQ_SORT_RAWBLOCK);
bufq_alloc(&sc->sc_doneq, "fcfs", BUFQ_SORT_RAWBLOCK);
/* arrange to allocate the kthread */
kthread_create(PRI_NONE, 0, NULL, spiflash_thread, sc,
&sc->sc_thread, "spiflash");
}
int
spiflash_open(dev_t dev, int flags, int mode, struct lwp *l)
{
spiflash_handle_t sc;
sc = device_lookup_private(&spiflash_cd, DISKUNIT(dev));
if (sc == 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 constraints -- 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;
sc = device_lookup_private(&spiflash_cd, DISKUNIT(dev));
if (sc == NULL)
return ENXIO;
return 0;
}
int
spiflash_read(dev_t dev, struct uio *uio, int ioflag)
{
/*
* due to other considerations, we are guaranteed that
* we will only have multiple buffers if they are all in
* the same erase sector. Therefore we never need to look
* beyond the first block to determine how much data we need
* to save.
*/
bp = bufq_peek(sc->sc_workq);
len = spiflash_nsectors(sc, bp) * sc->sc_erase_size;
blkno = bp->b_blkno;
base = (blkno * DEV_BSIZE) & ~ (sc->sc_erase_size - 1);
/* get ourself a scratch buffer */
save = malloc(len, M_DEVBUF, M_WAITOK);
disk_busy(&sc->sc_dk);
/* read in as much of the data as we need */
DPRINTF(("reading in %d bytes\n", len));
if ((err = sc->sc_read(sc, base, len, save)) != 0) {
bufq_move(sc->sc_doneq, sc->sc_workq);
spiflash_process_done(sc, err);
return;
}
/*
* now coalesce the writes into the save area, but also
* check to see if we need to do an erase
*/
while ((bp = bufq_get(sc->sc_workq)) != NULL) {
uint8_t *data, *dst;
int resid = bp->b_resid;
data = bp->b_data;
dst = save + (bp->b_blkno * DEV_BSIZE) - base;
/*
* NOR flash bits. We can clear a bit, but we cannot
* set a bit, without erasing. This should help reduce
* unnecessary erases.
*/
while (resid) {
if ((*data) & ~(*dst))
neederase = 1;
*dst++ = *data++;
resid--;
}
bufq_put(sc->sc_doneq, bp);
}
/*
* do the erase, if we need to.
*/
if (neederase) {
DPRINTF(("erasing from %zx - %zx\n", base, base + len));
if ((err = sc->sc_erase(sc, base, len)) != 0) {
spiflash_process_done(sc, err);
return;
}
}
/*
* now write our save area, and finish up.
*/
DPRINTF(("flashing %d bytes to %zx from %p\n", len, base, save));
err = sc->sc_write(sc, base, len, save);
spiflash_process_done(sc, err);
}
int
spiflash_nsectors(spiflash_handle_t sc, struct buf *bp)
{
unsigned addr, sector;
(void)splbio();
for (;;) {
if ((bp = bufq_get(sc->sc_waitq)) == NULL) {
tsleep(&sc->sc_thread, PRIBIO, "spiflash_thread", 0);
continue;
}
bufq_put(sc->sc_workq, bp);
if (bp->b_flags & B_READ) {
/* just do the read */
spiflash_process_read(sc);
continue;
}
/*
* Because writing a flash filesystem is particularly
* painful, involving erase, modify, write, we prefer
* to coalesce writes to the same sector together.
*/
sector = spiflash_sector(sc, bp);
/*
* if the write spans multiple sectors, skip
* coalescing. (It would be nice if we could break
* these up. minphys is honored for read/write, but
* not necessarily for bread.)
*/
if (sector < 0)
goto dowrite;
while ((bp = bufq_peek(sc->sc_waitq)) != NULL) {
/* can't deal with read requests! */
if (bp->b_flags & B_READ)
break;
/* is it for the same sector? */
if (spiflash_sector(sc, bp) != sector)
break;
bp = bufq_get(sc->sc_waitq);
bufq_put(sc->sc_workq, bp);
}
/*
* 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.
*/
/*
* 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;
/* 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;
/*
* 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;
data += cnt;
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;
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;
}