Commit 796211b7 authored by Ludovic Desroches's avatar Ludovic Desroches Committed by Chris Ball

mmc: atmel-mci: add pdc support and runtime capabilities detection

Add pdc support for atmel-mci. It makes at91-mci driver useless because it
was only used for the old atmel MCI core which has pdc but no dma support.
To allow removing at91-mci, the capabilities of the MCI core are detected
at runtime -- then the driver will use pio, pdc or dma transfers.
Warning: at91rm9200 is not supported, to support it we need to use swab32
on data but I have no board to test it.
Signed-off-by: default avatarLudovic Desroches <ludovic.desroches@atmel.com>
Signed-off-by: default avatarNicolas Ferre <nicolas.ferre@atmel.com>
Signed-off-by: default avatarChris Ball <cjb@laptop.org>
parent b9867f37
...@@ -88,8 +88,14 @@ ...@@ -88,8 +88,14 @@
# define ATMCI_BLKE ( 1 << 3) /* Data Block Ended */ # define ATMCI_BLKE ( 1 << 3) /* Data Block Ended */
# define ATMCI_DTIP ( 1 << 4) /* Data Transfer In Progress */ # define ATMCI_DTIP ( 1 << 4) /* Data Transfer In Progress */
# define ATMCI_NOTBUSY ( 1 << 5) /* Data Not Busy */ # define ATMCI_NOTBUSY ( 1 << 5) /* Data Not Busy */
# define ATMCI_ENDRX ( 1 << 6) /* End of RX Buffer */
# define ATMCI_ENDTX ( 1 << 7) /* End of TX Buffer */
# define ATMCI_SDIOIRQA ( 1 << 8) /* SDIO IRQ in slot A */ # define ATMCI_SDIOIRQA ( 1 << 8) /* SDIO IRQ in slot A */
# define ATMCI_SDIOIRQB ( 1 << 9) /* SDIO IRQ in slot B */ # define ATMCI_SDIOIRQB ( 1 << 9) /* SDIO IRQ in slot B */
# define ATMCI_SDIOWAIT ( 1 << 12) /* SDIO Read Wait Operation Status */
# define ATMCI_CSRCV ( 1 << 13) /* CE-ATA Completion Signal Received */
# define ATMCI_RXBUFF ( 1 << 14) /* RX Buffer Full */
# define ATMCI_TXBUFE ( 1 << 15) /* TX Buffer Empty */
# define ATMCI_RINDE ( 1 << 16) /* Response Index Error */ # define ATMCI_RINDE ( 1 << 16) /* Response Index Error */
# define ATMCI_RDIRE ( 1 << 17) /* Response Direction Error */ # define ATMCI_RDIRE ( 1 << 17) /* Response Direction Error */
# define ATMCI_RCRCE ( 1 << 18) /* Response CRC Error */ # define ATMCI_RCRCE ( 1 << 18) /* Response CRC Error */
...@@ -97,6 +103,13 @@ ...@@ -97,6 +103,13 @@
# define ATMCI_RTOE ( 1 << 20) /* Response Time-Out Error */ # define ATMCI_RTOE ( 1 << 20) /* Response Time-Out Error */
# define ATMCI_DCRCE ( 1 << 21) /* Data CRC Error */ # define ATMCI_DCRCE ( 1 << 21) /* Data CRC Error */
# define ATMCI_DTOE ( 1 << 22) /* Data Time-Out Error */ # define ATMCI_DTOE ( 1 << 22) /* Data Time-Out Error */
# define ATMCI_CSTOE ( 1 << 23) /* Completion Signal Time-out Error */
# define ATMCI_BLKOVRE ( 1 << 24) /* DMA Block Overrun Error */
# define ATMCI_DMADONE ( 1 << 25) /* DMA Transfer Done */
# define ATMCI_FIFOEMPTY ( 1 << 26) /* FIFO Empty Flag */
# define ATMCI_XFRDONE ( 1 << 27) /* Transfer Done Flag */
# define ATMCI_ACKRCV ( 1 << 28) /* Boot Operation Acknowledge Received */
# define ATMCI_ACKRCVE ( 1 << 29) /* Boot Operation Acknowledge Error */
# define ATMCI_OVRE ( 1 << 30) /* RX Overrun Error */ # define ATMCI_OVRE ( 1 << 30) /* RX Overrun Error */
# define ATMCI_UNRE ( 1 << 31) /* TX Underrun Error */ # define ATMCI_UNRE ( 1 << 31) /* TX Underrun Error */
#define ATMCI_DMA 0x0050 /* DMA Configuration[2] */ #define ATMCI_DMA 0x0050 /* DMA Configuration[2] */
...@@ -114,6 +127,7 @@ ...@@ -114,6 +127,7 @@
#define ATMCI_WPSR 0x00e8 /* Write Protection Status[2] */ #define ATMCI_WPSR 0x00e8 /* Write Protection Status[2] */
# define ATMCI_GET_WP_VS(x) ((x) & 0x0f) # define ATMCI_GET_WP_VS(x) ((x) & 0x0f)
# define ATMCI_GET_WP_VSRC(x) (((x) >> 8) & 0xffff) # define ATMCI_GET_WP_VSRC(x) (((x) >> 8) & 0xffff)
#define ATMCI_VERSION 0x00FC /* Version */
#define ATMCI_FIFO_APERTURE 0x0200 /* FIFO Aperture[2] */ #define ATMCI_FIFO_APERTURE 0x0200 /* FIFO Aperture[2] */
/* This is not including the FIFO Aperture on MCI2 */ /* This is not including the FIFO Aperture on MCI2 */
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include <mach/atmel-mci.h> #include <mach/atmel-mci.h>
#include <linux/atmel-mci.h> #include <linux/atmel-mci.h>
#include <linux/atmel_pdc.h>
#include <asm/io.h> #include <asm/io.h>
#include <asm/unaligned.h> #include <asm/unaligned.h>
...@@ -58,18 +59,35 @@ enum atmel_mci_state { ...@@ -58,18 +59,35 @@ enum atmel_mci_state {
STATE_DATA_ERROR, STATE_DATA_ERROR,
}; };
enum atmci_xfer_dir {
XFER_RECEIVE = 0,
XFER_TRANSMIT,
};
enum atmci_pdc_buf {
PDC_FIRST_BUF = 0,
PDC_SECOND_BUF,
};
struct atmel_mci_caps {
bool has_dma;
bool has_pdc;
bool has_cfg_reg;
bool has_cstor_reg;
bool has_highspeed;
bool has_rwproof;
};
struct atmel_mci_dma { struct atmel_mci_dma {
#ifdef CONFIG_MMC_ATMELMCI_DMA
struct dma_chan *chan; struct dma_chan *chan;
struct dma_async_tx_descriptor *data_desc; struct dma_async_tx_descriptor *data_desc;
#endif
}; };
/** /**
* struct atmel_mci - MMC controller state shared between all slots * struct atmel_mci - MMC controller state shared between all slots
* @lock: Spinlock protecting the queue and associated data. * @lock: Spinlock protecting the queue and associated data.
* @regs: Pointer to MMIO registers. * @regs: Pointer to MMIO registers.
* @sg: Scatterlist entry currently being processed by PIO code, if any. * @sg: Scatterlist entry currently being processed by PIO or PDC code.
* @pio_offset: Offset into the current scatterlist entry. * @pio_offset: Offset into the current scatterlist entry.
* @cur_slot: The slot which is currently using the controller. * @cur_slot: The slot which is currently using the controller.
* @mrq: The request currently being processed on @cur_slot, * @mrq: The request currently being processed on @cur_slot,
...@@ -77,6 +95,7 @@ struct atmel_mci_dma { ...@@ -77,6 +95,7 @@ struct atmel_mci_dma {
* @cmd: The command currently being sent to the card, or NULL. * @cmd: The command currently being sent to the card, or NULL.
* @data: The data currently being transferred, or NULL if no data * @data: The data currently being transferred, or NULL if no data
* transfer is in progress. * transfer is in progress.
* @data_size: just data->blocks * data->blksz.
* @dma: DMA client state. * @dma: DMA client state.
* @data_chan: DMA channel being used for the current data transfer. * @data_chan: DMA channel being used for the current data transfer.
* @cmd_status: Snapshot of SR taken upon completion of the current * @cmd_status: Snapshot of SR taken upon completion of the current
...@@ -103,6 +122,13 @@ struct atmel_mci_dma { ...@@ -103,6 +122,13 @@ struct atmel_mci_dma {
* @mck: The peripheral bus clock hooked up to the MMC controller. * @mck: The peripheral bus clock hooked up to the MMC controller.
* @pdev: Platform device associated with the MMC controller. * @pdev: Platform device associated with the MMC controller.
* @slot: Slots sharing this MMC controller. * @slot: Slots sharing this MMC controller.
* @caps: MCI capabilities depending on MCI version.
* @prepare_data: function to setup MCI before data transfer which
* depends on MCI capabilities.
* @submit_data: function to start data transfer which depends on MCI
* capabilities.
* @stop_transfer: function to stop data transfer which depends on MCI
* capabilities.
* *
* Locking * Locking
* ======= * =======
...@@ -143,6 +169,7 @@ struct atmel_mci { ...@@ -143,6 +169,7 @@ struct atmel_mci {
struct mmc_request *mrq; struct mmc_request *mrq;
struct mmc_command *cmd; struct mmc_command *cmd;
struct mmc_data *data; struct mmc_data *data;
unsigned int data_size;
struct atmel_mci_dma dma; struct atmel_mci_dma dma;
struct dma_chan *data_chan; struct dma_chan *data_chan;
...@@ -167,6 +194,12 @@ struct atmel_mci { ...@@ -167,6 +194,12 @@ struct atmel_mci {
struct platform_device *pdev; struct platform_device *pdev;
struct atmel_mci_slot *slot[ATMCI_MAX_NR_SLOTS]; struct atmel_mci_slot *slot[ATMCI_MAX_NR_SLOTS];
struct atmel_mci_caps caps;
u32 (*prepare_data)(struct atmel_mci *host, struct mmc_data *data);
void (*submit_data)(struct atmel_mci *host, struct mmc_data *data);
void (*stop_transfer)(struct atmel_mci *host);
}; };
/** /**
...@@ -219,31 +252,6 @@ struct atmel_mci_slot { ...@@ -219,31 +252,6 @@ struct atmel_mci_slot {
#define atmci_set_pending(host, event) \ #define atmci_set_pending(host, event) \
set_bit(event, &host->pending_events) set_bit(event, &host->pending_events)
/*
* Enable or disable features/registers based on
* whether the processor supports them
*/
static bool atmci_has_rwproof(void)
{
if (cpu_is_at91sam9261() || cpu_is_at91rm9200())
return false;
else
return true;
}
/*
* The new MCI2 module isn't 100% compatible with the old MCI module,
* and it has a few nice features which we want to use...
*/
static inline bool atmci_is_mci2(void)
{
if (cpu_is_at91sam9g45())
return true;
return false;
}
/* /*
* The debugfs stuff below is mostly optimized away when * The debugfs stuff below is mostly optimized away when
* CONFIG_DEBUG_FS is not set. * CONFIG_DEBUG_FS is not set.
...@@ -379,7 +387,7 @@ static int atmci_regs_show(struct seq_file *s, void *v) ...@@ -379,7 +387,7 @@ static int atmci_regs_show(struct seq_file *s, void *v)
buf[ATMCI_BLKR / 4], buf[ATMCI_BLKR / 4],
buf[ATMCI_BLKR / 4] & 0xffff, buf[ATMCI_BLKR / 4] & 0xffff,
(buf[ATMCI_BLKR / 4] >> 16) & 0xffff); (buf[ATMCI_BLKR / 4] >> 16) & 0xffff);
if (atmci_is_mci2()) if (host->caps.has_cstor_reg)
seq_printf(s, "CSTOR:\t0x%08x\n", buf[ATMCI_CSTOR / 4]); seq_printf(s, "CSTOR:\t0x%08x\n", buf[ATMCI_CSTOR / 4]);
/* Don't read RSPR and RDR; it will consume the data there */ /* Don't read RSPR and RDR; it will consume the data there */
...@@ -387,7 +395,7 @@ static int atmci_regs_show(struct seq_file *s, void *v) ...@@ -387,7 +395,7 @@ static int atmci_regs_show(struct seq_file *s, void *v)
atmci_show_status_reg(s, "SR", buf[ATMCI_SR / 4]); atmci_show_status_reg(s, "SR", buf[ATMCI_SR / 4]);
atmci_show_status_reg(s, "IMR", buf[ATMCI_IMR / 4]); atmci_show_status_reg(s, "IMR", buf[ATMCI_IMR / 4]);
if (atmci_is_mci2()) { if (host->caps.has_dma) {
u32 val; u32 val;
val = buf[ATMCI_DMA / 4]; val = buf[ATMCI_DMA / 4];
...@@ -396,6 +404,9 @@ static int atmci_regs_show(struct seq_file *s, void *v) ...@@ -396,6 +404,9 @@ static int atmci_regs_show(struct seq_file *s, void *v)
((val >> 4) & 3) ? ((val >> 4) & 3) ?
1 << (((val >> 4) & 3) + 1) : 1, 1 << (((val >> 4) & 3) + 1) : 1,
val & ATMCI_DMAEN ? " DMAEN" : ""); val & ATMCI_DMAEN ? " DMAEN" : "");
}
if (host->caps.has_cfg_reg) {
u32 val;
val = buf[ATMCI_CFG / 4]; val = buf[ATMCI_CFG / 4];
seq_printf(s, "CFG:\t0x%08x%s%s%s%s\n", seq_printf(s, "CFG:\t0x%08x%s%s%s%s\n",
...@@ -574,33 +585,109 @@ static void atmci_send_stop_cmd(struct atmel_mci *host, struct mmc_data *data) ...@@ -574,33 +585,109 @@ static void atmci_send_stop_cmd(struct atmel_mci *host, struct mmc_data *data)
atmci_writel(host, ATMCI_IER, ATMCI_CMDRDY); atmci_writel(host, ATMCI_IER, ATMCI_CMDRDY);
} }
#ifdef CONFIG_MMC_ATMELMCI_DMA /*
static void atmci_dma_cleanup(struct atmel_mci *host) * Configure given PDC buffer taking care of alignement issues.
* Update host->data_size and host->sg.
*/
static void atmci_pdc_set_single_buf(struct atmel_mci *host,
enum atmci_xfer_dir dir, enum atmci_pdc_buf buf_nb)
{
u32 pointer_reg, counter_reg;
if (dir == XFER_RECEIVE) {
pointer_reg = ATMEL_PDC_RPR;
counter_reg = ATMEL_PDC_RCR;
} else {
pointer_reg = ATMEL_PDC_TPR;
counter_reg = ATMEL_PDC_TCR;
}
if (buf_nb == PDC_SECOND_BUF) {
pointer_reg += 0x10;
counter_reg += 0x10;
}
atmci_writel(host, pointer_reg, sg_dma_address(host->sg));
if (host->data_size <= PAGE_SIZE) {
if (host->data_size & 0x3) {
/* If size is different from modulo 4, transfer bytes */
atmci_writel(host, counter_reg, host->data_size);
atmci_writel(host, ATMCI_MR, host->mode_reg | ATMCI_MR_PDCFBYTE);
} else {
/* Else transfer 32-bits words */
atmci_writel(host, counter_reg, host->data_size / 4);
}
host->data_size = 0;
} else {
/* We assume the size of a page is 32-bits aligned */
atmci_writel(host, counter_reg, PAGE_SIZE / 4);
host->data_size -= PAGE_SIZE;
if (host->data_size)
host->sg = sg_next(host->sg);
}
}
/*
* Configure PDC buffer according to the data size ie configuring one or two
* buffers. Don't use this function if you want to configure only the second
* buffer. In this case, use atmci_pdc_set_single_buf.
*/
static void atmci_pdc_set_both_buf(struct atmel_mci *host, int dir)
{
atmci_pdc_set_single_buf(host, dir, PDC_FIRST_BUF);
if (host->data_size)
atmci_pdc_set_single_buf(host, dir, PDC_SECOND_BUF);
}
/*
* Unmap sg lists, called when transfer is finished.
*/
static void atmci_pdc_cleanup(struct atmel_mci *host)
{ {
struct mmc_data *data = host->data; struct mmc_data *data = host->data;
if (data) if (data)
dma_unmap_sg(host->dma.chan->device->dev, dma_unmap_sg(&host->pdev->dev,
data->sg, data->sg_len, data->sg, data->sg_len,
((data->flags & MMC_DATA_WRITE) ((data->flags & MMC_DATA_WRITE)
? DMA_TO_DEVICE : DMA_FROM_DEVICE)); ? DMA_TO_DEVICE : DMA_FROM_DEVICE));
} }
static void atmci_stop_dma(struct atmel_mci *host) /*
* Disable PDC transfers. Update pending flags to EVENT_XFER_COMPLETE after
* having received ATMCI_TXBUFE or ATMCI_RXBUFF interrupt. Enable ATMCI_NOTBUSY
* interrupt needed for both transfer directions.
*/
static void atmci_pdc_complete(struct atmel_mci *host)
{ {
struct dma_chan *chan = host->data_chan; atmci_writel(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
atmci_pdc_cleanup(host);
if (chan) { /*
dmaengine_terminate_all(chan); * If the card was removed, data will be NULL. No point trying
atmci_dma_cleanup(host); * to send the stop command or waiting for NBUSY in this case.
} else { */
/* Data transfer was stopped by the interrupt handler */ if (host->data) {
atmci_set_pending(host, EVENT_XFER_COMPLETE); atmci_set_pending(host, EVENT_XFER_COMPLETE);
tasklet_schedule(&host->tasklet);
atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY); atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY);
} }
} }
/* This function is called by the DMA driver from tasklet context. */ static void atmci_dma_cleanup(struct atmel_mci *host)
{
struct mmc_data *data = host->data;
if (data)
dma_unmap_sg(host->dma.chan->device->dev,
data->sg, data->sg_len,
((data->flags & MMC_DATA_WRITE)
? DMA_TO_DEVICE : DMA_FROM_DEVICE));
}
/*
* This function is called by the DMA driver from tasklet context.
*/
static void atmci_dma_complete(void *arg) static void atmci_dma_complete(void *arg)
{ {
struct atmel_mci *host = arg; struct atmel_mci *host = arg;
...@@ -608,7 +695,7 @@ static void atmci_dma_complete(void *arg) ...@@ -608,7 +695,7 @@ static void atmci_dma_complete(void *arg)
dev_vdbg(&host->pdev->dev, "DMA complete\n"); dev_vdbg(&host->pdev->dev, "DMA complete\n");
if (atmci_is_mci2()) if (host->caps.has_dma)
/* Disable DMA hardware handshaking on MCI */ /* Disable DMA hardware handshaking on MCI */
atmci_writel(host, ATMCI_DMA, atmci_readl(host, ATMCI_DMA) & ~ATMCI_DMAEN); atmci_writel(host, ATMCI_DMA, atmci_readl(host, ATMCI_DMA) & ~ATMCI_DMAEN);
...@@ -646,7 +733,90 @@ static void atmci_dma_complete(void *arg) ...@@ -646,7 +733,90 @@ static void atmci_dma_complete(void *arg)
} }
} }
static int /*
* Returns a mask of interrupt flags to be enabled after the whole
* request has been prepared.
*/
static u32 atmci_prepare_data(struct atmel_mci *host, struct mmc_data *data)
{
u32 iflags;
data->error = -EINPROGRESS;
host->sg = data->sg;
host->data = data;
host->data_chan = NULL;
iflags = ATMCI_DATA_ERROR_FLAGS;
/*
* Errata: MMC data write operation with less than 12
* bytes is impossible.
*
* Errata: MCI Transmit Data Register (TDR) FIFO
* corruption when length is not multiple of 4.
*/
if (data->blocks * data->blksz < 12
|| (data->blocks * data->blksz) & 3)
host->need_reset = true;
host->pio_offset = 0;
if (data->flags & MMC_DATA_READ)
iflags |= ATMCI_RXRDY;
else
iflags |= ATMCI_TXRDY;
return iflags;
}
/*
* Set interrupt flags and set block length into the MCI mode register even
* if this value is also accessible in the MCI block register. It seems to be
* necessary before the High Speed MCI version. It also map sg and configure
* PDC registers.
*/
static u32
atmci_prepare_data_pdc(struct atmel_mci *host, struct mmc_data *data)
{
u32 iflags, tmp;
unsigned int sg_len;
enum dma_data_direction dir;
data->error = -EINPROGRESS;
host->data = data;
host->sg = data->sg;
iflags = ATMCI_DATA_ERROR_FLAGS;
/* Enable pdc mode */
atmci_writel(host, ATMCI_MR, host->mode_reg | ATMCI_MR_PDCMODE);
if (data->flags & MMC_DATA_READ) {
dir = DMA_FROM_DEVICE;
iflags |= ATMCI_ENDRX | ATMCI_RXBUFF;
} else {
dir = DMA_TO_DEVICE;
iflags |= ATMCI_ENDTX | ATMCI_TXBUFE;
}
/* Set BLKLEN */
tmp = atmci_readl(host, ATMCI_MR);
tmp &= 0x0000ffff;
tmp |= ATMCI_BLKLEN(data->blksz);
atmci_writel(host, ATMCI_MR, tmp);
/* Configure PDC */
host->data_size = data->blocks * data->blksz;
sg_len = dma_map_sg(&host->pdev->dev, data->sg, data->sg_len, dir);
BUG_ON(sg_len < host->data_size / PAGE_SIZE);
if (host->data_size)
atmci_pdc_set_both_buf(host,
((dir == DMA_FROM_DEVICE) ? XFER_RECEIVE : XFER_TRANSMIT));
return iflags;
}
static u32
atmci_prepare_data_dma(struct atmel_mci *host, struct mmc_data *data) atmci_prepare_data_dma(struct atmel_mci *host, struct mmc_data *data)
{ {
struct dma_chan *chan; struct dma_chan *chan;
...@@ -655,20 +825,29 @@ atmci_prepare_data_dma(struct atmel_mci *host, struct mmc_data *data) ...@@ -655,20 +825,29 @@ atmci_prepare_data_dma(struct atmel_mci *host, struct mmc_data *data)
unsigned int i; unsigned int i;
enum dma_data_direction direction; enum dma_data_direction direction;
unsigned int sglen; unsigned int sglen;
u32 iflags;
data->error = -EINPROGRESS;
WARN_ON(host->data);
host->sg = NULL;
host->data = data;
iflags = ATMCI_DATA_ERROR_FLAGS;
/* /*
* We don't do DMA on "complex" transfers, i.e. with * We don't do DMA on "complex" transfers, i.e. with
* non-word-aligned buffers or lengths. Also, we don't bother * non-word-aligned buffers or lengths. Also, we don't bother
* with all the DMA setup overhead for short transfers. * with all the DMA setup overhead for short transfers.
*/ */
if (data->blocks * data->blksz < ATATMCI_DMA_THRESHOLD) if (data->blocks * data->blksz < ATMCI_DMA_THRESHOLD)
return -EINVAL; return atmci_prepare_data(host, data);
if (data->blksz & 3) if (data->blksz & 3)
return -EINVAL; return atmci_prepare_data(host, data);
for_each_sg(data->sg, sg, data->sg_len, i) { for_each_sg(data->sg, sg, data->sg_len, i) {
if (sg->offset & 3 || sg->length & 3) if (sg->offset & 3 || sg->length & 3)
return -EINVAL; return atmci_prepare_data(host, data);
} }
/* If we don't have a channel, we can't do DMA */ /* If we don't have a channel, we can't do DMA */
...@@ -679,7 +858,7 @@ atmci_prepare_data_dma(struct atmel_mci *host, struct mmc_data *data) ...@@ -679,7 +858,7 @@ atmci_prepare_data_dma(struct atmel_mci *host, struct mmc_data *data)
if (!chan) if (!chan)
return -ENODEV; return -ENODEV;
if (atmci_is_mci2()) if (host->caps.has_dma)
atmci_writel(host, ATMCI_DMA, ATMCI_DMA_CHKSIZE(3) | ATMCI_DMAEN); atmci_writel(host, ATMCI_DMA, ATMCI_DMA_CHKSIZE(3) | ATMCI_DMAEN);
if (data->flags & MMC_DATA_READ) if (data->flags & MMC_DATA_READ)
...@@ -700,13 +879,32 @@ atmci_prepare_data_dma(struct atmel_mci *host, struct mmc_data *data) ...@@ -700,13 +879,32 @@ atmci_prepare_data_dma(struct atmel_mci *host, struct mmc_data *data)
desc->callback = atmci_dma_complete; desc->callback = atmci_dma_complete;
desc->callback_param = host; desc->callback_param = host;
return 0; return iflags;
unmap_exit: unmap_exit:
dma_unmap_sg(chan->device->dev, data->sg, data->sg_len, direction); dma_unmap_sg(chan->device->dev, data->sg, data->sg_len, direction);
return -ENOMEM; return -ENOMEM;
} }
static void atmci_submit_data(struct atmel_mci *host) static void
atmci_submit_data(struct atmel_mci *host, struct mmc_data *data)
{
return;
}
/*
* Start PDC according to transfer direction.
*/
static void
atmci_submit_data_pdc(struct atmel_mci *host, struct mmc_data *data)
{
if (data->flags & MMC_DATA_READ)
atmci_writel(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTEN);
else
atmci_writel(host, ATMEL_PDC_PTCR, ATMEL_PDC_TXTEN);
}
static void
atmci_submit_data_dma(struct atmel_mci *host, struct mmc_data *data)
{ {
struct dma_chan *chan = host->data_chan; struct dma_chan *chan = host->data_chan;
struct dma_async_tx_descriptor *desc = host->dma.data_desc; struct dma_async_tx_descriptor *desc = host->dma.data_desc;
...@@ -717,64 +915,39 @@ static void atmci_submit_data(struct atmel_mci *host) ...@@ -717,64 +915,39 @@ static void atmci_submit_data(struct atmel_mci *host)
} }
} }
#else /* CONFIG_MMC_ATMELMCI_DMA */ static void atmci_stop_transfer(struct atmel_mci *host)
static int atmci_prepare_data_dma(struct atmel_mci *host, struct mmc_data *data)
{ {
return -ENOSYS;
}
static void atmci_submit_data(struct atmel_mci *host) {}
static void atmci_stop_dma(struct atmel_mci *host)
{
/* Data transfer was stopped by the interrupt handler */
atmci_set_pending(host, EVENT_XFER_COMPLETE); atmci_set_pending(host, EVENT_XFER_COMPLETE);
atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY); atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY);
} }
#endif /* CONFIG_MMC_ATMELMCI_DMA */
/* /*
* Returns a mask of interrupt flags to be enabled after the whole * Stop data transfer because error(s) occured.
* request has been prepared.
*/ */
static u32 atmci_prepare_data(struct atmel_mci *host, struct mmc_data *data) static void atmci_stop_transfer_pdc(struct atmel_mci *host)
{ {
u32 iflags; atmci_set_pending(host, EVENT_XFER_COMPLETE);
atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY);
data->error = -EINPROGRESS; }
WARN_ON(host->data);
host->sg = NULL;
host->data = data;
iflags = ATMCI_DATA_ERROR_FLAGS;
if (atmci_prepare_data_dma(host, data)) {
host->data_chan = NULL;
/* static void atmci_stop_transfer_dma(struct atmel_mci *host)
* Errata: MMC data write operation with less than 12 {
* bytes is impossible. struct dma_chan *chan = host->data_chan;
*
* Errata: MCI Transmit Data Register (TDR) FIFO
* corruption when length is not multiple of 4.
*/
if (data->blocks * data->blksz < 12
|| (data->blocks * data->blksz) & 3)
host->need_reset = true;
host->sg = data->sg; if (chan) {
host->pio_offset = 0; dmaengine_terminate_all(chan);
if (data->flags & MMC_DATA_READ) atmci_dma_cleanup(host);
iflags |= ATMCI_RXRDY; } else {
else /* Data transfer was stopped by the interrupt handler */
iflags |= ATMCI_TXRDY; atmci_set_pending(host, EVENT_XFER_COMPLETE);
atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY);
} }
return iflags;
} }
/*
* Start a request: prepare data if needed, prepare the command and activate
* interrupts.
*/
static void atmci_start_request(struct atmel_mci *host, static void atmci_start_request(struct atmel_mci *host,
struct atmel_mci_slot *slot) struct atmel_mci_slot *slot)
{ {
...@@ -796,7 +969,7 @@ static void atmci_start_request(struct atmel_mci *host, ...@@ -796,7 +969,7 @@ static void atmci_start_request(struct atmel_mci *host,
atmci_writel(host, ATMCI_CR, ATMCI_CR_SWRST); atmci_writel(host, ATMCI_CR, ATMCI_CR_SWRST);
atmci_writel(host, ATMCI_CR, ATMCI_CR_MCIEN); atmci_writel(host, ATMCI_CR, ATMCI_CR_MCIEN);
atmci_writel(host, ATMCI_MR, host->mode_reg); atmci_writel(host, ATMCI_MR, host->mode_reg);
if (atmci_is_mci2()) if (host->caps.has_cfg_reg)
atmci_writel(host, ATMCI_CFG, host->cfg_reg); atmci_writel(host, ATMCI_CFG, host->cfg_reg);
host->need_reset = false; host->need_reset = false;
} }
...@@ -824,7 +997,7 @@ static void atmci_start_request(struct atmel_mci *host, ...@@ -824,7 +997,7 @@ static void atmci_start_request(struct atmel_mci *host,
dev_vdbg(&slot->mmc->class_dev, "BLKR=0x%08x\n", dev_vdbg(&slot->mmc->class_dev, "BLKR=0x%08x\n",
ATMCI_BCNT(data->blocks) | ATMCI_BLKLEN(data->blksz)); ATMCI_BCNT(data->blocks) | ATMCI_BLKLEN(data->blksz));
iflags |= atmci_prepare_data(host, data); iflags |= host->prepare_data(host, data);
} }
iflags |= ATMCI_CMDRDY; iflags |= ATMCI_CMDRDY;
...@@ -833,7 +1006,7 @@ static void atmci_start_request(struct atmel_mci *host, ...@@ -833,7 +1006,7 @@ static void atmci_start_request(struct atmel_mci *host,
atmci_start_command(host, cmd, cmdflags); atmci_start_command(host, cmd, cmdflags);
if (data) if (data)
atmci_submit_data(host); host->submit_data(host, data);
if (mrq->stop) { if (mrq->stop) {
host->stop_cmdr = atmci_prepare_command(slot->mmc, mrq->stop); host->stop_cmdr = atmci_prepare_command(slot->mmc, mrq->stop);
...@@ -929,7 +1102,7 @@ static void atmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) ...@@ -929,7 +1102,7 @@ static void atmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
clk_enable(host->mck); clk_enable(host->mck);
atmci_writel(host, ATMCI_CR, ATMCI_CR_SWRST); atmci_writel(host, ATMCI_CR, ATMCI_CR_SWRST);
atmci_writel(host, ATMCI_CR, ATMCI_CR_MCIEN); atmci_writel(host, ATMCI_CR, ATMCI_CR_MCIEN);
if (atmci_is_mci2()) if (host->caps.has_cfg_reg)
atmci_writel(host, ATMCI_CFG, host->cfg_reg); atmci_writel(host, ATMCI_CFG, host->cfg_reg);
} }
...@@ -960,10 +1133,10 @@ static void atmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) ...@@ -960,10 +1133,10 @@ static void atmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
* stopping the clock when the FIFO is full/empty. * stopping the clock when the FIFO is full/empty.
* This state is not expected to last for long. * This state is not expected to last for long.
*/ */
if (atmci_has_rwproof()) if (host->caps.has_rwproof)
host->mode_reg |= (ATMCI_MR_WRPROOF | ATMCI_MR_RDPROOF); host->mode_reg |= (ATMCI_MR_WRPROOF | ATMCI_MR_RDPROOF);
if (atmci_is_mci2()) { if (host->caps.has_cfg_reg) {
/* setup High Speed mode in relation with card capacity */ /* setup High Speed mode in relation with card capacity */
if (ios->timing == MMC_TIMING_SD_HS) if (ios->timing == MMC_TIMING_SD_HS)
host->cfg_reg |= ATMCI_CFG_HSMODE; host->cfg_reg |= ATMCI_CFG_HSMODE;
...@@ -973,7 +1146,7 @@ static void atmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) ...@@ -973,7 +1146,7 @@ static void atmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
if (list_empty(&host->queue)) { if (list_empty(&host->queue)) {
atmci_writel(host, ATMCI_MR, host->mode_reg); atmci_writel(host, ATMCI_MR, host->mode_reg);
if (atmci_is_mci2()) if (host->caps.has_cfg_reg)
atmci_writel(host, ATMCI_CFG, host->cfg_reg); atmci_writel(host, ATMCI_CFG, host->cfg_reg);
} else { } else {
host->need_clock_update = true; host->need_clock_update = true;
...@@ -1088,7 +1261,7 @@ static void atmci_request_end(struct atmel_mci *host, struct mmc_request *mrq) ...@@ -1088,7 +1261,7 @@ static void atmci_request_end(struct atmel_mci *host, struct mmc_request *mrq)
*/ */
if (host->need_clock_update) { if (host->need_clock_update) {
atmci_writel(host, ATMCI_MR, host->mode_reg); atmci_writel(host, ATMCI_MR, host->mode_reg);
if (atmci_is_mci2()) if (host->caps.has_cfg_reg)
atmci_writel(host, ATMCI_CFG, host->cfg_reg); atmci_writel(host, ATMCI_CFG, host->cfg_reg);
} }
...@@ -1137,7 +1310,7 @@ static void atmci_command_complete(struct atmel_mci *host, ...@@ -1137,7 +1310,7 @@ static void atmci_command_complete(struct atmel_mci *host,
"command error: status=0x%08x\n", status); "command error: status=0x%08x\n", status);
if (cmd->data) { if (cmd->data) {
atmci_stop_dma(host); host->stop_transfer(host);
host->data = NULL; host->data = NULL;
atmci_writel(host, ATMCI_IDR, ATMCI_NOTBUSY atmci_writel(host, ATMCI_IDR, ATMCI_NOTBUSY
| ATMCI_TXRDY | ATMCI_RXRDY | ATMCI_TXRDY | ATMCI_RXRDY
...@@ -1195,7 +1368,7 @@ static void atmci_detect_change(unsigned long data) ...@@ -1195,7 +1368,7 @@ static void atmci_detect_change(unsigned long data)
atmci_writel(host, ATMCI_CR, ATMCI_CR_SWRST); atmci_writel(host, ATMCI_CR, ATMCI_CR_SWRST);
atmci_writel(host, ATMCI_CR, ATMCI_CR_MCIEN); atmci_writel(host, ATMCI_CR, ATMCI_CR_MCIEN);
atmci_writel(host, ATMCI_MR, host->mode_reg); atmci_writel(host, ATMCI_MR, host->mode_reg);
if (atmci_is_mci2()) if (host->caps.has_cfg_reg)
atmci_writel(host, ATMCI_CFG, host->cfg_reg); atmci_writel(host, ATMCI_CFG, host->cfg_reg);
host->data = NULL; host->data = NULL;
...@@ -1211,7 +1384,7 @@ static void atmci_detect_change(unsigned long data) ...@@ -1211,7 +1384,7 @@ static void atmci_detect_change(unsigned long data)
/* fall through */ /* fall through */
case STATE_SENDING_DATA: case STATE_SENDING_DATA:
mrq->data->error = -ENOMEDIUM; mrq->data->error = -ENOMEDIUM;
atmci_stop_dma(host); host->stop_transfer(host);
break; break;
case STATE_DATA_BUSY: case STATE_DATA_BUSY:
case STATE_DATA_ERROR: case STATE_DATA_ERROR:
...@@ -1290,7 +1463,7 @@ static void atmci_tasklet_func(unsigned long priv) ...@@ -1290,7 +1463,7 @@ static void atmci_tasklet_func(unsigned long priv)
case STATE_SENDING_DATA: case STATE_SENDING_DATA:
if (atmci_test_and_clear_pending(host, if (atmci_test_and_clear_pending(host,
EVENT_DATA_ERROR)) { EVENT_DATA_ERROR)) {
atmci_stop_dma(host); host->stop_transfer(host);
if (data->stop) if (data->stop)
atmci_send_stop_cmd(host, data); atmci_send_stop_cmd(host, data);
state = STATE_DATA_ERROR; state = STATE_DATA_ERROR;
...@@ -1556,6 +1729,56 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id) ...@@ -1556,6 +1729,56 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id)
atmci_set_pending(host, EVENT_DATA_ERROR); atmci_set_pending(host, EVENT_DATA_ERROR);
tasklet_schedule(&host->tasklet); tasklet_schedule(&host->tasklet);
} }
if (pending & ATMCI_ENDTX) {
atmci_writel(host, ATMCI_IDR, ATMCI_ENDTX);
if (host->data_size) {
atmci_pdc_set_single_buf(host,
XFER_TRANSMIT, PDC_SECOND_BUF);
atmci_writel(host, ATMCI_IER, ATMCI_ENDTX);
}
}
if (pending & ATMCI_TXBUFE) {
atmci_writel(host, ATMCI_IDR, ATMCI_TXBUFE);
/*
* We can receive this interruption before having configured
* the second pdc buffer, so we need to reconfigure first and
* second buffers again
*/
if (host->data_size) {
atmci_pdc_set_both_buf(host, XFER_TRANSMIT);
atmci_writel(host, ATMCI_IER, ATMCI_TXBUFE);
} else {
atmci_pdc_complete(host);
}
}
if (pending & ATMCI_ENDRX) {
atmci_writel(host, ATMCI_IDR, ATMCI_ENDRX);
if (host->data_size) {
atmci_pdc_set_single_buf(host,
XFER_RECEIVE, PDC_SECOND_BUF);
atmci_writel(host, ATMCI_IER, ATMCI_ENDRX);
}
}
if (pending & ATMCI_RXBUFF) {
atmci_writel(host, ATMCI_IDR, ATMCI_RXBUFF);
/*
* We can receive this interruption before having configured
* the second pdc buffer, so we need to reconfigure first and
* second buffers again
*/
if (host->data_size) {
atmci_pdc_set_both_buf(host, XFER_RECEIVE);
atmci_writel(host, ATMCI_IER, ATMCI_RXBUFF);
} else {
atmci_pdc_complete(host);
}
}
if (pending & ATMCI_NOTBUSY) { if (pending & ATMCI_NOTBUSY) {
atmci_writel(host, ATMCI_IDR, atmci_writel(host, ATMCI_IDR,
ATMCI_DATA_ERROR_FLAGS | ATMCI_NOTBUSY); ATMCI_DATA_ERROR_FLAGS | ATMCI_NOTBUSY);
...@@ -1622,7 +1845,7 @@ static int __init atmci_init_slot(struct atmel_mci *host, ...@@ -1622,7 +1845,7 @@ static int __init atmci_init_slot(struct atmel_mci *host,
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
if (sdio_irq) if (sdio_irq)
mmc->caps |= MMC_CAP_SDIO_IRQ; mmc->caps |= MMC_CAP_SDIO_IRQ;
if (atmci_is_mci2()) if (host->caps.has_highspeed)
mmc->caps |= MMC_CAP_SD_HIGHSPEED; mmc->caps |= MMC_CAP_SD_HIGHSPEED;
if (slot_data->bus_width >= 4) if (slot_data->bus_width >= 4)
mmc->caps |= MMC_CAP_4_BIT_DATA; mmc->caps |= MMC_CAP_4_BIT_DATA;
...@@ -1705,7 +1928,6 @@ static void __exit atmci_cleanup_slot(struct atmel_mci_slot *slot, ...@@ -1705,7 +1928,6 @@ static void __exit atmci_cleanup_slot(struct atmel_mci_slot *slot,
mmc_free_host(slot->mmc); mmc_free_host(slot->mmc);
} }
#ifdef CONFIG_MMC_ATMELMCI_DMA
static bool atmci_filter(struct dma_chan *chan, void *slave) static bool atmci_filter(struct dma_chan *chan, void *slave)
{ {
struct mci_dma_data *sl = slave; struct mci_dma_data *sl = slave;
...@@ -1747,9 +1969,60 @@ static void atmci_configure_dma(struct atmel_mci *host) ...@@ -1747,9 +1969,60 @@ static void atmci_configure_dma(struct atmel_mci *host)
"Using %s for DMA transfers\n", "Using %s for DMA transfers\n",
dma_chan_name(host->dma.chan)); dma_chan_name(host->dma.chan));
} }
static inline unsigned int atmci_get_version(struct atmel_mci *host)
{
return atmci_readl(host, ATMCI_VERSION) & 0x00000fff;
}
/*
* HSMCI (High Speed MCI) module is not fully compatible with MCI module.
* HSMCI provides DMA support and a new config register but no more supports
* PDC.
*/
static void __init atmci_get_cap(struct atmel_mci *host)
{
unsigned int version;
version = atmci_get_version(host);
dev_info(&host->pdev->dev,
"version: 0x%x\n", version);
host->caps.has_dma = 0;
host->caps.has_pdc = 0;
host->caps.has_cfg_reg = 0;
host->caps.has_cstor_reg = 0;
host->caps.has_highspeed = 0;
host->caps.has_rwproof = 0;
/* keep only major version number */
switch (version & 0xf00) {
case 0x100:
case 0x200:
host->caps.has_pdc = 1;
host->caps.has_rwproof = 1;
break;
case 0x300:
case 0x400:
case 0x500:
#ifdef CONFIG_AT_HDMAC
host->caps.has_dma = 1;
#else #else
static void atmci_configure_dma(struct atmel_mci *host) {} host->caps.has_dma = 0;
dev_info(&host->pdev->dev,
"has dma capability but dma engine is not selected, then use pio\n");
#endif #endif
host->caps.has_cfg_reg = 1;
host->caps.has_cstor_reg = 1;
host->caps.has_highspeed = 1;
host->caps.has_rwproof = 1;
break;
default:
dev_warn(&host->pdev->dev,
"Unmanaged mci version, set minimum capabilities\n");
break;
}
}
static int __init atmci_probe(struct platform_device *pdev) static int __init atmci_probe(struct platform_device *pdev)
{ {
...@@ -1802,6 +2075,26 @@ static int __init atmci_probe(struct platform_device *pdev) ...@@ -1802,6 +2075,26 @@ static int __init atmci_probe(struct platform_device *pdev)
if (ret) if (ret)
goto err_request_irq; goto err_request_irq;
/* Get MCI capabilities and set operations according to it */
atmci_get_cap(host);
if (host->caps.has_dma) {
dev_info(&pdev->dev, "using DMA\n");
host->prepare_data = &atmci_prepare_data_dma;
host->submit_data = &atmci_submit_data_dma;
host->stop_transfer = &atmci_stop_transfer_dma;
} else if (host->caps.has_pdc) {
dev_info(&pdev->dev, "using PDC\n");
host->prepare_data = &atmci_prepare_data_pdc;
host->submit_data = &atmci_submit_data_pdc;
host->stop_transfer = &atmci_stop_transfer_pdc;
} else {
dev_info(&pdev->dev, "no DMA, no PDC\n");
host->prepare_data = &atmci_prepare_data;
host->submit_data = &atmci_submit_data;
host->stop_transfer = &atmci_stop_transfer;
}
if (host->caps.has_dma)
atmci_configure_dma(host); atmci_configure_dma(host);
platform_set_drvdata(pdev, host); platform_set_drvdata(pdev, host);
...@@ -1834,10 +2127,8 @@ static int __init atmci_probe(struct platform_device *pdev) ...@@ -1834,10 +2127,8 @@ static int __init atmci_probe(struct platform_device *pdev)
return 0; return 0;
err_init_slot: err_init_slot:
#ifdef CONFIG_MMC_ATMELMCI_DMA
if (host->dma.chan) if (host->dma.chan)
dma_release_channel(host->dma.chan); dma_release_channel(host->dma.chan);
#endif
free_irq(irq, host); free_irq(irq, host);
err_request_irq: err_request_irq:
iounmap(host->regs); iounmap(host->regs);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment