freebsd-dev/sys/arm/at91/at91_mci.c
Warner Losh b8b0747b3c Initialize the clocks before we call cninit() so that the serial
console so initialized will work upon return from cninit.  While this
is the very next line, other platforms setup all this stuff before
calling cninit.  Also, initialize the SDRAM base register in the inner
block in at91_ramsize().
2012-06-01 02:55:42 +00:00

865 lines
21 KiB
C

/*-
* Copyright (c) 2006 Bernd Walter. All rights reserved.
* Copyright (c) 2006 M. Warner Losh. All rights reserved.
* Copyright (c) 2010 Greg Ansley. 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 AUTHOR AND CONTRIBUTORS ``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 AUTHOR OR CONTRIBUTORS 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/endian.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/queue.h>
#include <sys/resource.h>
#include <sys/rman.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/timetc.h>
#include <sys/watchdog.h>
#include <machine/bus.h>
#include <machine/cpu.h>
#include <machine/cpufunc.h>
#include <machine/resource.h>
#include <machine/frame.h>
#include <machine/intr.h>
#include <arm/at91/at91var.h>
#include <arm/at91/at91_mcireg.h>
#include <arm/at91/at91_pdcreg.h>
#include <dev/mmc/bridge.h>
#include <dev/mmc/mmcreg.h>
#include <dev/mmc/mmcbrvar.h>
#include "mmcbr_if.h"
#include "opt_at91.h"
#define BBSZ 512
/*
* Note: This driver only supports the SlotA card. No attempt has been made
* to support SlotB.
*/
struct at91_mci_softc {
void *intrhand; /* Interrupt handle */
device_t dev;
int sc_cap;
#define CAP_HAS_4WIRE 1 /* Has 4 wire bus */
#define CAP_NEEDS_BYTESWAP 2 /* broken hardware needing bounce */
int flags;
int has_4wire;
#define CMD_STARTED 1
#define STOP_STARTED 2
struct resource *irq_res; /* IRQ resource */
struct resource *mem_res; /* Memory resource */
struct mtx sc_mtx;
bus_dma_tag_t dmatag;
bus_dmamap_t map;
int mapped;
struct mmc_host host;
int bus_busy;
struct mmc_request *req;
struct mmc_command *curcmd;
char bounce_buffer[BBSZ];
};
static inline uint32_t
RD4(struct at91_mci_softc *sc, bus_size_t off)
{
return (bus_read_4(sc->mem_res, off));
}
static inline void
WR4(struct at91_mci_softc *sc, bus_size_t off, uint32_t val)
{
bus_write_4(sc->mem_res, off, val);
}
/* bus entry points */
static int at91_mci_probe(device_t dev);
static int at91_mci_attach(device_t dev);
static int at91_mci_detach(device_t dev);
static void at91_mci_intr(void *);
/* helper routines */
static int at91_mci_activate(device_t dev);
static void at91_mci_deactivate(device_t dev);
static int at91_mci_is_mci1rev2xx(void);
#define AT91_MCI_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
#define AT91_MCI_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
#define AT91_MCI_LOCK_INIT(_sc) \
mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \
"mci", MTX_DEF)
#define AT91_MCI_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx);
#define AT91_MCI_ASSERT_LOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED);
#define AT91_MCI_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED);
static void
at91_mci_pdc_disable(struct at91_mci_softc *sc)
{
WR4(sc, PDC_PTCR, PDC_PTCR_TXTDIS | PDC_PTCR_RXTDIS);
WR4(sc, PDC_RPR, 0);
WR4(sc, PDC_RCR, 0);
WR4(sc, PDC_RNPR, 0);
WR4(sc, PDC_RNCR, 0);
WR4(sc, PDC_TPR, 0);
WR4(sc, PDC_TCR, 0);
WR4(sc, PDC_TNPR, 0);
WR4(sc, PDC_TNCR, 0);
}
static void
at91_mci_init(device_t dev)
{
struct at91_mci_softc *sc = device_get_softc(dev);
uint32_t val;
WR4(sc, MCI_CR, MCI_CR_MCIEN); /* Enable controller */
WR4(sc, MCI_IDR, 0xffffffff); /* Turn off interrupts */
WR4(sc, MCI_DTOR, MCI_DTOR_DTOMUL_1M | 1);
val = MCI_MR_PDCMODE;
val |= 0x34a; /* PWSDIV = 3; CLKDIV = 74 */
if (at91_mci_is_mci1rev2xx())
val |= MCI_MR_RDPROOF | MCI_MR_WRPROOF;
WR4(sc, MCI_MR, val);
#ifndef AT91_MCI_SLOT_B
WR4(sc, MCI_SDCR, 0); /* SLOT A, 1 bit bus */
#else
/* XXX Really should add second "unit" but nobody using using
* a two slot card that we know of. XXX */
WR4(sc, MCI_SDCR, 1); /* SLOT B, 1 bit bus */
#endif
}
static void
at91_mci_fini(device_t dev)
{
struct at91_mci_softc *sc = device_get_softc(dev);
WR4(sc, MCI_IDR, 0xffffffff); /* Turn off interrupts */
at91_mci_pdc_disable(sc);
WR4(sc, MCI_CR, MCI_CR_MCIDIS | MCI_CR_SWRST); /* Put the device into reset */
}
static int
at91_mci_probe(device_t dev)
{
device_set_desc(dev, "MCI mmc/sd host bridge");
return (0);
}
static int
at91_mci_attach(device_t dev)
{
struct at91_mci_softc *sc = device_get_softc(dev);
struct sysctl_ctx_list *sctx;
struct sysctl_oid *soid;
device_t child;
int err;
sc->dev = dev;
sc->sc_cap = 0;
if (at91_is_rm92())
sc->sc_cap |= CAP_NEEDS_BYTESWAP;
err = at91_mci_activate(dev);
if (err)
goto out;
AT91_MCI_LOCK_INIT(sc);
/*
* Allocate DMA tags and maps
*/
err = bus_dma_tag_create(bus_get_dma_tag(dev), 1, 0,
BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, MAXPHYS, 1,
MAXPHYS, BUS_DMA_ALLOCNOW, NULL, NULL, &sc->dmatag);
if (err != 0)
goto out;
err = bus_dmamap_create(sc->dmatag, 0, &sc->map);
if (err != 0)
goto out;
at91_mci_fini(dev);
at91_mci_init(dev);
/*
* Activate the interrupt
*/
err = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
NULL, at91_mci_intr, sc, &sc->intrhand);
if (err) {
AT91_MCI_LOCK_DESTROY(sc);
goto out;
}
sctx = device_get_sysctl_ctx(dev);
soid = device_get_sysctl_tree(dev);
SYSCTL_ADD_UINT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "4wire",
CTLFLAG_RW, &sc->has_4wire, 0, "has 4 wire SD Card bus");
#ifdef AT91_MCI_HAS_4WIRE
sc->has_4wire = 1;
#endif
if (sc->has_4wire)
sc->sc_cap |= CAP_HAS_4WIRE;
sc->host.f_min = at91_master_clock / 512;
sc->host.f_min = 375000;
sc->host.f_max = at91_master_clock / 2;
if (sc->host.f_max > 50000000)
sc->host.f_max = 50000000; /* Limit to 50MHz */
sc->host.host_ocr = MMC_OCR_320_330 | MMC_OCR_330_340;
sc->host.caps = 0;
/*
* The in-tree Linux driver doesn't allow 4-wire operation for the
* at91rm9200, but does for other members of the family. The atmel
* patches to this do allow it, or have in the past. It is unclear
* that the hardware even works, but my boot loader uses 4-bit bus
* in polling mode successfully.
*/
if (sc->sc_cap & CAP_HAS_4WIRE)
sc->host.caps |= MMC_CAP_4_BIT_DATA;
child = device_add_child(dev, "mmc", 0);
device_set_ivars(dev, &sc->host);
err = bus_generic_attach(dev);
out:
if (err)
at91_mci_deactivate(dev);
return (err);
}
static int
at91_mci_detach(device_t dev)
{
at91_mci_fini(dev);
at91_mci_deactivate(dev);
return (EBUSY); /* XXX */
}
static int
at91_mci_activate(device_t dev)
{
struct at91_mci_softc *sc;
int rid;
sc = device_get_softc(dev);
rid = 0;
sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
RF_ACTIVE);
if (sc->mem_res == NULL)
goto errout;
rid = 0;
sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
RF_ACTIVE);
if (sc->irq_res == NULL)
goto errout;
return (0);
errout:
at91_mci_deactivate(dev);
return (ENOMEM);
}
static void
at91_mci_deactivate(device_t dev)
{
struct at91_mci_softc *sc;
sc = device_get_softc(dev);
if (sc->intrhand)
bus_teardown_intr(dev, sc->irq_res, sc->intrhand);
sc->intrhand = 0;
bus_generic_detach(sc->dev);
if (sc->mem_res)
bus_release_resource(dev, SYS_RES_IOPORT,
rman_get_rid(sc->mem_res), sc->mem_res);
sc->mem_res = 0;
if (sc->irq_res)
bus_release_resource(dev, SYS_RES_IRQ,
rman_get_rid(sc->irq_res), sc->irq_res);
sc->irq_res = 0;
return;
}
static int
at91_mci_is_mci1rev2xx(void)
{
switch (AT91_CPU(at91_chip_id)) {
case AT91_CPU_SAM9260:
case AT91_CPU_SAM9263:
#ifdef notyet
case AT91_CPU_CAP9:
#endif
case AT91_CPU_SAM9G10:
case AT91_CPU_SAM9G20:
#ifdef notyet
case AT91_CPU_SAM9RL:
#endif
case AT91_CPU_SAM9XE128:
case AT91_CPU_SAM9XE256:
case AT91_CPU_SAM9XE512:
return(1);
}
return (0);
}
static void
at91_mci_getaddr(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
{
if (error != 0)
return;
*(bus_addr_t *)arg = segs[0].ds_addr;
}
static int
at91_mci_update_ios(device_t brdev, device_t reqdev)
{
struct at91_mci_softc *sc;
struct mmc_host *host;
struct mmc_ios *ios;
uint32_t clkdiv;
sc = device_get_softc(brdev);
host = &sc->host;
ios = &host->ios;
// bus mode?
if (ios->clock == 0) {
WR4(sc, MCI_CR, MCI_CR_MCIDIS);
clkdiv = 0;
} else {
WR4(sc, MCI_CR, MCI_CR_MCIEN);
if ((at91_master_clock % (ios->clock * 2)) == 0)
clkdiv = ((at91_master_clock / ios->clock) / 2) - 1;
else
clkdiv = (at91_master_clock / ios->clock) / 2;
}
if (ios->bus_width == bus_width_4)
WR4(sc, MCI_SDCR, MCI_SDCR_SDCBUS);
else
WR4(sc, MCI_SDCR, 0);
WR4(sc, MCI_MR, (RD4(sc, MCI_MR) & ~MCI_MR_CLKDIV) | clkdiv);
/* Do we need a settle time here? */
/* XXX We need to turn the device on/off here with a GPIO pin */
return (0);
}
static void
at91_mci_start_cmd(struct at91_mci_softc *sc, struct mmc_command *cmd)
{
size_t len;
uint32_t cmdr, ier = 0, mr;
uint32_t *src, *dst;
int i;
struct mmc_data *data;
void *vaddr;
bus_addr_t paddr;
sc->curcmd = cmd;
data = cmd->data;
cmdr = cmd->opcode;
/* XXX Upper layers don't always set this */
cmd->mrq = sc->req;
if (MMC_RSP(cmd->flags) == MMC_RSP_NONE)
cmdr |= MCI_CMDR_RSPTYP_NO;
else {
/* Allow big timeout for responses */
cmdr |= MCI_CMDR_MAXLAT;
if (cmd->flags & MMC_RSP_136)
cmdr |= MCI_CMDR_RSPTYP_136;
else
cmdr |= MCI_CMDR_RSPTYP_48;
}
if (cmd->opcode == MMC_STOP_TRANSMISSION)
cmdr |= MCI_CMDR_TRCMD_STOP;
if (sc->host.ios.bus_mode == opendrain)
cmdr |= MCI_CMDR_OPDCMD;
if (!data) {
// The no data case is fairly simple
at91_mci_pdc_disable(sc);
#ifdef AT91_MCI_DEBUG
printf("CMDR %x ARGR %x\n", cmdr, cmd->arg);
#endif
WR4(sc, MCI_ARGR, cmd->arg);
WR4(sc, MCI_CMDR, cmdr);
WR4(sc, MCI_IER, MCI_SR_ERROR | MCI_SR_CMDRDY);
return;
}
if (data->flags & MMC_DATA_READ)
cmdr |= MCI_CMDR_TRDIR;
if (data->flags & (MMC_DATA_READ | MMC_DATA_WRITE))
cmdr |= MCI_CMDR_TRCMD_START;
if (data->flags & MMC_DATA_STREAM)
cmdr |= MCI_CMDR_TRTYP_STREAM;
if (data->flags & MMC_DATA_MULTI)
cmdr |= MCI_CMDR_TRTYP_MULTIPLE;
// Set block size and turn on PDC mode for dma xfer and disable
// PDC until we're ready.
mr = RD4(sc, MCI_MR) & ~MCI_MR_BLKLEN;
WR4(sc, MCI_MR, mr | (data->len << 16) | MCI_MR_PDCMODE);
WR4(sc, PDC_PTCR, PDC_PTCR_RXTDIS | PDC_PTCR_TXTDIS);
if (cmdr & MCI_CMDR_TRCMD_START) {
len = data->len;
if (cmdr & MCI_CMDR_TRDIR)
vaddr = cmd->data->data;
else {
/* Use bounce buffer even if we don't need
* byteswap, since buffer may straddle a page
* boundry, and we don't handle multi-segment
* transfers in hardware.
* (page issues seen from 'bsdlabel -w' which
* uses raw geom access to the volume).
* Greg Ansley (gja (at) ansley.com)
*/
vaddr = sc->bounce_buffer;
src = (uint32_t *)cmd->data->data;
dst = (uint32_t *)vaddr;
/*
* If this is MCI1 revision 2xx controller, apply
* a work-around for the "Data Write Operation and
* number of bytes" erratum.
*/
if (at91_mci_is_mci1rev2xx() && data->len < 12) {
len = 12;
memset(dst, 0, 12);
}
if (sc->sc_cap & CAP_NEEDS_BYTESWAP) {
for (i = 0; i < data->len / 4; i++)
dst[i] = bswap32(src[i]);
} else
memcpy(dst, src, data->len);
}
data->xfer_len = 0;
if (bus_dmamap_load(sc->dmatag, sc->map, vaddr, len,
at91_mci_getaddr, &paddr, 0) != 0) {
cmd->error = MMC_ERR_NO_MEMORY;
sc->req = NULL;
sc->curcmd = NULL;
cmd->mrq->done(cmd->mrq);
return;
}
sc->mapped++;
if (cmdr & MCI_CMDR_TRDIR) {
bus_dmamap_sync(sc->dmatag, sc->map, BUS_DMASYNC_PREREAD);
WR4(sc, PDC_RPR, paddr);
WR4(sc, PDC_RCR, len / 4);
ier = MCI_SR_ENDRX;
} else {
bus_dmamap_sync(sc->dmatag, sc->map, BUS_DMASYNC_PREWRITE);
WR4(sc, PDC_TPR, paddr);
WR4(sc, PDC_TCR, len / 4);
ier = MCI_SR_TXBUFE;
}
}
#ifdef AT91_MCI_DEBUG
printf("CMDR %x ARGR %x with data\n", cmdr, cmd->arg);
#endif
WR4(sc, MCI_ARGR, cmd->arg);
if (cmdr & MCI_CMDR_TRCMD_START) {
if (cmdr & MCI_CMDR_TRDIR) {
WR4(sc, PDC_PTCR, PDC_PTCR_RXTEN);
WR4(sc, MCI_CMDR, cmdr);
} else {
WR4(sc, MCI_CMDR, cmdr);
WR4(sc, PDC_PTCR, PDC_PTCR_TXTEN);
}
}
WR4(sc, MCI_IER, MCI_SR_ERROR | ier);
}
static void
at91_mci_start(struct at91_mci_softc *sc)
{
struct mmc_request *req;
req = sc->req;
if (req == NULL)
return;
// assert locked
if (!(sc->flags & CMD_STARTED)) {
sc->flags |= CMD_STARTED;
// printf("Starting CMD\n");
at91_mci_start_cmd(sc, req->cmd);
return;
}
if (!(sc->flags & STOP_STARTED) && req->stop) {
// printf("Starting Stop\n");
sc->flags |= STOP_STARTED;
at91_mci_start_cmd(sc, req->stop);
return;
}
/* We must be done -- bad idea to do this while locked? */
sc->req = NULL;
sc->curcmd = NULL;
req->done(req);
/*
* Attempted hack-a-round for the DMA bug for multiple reads.
*/
if (req->cmd->opcode == MMC_READ_MULTIPLE_BLOCK) {
at91_mci_fini(sc->dev);
at91_mci_init(sc->dev);
at91_mci_update_ios(sc->dev, NULL);
}
}
static int
at91_mci_request(device_t brdev, device_t reqdev, struct mmc_request *req)
{
struct at91_mci_softc *sc = device_get_softc(brdev);
AT91_MCI_LOCK(sc);
// XXX do we want to be able to queue up multiple commands?
// XXX sounds like a good idea, but all protocols are sync, so
// XXX maybe the idea is naive...
if (sc->req != NULL) {
AT91_MCI_UNLOCK(sc);
return (EBUSY);
}
sc->req = req;
sc->flags = 0;
at91_mci_start(sc);
AT91_MCI_UNLOCK(sc);
return (0);
}
static int
at91_mci_get_ro(device_t brdev, device_t reqdev)
{
return (0);
}
static int
at91_mci_acquire_host(device_t brdev, device_t reqdev)
{
struct at91_mci_softc *sc = device_get_softc(brdev);
int err = 0;
AT91_MCI_LOCK(sc);
while (sc->bus_busy)
msleep(sc, &sc->sc_mtx, PZERO, "mciah", hz / 5);
sc->bus_busy++;
AT91_MCI_UNLOCK(sc);
return (err);
}
static int
at91_mci_release_host(device_t brdev, device_t reqdev)
{
struct at91_mci_softc *sc = device_get_softc(brdev);
AT91_MCI_LOCK(sc);
sc->bus_busy--;
wakeup(sc);
AT91_MCI_UNLOCK(sc);
return (0);
}
static void
at91_mci_read_done(struct at91_mci_softc *sc)
{
uint32_t *walker;
struct mmc_command *cmd;
int i, len;
#ifdef AT91_MCI_DEBUG
char *w2;
#endif
cmd = sc->curcmd;
bus_dmamap_sync(sc->dmatag, sc->map, BUS_DMASYNC_POSTREAD);
bus_dmamap_unload(sc->dmatag, sc->map);
sc->mapped--;
if (sc->sc_cap & CAP_NEEDS_BYTESWAP) {
walker = (uint32_t *)cmd->data->data;
len = cmd->data->len / 4;
for (i = 0; i < len; i++)
walker[i] = bswap32(walker[i]);
}
#ifdef AT91_MCI_DEBUG
printf("Read data\n");
for (i = 0, w2 = cmd->data->data; i < cmd->data->len; i++) {
if (i % 16 == 0)
printf("%08x ", cmd->arg + i);
printf("%02x%s", w2[i], (i + 1) % 16 ? " " : "\n");
}
printf("\n");
#endif
// Finish up the sequence...
WR4(sc, MCI_IDR, MCI_SR_ENDRX);
WR4(sc, MCI_IER, MCI_SR_RXBUFF);
WR4(sc, PDC_PTCR, PDC_PTCR_RXTDIS | PDC_PTCR_TXTDIS);
}
static void
at91_mci_xmit_done(struct at91_mci_softc *sc)
{
// Finish up the sequence...
WR4(sc, PDC_PTCR, PDC_PTCR_RXTDIS | PDC_PTCR_TXTDIS);
WR4(sc, MCI_IDR, MCI_SR_TXBUFE);
WR4(sc, MCI_IER, MCI_SR_NOTBUSY);
bus_dmamap_sync(sc->dmatag, sc->map, BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->dmatag, sc->map);
sc->mapped--;
}
static void
at91_mci_intr(void *arg)
{
struct at91_mci_softc *sc = (struct at91_mci_softc*)arg;
uint32_t sr;
int i, done = 0;
struct mmc_command *cmd;
AT91_MCI_LOCK(sc);
sr = RD4(sc, MCI_SR) & RD4(sc, MCI_IMR);
// printf("i 0x%x\n", sr);
cmd = sc->curcmd;
if (sr & MCI_SR_ERROR) {
// Ignore CRC errors on CMD2 and ACMD47, per relevant standards
if ((sr & MCI_SR_RCRCE) && (cmd->opcode == MMC_SEND_OP_COND ||
cmd->opcode == ACMD_SD_SEND_OP_COND))
cmd->error = MMC_ERR_NONE;
else if (sr & (MCI_SR_RTOE | MCI_SR_DTOE)) {
printf("TIMEOUT %#x\n", sr);
cmd->error = MMC_ERR_TIMEOUT;
} else if (sr & (MCI_SR_RCRCE | MCI_SR_DCRCE)) {
printf("CRC %#x\n", sr);
cmd->error = MMC_ERR_BADCRC;
} else if (sr & (MCI_SR_OVRE | MCI_SR_UNRE)) {
printf("FIFO %#x\n", sr);
cmd->error = MMC_ERR_FIFO;
} else {
printf("FAILED %#x\n", sr);
cmd->error = MMC_ERR_FAILED;
}
done = 1;
if (sc->mapped && cmd->error) {
bus_dmamap_unload(sc->dmatag, sc->map);
sc->mapped--;
}
} else {
if (sr & MCI_SR_TXBUFE) {
// printf("TXBUFE\n");
at91_mci_xmit_done(sc);
}
if (sr & MCI_SR_RXBUFF) {
// printf("RXBUFF\n");
WR4(sc, MCI_IDR, MCI_SR_RXBUFF);
WR4(sc, MCI_IER, MCI_SR_CMDRDY);
}
if (sr & MCI_SR_ENDTX) {
// printf("ENDTX\n");
}
if (sr & MCI_SR_ENDRX) {
// printf("ENDRX\n");
at91_mci_read_done(sc);
}
if (sr & MCI_SR_NOTBUSY) {
// printf("NOTBUSY\n");
WR4(sc, MCI_IDR, MCI_SR_NOTBUSY);
WR4(sc, MCI_IER, MCI_SR_CMDRDY);
}
if (sr & MCI_SR_DTIP) {
// printf("Data transfer in progress\n");
}
if (sr & MCI_SR_BLKE) {
// printf("Block transfer end\n");
}
if (sr & MCI_SR_TXRDY) {
// printf("Ready to transmit\n");
}
if (sr & MCI_SR_RXRDY) {
// printf("Ready to receive\n");
}
if (sr & MCI_SR_CMDRDY) {
// printf("Command ready\n");
done = 1;
cmd->error = MMC_ERR_NONE;
}
}
if (done) {
WR4(sc, MCI_IDR, 0xffffffff);
if (cmd != NULL && (cmd->flags & MMC_RSP_PRESENT)) {
for (i = 0; i < ((cmd->flags & MMC_RSP_136) ? 4 : 1);
i++) {
cmd->resp[i] = RD4(sc, MCI_RSPR + i * 4);
// printf("RSPR[%d] = %x\n", i, cmd->resp[i]);
}
}
at91_mci_start(sc);
}
AT91_MCI_UNLOCK(sc);
}
static int
at91_mci_read_ivar(device_t bus, device_t child, int which, uintptr_t *result)
{
struct at91_mci_softc *sc = device_get_softc(bus);
switch (which) {
default:
return (EINVAL);
case MMCBR_IVAR_BUS_MODE:
*(int *)result = sc->host.ios.bus_mode;
break;
case MMCBR_IVAR_BUS_WIDTH:
*(int *)result = sc->host.ios.bus_width;
break;
case MMCBR_IVAR_CHIP_SELECT:
*(int *)result = sc->host.ios.chip_select;
break;
case MMCBR_IVAR_CLOCK:
*(int *)result = sc->host.ios.clock;
break;
case MMCBR_IVAR_F_MIN:
*(int *)result = sc->host.f_min;
break;
case MMCBR_IVAR_F_MAX:
*(int *)result = sc->host.f_max;
break;
case MMCBR_IVAR_HOST_OCR:
*(int *)result = sc->host.host_ocr;
break;
case MMCBR_IVAR_MODE:
*(int *)result = sc->host.mode;
break;
case MMCBR_IVAR_OCR:
*(int *)result = sc->host.ocr;
break;
case MMCBR_IVAR_POWER_MODE:
*(int *)result = sc->host.ios.power_mode;
break;
case MMCBR_IVAR_VDD:
*(int *)result = sc->host.ios.vdd;
break;
case MMCBR_IVAR_CAPS:
if (sc->has_4wire) {
sc->sc_cap |= CAP_HAS_4WIRE;
sc->host.caps |= MMC_CAP_4_BIT_DATA;
} else {
sc->sc_cap &= ~CAP_HAS_4WIRE;
sc->host.caps &= ~MMC_CAP_4_BIT_DATA;
}
*(int *)result = sc->host.caps;
break;
case MMCBR_IVAR_MAX_DATA:
*(int *)result = 1024;
break;
}
return (0);
}
static int
at91_mci_write_ivar(device_t bus, device_t child, int which, uintptr_t value)
{
struct at91_mci_softc *sc = device_get_softc(bus);
switch (which) {
default:
return (EINVAL);
case MMCBR_IVAR_BUS_MODE:
sc->host.ios.bus_mode = value;
break;
case MMCBR_IVAR_BUS_WIDTH:
sc->host.ios.bus_width = value;
break;
case MMCBR_IVAR_CHIP_SELECT:
sc->host.ios.chip_select = value;
break;
case MMCBR_IVAR_CLOCK:
sc->host.ios.clock = value;
break;
case MMCBR_IVAR_MODE:
sc->host.mode = value;
break;
case MMCBR_IVAR_OCR:
sc->host.ocr = value;
break;
case MMCBR_IVAR_POWER_MODE:
sc->host.ios.power_mode = value;
break;
case MMCBR_IVAR_VDD:
sc->host.ios.vdd = value;
break;
/* These are read-only */
case MMCBR_IVAR_CAPS:
case MMCBR_IVAR_HOST_OCR:
case MMCBR_IVAR_F_MIN:
case MMCBR_IVAR_F_MAX:
case MMCBR_IVAR_MAX_DATA:
return (EINVAL);
}
return (0);
}
static device_method_t at91_mci_methods[] = {
/* device_if */
DEVMETHOD(device_probe, at91_mci_probe),
DEVMETHOD(device_attach, at91_mci_attach),
DEVMETHOD(device_detach, at91_mci_detach),
/* Bus interface */
DEVMETHOD(bus_read_ivar, at91_mci_read_ivar),
DEVMETHOD(bus_write_ivar, at91_mci_write_ivar),
/* mmcbr_if */
DEVMETHOD(mmcbr_update_ios, at91_mci_update_ios),
DEVMETHOD(mmcbr_request, at91_mci_request),
DEVMETHOD(mmcbr_get_ro, at91_mci_get_ro),
DEVMETHOD(mmcbr_acquire_host, at91_mci_acquire_host),
DEVMETHOD(mmcbr_release_host, at91_mci_release_host),
DEVMETHOD_END
};
static driver_t at91_mci_driver = {
"at91_mci",
at91_mci_methods,
sizeof(struct at91_mci_softc),
};
static devclass_t at91_mci_devclass;
DRIVER_MODULE(at91_mci, atmelarm, at91_mci_driver, at91_mci_devclass, NULL,
NULL);