Contribution from Petri Laakso:

- DMA driver stub code replaced with working code.
- Add support to multi block DMA in ssp driver.
This commit is contained in:
jkunz 2013-03-03 10:33:56 +00:00
parent 882558bd95
commit aca15765bd
8 changed files with 1049 additions and 336 deletions

View File

@ -1,4 +1,4 @@
/* $Id: imx23_apbdma.c,v 1.2 2012/12/16 19:40:00 jkunz Exp $ */
/* $Id: imx23_apbdma.c,v 1.3 2013/03/03 10:33:56 jkunz Exp $ */
/*
* Copyright (c) 2012 The NetBSD Foundation, Inc.
@ -30,53 +30,25 @@
*/
#include <sys/param.h>
#include <sys/types.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/errno.h>
#include <sys/mutex.h>
#include <sys/kmem.h>
#include <sys/queue.h>
#include <sys/systm.h>
#include <arm/imx/imx23_apbdma.h>
#include <arm/imx/imx23_apbdmareg.h>
#include <arm/imx/imx23_apbdmavar.h>
#include <arm/imx/imx23_apbhdmareg.h>
#include <arm/imx/imx23_apbxdmareg.h>
#include <arm/imx/imx23_apbdma.h>
#include <arm/imx/imx23var.h>
static int apbdma_match(device_t, cfdata_t, void *);
static void apbdma_attach(device_t, device_t, void *);
static int apbdma_activate(device_t, enum devact);
#define APBDMA_SOFT_RST_LOOP 455 /* At least 1 us ... */
#define DMACTRL_RD(sc, reg) \
bus_space_read_4(sc->sc_iot, sc->sc_hdl, (reg))
#define DMACTRL_WR(sc, reg, val) \
bus_space_write_4(sc->sc_iot, sc->sc_hdl, (reg), (val))
struct apbdma_softc {
device_t sc_dev;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_hdl;
bus_dma_tag_t sc_dmat;
bus_dmamap_t sc_dmamp;
struct imx23_dma_channel *sc_channel;
int n_channel;
};
struct imx23_dma_cmd {
uint32_t next_cmd;
uint32_t cmd;
uint32_t buffer;
uint32_t pio[CMDPIOWORDS_MAX];
SIMPLEQ_ENTRY(imx23_dma_cmd) entries;
};
struct imx23_dma_channel {
SIMPLEQ_HEAD(simplehead, imx23_dma_cmd) head;
struct simplehead *headp;
struct apbdma_softc *sc;
};
CFATTACH_DECL3_NEW(apbdma,
sizeof(struct apbdma_softc),
apbdma_match,
@ -88,6 +60,14 @@ CFATTACH_DECL3_NEW(apbdma,
0);
static void apbdma_reset(struct apbdma_softc *);
static void apbdma_init(struct apbdma_softc *);
#define DMA_RD(sc, reg) \
bus_space_read_4(sc->sc_iot, sc->sc_ioh, (reg))
#define DMA_WR(sc, reg, val) \
bus_space_write_4(sc->sc_iot, sc->sc_ioh, (reg), (val))
#define APBDMA_SOFT_RST_LOOP 455 /* At least 1 us ... */
static int
apbdma_match(device_t parent, cfdata_t match, void *aux)
@ -108,67 +88,44 @@ apbdma_attach(device_t parent, device_t self, void *aux)
{
struct apb_attach_args *aa = aux;
struct apbdma_softc *sc = device_private(self);
//struct apb_softc *scp = device_private(parent);
struct apb_softc *sc_parent = device_private(parent);
static u_int apbdma_attached = 0;
// static int apbdma_attached = 0;
// struct imx23_dma_channel *chan;
// int i;
int error;
// if (apbdma_attached)
// return;
if ((strncmp(device_xname(parent), "apbh", 4) == 0) &&
(apbdma_attached & F_AHBH_DMA))
return;
if ((strncmp(device_xname(parent), "apbx", 4) == 0) &&
(apbdma_attached & F_AHBX_DMA))
return;
sc->sc_dev = self;
sc->sc_iot = aa->aa_iot;
sc->sc_dmat = aa->aa_dmat;
/*
* Parent bus softc has a pointer to DMA controller device_t for
* specific bus. As different busses need different instances of the
* DMA driver. The apb_softc.dmac is set up here. Now device drivers
* which use DMA can pass apb_softc.dmac from their parent to apbdma
* functions.
*/
if (bus_space_map(sc->sc_iot,
aa->aa_addr, aa->aa_size, 0, &(sc->sc_hdl))) {
aa->aa_addr, aa->aa_size, 0, &sc->sc_ioh)) {
aprint_error_dev(sc->sc_dev, "unable to map bus space\n");
return;
}
error = bus_dmamap_create(sc->sc_dmat, PAGE_SIZE, 1,
PAGE_SIZE, 0, BUS_DMA_NOWAIT|BUS_DMA_ALLOCNOW, &sc->sc_dmamp);
if (error) {
aprint_error_dev(sc->sc_dev,
"couldn't create dma map. (error=%d)\n", error);
return;
}
#ifdef notyet
if (aa->aa_addr == HW_APBHDMA_BASE && aa->aa_size == HW_APBHDMA_SIZE) {
sc->sc_channel = kmem_alloc(sizeof(struct imx23_dma_channel)
* APBH_DMA_N_CHANNELS, KM_SLEEP);
sc->n_channel = APBH_DMA_N_CHANNELS;
}
if (strncmp(device_xname(parent), "apbh", 4) == 0)
sc->flags = F_AHBH_DMA;
if (aa->aa_addr == HW_APBXDMA_BASE && aa->aa_size == HW_APBXDMA_SIZE) {
sc->sc_channel = kmem_alloc(sizeof(struct imx23_dma_channel)
* APBX_DMA_N_CHANNELS, KM_SLEEP);
sc->n_channel = APBX_DMA_N_CHANNELS;
}
if (strncmp(device_xname(parent), "apbx", 4) == 0)
sc->flags = F_AHBX_DMA;
if (sc->sc_channel == NULL) {
aprint_error_dev(sc->sc_dev, "unable to allocate memory for"
" DMA channel structures\n");
return;
}
for (i=0; i < sc->n_channel; i++) {
chan = (struct imx23_dma_channel *)sc->sc_channel+i;
chan->sc = sc;
SIMPLEQ_INIT(&chan->head);
}
#endif
apbdma_reset(sc);
// apbdma_attached = 1;
apbdma_init(sc);
if (sc->flags & F_AHBH_DMA)
apbdma_attached |= F_AHBH_DMA;
if (sc->flags & F_AHBX_DMA)
apbdma_attached |= F_AHBX_DMA;
sc_parent->dmac = self;
/* Initialize mutex to control concurrent access from the drivers. */
mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_HIGH);
aprint_normal("\n");
@ -195,85 +152,242 @@ apbdma_reset(struct apbdma_softc *sc)
* Prepare for soft-reset by making sure that SFTRST is not currently
* asserted. Also clear CLKGATE so we can wait for its assertion below.
*/
DMACTRL_WR(sc, HW_APB_CTRL0_CLR, HW_APB_CTRL0_SFTRST);
DMA_WR(sc, HW_APB_CTRL0_CLR, HW_APB_CTRL0_SFTRST);
/* Wait at least a microsecond for SFTRST to deassert. */
loop = 0;
while ((DMACTRL_RD(sc, HW_APB_CTRL0) & HW_APB_CTRL0_SFTRST) ||
(loop < APBDMA_SOFT_RST_LOOP))
{
while ((DMA_RD(sc, HW_APB_CTRL0) & HW_APB_CTRL0_SFTRST) ||
(loop < APBDMA_SOFT_RST_LOOP))
loop++;
}
/* Clear CLKGATE so we can wait for its assertion below. */
DMACTRL_WR(sc, HW_APB_CTRL0_CLR, HW_APB_CTRL0_CLKGATE);
DMA_WR(sc, HW_APB_CTRL0_CLR, HW_APB_CTRL0_CLKGATE);
/* Soft-reset the block. */
DMACTRL_WR(sc, HW_APB_CTRL0_SET, HW_APB_CTRL0_SFTRST);
DMA_WR(sc, HW_APB_CTRL0_SET, HW_APB_CTRL0_SFTRST);
/* Wait until clock is in the gated state. */
while (!(DMACTRL_RD(sc, HW_APB_CTRL0) & HW_APB_CTRL0_CLKGATE));
while (!(DMA_RD(sc, HW_APB_CTRL0) & HW_APB_CTRL0_CLKGATE));
/* Bring block out of reset. */
DMACTRL_WR(sc, HW_APB_CTRL0_CLR, HW_APB_CTRL0_SFTRST);
DMA_WR(sc, HW_APB_CTRL0_CLR, HW_APB_CTRL0_SFTRST);
loop = 0;
while ((DMACTRL_RD(sc, HW_APB_CTRL0) & HW_APB_CTRL0_SFTRST) ||
(loop < APBDMA_SOFT_RST_LOOP))
{
while ((DMA_RD(sc, HW_APB_CTRL0) & HW_APB_CTRL0_SFTRST) ||
(loop < APBDMA_SOFT_RST_LOOP))
loop++;
}
DMACTRL_WR(sc, HW_APB_CTRL0_CLR, HW_APB_CTRL0_CLKGATE);
/* Wait until clock is in the NON-gated state. */
while (DMACTRL_RD(sc, HW_APB_CTRL0) & HW_APB_CTRL0_CLKGATE);
DMA_WR(sc, HW_APB_CTRL0_CLR, HW_APB_CTRL0_CLKGATE);
return;
/* Wait until clock is in the NON-gated state. */
while (DMA_RD(sc, HW_APB_CTRL0) & HW_APB_CTRL0_CLKGATE);
return;
}
/*
* Allocate DMA safe memory for commands.
* Initialize APB{H,X}DMA block.
*/
void *
apbdma_dmamem_alloc(device_t dmac, int channel, bus_size_t size)
static void
apbdma_init(struct apbdma_softc *sc)
{
struct apbdma_softc *sc = device_private(dmac);
bus_dma_segment_t segs[1]; /* bus_dmamem_free needs. */
int rsegs;
int error;
void *ptr = NULL; /* bus_dmamem_unmap needs (size also) */
if (size > PAGE_SIZE)
return NULL;
error = bus_dmamem_alloc(sc->sc_dmat, size, PAGE_SIZE, 0, segs, 1,
&rsegs, BUS_DMA_NOWAIT);
if (error)
goto out;
//XXX:
printf("segs[0].ds_addr=%lx, segs[0].ds_len=%lx, rsegs=%d\n", segs[0].ds_addr, segs[0].ds_len, rsegs);
error = bus_dmamem_map(sc->sc_dmat, segs, 1, size, &ptr,
BUS_DMA_NOWAIT);
if (error)
goto free;
//XXX:
printf("segs[0].ds_addr=%lx, segs[0].ds_len=%lx, ptr=%p\n", segs[0].ds_addr, segs[0].ds_len, ptr);
error = bus_dmamap_load(sc->sc_dmat, sc->sc_dmamp, ptr, size, NULL,
BUS_DMA_NOWAIT | BUS_DMA_WRITE);
if (error)
goto unmap;
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamp, 0, size,
BUS_DMASYNC_PREWRITE);
// return usable memory
unmap:
bus_dmamem_unmap(sc->sc_dmat, ptr, size);
free:
bus_dmamem_free(sc->sc_dmat, segs, 1);
out:
return NULL;
if (sc->flags & F_AHBH_DMA) {
DMA_WR(sc, HW_APBH_CTRL0_SET, HW_APBH_CTRL0_AHB_BURST8_EN);
DMA_WR(sc, HW_APBH_CTRL0_SET, HW_APBH_CTRL0_APB_BURST4_EN);
}
return;
}
/*
* Chain DMA commands together.
*
* Set src->next point to trg's physical DMA mapped address.
*/
void
apbdma_cmd_chain(apbdma_command_t src, apbdma_command_t trg, void *buf,
bus_dmamap_t dmap)
{
int i;
bus_size_t daddr;
bus_addr_t trg_offset;
trg_offset = (bus_addr_t)trg - (bus_addr_t)buf;
daddr = 0;
for (i = 0; i < dmap->dm_nsegs; i++) {
daddr += dmap->dm_segs[i].ds_len;
if (trg_offset < daddr) {
src->next = (void *)(dmap->dm_segs[i].ds_addr +
(trg_offset - (daddr - dmap->dm_segs[i].ds_len)));
break;
}
}
return;
}
/*
* Set DMA command buffer.
*
* Set cmd->buffer point to physical DMA address at offset in DMA map.
*/
void
apbdma_cmd_buf(apbdma_command_t cmd, bus_addr_t offset, bus_dmamap_t dmap)
{
int i;
bus_size_t daddr;
daddr = 0;
for (i = 0; i < dmap->dm_nsegs; i++) {
daddr += dmap->dm_segs[i].ds_len;
if (offset < daddr) {
cmd->buffer = (void *)(dmap->dm_segs[i].ds_addr +
(offset - (daddr - dmap->dm_segs[i].ds_len)));
break;
}
}
return;
}
/*
* Initialize DMA channel.
*/
void
apbdma_chan_init(struct apbdma_softc *sc, unsigned int channel)
{
mutex_enter(&sc->sc_lock);
/* Enable CMDCMPLT_IRQ. */
DMA_WR(sc, HW_APB_CTRL1_SET, (1<<channel)<<16);
mutex_exit(&sc->sc_lock);
return;
}
/*
* Set command chain for DMA channel.
*/
#define HW_APB_CHN_NXTCMDAR(base, channel) (base + (0x70 * channel))
void
apbdma_chan_set_chain(struct apbdma_softc *sc, unsigned int channel,
bus_dmamap_t dmap)
{
uint32_t reg;
if (sc->flags & F_AHBH_DMA)
reg = HW_APB_CHN_NXTCMDAR(HW_APBH_CH0_NXTCMDAR, channel);
else
reg = HW_APB_CHN_NXTCMDAR(HW_APBX_CH0_NXTCMDAR, channel);
mutex_enter(&sc->sc_lock);
DMA_WR(sc, reg, dmap->dm_segs[0].ds_addr);
mutex_exit(&sc->sc_lock);
return;
}
/*
* Initiate DMA transfer.
*/
#define HW_APB_CHN_SEMA(base, channel) (base + (0x70 * channel))
void
apbdma_run(struct apbdma_softc *sc, unsigned int channel)
{
uint32_t reg;
uint8_t val;
if (sc->flags & F_AHBH_DMA) {
reg = HW_APB_CHN_SEMA(HW_APBH_CH0_SEMA, channel);
val = __SHIFTIN(1, HW_APBH_CH0_SEMA_INCREMENT_SEMA);
} else {
reg = HW_APB_CHN_SEMA(HW_APBX_CH0_SEMA, channel);
val = __SHIFTIN(1, HW_APBX_CH0_SEMA_INCREMENT_SEMA);
}
mutex_enter(&sc->sc_lock);
DMA_WR(sc, reg, val);
mutex_exit(&sc->sc_lock);
return;
}
/*
* Acknowledge command complete IRQ.
*/
void
apbdma_ack_intr(struct apbdma_softc *sc, unsigned int channel)
{
mutex_enter(&sc->sc_lock);
DMA_WR(sc, HW_APB_CTRL1_CLR, (1<<channel));
mutex_exit(&sc->sc_lock);
return;
}
/*
* Acknowledge error IRQ.
*/
void
apbdma_ack_error_intr(struct apbdma_softc *sc, unsigned int channel)
{
mutex_enter(&sc->sc_lock);
DMA_WR(sc, HW_APB_CTRL2_CLR, (1<<channel));
mutex_exit(&sc->sc_lock);
return;
}
/*
* Return reason for the IRQ.
*/
unsigned int
apbdma_intr_status(struct apbdma_softc *sc, unsigned int channel)
{
unsigned int reason;
reason = 0;
mutex_enter(&sc->sc_lock);
/* Check if this was command complete IRQ. */
if (DMA_RD(sc, HW_APB_CTRL1) & (1<<channel))
reason = DMA_IRQ_CMDCMPLT;
/* Check if error was set. */
if (DMA_RD(sc, HW_APB_CTRL2) & (1<<channel)) {
if (DMA_RD(sc, HW_APB_CTRL2) & (1<<channel)<<16)
reason = DMA_IRQ_BUS_ERROR;
else
reason = DMA_IRQ_TERM;
}
mutex_exit(&sc->sc_lock);
return reason;
}
/*
* Reset DMA channel.
* Use only for devices on APBH bus.
*/
void
apbdma_chan_reset(struct apbdma_softc *sc, unsigned int channel)
{
mutex_enter(&sc->sc_lock);
DMA_WR(sc, HW_APB_CTRL0_SET,
__SHIFTIN((1<<channel), HW_APBH_CTRL0_RESET_CHANNEL));
while(DMA_RD(sc, HW_APB_CTRL0) & HW_APBH_CTRL0_RESET_CHANNEL);
mutex_exit(&sc->sc_lock);
return;
}

View File

@ -1,4 +1,4 @@
/* $Id: imx23_apbdmareg.h,v 1.1 2012/11/20 19:06:12 jkunz Exp $ */
/* $Id: imx23_apbdmareg.h,v 1.2 2013/03/03 10:33:56 jkunz Exp $ */
/*
* Copyright (c) 2012 The NetBSD Foundation, Inc.
@ -50,4 +50,20 @@
#define HW_APB_CTRL0_CLKGATE __BIT(30)
#define HW_APB_CTRL0_RSVD0 __BITS(29, 0)
/*
* AHB to APB{H,X} Bridge Control Register 1.
*/
#define HW_APB_CTRL1 0x010
#define HW_APB_CTRL1_SET 0x014
#define HW_APB_CTRL1_CLR 0x018
#define HW_APB_CTRL1_TOG 0x01C
/*
* AHB to APB{H,X} Bridge Control and Status Register 2.
*/
#define HW_APB_CTRL2 0x020
#define HW_APB_CTRL2_SET 0x024
#define HW_APB_CTRL2_CLR 0x028
#define HW_APB_CTRL2_TOG 0x02C
#endif /* !_ARM_IMX_IMX23_APBDMAREG_H_ */

View File

@ -0,0 +1,137 @@
/* $Id: imx23_apbdmavar.h,v 1.1 2013/03/03 10:33:56 jkunz Exp $ */
/*
* Copyright (c) 2013 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Petri Laakso.
*
* 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 THE NETBSD FOUNDATION, INC. 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 THE FOUNDATION 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.
*/
#ifndef _ARM_IMX_IMX23_APBDMAVAR_H_
#define _ARM_IMX_IMX23_APBDMAVAR_H_
#include <sys/cdefs.h>
#include <sys/types.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/mutex.h>
/* DMA command control register bits. */
#define APBDMA_CMD_XFER_COUNT __BITS(31, 16)
#define APBDMA_CMD_CMDPIOWORDS __BITS(15, 12)
#define APBDMA_CMD_RESERVED __BITS(11, 9)
#define APBDMA_CMD_HALTONTERMINATE __BIT(8)
#define APBDMA_CMD_WAIT4ENDCMD __BIT(7)
#define APBDMA_CMD_SEMAPHORE __BIT(6)
#define APBDMA_CMD_NANDWAIT4READY __BIT(5)
#define APBDMA_CMD_NANDLOCK __BIT(4)
#define APBDMA_CMD_IRQONCMPLT __BIT(3)
#define APBDMA_CMD_CHAIN __BIT(2)
#define APBDMA_CMD_COMMAND __BITS(1, 0)
/* DMA command types. */
#define APBDMA_CMD_NO_DMA_XFER 0
#define APBDMA_CMD_DMA_WRITE 1
#define APBDMA_CMD_DMA_READ 2
#define APBDMA_CMD_DMA_SENSE 3
/* Flags. */
#define F_AHBH_DMA __BIT(0)
#define F_AHBX_DMA __BIT(1)
/* Number of channels. */
#define AHBH_DMA_CHANNELS 8
#define AHBX_DMA_CHANNELS 16
/* APBH DMA channel assignments. */
#define APBH_DMA_CHANNEL_RES0 0 /* Reserved. */
#define APBH_DMA_CHANNEL_SSP1 1 /* SSP1. */
#define APBH_DMA_CHANNEL_SSP2 2 /* SSP2. */
#define APBH_DMA_CHANNEL_RES1 3 /* Reserved. */
#define APBH_DMA_CHANNEL_NAND_DEVICE0 4 /* NAND_DEVICE0. */
#define APBH_DMA_CHANNEL_NAND_DEVICE1 5 /* NAND_DEVICE1. */
#define APBH_DMA_CHANNEL_NAND_DEVICE2 6 /* NAND_DEVICE2. */
#define APBH_DMA_CHANNEL_NAND_DEVICE3 7 /* NAND_DEVICE3. */
/* APBX DMA channel assignments. */
#define APBX_DMA_CHANNEL_AUDIO_ADC 0 /* Audio ADCs. */
#define APBX_DMA_CHANNEL_AUDIO_DAC 1 /* Audio DACs. */
#define APBX_DMA_CHANNEL_SPDIF_TX 2 /* SPDIF TX. */
#define APBX_DMA_CHANNEL_I2C 3 /* I2C. */
#define APBX_DMA_CHANNEL_SAIF1 4 /* SAIF1. */
#define APBX_DMA_CHANNEL_RES0 5 /* Reserved. */
#define APBX_DMA_CHANNEL_UART1_RX 6 /* UART1 RX, IrDA RX. */
#define APBX_DMA_CHANNEL_UART1_TX 7 /* UART1 TX, IrDA TX. */
#define APBX_DMA_CHANNEL_UART2_RX 8 /* UART2 RX. */
#define APBX_DMA_CHANNEL_UART2_TX 9 /* UART2 TX. */
#define APBX_DMA_CHANNEL_SAIF2 10 /* SAIF2. */
#define APBX_DMA_CHANNEL_RES1 11 /* Reserved. */
#define APBX_DMA_CHANNEL_RES2 12 /* Reserved. */
#define APBX_DMA_CHANNEL_RES3 13 /* Reserved. */
#define APBX_DMA_CHANNEL_RES4 14 /* Reserved. */
#define APBX_DMA_CHANNEL_RES5 15 /* Reserved. */
/* Return codes for apbdma_intr_status() */
#define DMA_IRQ_CMDCMPLT 0
#define DMA_IRQ_TERM 1
#define DMA_IRQ_BUS_ERROR 2
#define PIO_WORDS_MAX 15
/*
* How many PIO words apbdma_command structure has.
*
* XXX: If you change this value, make sure drivers are prepared for that.
* That means you have to allocate enough DMA memory for command chains.
*/
#define PIO_WORDS 3
typedef struct apbdma_softc {
device_t sc_dev;
bus_dma_tag_t sc_dmat;
bus_space_handle_t sc_ioh;
bus_space_tag_t sc_iot;
kmutex_t sc_lock;
u_int flags;
} *apbdma_softc_t;
typedef struct apbdma_command {
void *next; /* Physical address. */
uint32_t control;
void *buffer; /* Physical address. */
uint32_t pio_words[PIO_WORDS];
} *apbdma_command_t;
void apbdma_cmd_chain(apbdma_command_t, apbdma_command_t, void *, bus_dmamap_t);
void apbdma_cmd_buf(apbdma_command_t, bus_addr_t, bus_dmamap_t);
void apbdma_chan_init(struct apbdma_softc *, unsigned int);
void apbdma_chan_set_chain(struct apbdma_softc *, unsigned int, bus_dmamap_t);
void apbdma_run(struct apbdma_softc *, unsigned int);
void apbdma_ack_intr(struct apbdma_softc *, unsigned int);
void apbdma_ack_error_intr(struct apbdma_softc *, unsigned int);
unsigned int apbdma_intr_status(struct apbdma_softc *, unsigned int);
void apbdma_chan_reset(struct apbdma_softc *, unsigned int);
#endif /* !_ARM_IMX_IMX23_APBDMAVAR_H_ */

