NetBSD/sys/dev/spi/spi.c

380 lines
9.2 KiB
C

/* $NetBSD: spi.c,v 1.2 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: spi.c,v 1.2 2006/10/07 07:21:13 gdamore Exp $");
#include "locators.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/errno.h>
#include <dev/spi/spivar.h>
struct spi_softc {
struct device sc_dev;
struct spi_controller sc_controller;
int sc_mode;
int sc_speed;
int sc_nslaves;
struct spi_handle *sc_slaves;
};
/*
* SPI slave device. We have one of these per slave.
*/
struct spi_handle {
struct spi_softc *sh_sc;
struct spi_controller *sh_controller;
int sh_slave;
};
/*
* API for bus drivers.
*/
int
spibus_print(void *aux, const char *pnp)
{
if (pnp != NULL)
aprint_normal("spi at %s", pnp);
return (UNCONF);
}
static int
spi_match(struct device *parent, struct cfdata *cf, void *aux)
{
return 1;
}
static int
spi_print(void *aux, const char *pnp)
{
struct spi_attach_args *sa = aux;
if (sa->sa_handle->sh_slave != -1)
aprint_normal(" slave %d", sa->sa_handle->sh_slave);
return (UNCONF);
}
static int
spi_search(struct device *parent, struct cfdata *cf, const int *ldesc,
void *aux)
{
struct spi_softc *sc = (void *)parent;
struct spi_attach_args sa;
int addr;
addr = cf->cf_loc[SPICF_SLAVE];
if ((addr < 0) || (addr >= sc->sc_controller.sct_nslaves)) {
return -1;
}
sa.sa_handle = &sc->sc_slaves[addr];
if (config_match(parent, cf, &sa) > 0)
config_attach(parent, cf, &sa, spi_print);
return 0;
}
/*
* API for device drivers.
*
* We provide wrapper routines to decouple the ABI for the SPI
* device drivers from the ABI for the SPI bus drivers.
*/
static void
spi_attach(struct device *parent, struct device *self, void *aux)
{
struct spi_softc *sc = device_private(self);
struct spibus_attach_args *sba = aux;
int i;
aprint_naive(": SPI bus\n");
aprint_normal(": SPI bus\n");
sc->sc_controller = *sba->sba_controller;
/* allocate slave structures */
sc->sc_slaves = malloc(sizeof (struct spi_handle) * sc->sc_nslaves,
M_DEVBUF, M_WAITOK | M_ZERO);
sc->sc_speed = 0;
sc->sc_mode = -1;
/*
* Initialize slave handles
*/
sc->sc_nslaves = sba->sba_controller->sct_nslaves;
for (i = 0; i < sc->sc_nslaves; i++) {
sc->sc_slaves[i].sh_slave = i;
sc->sc_slaves[i].sh_sc = sc;
sc->sc_slaves[i].sh_controller = &sc->sc_controller;
}
/*
* Locate and attach child devices
*/
config_search_ia(spi_search, self, "spi", NULL);
}
CFATTACH_DECL(spi, sizeof(struct spi_softc),
spi_match, spi_attach, NULL, NULL);
/*
* Configure. This should be the first thing that the SPI driver
* should do, to configure which mode (e.g. SPI_MODE_0, which is the
* same as Philips Microwire mode), and speed. If the bus driver
* cannot run fast enough, then it should just configure the fastest
* mode that it can support. If the bus driver cannot run slow
* enough, then the device is incompatible and an error should be
* returned.
*/
int
spi_configure(struct spi_handle *sh, int mode, int speed)
{
int s, rv;
struct spi_softc *sc = sh->sh_sc;
struct spi_controller *tag = sh->sh_controller;
/* ensure that request is compatible with other devices on the bus */
if ((sc->sc_mode >= 0) && (sc->sc_mode != mode))
return EINVAL;
s = splserial();
/* pick lowest configured speed */
if (speed == 0)
speed = sc->sc_speed;
if (sc->sc_speed)
speed = min(sc->sc_speed, speed);
rv = (*tag->sct_configure)(tag->sct_cookie, sh->sh_slave,
mode, speed);
if (rv == 0) {
sc->sc_mode = mode;
sc->sc_speed = speed;
}
splx(s);
return rv;
}
void
spi_transfer_init(struct spi_transfer *st)
{
simple_lock_init(&st->st_lock);
st->st_flags = 0;
st->st_errno = 0;
st->st_done = NULL;
st->st_chunks = NULL;
st->st_private = NULL;
st->st_slave = -1;
}
void
spi_chunk_init(struct spi_chunk *chunk, int cnt, const uint8_t *wptr,
uint8_t *rptr)
{
chunk->chunk_write = chunk->chunk_wptr = wptr;
chunk->chunk_read = chunk->chunk_read = rptr;
chunk->chunk_rresid = chunk->chunk_wresid = chunk->chunk_count = cnt;
chunk->chunk_next = NULL;
}
void
spi_transfer_add(struct spi_transfer *st, struct spi_chunk *chunk)
{
struct spi_chunk **cpp;
/* this is an O(n) insert -- perhaps we should use a simpleq? */
for (cpp = &st->st_chunks; *cpp; cpp = &(*cpp)->chunk_next);
*cpp = chunk;
}
int
spi_transfer(struct spi_handle *sh, struct spi_transfer *st)
{
struct spi_controller *tag = sh->sh_controller;
struct spi_chunk *chunk;
/*
* Initialize "resid" counters and pointers, so that callers
* and bus drivers don't have to.
*/
for (chunk = st->st_chunks; chunk; chunk = chunk->chunk_next) {
chunk->chunk_wresid = chunk->chunk_rresid = chunk->chunk_count;
chunk->chunk_wptr = chunk->chunk_write;
chunk->chunk_rptr = chunk->chunk_read;
}
/*
* Match slave to handle's slave.
*/
st->st_slave = sh->sh_slave;
return (*tag->sct_transfer)(tag->sct_cookie, st);
}
void
spi_wait(struct spi_transfer *st)
{
int s;
s = splserial();
simple_lock(&st->st_lock);
while (!st->st_flags & SPI_F_DONE) {
ltsleep(st, PWAIT, "spi_wait", 0, &st->st_lock);
}
simple_unlock(&st->st_lock);
splx(s);
}
void
spi_done(struct spi_transfer *st, int err)
{
int s;
s = splserial();
if ((st->st_errno = err) != 0) {
st->st_flags |= SPI_F_ERROR;
}
st->st_flags |= SPI_F_DONE;
if (st->st_done != NULL) {
(*st->st_done)(st);
} else {
simple_lock(&st->st_lock);
wakeup(st);
simple_unlock(&st->st_lock);
}
splx(s);
}
/*
* Some convenience routines. These routines block until the work
* is done.
*
* spi_recv - receives data from the bus
*
* spi_send - sends data to the bus
*
* spi_send_recv - sends data to the bus, and then receives. Note that this is
* done synchronously, i.e. send a command and get the response. This is
* not full duplex. If you wnat full duplex, you can't use these convenience
* wrappers.
*/
int
spi_recv(struct spi_handle *sh, int cnt, uint8_t *data)
{
struct spi_transfer trans;
struct spi_chunk chunk;
spi_transfer_init(&trans);
spi_chunk_init(&chunk, cnt, NULL, data);
spi_transfer_add(&trans, &chunk);
/* enqueue it and wait for it to complete */
spi_transfer(sh, &trans);
spi_wait(&trans);
if (trans.st_flags & SPI_F_ERROR)
return trans.st_errno;
return 0;
}
int
spi_send(struct spi_handle *sh, int cnt, const uint8_t *data)
{
struct spi_transfer trans;
struct spi_chunk chunk;
spi_transfer_init(&trans);
spi_chunk_init(&chunk, cnt, data, NULL);
spi_transfer_add(&trans, &chunk);
/* enqueue it and wait for it to complete */
spi_transfer(sh, &trans);
spi_wait(&trans);
if (trans.st_flags & SPI_F_ERROR)
return trans.st_errno;
return 0;
}
int
spi_send_recv(struct spi_handle *sh, int scnt, const uint8_t *snd,
int rcnt, uint8_t *rcv)
{
struct spi_transfer trans;
struct spi_chunk chunk1, chunk2;
spi_transfer_init(&trans);
spi_chunk_init(&chunk1, scnt, snd, NULL);
spi_chunk_init(&chunk2, rcnt, NULL, rcv);
spi_transfer_add(&trans, &chunk1);
spi_transfer_add(&trans, &chunk2);
/* enqueue it and wait for it to complete */
spi_transfer(sh, &trans);
spi_wait(&trans);
if (trans.st_flags & SPI_F_ERROR)
return trans.st_errno;
return 0;
}