Add spiflash driver, and M25P instance, used for STMicro flash devices

found on Meraki Mini (for example).
This commit is contained in:
gdamore 2006-10-07 07:21:13 +00:00
parent 5bf8068cbb
commit c36ea7cf37
6 changed files with 1007 additions and 4 deletions

View File

@ -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

206
sys/dev/spi/m25p.c Normal file
View File

@ -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 <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: m25p.c,v 1.1 2006/10/07 07:21:13 gdamore Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <dev/sysmon/sysmonvar.h>
#include <dev/spi/spivar.h>
#include <dev/spi/spiflash.h>
/*
* 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]);
}

View File

@ -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 <sys/cdefs.h>
__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

628
sys/dev/spi/spiflash.c Normal file
View File

@ -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 <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: spiflash.c,v 1.1 2006/10/07 07:21:13 gdamore Exp $");
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/proc.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/disk.h>
#include <sys/disklabel.h>
#include <sys/buf.h>
#include <sys/bufq.h>
#include <sys/uio.h>
#include <sys/kthread.h>
#include <sys/malloc.h>
#include <sys/errno.h>
#include <dev/spi/spivar.h>
#include <dev/spi/spiflash.h>
/*
* 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;
}

153
sys/dev/spi/spiflash.h Normal file
View File

@ -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_ */

View File

@ -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)