View File

@ -1,4 +1,4 @@
/* $Id: imx23_apbhdmareg.h,v 1.1 2012/11/20 19:06:13 jkunz Exp $ */
/* $Id: imx23_apbhdmareg.h,v 1.2 2013/03/03 10:33:56 jkunz Exp $ */
/*
* Copyright (c) 2012 The NetBSD Foundation, Inc.
@ -122,6 +122,30 @@
#define HW_APBH_DEVSEL_CH1 __BITS(7, 4)
#define HW_APBH_DEVSEL_CH0 __BITS(3, 0)
/*
* APBH DMA Channel 0 Current Command Address Register.
*/
#define HW_APBH_CH0_CURCMDAR 0x040
#define HW_APBH_CH0_CURCMDAR_CMD_ADDR __BITS(31, 0)
/*
* APBH DMA Channel 0 Next Command Address.
*/
#define HW_APBH_CH0_NXTCMDAR 0x050
#define HW_APBH_CH0_NXTCMDAR_CMD_ADDR __BITS(31, 0)
/*
* APBH DMA Channel 0 Semaphore Register.
*/
#define HW_APBH_CH0_SEMA 0x080
#define HW_APBH_CH0_SEMA_RSVD2 __BITS(31, 24)
#define HW_APBH_CH0_SEMA_PHORE __BITS(23, 16)
#define HW_APBH_CH0_SEMA_RSVD1 __BITS(15, 8)
#define HW_APBH_CH0_SEMA_INCREMENT_SEMA __BITS(7, 0)
/*
* APBH DMA Channel 1 Current Command Address Register.
*/

View File

@ -1,4 +1,4 @@
/* $Id: imx23_apbxdmareg.h,v 1.1 2012/11/20 19:06:13 jkunz Exp $ */
/* $Id: imx23_apbxdmareg.h,v 1.2 2013/03/03 10:33:56 jkunz Exp $ */
/*
* Copyright (c) 2012 The NetBSD Foundation, Inc.
@ -34,7 +34,31 @@
#include <sys/cdefs.h>
#define HW_APBXDMA_BASE 0x80024000
#define HW_APBXDMA_SIZE 0x2000 /* 8 kB */
#define HW_APBXDMA_BASE 0x80024000
#define HW_APBXDMA_SIZE 0x2000 /* 8 kB */
/*
* APBX DMA Channel 0 Current Command Address Register.
*/
#define HW_APBX_CH0_CURCMDAR 0x100
#define HW_APBX_CH0_CURCMDAR_CMD_ADDR __BITS(31, 0)
/*
* APBX DMA Channel 0 Next Command Address Register.
*/
#define HW_APBX_CH0_NXTCMDAR 0x110
#define HW_APBX_CH0_NXTCMDAR_CMD_ADDR __BITS(31, 0)
/*
* APBX DMA Channel 0 Semaphore Register.
*/
#define HW_APBX_CH0_SEMA 0x140
#define HW_APBX_CH0_SEMA_RSVD2 __BITS(31, 24)
#define HW_APBX_CH0_SEMA_PHORE __BITS(23, 16)
#define HW_APBX_CH0_SEMA_RSVD1 __BITS(15, 8)
#define HW_APBX_CH0_SEMA_INCREMENT_SEMA __BITS(7, 0)
#endif /* !_ARM_IMX_IMX23_APBXDMAREG_H_ */

View File

@ -1,4 +1,4 @@
/* $Id: imx23_icollreg.h,v 1.2 2012/12/16 19:40:00 jkunz Exp $ */
/* $Id: imx23_icollreg.h,v 1.3 2013/03/03 10:33:56 jkunz Exp $ */
/*
* Copyright (c) 2012 The NetBSD Foundation, Inc.
@ -63,7 +63,7 @@
* suspended. */
#define IRQ_GPMI_DMA 13 /* From DMA channel for GPMI */
#define IRQ_SSP1_DMA 14 /* From DMA channel for SSP1 */
#define IRQ_SSP_ERROR 15 /* SSP1 device-level error and status */
#define IRQ_SSP1_ERROR 15 /* SSP1 device-level error and status */
#define IRQ_GPIO0 16 /* GPIO bank 0 interrupt */
#define IRQ_GPIO1 17 /* GPIO bank 1 interrupt */
#define IRQ_GPIO2 18 /* GPIO bank 2 interrupt */

View File

@ -1,4 +1,4 @@
/* $Id: imx23_ssp.c,v 1.2 2012/12/16 19:45:52 jkunz Exp $ */
/* $Id: imx23_ssp.c,v 1.3 2013/03/03 10:33:56 jkunz Exp $ */
/*
* Copyright (c) 2012 The NetBSD Foundation, Inc.
@ -33,10 +33,16 @@
#include <sys/types.h>
#include <sys/bus.h>
#include <sys/cdefs.h>
#include <sys/condvar.h>
#include <sys/device.h>
#include <sys/errno.h>
#include <sys/mutex.h>
#include <sys/systm.h>
#include <arm/pic/picvar.h>
#include <arm/imx/imx23_apbdmavar.h>
#include <arm/imx/imx23_icollreg.h>
#include <arm/imx/imx23_sspreg.h>
#include <arm/imx/imx23var.h>
@ -46,15 +52,33 @@
/*
* SD/MMC host controller driver for i.MX23.
*
* TODO:
*
* - Add support for SMC_CAPS_AUTO_STOP.
*/
struct issp_softc {
#define DMA_MAXNSEGS ((MAXPHYS / PAGE_SIZE) + 1)
typedef struct issp_softc {
device_t sc_dev;
bus_space_tag_t sc_iot;
apbdma_softc_t sc_dmac;
bus_dma_tag_t sc_dmat;
bus_dmamap_t sc_dmamp;
bus_size_t sc_chnsiz;
bus_dma_segment_t sc_ds[1];
int sc_rseg;
bus_space_handle_t sc_hdl;
bus_space_tag_t sc_iot;
device_t sc_sdmmc;
device_t dmac;
};
kmutex_t sc_lock;
struct kcondvar sc_intr_cv;
unsigned int dma_channel;
uint32_t sc_dma_error;
uint32_t sc_irq_error;
uint8_t sc_state;
uint8_t sc_bus_width;
} *issp_softc_t;
static int issp_match(device_t, cfdata_t, void *);
static void issp_attach(device_t, device_t, void *);
@ -62,7 +86,16 @@ static int issp_activate(device_t, enum devact);
static void issp_reset(struct issp_softc *);
static void issp_init(struct issp_softc *);
static uint32_t issp_set_sck(struct issp_softc *, uint32_t target);
static uint32_t issp_set_sck(struct issp_softc *, uint32_t);
static int issp_dma_intr(void *);
static int issp_error_intr(void *);
static void issp_ack_intr(struct issp_softc *);
static void issp_create_dma_cmd_list_multi(issp_softc_t, void *,
struct sdmmc_command *);
static void issp_create_dma_cmd_list_single(issp_softc_t, void *,
struct sdmmc_command *);
static void issp_create_dma_cmd_list(issp_softc_t, void *,
struct sdmmc_command *);
/* sdmmc(4) driver chip function prototypes. */
static int issp_host_reset(sdmmc_chipset_handle_t);
@ -75,7 +108,7 @@ static int issp_bus_clock(sdmmc_chipset_handle_t, int);
static int issp_bus_width(sdmmc_chipset_handle_t, int);
static int issp_bus_rod(sdmmc_chipset_handle_t, int);
static void issp_exec_command(sdmmc_chipset_handle_t,
struct sdmmc_command *);
struct sdmmc_command *);
static void issp_card_enable_intr(sdmmc_chipset_handle_t, int);
static void issp_card_intr_ack(sdmmc_chipset_handle_t);
@ -102,7 +135,8 @@ CFATTACH_DECL3_NEW(ssp,
issp_activate,
NULL,
NULL,
0);
0
);
#define SSP_SOFT_RST_LOOP 455 /* At least 1 us ... */
@ -115,24 +149,37 @@ CFATTACH_DECL3_NEW(ssp,
#define SSP_CLK_MIN 400 /* 400 kHz */
#define SSP_CLK_MAX 48000 /* 48 MHz */
#define SSP_BUSY (HW_SSP_STATUS_CMD_BUSY | \
HW_SSP_STATUS_DATA_BUSY | \
HW_SSP_STATUS_BUSY)
#define SSP_RUN_ERR (HW_SSP_STATUS_RESP_CRC_ERR | \
HW_SSP_STATUS_RESP_ERR | \
HW_SSP_STATUS_RESP_TIMEOUT | \
HW_SSP_STATUS_DATA_CRC_ERR | \
HW_SSP_STATUS_TIMEOUT)
#define BLKIO_NONE 0
#define BLKIO_RD 1
#define BLKIO_WR 2
/* DATA_TIMEOUT is calculated as: * (1 / SSP_CLK) * (DATA_TIMEOUT * 4096) */
#define DATA_TIMEOUT 0x4240 /* 723ms */
#define BUS_WIDTH_1_BIT 0x0
#define BUS_WIDTH_4_BIT 0x1
#define BUS_WIDTH_8_BIT 0x2
#define SSP1_ATTACHED 1
#define SSP2_ATTACHED 2
/* Flags for sc_state. */
#define SSP_STATE_IDLE 0
#define SSP_STATE_DMA 1
#define PIO_WORD_CTRL0 0
#define PIO_WORD_CMD0 1
#define PIO_WORD_CMD1 2
#define HW_SSP_CTRL1_IRQ_MASK ( \
HW_SSP_CTRL1_SDIO_IRQ | \
HW_SSP_CTRL1_RESP_ERR_IRQ | \
HW_SSP_CTRL1_RESP_TIMEOUT_IRQ | \
HW_SSP_CTRL1_DATA_TIMEOUT_IRQ | \
HW_SSP_CTRL1_DATA_CRC_IRQ | \
HW_SSP_CTRL1_FIFO_UNDERRUN_IRQ | \
HW_SSP_CTRL1_RECV_TIMEOUT_IRQ | \
HW_SSP_CTRL1_FIFO_OVERRUN_IRQ)
/* SSP does not support over 64k transfer size. */
#define MAX_TRANSFER_SIZE 65536
static int
issp_match(device_t parent, cfdata_t match, void *aux)
{
@ -154,18 +201,108 @@ issp_attach(device_t parent, device_t self, void *aux)
struct apb_softc *sc_parent = device_private(parent);
struct apb_attach_args *aa = aux;
struct sdmmcbus_attach_args saa;
static int issp_attached = 0;
if (issp_attached)
return;
static int ssp_attached = 0;
int error;
void *intr;
sc->sc_dev = self;
sc->sc_iot = aa->aa_iot;
sc->dmac = sc_parent->dmac;
sc->sc_dmat = aa->aa_dmat;
if (bus_space_map(sc->sc_iot,
aa->aa_addr, aa->aa_size, 0, &(sc->sc_hdl))) {
aprint_error_dev(sc->sc_dev, "unable to map bus space\n");
/* Test if device instance is already attached. */
if (aa->aa_addr == HW_SSP1_BASE && ISSET(ssp_attached, SSP1_ATTACHED)) {
aprint_error_dev(sc->sc_dev, "SSP1 already attached\n");
return;
}
if (aa->aa_addr == HW_SSP2_BASE && ISSET(ssp_attached, SSP2_ATTACHED)) {
aprint_error_dev(sc->sc_dev, "SSP2 already attached\n");
return;
}
if (aa->aa_addr == HW_SSP1_BASE) {
sc->dma_channel = APBH_DMA_CHANNEL_SSP1;
}
if (aa->aa_addr == HW_SSP2_BASE) {
sc->dma_channel = APBH_DMA_CHANNEL_SSP2;
}
/* This driver requires DMA functionality from the bus.
* Parent bus passes handle to the DMA controller instance. */
if (sc_parent->dmac == NULL) {
aprint_error_dev(sc->sc_dev, "DMA functionality missing\n");
return;
}
sc->sc_dmac = device_private(sc_parent->dmac);
/* Initialize lock. */
mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SDMMC);
/* Condvar to wait interrupt complete. */
cv_init(&sc->sc_intr_cv, "ssp_intr");
/* Establish interrupt handlers for SSP errors and SSP DMA. */
if (aa->aa_addr == HW_SSP1_BASE) {
intr = intr_establish(IRQ_SSP1_DMA, IPL_SDMMC, IST_LEVEL,
issp_dma_intr, sc);
if (intr == NULL) {
aprint_error_dev(sc->sc_dev, "Unable to establish "
"interrupt for SSP1 DMA\n");
return;
}
intr = intr_establish(IRQ_SSP1_ERROR, IPL_SDMMC, IST_LEVEL,
issp_error_intr, sc);
if (intr == NULL) {
aprint_error_dev(sc->sc_dev, "Unable to establish "
"interrupt for SSP1 ERROR\n");
return;
}
}
if (aa->aa_addr == HW_SSP2_BASE) {
intr = intr_establish(IRQ_SSP2_DMA, IPL_SDMMC, IST_LEVEL,
issp_dma_intr, sc);
if (intr == NULL) {
aprint_error_dev(sc->sc_dev, "Unable to establish "
"interrupt for SSP2 DMA\n");
return;
}
intr = intr_establish(IRQ_SSP2_ERROR, IPL_SDMMC, IST_LEVEL,
issp_error_intr, sc);
if (intr == NULL) {
aprint_error_dev(sc->sc_dev, "Unable to establish "
"interrupt for SSP2 ERROR\n");
return;
}
}
/* Allocate DMA handle. */
error = bus_dmamap_create(sc->sc_dmat, MAXPHYS, 1, MAXPHYS,
0, BUS_DMA_NOWAIT|BUS_DMA_ALLOCNOW, &sc->sc_dmamp);
if (error) {
aprint_error_dev(sc->sc_dev,
"Unable to allocate DMA handle\n");
return;
}
/* Allocate memory for DMA command chain. */
sc->sc_chnsiz = sizeof(struct apbdma_command) *
(MAX_TRANSFER_SIZE / SDMMC_SECTOR_SIZE);
error = bus_dmamem_alloc(sc->sc_dmat, sc->sc_chnsiz, PAGE_SIZE, 0,
sc->sc_ds, 1, &sc->sc_rseg, BUS_DMA_NOWAIT);
if (error) {
aprint_error_dev(sc->sc_dev,
"Unable to allocate DMA memory\n");
return;
}
/* Initialize DMA channel. */
apbdma_chan_init(sc->sc_dmac, sc->dma_channel);
/* Map SSP bus space. */
if (bus_space_map(sc->sc_iot, aa->aa_addr, aa->aa_size, 0,
&sc->sc_hdl)) {
aprint_error_dev(sc->sc_dev, "Unable to map SSP bus space\n");
return;
}
@ -177,6 +314,7 @@ issp_attach(device_t parent, device_t self, void *aux)
__SHIFTOUT(issp_vers, HW_SSP_VERSION_MAJOR),
__SHIFTOUT(issp_vers, HW_SSP_VERSION_MINOR));
/* Attach sdmmc to ssp bus. */
saa.saa_busname = "sdmmc";
saa.saa_sct = &issp_functions;
saa.saa_spi_sct = NULL;
@ -184,8 +322,8 @@ issp_attach(device_t parent, device_t self, void *aux)
saa.saa_dmat = aa->aa_dmat;
saa.saa_clkmin = SSP_CLK_MIN;
saa.saa_clkmax = SSP_CLK_MAX;
/* Add SMC_CAPS_DMA capability when DMA funtionality is implemented. */
saa.saa_caps = SMC_CAPS_4BIT_MODE | SMC_CAPS_SINGLE_ONLY;
saa.saa_caps = SMC_CAPS_DMA | SMC_CAPS_4BIT_MODE |
SMC_CAPS_MULTI_SEG_DMA;
sc->sc_sdmmc = config_found(sc->sc_dev, &saa, NULL);
if (sc->sc_sdmmc == NULL) {
@ -193,7 +331,11 @@ issp_attach(device_t parent, device_t self, void *aux)
return;
}
issp_attached = 1;
/* Device instance was succesfully attached. */
if (aa->aa_addr == HW_SSP1_BASE)
ssp_attached |= SSP1_ATTACHED;
if (aa->aa_addr == HW_SSP2_BASE)
ssp_attached |= SSP2_ATTACHED;
return;
}
@ -211,16 +353,14 @@ static int
issp_host_reset(sdmmc_chipset_handle_t sch)
{
struct issp_softc *sc = sch;
issp_reset(sc);
return 0;
}
static uint32_t
issp_host_ocr(sdmmc_chipset_handle_t sch)
{
/* SSP supports at least 3.2-3.3v */
/* SSP supports at least 3.2 - 3.3v */
return MMC_OCR_3_2V_3_3V;
}
@ -237,29 +377,6 @@ issp_host_maxblklen(sdmmc_chipset_handle_t sch)
static int
issp_card_detect(sdmmc_chipset_handle_t sch)
{
/* struct issp_softc *sc = sch;
*
* In the perfect world I'll just:
* return SSP_RD(sc, HW_SSP_STATUS) & HW_SSP_STATUS_CARD_DETECT;
* and call it a day.
*
* But on i.MX23 OLinuXino MAXI, SSP1_DETECT is not used for the SD
* card detection but SSP1_DATA3 is, as Tsvetan put it:
*
* < Tsvetan> if you want to know if SD card is inserted watch
* CD/DAT3/CS port
* < Tsvetan> without card there is R20 weak pulldown
* < Tsvetan> all cards have 40K pullup to this pin
* < Tsvetan> so when card is inserted you will read it high
*
* Which means I should to do something like this:
* #if BOARDTYPE == MAXI (Possibly MINI & MICRO)
* return GPIO_READ(PIN_125) & PIN_125
* #else
* return SSP_RD(sc, STATUS) & CARD_DETECT;
* #endif
* Until GPIO functionality is not present I am just going to */
return 1;
}
@ -283,13 +400,22 @@ issp_bus_clock(sdmmc_chipset_handle_t sch, int clock)
struct issp_softc *sc = sch;
uint32_t sck;
sck = issp_set_sck(sc, clock * 1000);
if (clock < SSP_CLK_MIN)
sck = issp_set_sck(sc, SSP_CLK_MIN * 1000);
else
sck = issp_set_sck(sc, clock * 1000);
/* Notify user if we didn't get exact clock rate from SSP that was
* requested. */
if (sck != clock * 1000)
aprint_normal_dev(sc->sc_dev, "requested clock %dHz, "
"but got %dHz\n", clock * 1000, sck);
/* Notify user if we didn't get the exact clock rate from SSP that was
* requested from the SDMMC subsystem. */
if (sck != clock * 1000) {
sck = sck / 1000;
if (((sck) / 1000) != 0)
aprint_normal_dev(sc->sc_dev, "bus clock @ %u.%03u "
"MHz\n", sck / 1000, sck % 1000);
else
aprint_normal_dev(sc->sc_dev, "bus clock @ %u KHz\n",
sck % 1000);
}
return 0;
}
@ -298,27 +424,21 @@ static int
issp_bus_width(sdmmc_chipset_handle_t sch, int width)
{
struct issp_softc *sc = sch;
uint32_t reg;
reg = SSP_RD(sc, HW_SSP_CTRL0);
reg &= ~(HW_SSP_CTRL0_BUS_WIDTH);
switch(width) {
case(1):
reg |= __SHIFTIN(BUS_WIDTH_1_BIT, HW_SSP_CTRL0_BUS_WIDTH);
sc->sc_bus_width = BUS_WIDTH_1_BIT;
break;
case(4):
reg |= __SHIFTIN(BUS_WIDTH_4_BIT, HW_SSP_CTRL0_BUS_WIDTH);
sc->sc_bus_width = BUS_WIDTH_4_BIT;
break;
case(8):
reg |= __SHIFTIN(BUS_WIDTH_8_BIT, HW_SSP_CTRL0_BUS_WIDTH);
sc->sc_bus_width = BUS_WIDTH_8_BIT;
break;
default:
return 1;
}
SSP_WR(sc, HW_SSP_CTRL0, reg);
return 0;
}
@ -332,112 +452,104 @@ issp_bus_rod(sdmmc_chipset_handle_t sch, int rod)
static void
issp_exec_command(sdmmc_chipset_handle_t sch, struct sdmmc_command *cmd)
{
struct issp_softc *sc = sch;
uint32_t reg;
uint32_t do_blkio;
uint32_t i;
issp_softc_t sc = sch;
void *dma_chain;
int error;
do_blkio = 0;
/* Wait until SSP done. (data I/O error + retry...) */
while (SSP_RD(sc, HW_SSP_STATUS) & SSP_BUSY)
;
/* Set expected response type. */
SSP_WR(sc, HW_SSP_CTRL0_CLR,
HW_SSP_CTRL0_GET_RESP | HW_SSP_CTRL0_LONG_RESP);
if (ISSET(cmd->c_flags, SCF_RSP_PRESENT)) {
SSP_WR(sc, HW_SSP_CTRL0_SET, HW_SSP_CTRL0_GET_RESP);
if (ISSET(cmd->c_flags, SCF_RSP_136))
SSP_WR(sc, HW_SSP_CTRL0_SET, HW_SSP_CTRL0_LONG_RESP);
/* SSP does not support over 64k transfer size. */
if (cmd->c_data != NULL && cmd->c_datalen > MAX_TRANSFER_SIZE) {
aprint_error_dev(sc->sc_dev, "transfer size over %d: %d\n",
MAX_TRANSFER_SIZE, cmd->c_datalen);
cmd->c_error = ENODEV;
return;
}
/* If CMD does not need CRC validation, tell it to SSP. */
if (ISSET(cmd->c_flags, SCF_RSP_CRC))
SSP_WR(sc, HW_SSP_CTRL0_CLR, HW_SSP_CTRL0_IGNORE_CRC);
else
SSP_WR(sc, HW_SSP_CTRL0_SET, HW_SSP_CTRL0_IGNORE_CRC);
/* Map dma_chain to point allocated previously allocated DMA chain. */
error = bus_dmamem_map(sc->sc_dmat, sc->sc_ds, 1, sc->sc_chnsiz,
&dma_chain, BUS_DMA_NOWAIT);
if (error) {
aprint_error_dev(sc->sc_dev, "bus_dmamem_map: %d\n", error);
cmd->c_error = error;
goto out;
}
/* Set command. */
SSP_WR(sc, HW_SSP_CMD0_CLR, HW_SSP_CMD0_CMD);
SSP_WR(sc, HW_SSP_CMD0_SET,
__SHIFTIN(cmd->c_opcode, HW_SSP_CMD0_CMD));
error = bus_dmamap_load(sc->sc_dmat, sc->sc_dmamp, dma_chain,
sc->sc_chnsiz, NULL, BUS_DMA_NOWAIT|BUS_DMA_WRITE);
if (error) {
aprint_error_dev(sc->sc_dev, "bus_dmamap_load: %d\n", error);
cmd->c_error = error;
goto dmamem_unmap;
}
/* Set command argument. */
SSP_WR(sc, HW_SSP_CMD1, cmd->c_arg);
memset(dma_chain, 0, sc->sc_chnsiz);
/* Is data to be transferred? */
if (cmd->c_datalen > 0 && cmd->c_data != NULL) {
SSP_WR(sc, HW_SSP_CTRL0_SET, HW_SSP_CTRL0_DATA_XFER);
/* Transfer XFER_COUNT of 8-bit words. */
SSP_WR(sc, HW_SSP_CTRL0_CLR, HW_SSP_CTRL0_XFER_COUNT);
SSP_WR(sc, HW_SSP_CTRL0_SET,
__SHIFTIN(cmd->c_datalen, HW_SSP_CTRL0_XFER_COUNT));
/* XXX: why 8CYC? Bit is never cleaned. */
SSP_WR(sc, HW_SSP_CMD0_SET, HW_SSP_CMD0_APPEND_8CYC);
if (ISSET(cmd->c_flags, SCF_CMD_READ)) {
/* Read mode. */
do_blkio |= BLKIO_RD;
SSP_WR(sc, HW_SSP_CTRL0_SET, HW_SSP_CTRL0_READ);
} else {
/* Write mode. */
do_blkio |= BLKIO_WR;
SSP_WR(sc, HW_SSP_CTRL0_CLR, HW_SSP_CTRL0_READ);
}
/* Setup DMA command chain.*/
if (cmd->c_data != NULL && (cmd->c_datalen / cmd->c_blklen) > 1) {
/* Multi block transfer. */
issp_create_dma_cmd_list_multi(sc, dma_chain, cmd);
} else if (cmd->c_data != NULL && cmd->c_datalen) {
/* Single block transfer. */
issp_create_dma_cmd_list_single(sc, dma_chain, cmd);
} else {
/* No data to be transferred. */
SSP_WR(sc, HW_SSP_CTRL0_CLR, HW_SSP_CTRL0_DATA_XFER);
/* Only command, no data. */
issp_create_dma_cmd_list(sc, dma_chain, cmd);
}
/* Run the command. */
SSP_WR(sc, HW_SSP_CTRL0_SET, HW_SSP_CTRL0_RUN);
/* Tell DMA controller where it can find just initialized DMA chain. */
apbdma_chan_set_chain(sc->sc_dmac, sc->dma_channel, sc->sc_dmamp);
if (ISSET(do_blkio, BLKIO_RD)) {
for (i = 0; i < cmd->c_datalen / 4; i++) {
/* Wait until data arrives to FIFO. */
while (SSP_RD(sc, HW_SSP_STATUS)
& HW_SSP_STATUS_FIFO_EMPTY) {
/* Abort if error while waiting. */
if (SSP_RD(sc, HW_SSP_STATUS) & SSP_RUN_ERR) {
aprint_normal_dev(sc->sc_dev,
"RD_ERR: %x\n",
SSP_RD(sc, HW_SSP_STATUS));
cmd->c_error = 1;
goto pioerr;
}
}
*((uint32_t *)cmd->c_data+i) = SSP_RD(sc, HW_SSP_DATA);
/* Synchronize command chain before DMA controller accesses it. */
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamp, 0, sc->sc_chnsiz,
BUS_DMASYNC_PREWRITE);
sc->sc_state = SSP_STATE_DMA;
sc->sc_irq_error = 0;
sc->sc_dma_error = 0;
cmd->c_error = 0;
mutex_enter(&sc->sc_lock);
/* Run DMA command chain. */
apbdma_run(sc->sc_dmac, sc->dma_channel);
/* Wait DMA to complete. */
while (sc->sc_state == SSP_STATE_DMA)
cv_wait(&sc->sc_intr_cv, &sc->sc_lock);
mutex_exit(&sc->sc_lock);
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamp, 0, sc->sc_chnsiz,
BUS_DMASYNC_POSTWRITE);
if (sc->sc_dma_error) {
if (sc->sc_dma_error == DMA_IRQ_TERM) {
apbdma_chan_reset(sc->sc_dmac, sc->dma_channel);
cmd->c_error = sc->sc_dma_error;
}
} else if (ISSET(do_blkio, BLKIO_WR)) {
for (i = 0; i < (cmd->c_datalen / 4); i++) {
while (SSP_RD(sc, HW_SSP_STATUS)
& HW_SSP_STATUS_FIFO_FULL) {
/* Abort if error while waiting. */
if (SSP_RD(sc, HW_SSP_STATUS) & SSP_RUN_ERR) {
aprint_normal_dev(sc->sc_dev,
"WR_ERR: %x\n",
SSP_RD(sc, HW_SSP_STATUS));
cmd->c_error = 1;
goto pioerr;
}
}
SSP_WR(sc, HW_SSP_DATA, *((uint32_t *)cmd->c_data+i));
else if (sc->sc_dma_error == DMA_IRQ_BUS_ERROR) {
aprint_error_dev(sc->sc_dev, "DMA_IRQ_BUS_ERROR: %d\n",
sc->sc_irq_error);
cmd->c_error = sc->sc_dma_error;
}
}
/* Wait until SSP is done. */
while (SSP_RD(sc, HW_SSP_STATUS) & SSP_BUSY)
;
if (sc->sc_irq_error) {
/* Do not log RESP_TIMEOUT_IRQ error if bus width is 0 as it is
* expected during SD card initialization phase. */
if (sc->sc_bus_width) {
aprint_error_dev(sc->sc_dev, "SSP_ERROR_IRQ: %d\n",
sc->sc_irq_error);
}
else if(!(sc->sc_irq_error & HW_SSP_CTRL1_RESP_TIMEOUT_IRQ)) {
aprint_error_dev(sc->sc_dev, "SSP_ERROR_IRQ: %d\n",
sc->sc_irq_error);
}
/* Check if the command ran successfully. */
reg = SSP_RD(sc, HW_SSP_STATUS);
if (reg & SSP_RUN_ERR)
cmd->c_error = reg & SSP_RUN_ERR;
/* Shift unsigned error code so it fits nicely to signed int. */
cmd->c_error = sc->sc_irq_error >> 8;
}
/* Read response if such was requested. */
/* Check reponse from the card if such was requested. */
if (ISSET(cmd->c_flags, SCF_RSP_PRESENT)) {
cmd->c_resp[0] = SSP_RD(sc, HW_SSP_SDRESP0);
if (ISSET(cmd->c_flags, SCF_RSP_136)) {
@ -457,7 +569,12 @@ issp_exec_command(sdmmc_chipset_handle_t sch, struct sdmmc_command *cmd)
cmd->c_resp[3] >>= 8;
}
}
pioerr:
bus_dmamap_unload(sc->sc_dmat, sc->sc_dmamp);
dmamem_unmap:
bus_dmamem_unmap(sc->sc_dmat, dma_chain, sc->sc_chnsiz);
out:
return;
}
@ -465,10 +582,7 @@ static void
issp_card_enable_intr(sdmmc_chipset_handle_t sch, int irq)
{
struct issp_softc *sc = sch;
aprint_normal_dev(sc->sc_dev,
"issp_card_enable_intr NOT IMPLEMENTED!\n");
aprint_error_dev(sc->sc_dev, "issp_card_enable_intr not implemented\n");
return;
}
@ -476,9 +590,7 @@ static void
issp_card_intr_ack(sdmmc_chipset_handle_t sch)
{
struct issp_softc *sc = sch;
aprint_normal_dev(sc->sc_dev, "issp_card_intr_ack NOT IMPLEMENTED!\n");
aprint_error_dev(sc->sc_dev, "issp_card_intr_ack not implemented\n");
return;
}
@ -528,13 +640,6 @@ issp_reset(struct issp_softc *sc)
return;
}
/*
* DATA_TIMEOUT is calculated as:
* (1 / SSP_CLK) * (DATA_TIMEOUT * 4096)
*/
#define DATA_TIMEOUT 0x4240 /* 723ms */
/*
* Initialize SSP controller to SD/MMC mode.
*/
@ -543,27 +648,39 @@ issp_init(struct issp_softc *sc)
{
uint32_t reg;
/* Initial data bus width is 1-bit. */
reg = SSP_RD(sc, HW_SSP_CTRL0);
reg |= HW_SSP_CTRL0_ENABLE;
/* Initial data bus width is 1-bit. */
reg &= ~(HW_SSP_CTRL0_BUS_WIDTH);
reg |= __SHIFTIN(BUS_WIDTH_1_BIT, HW_SSP_CTRL0_BUS_WIDTH) |
HW_SSP_CTRL0_WAIT_FOR_IRQ | HW_SSP_CTRL0_ENABLE;
SSP_WR(sc, HW_SSP_CTRL0, reg);
sc->sc_bus_width = BUS_WIDTH_1_BIT;
/* Set data timeout. */
reg = SSP_RD(sc, HW_SSP_TIMING);
reg &= ~(HW_SSP_TIMING_TIMEOUT);
reg |= __SHIFTIN(DATA_TIMEOUT, HW_SSP_TIMING_TIMEOUT);
SSP_WR(sc, HW_SSP_TIMING, reg);
/* Set initial clock rate to minimum. */
issp_set_sck(sc, SSP_CLK_MIN * 1000);
SSP_WR(sc, HW_SSP_TIMING, reg);
/* Enable SD/MMC mode and use use 8-bits per word. */
reg = SSP_RD(sc, HW_SSP_CTRL1);
/* Enable all but SDIO IRQ's. */
reg |= HW_SSP_CTRL1_RESP_ERR_IRQ_EN |
HW_SSP_CTRL1_RESP_TIMEOUT_IRQ_EN |
HW_SSP_CTRL1_DATA_TIMEOUT_IRQ_EN |
HW_SSP_CTRL1_DATA_CRC_IRQ_EN |
HW_SSP_CTRL1_FIFO_UNDERRUN_EN |
HW_SSP_CTRL1_RECV_TIMEOUT_IRQ_EN |
HW_SSP_CTRL1_FIFO_OVERRUN_IRQ_EN;
reg |= HW_SSP_CTRL1_DMA_ENABLE;
reg |= HW_SSP_CTRL1_POLARITY;
/* Set SD/MMC mode and use use 8-bits per word. */
reg &= ~(HW_SSP_CTRL1_WORD_LENGTH | HW_SSP_CTRL1_SSP_MODE);
reg |= HW_SSP_CTRL1_POLARITY |
__SHIFTIN(0x7, HW_SSP_CTRL1_WORD_LENGTH) |
reg |= __SHIFTIN(0x7, HW_SSP_CTRL1_WORD_LENGTH) |
__SHIFTIN(0x3, HW_SSP_CTRL1_SSP_MODE);
SSP_WR(sc, HW_SSP_CTRL1, reg);
@ -575,7 +692,7 @@ issp_init(struct issp_softc *sc)
*
* SSP_SCK is calculated as: SSP_CLK / (CLOCK_DIVIDE * (1 + CLOCK_RATE))
*
* issp_set_sck find the most suitable CLOCK_DIVIDE and CLOCK_RATE register
* issp_set_sck finds the most suitable CLOCK_DIVIDE and CLOCK_RATE register
* values for the target clock rate by iterating through all possible register
* values.
*/
@ -612,3 +729,280 @@ out:
return SSP_CLK / (div * (1 + rate));
}
/*
* IRQ from DMA.
*/
static int
issp_dma_intr(void *arg)
{
issp_softc_t sc = arg;
unsigned int dma_err;
dma_err = apbdma_intr_status(sc->sc_dmac, sc->dma_channel);
if (dma_err) {
apbdma_ack_error_intr(sc->sc_dmac, sc->dma_channel);
} else {
apbdma_ack_intr(sc->sc_dmac, sc->dma_channel);
}
mutex_enter(&sc->sc_lock);
sc->sc_dma_error = dma_err;
sc->sc_state = SSP_STATE_IDLE;
/* Signal thread that interrupt was handled. */
cv_signal(&sc->sc_intr_cv);
mutex_exit(&sc->sc_lock);
/* Return 1 to acknowledge IRQ. */
return 1;
}
/*
* IRQ from SSP block.
*
* When SSP receives IRQ it terminates ongoing DMA transfer by issuing DMATERM
* signal to DMA block.
*/
static int
issp_error_intr(void *arg)
{
issp_softc_t sc = arg;
mutex_enter(&sc->sc_lock);
sc->sc_irq_error =
SSP_RD(sc, HW_SSP_CTRL1) & HW_SSP_CTRL1_IRQ_MASK;
issp_ack_intr(sc);
mutex_exit(&sc->sc_lock);
/* Return 1 to acknowledge IRQ. */
return 1;
}
/*
* Acknowledge SSP error IRQ.
*/
static void
issp_ack_intr(struct issp_softc *sc)
{
/* Acknowledge all IRQ's. */
SSP_WR(sc, HW_SSP_CTRL1_CLR, HW_SSP_CTRL1_IRQ_MASK);
return;
}
/*
* Set up multi block DMA transfer.
*/
static void
issp_create_dma_cmd_list_multi(issp_softc_t sc, void *dma_chain,
struct sdmmc_command *cmd)
{
apbdma_command_t dma_cmd;
int blocks;
int nblk;
blocks = cmd->c_datalen / cmd->c_blklen;
nblk = 0;
dma_cmd = dma_chain;
/* HEAD */
apbdma_cmd_buf(&dma_cmd[nblk], cmd->c_blklen * nblk, cmd->c_dmamap);
apbdma_cmd_chain(&dma_cmd[nblk], &dma_cmd[nblk+1], dma_chain,
sc->sc_dmamp);
dma_cmd[nblk].control =
__SHIFTIN(cmd->c_blklen, APBDMA_CMD_XFER_COUNT) |
__SHIFTIN(3, APBDMA_CMD_CMDPIOWORDS) | APBDMA_CMD_HALTONTERMINATE |
APBDMA_CMD_CHAIN;
if (!ISSET(cmd->c_flags, SCF_RSP_CRC)) {
dma_cmd[nblk].pio_words[PIO_WORD_CTRL0] |=
HW_SSP_CTRL0_IGNORE_CRC;
}
dma_cmd[nblk].pio_words[PIO_WORD_CTRL0] |= HW_SSP_CTRL0_DATA_XFER |
__SHIFTIN(sc->sc_bus_width, HW_SSP_CTRL0_BUS_WIDTH) |
HW_SSP_CTRL0_WAIT_FOR_IRQ |
__SHIFTIN(cmd->c_datalen, HW_SSP_CTRL0_XFER_COUNT);
if (ISSET(cmd->c_flags, SCF_RSP_PRESENT)) {
dma_cmd[nblk].pio_words[PIO_WORD_CTRL0] |=
HW_SSP_CTRL0_GET_RESP;
if (ISSET(cmd->c_flags, SCF_RSP_136)) {
dma_cmd[nblk].pio_words[PIO_WORD_CTRL0] |=
HW_SSP_CTRL0_LONG_RESP;
}
}
dma_cmd[nblk].pio_words[PIO_WORD_CTRL0] |= HW_SSP_CTRL0_ENABLE;
dma_cmd[nblk].pio_words[PIO_WORD_CMD0] =
__SHIFTIN(ffs(cmd->c_blklen) - 1, HW_SSP_CMD0_BLOCK_SIZE) |
__SHIFTIN(blocks - 1, HW_SSP_CMD0_BLOCK_COUNT) |
__SHIFTIN(cmd->c_opcode, HW_SSP_CMD0_CMD);
dma_cmd[nblk].pio_words[PIO_WORD_CMD1] = cmd->c_arg;
if (ISSET(cmd->c_flags, SCF_CMD_READ)) {
dma_cmd[nblk].control |=
__SHIFTIN(APBDMA_CMD_DMA_WRITE, APBDMA_CMD_COMMAND);
dma_cmd[nblk].pio_words[PIO_WORD_CTRL0] |= HW_SSP_CTRL0_READ;
} else {
dma_cmd[nblk].control |=
__SHIFTIN(APBDMA_CMD_DMA_READ, APBDMA_CMD_COMMAND);
}
nblk++;
/* BODY: Build commands for blocks between head and tail, if any. */
for (; nblk < blocks - 1; nblk++) {
apbdma_cmd_buf(&dma_cmd[nblk], cmd->c_blklen * nblk,
cmd->c_dmamap);
apbdma_cmd_chain(&dma_cmd[nblk], &dma_cmd[nblk+1], dma_chain,
sc->sc_dmamp);
dma_cmd[nblk].control =
__SHIFTIN(cmd->c_blklen, APBDMA_CMD_XFER_COUNT) |
APBDMA_CMD_HALTONTERMINATE | APBDMA_CMD_CHAIN;
if (ISSET(cmd->c_flags, SCF_CMD_READ)) {
dma_cmd[nblk].control |=
__SHIFTIN(APBDMA_CMD_DMA_WRITE,
APBDMA_CMD_COMMAND);
} else {
dma_cmd[nblk].control |=
__SHIFTIN(APBDMA_CMD_DMA_READ, APBDMA_CMD_COMMAND);
}
}
/* TAIL
*
* TODO: Send CMD12/STOP with last DMA command to support
* SMC_CAPS_AUTO_STOP.
*/
apbdma_cmd_buf(&dma_cmd[nblk], cmd->c_blklen * nblk, cmd->c_dmamap);
/* next = NULL */
dma_cmd[nblk].control =
__SHIFTIN(cmd->c_blklen, APBDMA_CMD_XFER_COUNT) |
APBDMA_CMD_HALTONTERMINATE | APBDMA_CMD_WAIT4ENDCMD |
APBDMA_CMD_SEMAPHORE | APBDMA_CMD_IRQONCMPLT;
if (ISSET(cmd->c_flags, SCF_CMD_READ)) {
dma_cmd[nblk].control |= __SHIFTIN(APBDMA_CMD_DMA_WRITE,
APBDMA_CMD_COMMAND);
} else {
dma_cmd[nblk].control |= __SHIFTIN(APBDMA_CMD_DMA_READ,
APBDMA_CMD_COMMAND);
}
return;
}
/*
* Set up single block DMA transfer.
*/
static void
issp_create_dma_cmd_list_single(issp_softc_t sc, void *dma_chain,
struct sdmmc_command *cmd)
{
apbdma_command_t dma_cmd;
dma_cmd = dma_chain;
dma_cmd[0].control = __SHIFTIN(cmd->c_datalen, APBDMA_CMD_XFER_COUNT) |
__SHIFTIN(3, APBDMA_CMD_CMDPIOWORDS) |
APBDMA_CMD_HALTONTERMINATE | APBDMA_CMD_WAIT4ENDCMD |
APBDMA_CMD_SEMAPHORE | APBDMA_CMD_IRQONCMPLT;
/* Transfer single block to the beginning of the DMA buffer. */
apbdma_cmd_buf(&dma_cmd[0], 0, cmd->c_dmamap);
if (!ISSET(cmd->c_flags, SCF_RSP_CRC)) {
dma_cmd[0].pio_words[PIO_WORD_CTRL0] |=
HW_SSP_CTRL0_IGNORE_CRC;
}
dma_cmd[0].pio_words[PIO_WORD_CTRL0] |=
HW_SSP_CTRL0_DATA_XFER |
__SHIFTIN(sc->sc_bus_width, HW_SSP_CTRL0_BUS_WIDTH) |
HW_SSP_CTRL0_WAIT_FOR_IRQ |
__SHIFTIN(cmd->c_datalen, HW_SSP_CTRL0_XFER_COUNT);
if (ISSET(cmd->c_flags, SCF_RSP_PRESENT)) {
dma_cmd[0].pio_words[PIO_WORD_CTRL0] |= HW_SSP_CTRL0_GET_RESP;
if (ISSET(cmd->c_flags, SCF_RSP_136)) {
dma_cmd[0].pio_words[PIO_WORD_CTRL0] |=
HW_SSP_CTRL0_LONG_RESP;
}
}
dma_cmd[0].pio_words[PIO_WORD_CTRL0] |= HW_SSP_CTRL0_ENABLE;
dma_cmd[0].pio_words[PIO_WORD_CMD0] =
HW_SSP_CMD0_APPEND_8CYC |
__SHIFTIN(cmd->c_opcode, HW_SSP_CMD0_CMD);
dma_cmd[0].pio_words[PIO_WORD_CMD1] = cmd->c_arg;
if (ISSET(cmd->c_flags, SCF_CMD_READ)) {
dma_cmd[0].control |=
__SHIFTIN(APBDMA_CMD_DMA_WRITE, APBDMA_CMD_COMMAND);
dma_cmd[0].pio_words[PIO_WORD_CTRL0] |= HW_SSP_CTRL0_READ;
} else {
dma_cmd[0].control |=
__SHIFTIN(APBDMA_CMD_DMA_READ, APBDMA_CMD_COMMAND);
}
return;
}
/*
* Do DMA PIO (issue CMD). No block transfers.
*/
static void
issp_create_dma_cmd_list(issp_softc_t sc, void *dma_chain,
struct sdmmc_command *cmd)
{
apbdma_command_t dma_cmd;
dma_cmd = dma_chain;
dma_cmd[0].control = __SHIFTIN(3, APBDMA_CMD_CMDPIOWORDS) |
APBDMA_CMD_HALTONTERMINATE | APBDMA_CMD_WAIT4ENDCMD |
APBDMA_CMD_SEMAPHORE | APBDMA_CMD_IRQONCMPLT |
__SHIFTIN(APBDMA_CMD_NO_DMA_XFER, APBDMA_CMD_COMMAND);
if (!ISSET(cmd->c_flags, SCF_RSP_CRC)) {
dma_cmd[0].pio_words[PIO_WORD_CTRL0] |=
HW_SSP_CTRL0_IGNORE_CRC;
}
dma_cmd[0].pio_words[PIO_WORD_CTRL0] |=
__SHIFTIN(sc->sc_bus_width, HW_SSP_CTRL0_BUS_WIDTH) |
HW_SSP_CTRL0_WAIT_FOR_IRQ;
if (ISSET(cmd->c_flags, SCF_RSP_PRESENT)) {
dma_cmd[0].pio_words[PIO_WORD_CTRL0] |= HW_SSP_CTRL0_GET_RESP;
if (ISSET(cmd->c_flags, SCF_RSP_136)) {
dma_cmd[0].pio_words[PIO_WORD_CTRL0] |=
HW_SSP_CTRL0_LONG_RESP;
}
}
dma_cmd[0].pio_words[PIO_WORD_CTRL0] |= HW_SSP_CTRL0_ENABLE;
dma_cmd[0].pio_words[PIO_WORD_CMD0] =
__SHIFTIN(cmd->c_opcode, HW_SSP_CMD0_CMD);
dma_cmd[0].pio_words[PIO_WORD_CMD1] = cmd->c_arg;
return;
}

View File

@ -1,4 +1,4 @@
# $Id: IMX23_OLINUXINO,v 1.2 2012/12/16 19:45:52 jkunz Exp $
# $Id: IMX23_OLINUXINO,v 1.3 2013/03/03 10:33:56 jkunz Exp $
#
# IMX23_OLINUXINO -- Olimex i.MX23 OLinuXino kernel configuration file.
#
@ -7,7 +7,7 @@ include "arch/evbarm/conf/std.imx23_olinuxino"
maxusers 8
config netbsd root on ld0a type ?
config netbsd root on ? type ?
# The main bus device
mainbus0 at root
@ -16,10 +16,10 @@ mainbus0 at root
cpu0 at mainbus?
# APBH bus
apbh0 at mainbus? base 0x80000000 size 0x00040000
apbh0 at mainbus? base 0x80000000 size 0x40000
# APBH DMA
#apbdma0 at apbh? addr 0x80004000 size 0x2000 irq -1
apbdma0 at apbh? addr 0x80004000 size 0x2000 irq -1
# Interrupt controller
icoll0 at apbh? addr 0x80000000 size 0x2000 irq -1
@ -27,13 +27,13 @@ icoll0 at apbh? addr 0x80000000 size 0x2000 irq -1
# Synchronous serial port for SD/MMC
ssp0 at apbh? addr 0x80010000 size 0x2000 irq 15
sdmmc* at ssp?
ld* at sdmmc?
ld* at sdmmc?
# APBX bus
apbx0 at mainbus? base 0x80040000 size 0x00040000
apbx0 at mainbus? base 0x80040000 size 0x40000
# APBX DMA
#apbdma1 at apbx? addr 0x80024000 size 0x2000 irq -1
apbdma1 at apbx? addr 0x80024000 size 0x2000 irq -1
# Timers and rotary decoder
timrot0 at apbx? addr 0x80068020 size 0x20 irq 28
@ -50,4 +50,8 @@ options HZ=100
file-system FFS
file-system EXT2FS
file-system MSDOSFS
file-system KERNFS
file-system PROCFS
file-system PTYFS
pseudo-device pty # pseudo-terminals