|
@@ -45,19 +45,19 @@
|
|
|
#define ATMCI_DMA_THRESHOLD 16
|
|
#define ATMCI_DMA_THRESHOLD 16
|
|
|
|
|
|
|
|
enum {
|
|
enum {
|
|
|
- EVENT_CMD_COMPLETE = 0,
|
|
|
|
|
|
|
+ EVENT_CMD_RDY = 0,
|
|
|
EVENT_XFER_COMPLETE,
|
|
EVENT_XFER_COMPLETE,
|
|
|
- EVENT_DATA_COMPLETE,
|
|
|
|
|
|
|
+ EVENT_NOTBUSY,
|
|
|
EVENT_DATA_ERROR,
|
|
EVENT_DATA_ERROR,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
enum atmel_mci_state {
|
|
enum atmel_mci_state {
|
|
|
STATE_IDLE = 0,
|
|
STATE_IDLE = 0,
|
|
|
STATE_SENDING_CMD,
|
|
STATE_SENDING_CMD,
|
|
|
- STATE_SENDING_DATA,
|
|
|
|
|
- STATE_DATA_BUSY,
|
|
|
|
|
|
|
+ STATE_DATA_XFER,
|
|
|
|
|
+ STATE_WAITING_NOTBUSY,
|
|
|
STATE_SENDING_STOP,
|
|
STATE_SENDING_STOP,
|
|
|
- STATE_DATA_ERROR,
|
|
|
|
|
|
|
+ STATE_END_REQUEST,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
enum atmci_xfer_dir {
|
|
enum atmci_xfer_dir {
|
|
@@ -78,6 +78,9 @@ struct atmel_mci_caps {
|
|
|
bool has_highspeed;
|
|
bool has_highspeed;
|
|
|
bool has_rwproof;
|
|
bool has_rwproof;
|
|
|
bool has_odd_clk_div;
|
|
bool has_odd_clk_div;
|
|
|
|
|
+ bool has_bad_data_ordering;
|
|
|
|
|
+ bool need_reset_after_xfer;
|
|
|
|
|
+ bool need_blksz_mul_4;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
struct atmel_mci_dma {
|
|
struct atmel_mci_dma {
|
|
@@ -91,6 +94,11 @@ struct atmel_mci_dma {
|
|
|
* @regs: Pointer to MMIO registers.
|
|
* @regs: Pointer to MMIO registers.
|
|
|
* @sg: Scatterlist entry currently being processed by PIO or PDC code.
|
|
* @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.
|
|
|
|
|
+ * @buffer: Buffer used if we don't have the r/w proof capability. We
|
|
|
|
|
+ * don't have the time to switch pdc buffers so we have to use only
|
|
|
|
|
+ * one buffer for the full transaction.
|
|
|
|
|
+ * @buf_size: size of the buffer.
|
|
|
|
|
+ * @phys_buf_addr: buffer address needed for pdc.
|
|
|
* @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,
|
|
|
* or NULL if the controller is idle.
|
|
* or NULL if the controller is idle.
|
|
@@ -116,6 +124,7 @@ struct atmel_mci_dma {
|
|
|
* @queue: List of slots waiting for access to the controller.
|
|
* @queue: List of slots waiting for access to the controller.
|
|
|
* @need_clock_update: Update the clock rate before the next request.
|
|
* @need_clock_update: Update the clock rate before the next request.
|
|
|
* @need_reset: Reset controller before next request.
|
|
* @need_reset: Reset controller before next request.
|
|
|
|
|
+ * @timer: Timer to balance the data timeout error flag which cannot rise.
|
|
|
* @mode_reg: Value of the MR register.
|
|
* @mode_reg: Value of the MR register.
|
|
|
* @cfg_reg: Value of the CFG register.
|
|
* @cfg_reg: Value of the CFG register.
|
|
|
* @bus_hz: The rate of @mck in Hz. This forms the basis for MMC bus
|
|
* @bus_hz: The rate of @mck in Hz. This forms the basis for MMC bus
|
|
@@ -166,6 +175,9 @@ struct atmel_mci {
|
|
|
|
|
|
|
|
struct scatterlist *sg;
|
|
struct scatterlist *sg;
|
|
|
unsigned int pio_offset;
|
|
unsigned int pio_offset;
|
|
|
|
|
+ unsigned int *buffer;
|
|
|
|
|
+ unsigned int buf_size;
|
|
|
|
|
+ dma_addr_t buf_phys_addr;
|
|
|
|
|
|
|
|
struct atmel_mci_slot *cur_slot;
|
|
struct atmel_mci_slot *cur_slot;
|
|
|
struct mmc_request *mrq;
|
|
struct mmc_request *mrq;
|
|
@@ -189,6 +201,7 @@ struct atmel_mci {
|
|
|
|
|
|
|
|
bool need_clock_update;
|
|
bool need_clock_update;
|
|
|
bool need_reset;
|
|
bool need_reset;
|
|
|
|
|
+ struct timer_list timer;
|
|
|
u32 mode_reg;
|
|
u32 mode_reg;
|
|
|
u32 cfg_reg;
|
|
u32 cfg_reg;
|
|
|
unsigned long bus_hz;
|
|
unsigned long bus_hz;
|
|
@@ -480,6 +493,32 @@ static void atmci_init_debugfs(struct atmel_mci_slot *slot)
|
|
|
dev_err(&mmc->class_dev, "failed to initialize debugfs for slot\n");
|
|
dev_err(&mmc->class_dev, "failed to initialize debugfs for slot\n");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+static inline unsigned int atmci_get_version(struct atmel_mci *host)
|
|
|
|
|
+{
|
|
|
|
|
+ return atmci_readl(host, ATMCI_VERSION) & 0x00000fff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void atmci_timeout_timer(unsigned long data)
|
|
|
|
|
+{
|
|
|
|
|
+ struct atmel_mci *host;
|
|
|
|
|
+
|
|
|
|
|
+ host = (struct atmel_mci *)data;
|
|
|
|
|
+
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "software timeout\n");
|
|
|
|
|
+
|
|
|
|
|
+ if (host->mrq->cmd->data) {
|
|
|
|
|
+ host->mrq->cmd->data->error = -ETIMEDOUT;
|
|
|
|
|
+ host->data = NULL;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ host->mrq->cmd->error = -ETIMEDOUT;
|
|
|
|
|
+ host->cmd = NULL;
|
|
|
|
|
+ }
|
|
|
|
|
+ host->need_reset = 1;
|
|
|
|
|
+ host->state = STATE_END_REQUEST;
|
|
|
|
|
+ smp_wmb();
|
|
|
|
|
+ tasklet_schedule(&host->tasklet);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
static inline unsigned int atmci_ns_to_clocks(struct atmel_mci *host,
|
|
static inline unsigned int atmci_ns_to_clocks(struct atmel_mci *host,
|
|
|
unsigned int ns)
|
|
unsigned int ns)
|
|
|
{
|
|
{
|
|
@@ -591,6 +630,7 @@ static void atmci_send_command(struct atmel_mci *host,
|
|
|
|
|
|
|
|
static void atmci_send_stop_cmd(struct atmel_mci *host, struct mmc_data *data)
|
|
static void atmci_send_stop_cmd(struct atmel_mci *host, struct mmc_data *data)
|
|
|
{
|
|
{
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "send stop command\n");
|
|
|
atmci_send_command(host, data->stop, host->stop_cmdr);
|
|
atmci_send_command(host, data->stop, host->stop_cmdr);
|
|
|
atmci_writel(host, ATMCI_IER, ATMCI_CMDRDY);
|
|
atmci_writel(host, ATMCI_IER, ATMCI_CMDRDY);
|
|
|
}
|
|
}
|
|
@@ -603,6 +643,7 @@ static void atmci_pdc_set_single_buf(struct atmel_mci *host,
|
|
|
enum atmci_xfer_dir dir, enum atmci_pdc_buf buf_nb)
|
|
enum atmci_xfer_dir dir, enum atmci_pdc_buf buf_nb)
|
|
|
{
|
|
{
|
|
|
u32 pointer_reg, counter_reg;
|
|
u32 pointer_reg, counter_reg;
|
|
|
|
|
+ unsigned int buf_size;
|
|
|
|
|
|
|
|
if (dir == XFER_RECEIVE) {
|
|
if (dir == XFER_RECEIVE) {
|
|
|
pointer_reg = ATMEL_PDC_RPR;
|
|
pointer_reg = ATMEL_PDC_RPR;
|
|
@@ -617,8 +658,15 @@ static void atmci_pdc_set_single_buf(struct atmel_mci *host,
|
|
|
counter_reg += ATMEL_PDC_SCND_BUF_OFF;
|
|
counter_reg += ATMEL_PDC_SCND_BUF_OFF;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- atmci_writel(host, pointer_reg, sg_dma_address(host->sg));
|
|
|
|
|
- if (host->data_size <= sg_dma_len(host->sg)) {
|
|
|
|
|
|
|
+ if (!host->caps.has_rwproof) {
|
|
|
|
|
+ buf_size = host->buf_size;
|
|
|
|
|
+ atmci_writel(host, pointer_reg, host->buf_phys_addr);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ buf_size = sg_dma_len(host->sg);
|
|
|
|
|
+ atmci_writel(host, pointer_reg, sg_dma_address(host->sg));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (host->data_size <= buf_size) {
|
|
|
if (host->data_size & 0x3) {
|
|
if (host->data_size & 0x3) {
|
|
|
/* If size is different from modulo 4, transfer bytes */
|
|
/* If size is different from modulo 4, transfer bytes */
|
|
|
atmci_writel(host, counter_reg, host->data_size);
|
|
atmci_writel(host, counter_reg, host->data_size);
|
|
@@ -670,7 +718,20 @@ static void atmci_pdc_cleanup(struct atmel_mci *host)
|
|
|
*/
|
|
*/
|
|
|
static void atmci_pdc_complete(struct atmel_mci *host)
|
|
static void atmci_pdc_complete(struct atmel_mci *host)
|
|
|
{
|
|
{
|
|
|
|
|
+ int transfer_size = host->data->blocks * host->data->blksz;
|
|
|
|
|
+ int i;
|
|
|
|
|
+
|
|
|
atmci_writel(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
|
|
atmci_writel(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
|
|
|
|
|
+
|
|
|
|
|
+ if ((!host->caps.has_rwproof)
|
|
|
|
|
+ && (host->data->flags & MMC_DATA_READ)) {
|
|
|
|
|
+ if (host->caps.has_bad_data_ordering)
|
|
|
|
|
+ for (i = 0; i < transfer_size; i++)
|
|
|
|
|
+ host->buffer[i] = swab32(host->buffer[i]);
|
|
|
|
|
+ sg_copy_from_buffer(host->data->sg, host->data->sg_len,
|
|
|
|
|
+ host->buffer, transfer_size);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
atmci_pdc_cleanup(host);
|
|
atmci_pdc_cleanup(host);
|
|
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -678,9 +739,10 @@ static void atmci_pdc_complete(struct atmel_mci *host)
|
|
|
* to send the stop command or waiting for NBUSY in this case.
|
|
* to send the stop command or waiting for NBUSY in this case.
|
|
|
*/
|
|
*/
|
|
|
if (host->data) {
|
|
if (host->data) {
|
|
|
|
|
+ dev_dbg(&host->pdev->dev,
|
|
|
|
|
+ "(%s) set pending xfer complete\n", __func__);
|
|
|
atmci_set_pending(host, EVENT_XFER_COMPLETE);
|
|
atmci_set_pending(host, EVENT_XFER_COMPLETE);
|
|
|
tasklet_schedule(&host->tasklet);
|
|
tasklet_schedule(&host->tasklet);
|
|
|
- atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY);
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -716,6 +778,8 @@ static void atmci_dma_complete(void *arg)
|
|
|
* to send the stop command or waiting for NBUSY in this case.
|
|
* to send the stop command or waiting for NBUSY in this case.
|
|
|
*/
|
|
*/
|
|
|
if (data) {
|
|
if (data) {
|
|
|
|
|
+ dev_dbg(&host->pdev->dev,
|
|
|
|
|
+ "(%s) set pending xfer complete\n", __func__);
|
|
|
atmci_set_pending(host, EVENT_XFER_COMPLETE);
|
|
atmci_set_pending(host, EVENT_XFER_COMPLETE);
|
|
|
tasklet_schedule(&host->tasklet);
|
|
tasklet_schedule(&host->tasklet);
|
|
|
|
|
|
|
@@ -791,6 +855,7 @@ atmci_prepare_data_pdc(struct atmel_mci *host, struct mmc_data *data)
|
|
|
u32 iflags, tmp;
|
|
u32 iflags, tmp;
|
|
|
unsigned int sg_len;
|
|
unsigned int sg_len;
|
|
|
enum dma_data_direction dir;
|
|
enum dma_data_direction dir;
|
|
|
|
|
+ int i;
|
|
|
|
|
|
|
|
data->error = -EINPROGRESS;
|
|
data->error = -EINPROGRESS;
|
|
|
|
|
|
|
@@ -806,7 +871,7 @@ atmci_prepare_data_pdc(struct atmel_mci *host, struct mmc_data *data)
|
|
|
iflags |= ATMCI_ENDRX | ATMCI_RXBUFF;
|
|
iflags |= ATMCI_ENDRX | ATMCI_RXBUFF;
|
|
|
} else {
|
|
} else {
|
|
|
dir = DMA_TO_DEVICE;
|
|
dir = DMA_TO_DEVICE;
|
|
|
- iflags |= ATMCI_ENDTX | ATMCI_TXBUFE;
|
|
|
|
|
|
|
+ iflags |= ATMCI_ENDTX | ATMCI_TXBUFE | ATMCI_BLKE;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Set BLKLEN */
|
|
/* Set BLKLEN */
|
|
@@ -818,6 +883,16 @@ atmci_prepare_data_pdc(struct atmel_mci *host, struct mmc_data *data)
|
|
|
/* Configure PDC */
|
|
/* Configure PDC */
|
|
|
host->data_size = data->blocks * data->blksz;
|
|
host->data_size = data->blocks * data->blksz;
|
|
|
sg_len = dma_map_sg(&host->pdev->dev, data->sg, data->sg_len, dir);
|
|
sg_len = dma_map_sg(&host->pdev->dev, data->sg, data->sg_len, dir);
|
|
|
|
|
+
|
|
|
|
|
+ if ((!host->caps.has_rwproof)
|
|
|
|
|
+ && (host->data->flags & MMC_DATA_WRITE)) {
|
|
|
|
|
+ sg_copy_to_buffer(host->data->sg, host->data->sg_len,
|
|
|
|
|
+ host->buffer, host->data_size);
|
|
|
|
|
+ if (host->caps.has_bad_data_ordering)
|
|
|
|
|
+ for (i = 0; i < host->data_size; i++)
|
|
|
|
|
+ host->buffer[i] = swab32(host->buffer[i]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if (host->data_size)
|
|
if (host->data_size)
|
|
|
atmci_pdc_set_both_buf(host,
|
|
atmci_pdc_set_both_buf(host,
|
|
|
((dir == DMA_FROM_DEVICE) ? XFER_RECEIVE : XFER_TRANSMIT));
|
|
((dir == DMA_FROM_DEVICE) ? XFER_RECEIVE : XFER_TRANSMIT));
|
|
@@ -931,6 +1006,8 @@ atmci_submit_data_dma(struct atmel_mci *host, struct mmc_data *data)
|
|
|
|
|
|
|
|
static void atmci_stop_transfer(struct atmel_mci *host)
|
|
static void atmci_stop_transfer(struct atmel_mci *host)
|
|
|
{
|
|
{
|
|
|
|
|
+ dev_dbg(&host->pdev->dev,
|
|
|
|
|
+ "(%s) set pending xfer complete\n", __func__);
|
|
|
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);
|
|
|
}
|
|
}
|
|
@@ -940,8 +1017,7 @@ static void atmci_stop_transfer(struct atmel_mci *host)
|
|
|
*/
|
|
*/
|
|
|
static void atmci_stop_transfer_pdc(struct atmel_mci *host)
|
|
static void atmci_stop_transfer_pdc(struct atmel_mci *host)
|
|
|
{
|
|
{
|
|
|
- atmci_set_pending(host, EVENT_XFER_COMPLETE);
|
|
|
|
|
- atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY);
|
|
|
|
|
|
|
+ atmci_writel(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void atmci_stop_transfer_dma(struct atmel_mci *host)
|
|
static void atmci_stop_transfer_dma(struct atmel_mci *host)
|
|
@@ -953,6 +1029,8 @@ static void atmci_stop_transfer_dma(struct atmel_mci *host)
|
|
|
atmci_dma_cleanup(host);
|
|
atmci_dma_cleanup(host);
|
|
|
} else {
|
|
} else {
|
|
|
/* Data transfer was stopped by the interrupt handler */
|
|
/* Data transfer was stopped by the interrupt handler */
|
|
|
|
|
+ dev_dbg(&host->pdev->dev,
|
|
|
|
|
+ "(%s) set pending xfer complete\n", __func__);
|
|
|
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);
|
|
|
}
|
|
}
|
|
@@ -977,9 +1055,12 @@ static void atmci_start_request(struct atmel_mci *host,
|
|
|
|
|
|
|
|
host->pending_events = 0;
|
|
host->pending_events = 0;
|
|
|
host->completed_events = 0;
|
|
host->completed_events = 0;
|
|
|
|
|
+ host->cmd_status = 0;
|
|
|
host->data_status = 0;
|
|
host->data_status = 0;
|
|
|
|
|
|
|
|
- if (host->need_reset) {
|
|
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "start request: cmd %u\n", mrq->cmd->opcode);
|
|
|
|
|
+
|
|
|
|
|
+ if (host->need_reset || host->caps.need_reset_after_xfer) {
|
|
|
iflags = atmci_readl(host, ATMCI_IMR);
|
|
iflags = atmci_readl(host, ATMCI_IMR);
|
|
|
iflags &= (ATMCI_SDIOIRQA | ATMCI_SDIOIRQB);
|
|
iflags &= (ATMCI_SDIOIRQA | ATMCI_SDIOIRQB);
|
|
|
atmci_writel(host, ATMCI_CR, ATMCI_CR_SWRST);
|
|
atmci_writel(host, ATMCI_CR, ATMCI_CR_SWRST);
|
|
@@ -994,7 +1075,7 @@ static void atmci_start_request(struct atmel_mci *host,
|
|
|
|
|
|
|
|
iflags = atmci_readl(host, ATMCI_IMR);
|
|
iflags = atmci_readl(host, ATMCI_IMR);
|
|
|
if (iflags & ~(ATMCI_SDIOIRQA | ATMCI_SDIOIRQB))
|
|
if (iflags & ~(ATMCI_SDIOIRQA | ATMCI_SDIOIRQB))
|
|
|
- dev_warn(&slot->mmc->class_dev, "WARNING: IMR=0x%08x\n",
|
|
|
|
|
|
|
+ dev_dbg(&slot->mmc->class_dev, "WARNING: IMR=0x%08x\n",
|
|
|
iflags);
|
|
iflags);
|
|
|
|
|
|
|
|
if (unlikely(test_and_clear_bit(ATMCI_CARD_NEED_INIT, &slot->flags))) {
|
|
if (unlikely(test_and_clear_bit(ATMCI_CARD_NEED_INIT, &slot->flags))) {
|
|
@@ -1043,6 +1124,8 @@ static void atmci_start_request(struct atmel_mci *host,
|
|
|
* prepared yet.)
|
|
* prepared yet.)
|
|
|
*/
|
|
*/
|
|
|
atmci_writel(host, ATMCI_IER, iflags);
|
|
atmci_writel(host, ATMCI_IER, iflags);
|
|
|
|
|
+
|
|
|
|
|
+ mod_timer(&host->timer, jiffies + msecs_to_jiffies(2000));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void atmci_queue_request(struct atmel_mci *host,
|
|
static void atmci_queue_request(struct atmel_mci *host,
|
|
@@ -1057,6 +1140,7 @@ static void atmci_queue_request(struct atmel_mci *host,
|
|
|
host->state = STATE_SENDING_CMD;
|
|
host->state = STATE_SENDING_CMD;
|
|
|
atmci_start_request(host, slot);
|
|
atmci_start_request(host, slot);
|
|
|
} else {
|
|
} else {
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "queue request\n");
|
|
|
list_add_tail(&slot->queue_node, &host->queue);
|
|
list_add_tail(&slot->queue_node, &host->queue);
|
|
|
}
|
|
}
|
|
|
spin_unlock_bh(&host->lock);
|
|
spin_unlock_bh(&host->lock);
|
|
@@ -1069,6 +1153,7 @@ static void atmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
|
|
struct mmc_data *data;
|
|
struct mmc_data *data;
|
|
|
|
|
|
|
|
WARN_ON(slot->mrq);
|
|
WARN_ON(slot->mrq);
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "MRQ: cmd %u\n", mrq->cmd->opcode);
|
|
|
|
|
|
|
|
/*
|
|
/*
|
|
|
* We may "know" the card is gone even though there's still an
|
|
* We may "know" the card is gone even though there's still an
|
|
@@ -1308,6 +1393,8 @@ static void atmci_request_end(struct atmel_mci *host, struct mmc_request *mrq)
|
|
|
host->state = STATE_IDLE;
|
|
host->state = STATE_IDLE;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ del_timer(&host->timer);
|
|
|
|
|
+
|
|
|
spin_unlock(&host->lock);
|
|
spin_unlock(&host->lock);
|
|
|
mmc_request_done(prev_mmc, mrq);
|
|
mmc_request_done(prev_mmc, mrq);
|
|
|
spin_lock(&host->lock);
|
|
spin_lock(&host->lock);
|
|
@@ -1330,21 +1417,13 @@ static void atmci_command_complete(struct atmel_mci *host,
|
|
|
cmd->error = -EILSEQ;
|
|
cmd->error = -EILSEQ;
|
|
|
else if (status & (ATMCI_RINDE | ATMCI_RDIRE | ATMCI_RENDE))
|
|
else if (status & (ATMCI_RINDE | ATMCI_RDIRE | ATMCI_RENDE))
|
|
|
cmd->error = -EIO;
|
|
cmd->error = -EIO;
|
|
|
- else
|
|
|
|
|
- cmd->error = 0;
|
|
|
|
|
-
|
|
|
|
|
- if (cmd->error) {
|
|
|
|
|
- dev_dbg(&host->pdev->dev,
|
|
|
|
|
- "command error: status=0x%08x\n", status);
|
|
|
|
|
-
|
|
|
|
|
- if (cmd->data) {
|
|
|
|
|
- host->stop_transfer(host);
|
|
|
|
|
- host->data = NULL;
|
|
|
|
|
- atmci_writel(host, ATMCI_IDR, ATMCI_NOTBUSY
|
|
|
|
|
- | ATMCI_TXRDY | ATMCI_RXRDY
|
|
|
|
|
- | ATMCI_DATA_ERROR_FLAGS);
|
|
|
|
|
|
|
+ else if (host->mrq->data && (host->mrq->data->blksz & 3)) {
|
|
|
|
|
+ if (host->caps.need_blksz_mul_4) {
|
|
|
|
|
+ cmd->error = -EINVAL;
|
|
|
|
|
+ host->need_reset = 1;
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
|
|
+ } else
|
|
|
|
|
+ cmd->error = 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void atmci_detect_change(unsigned long data)
|
|
static void atmci_detect_change(unsigned long data)
|
|
@@ -1407,23 +1486,21 @@ static void atmci_detect_change(unsigned long data)
|
|
|
break;
|
|
break;
|
|
|
case STATE_SENDING_CMD:
|
|
case STATE_SENDING_CMD:
|
|
|
mrq->cmd->error = -ENOMEDIUM;
|
|
mrq->cmd->error = -ENOMEDIUM;
|
|
|
- if (!mrq->data)
|
|
|
|
|
- break;
|
|
|
|
|
- /* fall through */
|
|
|
|
|
- case STATE_SENDING_DATA:
|
|
|
|
|
|
|
+ if (mrq->data)
|
|
|
|
|
+ host->stop_transfer(host);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case STATE_DATA_XFER:
|
|
|
mrq->data->error = -ENOMEDIUM;
|
|
mrq->data->error = -ENOMEDIUM;
|
|
|
host->stop_transfer(host);
|
|
host->stop_transfer(host);
|
|
|
break;
|
|
break;
|
|
|
- case STATE_DATA_BUSY:
|
|
|
|
|
- case STATE_DATA_ERROR:
|
|
|
|
|
- if (mrq->data->error == -EINPROGRESS)
|
|
|
|
|
- mrq->data->error = -ENOMEDIUM;
|
|
|
|
|
- if (!mrq->stop)
|
|
|
|
|
- break;
|
|
|
|
|
- /* fall through */
|
|
|
|
|
|
|
+ case STATE_WAITING_NOTBUSY:
|
|
|
|
|
+ mrq->data->error = -ENOMEDIUM;
|
|
|
|
|
+ break;
|
|
|
case STATE_SENDING_STOP:
|
|
case STATE_SENDING_STOP:
|
|
|
mrq->stop->error = -ENOMEDIUM;
|
|
mrq->stop->error = -ENOMEDIUM;
|
|
|
break;
|
|
break;
|
|
|
|
|
+ case STATE_END_REQUEST:
|
|
|
|
|
+ break;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
atmci_request_end(host, mrq);
|
|
atmci_request_end(host, mrq);
|
|
@@ -1451,7 +1528,6 @@ static void atmci_tasklet_func(unsigned long priv)
|
|
|
struct atmel_mci *host = (struct atmel_mci *)priv;
|
|
struct atmel_mci *host = (struct atmel_mci *)priv;
|
|
|
struct mmc_request *mrq = host->mrq;
|
|
struct mmc_request *mrq = host->mrq;
|
|
|
struct mmc_data *data = host->data;
|
|
struct mmc_data *data = host->data;
|
|
|
- struct mmc_command *cmd = host->cmd;
|
|
|
|
|
enum atmel_mci_state state = host->state;
|
|
enum atmel_mci_state state = host->state;
|
|
|
enum atmel_mci_state prev_state;
|
|
enum atmel_mci_state prev_state;
|
|
|
u32 status;
|
|
u32 status;
|
|
@@ -1467,107 +1543,186 @@ static void atmci_tasklet_func(unsigned long priv)
|
|
|
|
|
|
|
|
do {
|
|
do {
|
|
|
prev_state = state;
|
|
prev_state = state;
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "FSM: state=%d\n", state);
|
|
|
|
|
|
|
|
switch (state) {
|
|
switch (state) {
|
|
|
case STATE_IDLE:
|
|
case STATE_IDLE:
|
|
|
break;
|
|
break;
|
|
|
|
|
|
|
|
case STATE_SENDING_CMD:
|
|
case STATE_SENDING_CMD:
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Command has been sent, we are waiting for command
|
|
|
|
|
+ * ready. Then we have three next states possible:
|
|
|
|
|
+ * END_REQUEST by default, WAITING_NOTBUSY if it's a
|
|
|
|
|
+ * command needing it or DATA_XFER if there is data.
|
|
|
|
|
+ */
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "FSM: cmd ready?\n");
|
|
|
if (!atmci_test_and_clear_pending(host,
|
|
if (!atmci_test_and_clear_pending(host,
|
|
|
- EVENT_CMD_COMPLETE))
|
|
|
|
|
|
|
+ EVENT_CMD_RDY))
|
|
|
break;
|
|
break;
|
|
|
|
|
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "set completed cmd ready\n");
|
|
|
host->cmd = NULL;
|
|
host->cmd = NULL;
|
|
|
- atmci_set_completed(host, EVENT_CMD_COMPLETE);
|
|
|
|
|
|
|
+ atmci_set_completed(host, EVENT_CMD_RDY);
|
|
|
atmci_command_complete(host, mrq->cmd);
|
|
atmci_command_complete(host, mrq->cmd);
|
|
|
- if (!mrq->data || cmd->error) {
|
|
|
|
|
- atmci_request_end(host, host->mrq);
|
|
|
|
|
- goto unlock;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (mrq->data) {
|
|
|
|
|
+ dev_dbg(&host->pdev->dev,
|
|
|
|
|
+ "command with data transfer");
|
|
|
|
|
+ /*
|
|
|
|
|
+ * If there is a command error don't start
|
|
|
|
|
+ * data transfer.
|
|
|
|
|
+ */
|
|
|
|
|
+ if (mrq->cmd->error) {
|
|
|
|
|
+ host->stop_transfer(host);
|
|
|
|
|
+ host->data = NULL;
|
|
|
|
|
+ atmci_writel(host, ATMCI_IDR,
|
|
|
|
|
+ ATMCI_TXRDY | ATMCI_RXRDY
|
|
|
|
|
+ | ATMCI_DATA_ERROR_FLAGS);
|
|
|
|
|
+ state = STATE_END_REQUEST;
|
|
|
|
|
+ } else
|
|
|
|
|
+ state = STATE_DATA_XFER;
|
|
|
|
|
+ } else if ((!mrq->data) && (mrq->cmd->flags & MMC_RSP_BUSY)) {
|
|
|
|
|
+ dev_dbg(&host->pdev->dev,
|
|
|
|
|
+ "command response need waiting notbusy");
|
|
|
|
|
+ atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY);
|
|
|
|
|
+ state = STATE_WAITING_NOTBUSY;
|
|
|
|
|
+ } else
|
|
|
|
|
+ state = STATE_END_REQUEST;
|
|
|
|
|
|
|
|
- prev_state = state = STATE_SENDING_DATA;
|
|
|
|
|
- /* fall through */
|
|
|
|
|
|
|
+ break;
|
|
|
|
|
|
|
|
- case STATE_SENDING_DATA:
|
|
|
|
|
|
|
+ case STATE_DATA_XFER:
|
|
|
if (atmci_test_and_clear_pending(host,
|
|
if (atmci_test_and_clear_pending(host,
|
|
|
EVENT_DATA_ERROR)) {
|
|
EVENT_DATA_ERROR)) {
|
|
|
- host->stop_transfer(host);
|
|
|
|
|
- if (data->stop)
|
|
|
|
|
- atmci_send_stop_cmd(host, data);
|
|
|
|
|
- state = STATE_DATA_ERROR;
|
|
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "set completed data error\n");
|
|
|
|
|
+ atmci_set_completed(host, EVENT_DATA_ERROR);
|
|
|
|
|
+ state = STATE_END_REQUEST;
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /*
|
|
|
|
|
+ * A data transfer is in progress. The event expected
|
|
|
|
|
+ * to move to the next state depends of data transfer
|
|
|
|
|
+ * type (PDC or DMA). Once transfer done we can move
|
|
|
|
|
+ * to the next step which is WAITING_NOTBUSY in write
|
|
|
|
|
+ * case and directly SENDING_STOP in read case.
|
|
|
|
|
+ */
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "FSM: xfer complete?\n");
|
|
|
if (!atmci_test_and_clear_pending(host,
|
|
if (!atmci_test_and_clear_pending(host,
|
|
|
EVENT_XFER_COMPLETE))
|
|
EVENT_XFER_COMPLETE))
|
|
|
break;
|
|
break;
|
|
|
|
|
|
|
|
|
|
+ dev_dbg(&host->pdev->dev,
|
|
|
|
|
+ "(%s) set completed xfer complete\n",
|
|
|
|
|
+ __func__);
|
|
|
atmci_set_completed(host, EVENT_XFER_COMPLETE);
|
|
atmci_set_completed(host, EVENT_XFER_COMPLETE);
|
|
|
- prev_state = state = STATE_DATA_BUSY;
|
|
|
|
|
- /* fall through */
|
|
|
|
|
|
|
|
|
|
- case STATE_DATA_BUSY:
|
|
|
|
|
- if (!atmci_test_and_clear_pending(host,
|
|
|
|
|
- EVENT_DATA_COMPLETE))
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- host->data = NULL;
|
|
|
|
|
- atmci_set_completed(host, EVENT_DATA_COMPLETE);
|
|
|
|
|
- status = host->data_status;
|
|
|
|
|
- if (unlikely(status & ATMCI_DATA_ERROR_FLAGS)) {
|
|
|
|
|
- if (status & ATMCI_DTOE) {
|
|
|
|
|
- dev_dbg(&host->pdev->dev,
|
|
|
|
|
- "data timeout error\n");
|
|
|
|
|
- data->error = -ETIMEDOUT;
|
|
|
|
|
- } else if (status & ATMCI_DCRCE) {
|
|
|
|
|
- dev_dbg(&host->pdev->dev,
|
|
|
|
|
- "data CRC error\n");
|
|
|
|
|
- data->error = -EILSEQ;
|
|
|
|
|
- } else {
|
|
|
|
|
- dev_dbg(&host->pdev->dev,
|
|
|
|
|
- "data FIFO error (status=%08x)\n",
|
|
|
|
|
- status);
|
|
|
|
|
- data->error = -EIO;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (host->data->flags & MMC_DATA_WRITE) {
|
|
|
|
|
+ atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY);
|
|
|
|
|
+ state = STATE_WAITING_NOTBUSY;
|
|
|
|
|
+ } else if (host->mrq->stop) {
|
|
|
|
|
+ atmci_writel(host, ATMCI_IER, ATMCI_CMDRDY);
|
|
|
|
|
+ atmci_send_stop_cmd(host, data);
|
|
|
|
|
+ state = STATE_SENDING_STOP;
|
|
|
} else {
|
|
} else {
|
|
|
|
|
+ host->data = NULL;
|
|
|
data->bytes_xfered = data->blocks * data->blksz;
|
|
data->bytes_xfered = data->blocks * data->blksz;
|
|
|
data->error = 0;
|
|
data->error = 0;
|
|
|
- atmci_writel(host, ATMCI_IDR, ATMCI_DATA_ERROR_FLAGS);
|
|
|
|
|
|
|
+ state = STATE_END_REQUEST;
|
|
|
}
|
|
}
|
|
|
|
|
+ break;
|
|
|
|
|
|
|
|
- if (!data->stop) {
|
|
|
|
|
- atmci_request_end(host, host->mrq);
|
|
|
|
|
- goto unlock;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ case STATE_WAITING_NOTBUSY:
|
|
|
|
|
+ /*
|
|
|
|
|
+ * We can be in the state for two reasons: a command
|
|
|
|
|
+ * requiring waiting not busy signal (stop command
|
|
|
|
|
+ * included) or a write operation. In the latest case,
|
|
|
|
|
+ * we need to send a stop command.
|
|
|
|
|
+ */
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "FSM: not busy?\n");
|
|
|
|
|
+ if (!atmci_test_and_clear_pending(host,
|
|
|
|
|
+ EVENT_NOTBUSY))
|
|
|
|
|
+ break;
|
|
|
|
|
|
|
|
- prev_state = state = STATE_SENDING_STOP;
|
|
|
|
|
- if (!data->error)
|
|
|
|
|
- atmci_send_stop_cmd(host, data);
|
|
|
|
|
- /* fall through */
|
|
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "set completed not busy\n");
|
|
|
|
|
+ atmci_set_completed(host, EVENT_NOTBUSY);
|
|
|
|
|
+
|
|
|
|
|
+ if (host->data) {
|
|
|
|
|
+ /*
|
|
|
|
|
+ * For some commands such as CMD53, even if
|
|
|
|
|
+ * there is data transfer, there is no stop
|
|
|
|
|
+ * command to send.
|
|
|
|
|
+ */
|
|
|
|
|
+ if (host->mrq->stop) {
|
|
|
|
|
+ atmci_writel(host, ATMCI_IER,
|
|
|
|
|
+ ATMCI_CMDRDY);
|
|
|
|
|
+ atmci_send_stop_cmd(host, data);
|
|
|
|
|
+ state = STATE_SENDING_STOP;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ host->data = NULL;
|
|
|
|
|
+ data->bytes_xfered = data->blocks
|
|
|
|
|
+ * data->blksz;
|
|
|
|
|
+ data->error = 0;
|
|
|
|
|
+ state = STATE_END_REQUEST;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else
|
|
|
|
|
+ state = STATE_END_REQUEST;
|
|
|
|
|
+ break;
|
|
|
|
|
|
|
|
case STATE_SENDING_STOP:
|
|
case STATE_SENDING_STOP:
|
|
|
|
|
+ /*
|
|
|
|
|
+ * In this state, it is important to set host->data to
|
|
|
|
|
+ * NULL (which is tested in the waiting notbusy state)
|
|
|
|
|
+ * in order to go to the end request state instead of
|
|
|
|
|
+ * sending stop again.
|
|
|
|
|
+ */
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "FSM: cmd ready?\n");
|
|
|
if (!atmci_test_and_clear_pending(host,
|
|
if (!atmci_test_and_clear_pending(host,
|
|
|
- EVENT_CMD_COMPLETE))
|
|
|
|
|
|
|
+ EVENT_CMD_RDY))
|
|
|
break;
|
|
break;
|
|
|
|
|
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "FSM: cmd ready\n");
|
|
|
host->cmd = NULL;
|
|
host->cmd = NULL;
|
|
|
|
|
+ host->data = NULL;
|
|
|
|
|
+ data->bytes_xfered = data->blocks * data->blksz;
|
|
|
|
|
+ data->error = 0;
|
|
|
atmci_command_complete(host, mrq->stop);
|
|
atmci_command_complete(host, mrq->stop);
|
|
|
- atmci_request_end(host, host->mrq);
|
|
|
|
|
- goto unlock;
|
|
|
|
|
|
|
+ if (mrq->stop->error) {
|
|
|
|
|
+ host->stop_transfer(host);
|
|
|
|
|
+ atmci_writel(host, ATMCI_IDR,
|
|
|
|
|
+ ATMCI_TXRDY | ATMCI_RXRDY
|
|
|
|
|
+ | ATMCI_DATA_ERROR_FLAGS);
|
|
|
|
|
+ state = STATE_END_REQUEST;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY);
|
|
|
|
|
+ state = STATE_WAITING_NOTBUSY;
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
|
|
|
- case STATE_DATA_ERROR:
|
|
|
|
|
- if (!atmci_test_and_clear_pending(host,
|
|
|
|
|
- EVENT_XFER_COMPLETE))
|
|
|
|
|
- break;
|
|
|
|
|
|
|
+ case STATE_END_REQUEST:
|
|
|
|
|
+ atmci_writel(host, ATMCI_IDR, ATMCI_TXRDY | ATMCI_RXRDY
|
|
|
|
|
+ | ATMCI_DATA_ERROR_FLAGS);
|
|
|
|
|
+ status = host->data_status;
|
|
|
|
|
+ if (unlikely(status)) {
|
|
|
|
|
+ host->stop_transfer(host);
|
|
|
|
|
+ host->data = NULL;
|
|
|
|
|
+ if (status & ATMCI_DTOE) {
|
|
|
|
|
+ data->error = -ETIMEDOUT;
|
|
|
|
|
+ } else if (status & ATMCI_DCRCE) {
|
|
|
|
|
+ data->error = -EILSEQ;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ data->error = -EIO;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- state = STATE_DATA_BUSY;
|
|
|
|
|
|
|
+ atmci_request_end(host, host->mrq);
|
|
|
|
|
+ state = STATE_IDLE;
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
} while (state != prev_state);
|
|
} while (state != prev_state);
|
|
|
|
|
|
|
|
host->state = state;
|
|
host->state = state;
|
|
|
|
|
|
|
|
-unlock:
|
|
|
|
|
spin_unlock(&host->lock);
|
|
spin_unlock(&host->lock);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1620,9 +1775,6 @@ static void atmci_read_data_pio(struct atmel_mci *host)
|
|
|
| ATMCI_DATA_ERROR_FLAGS));
|
|
| ATMCI_DATA_ERROR_FLAGS));
|
|
|
host->data_status = status;
|
|
host->data_status = status;
|
|
|
data->bytes_xfered += nbytes;
|
|
data->bytes_xfered += nbytes;
|
|
|
- smp_wmb();
|
|
|
|
|
- atmci_set_pending(host, EVENT_DATA_ERROR);
|
|
|
|
|
- tasklet_schedule(&host->tasklet);
|
|
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
} while (status & ATMCI_RXRDY);
|
|
} while (status & ATMCI_RXRDY);
|
|
@@ -1691,9 +1843,6 @@ static void atmci_write_data_pio(struct atmel_mci *host)
|
|
|
| ATMCI_DATA_ERROR_FLAGS));
|
|
| ATMCI_DATA_ERROR_FLAGS));
|
|
|
host->data_status = status;
|
|
host->data_status = status;
|
|
|
data->bytes_xfered += nbytes;
|
|
data->bytes_xfered += nbytes;
|
|
|
- smp_wmb();
|
|
|
|
|
- atmci_set_pending(host, EVENT_DATA_ERROR);
|
|
|
|
|
- tasklet_schedule(&host->tasklet);
|
|
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
} while (status & ATMCI_TXRDY);
|
|
} while (status & ATMCI_TXRDY);
|
|
@@ -1711,16 +1860,6 @@ static void atmci_write_data_pio(struct atmel_mci *host)
|
|
|
atmci_set_pending(host, EVENT_XFER_COMPLETE);
|
|
atmci_set_pending(host, EVENT_XFER_COMPLETE);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-static void atmci_cmd_interrupt(struct atmel_mci *host, u32 status)
|
|
|
|
|
-{
|
|
|
|
|
- atmci_writel(host, ATMCI_IDR, ATMCI_CMDRDY);
|
|
|
|
|
-
|
|
|
|
|
- host->cmd_status = status;
|
|
|
|
|
- smp_wmb();
|
|
|
|
|
- atmci_set_pending(host, EVENT_CMD_COMPLETE);
|
|
|
|
|
- tasklet_schedule(&host->tasklet);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
static void atmci_sdio_interrupt(struct atmel_mci *host, u32 status)
|
|
static void atmci_sdio_interrupt(struct atmel_mci *host, u32 status)
|
|
|
{
|
|
{
|
|
|
int i;
|
|
int i;
|
|
@@ -1748,17 +1887,21 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id)
|
|
|
break;
|
|
break;
|
|
|
|
|
|
|
|
if (pending & ATMCI_DATA_ERROR_FLAGS) {
|
|
if (pending & ATMCI_DATA_ERROR_FLAGS) {
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "IRQ: data error\n");
|
|
|
atmci_writel(host, ATMCI_IDR, ATMCI_DATA_ERROR_FLAGS
|
|
atmci_writel(host, ATMCI_IDR, ATMCI_DATA_ERROR_FLAGS
|
|
|
- | ATMCI_RXRDY | ATMCI_TXRDY);
|
|
|
|
|
- pending &= atmci_readl(host, ATMCI_IMR);
|
|
|
|
|
|
|
+ | ATMCI_RXRDY | ATMCI_TXRDY
|
|
|
|
|
+ | ATMCI_ENDRX | ATMCI_ENDTX
|
|
|
|
|
+ | ATMCI_RXBUFF | ATMCI_TXBUFE);
|
|
|
|
|
|
|
|
host->data_status = status;
|
|
host->data_status = status;
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "set pending data error\n");
|
|
|
smp_wmb();
|
|
smp_wmb();
|
|
|
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_TXBUFE) {
|
|
if (pending & ATMCI_TXBUFE) {
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "IRQ: tx buffer empty\n");
|
|
|
atmci_writel(host, ATMCI_IDR, ATMCI_TXBUFE);
|
|
atmci_writel(host, ATMCI_IDR, ATMCI_TXBUFE);
|
|
|
atmci_writel(host, ATMCI_IDR, ATMCI_ENDTX);
|
|
atmci_writel(host, ATMCI_IDR, ATMCI_ENDTX);
|
|
|
/*
|
|
/*
|
|
@@ -1774,6 +1917,7 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id)
|
|
|
atmci_pdc_complete(host);
|
|
atmci_pdc_complete(host);
|
|
|
}
|
|
}
|
|
|
} else if (pending & ATMCI_ENDTX) {
|
|
} else if (pending & ATMCI_ENDTX) {
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "IRQ: end of tx buffer\n");
|
|
|
atmci_writel(host, ATMCI_IDR, ATMCI_ENDTX);
|
|
atmci_writel(host, ATMCI_IDR, ATMCI_ENDTX);
|
|
|
|
|
|
|
|
if (host->data_size) {
|
|
if (host->data_size) {
|
|
@@ -1784,6 +1928,7 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (pending & ATMCI_RXBUFF) {
|
|
if (pending & ATMCI_RXBUFF) {
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "IRQ: rx buffer full\n");
|
|
|
atmci_writel(host, ATMCI_IDR, ATMCI_RXBUFF);
|
|
atmci_writel(host, ATMCI_IDR, ATMCI_RXBUFF);
|
|
|
atmci_writel(host, ATMCI_IDR, ATMCI_ENDRX);
|
|
atmci_writel(host, ATMCI_IDR, ATMCI_ENDRX);
|
|
|
/*
|
|
/*
|
|
@@ -1799,6 +1944,7 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id)
|
|
|
atmci_pdc_complete(host);
|
|
atmci_pdc_complete(host);
|
|
|
}
|
|
}
|
|
|
} else if (pending & ATMCI_ENDRX) {
|
|
} else if (pending & ATMCI_ENDRX) {
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "IRQ: end of rx buffer\n");
|
|
|
atmci_writel(host, ATMCI_IDR, ATMCI_ENDRX);
|
|
atmci_writel(host, ATMCI_IDR, ATMCI_ENDRX);
|
|
|
|
|
|
|
|
if (host->data_size) {
|
|
if (host->data_size) {
|
|
@@ -1808,23 +1954,44 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /*
|
|
|
|
|
+ * First mci IPs, so mainly the ones having pdc, have some
|
|
|
|
|
+ * issues with the notbusy signal. You can't get it after
|
|
|
|
|
+ * data transmission if you have not sent a stop command.
|
|
|
|
|
+ * The appropriate workaround is to use the BLKE signal.
|
|
|
|
|
+ */
|
|
|
|
|
+ if (pending & ATMCI_BLKE) {
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "IRQ: blke\n");
|
|
|
|
|
+ atmci_writel(host, ATMCI_IDR, ATMCI_BLKE);
|
|
|
|
|
+ smp_wmb();
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "set pending notbusy\n");
|
|
|
|
|
+ atmci_set_pending(host, EVENT_NOTBUSY);
|
|
|
|
|
+ tasklet_schedule(&host->tasklet);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
if (pending & ATMCI_NOTBUSY) {
|
|
if (pending & ATMCI_NOTBUSY) {
|
|
|
- atmci_writel(host, ATMCI_IDR,
|
|
|
|
|
- ATMCI_DATA_ERROR_FLAGS | ATMCI_NOTBUSY);
|
|
|
|
|
- if (!host->data_status)
|
|
|
|
|
- host->data_status = status;
|
|
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "IRQ: not_busy\n");
|
|
|
|
|
+ atmci_writel(host, ATMCI_IDR, ATMCI_NOTBUSY);
|
|
|
smp_wmb();
|
|
smp_wmb();
|
|
|
- atmci_set_pending(host, EVENT_DATA_COMPLETE);
|
|
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "set pending notbusy\n");
|
|
|
|
|
+ atmci_set_pending(host, EVENT_NOTBUSY);
|
|
|
tasklet_schedule(&host->tasklet);
|
|
tasklet_schedule(&host->tasklet);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
if (pending & ATMCI_RXRDY)
|
|
if (pending & ATMCI_RXRDY)
|
|
|
atmci_read_data_pio(host);
|
|
atmci_read_data_pio(host);
|
|
|
if (pending & ATMCI_TXRDY)
|
|
if (pending & ATMCI_TXRDY)
|
|
|
atmci_write_data_pio(host);
|
|
atmci_write_data_pio(host);
|
|
|
|
|
|
|
|
- if (pending & ATMCI_CMDRDY)
|
|
|
|
|
- atmci_cmd_interrupt(host, status);
|
|
|
|
|
|
|
+ if (pending & ATMCI_CMDRDY) {
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "IRQ: cmd ready\n");
|
|
|
|
|
+ atmci_writel(host, ATMCI_IDR, ATMCI_CMDRDY);
|
|
|
|
|
+ host->cmd_status = status;
|
|
|
|
|
+ smp_wmb();
|
|
|
|
|
+ dev_dbg(&host->pdev->dev, "set pending cmd rdy\n");
|
|
|
|
|
+ atmci_set_pending(host, EVENT_CMD_RDY);
|
|
|
|
|
+ tasklet_schedule(&host->tasklet);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
if (pending & (ATMCI_SDIOIRQA | ATMCI_SDIOIRQB))
|
|
if (pending & (ATMCI_SDIOIRQA | ATMCI_SDIOIRQB))
|
|
|
atmci_sdio_interrupt(host, status);
|
|
atmci_sdio_interrupt(host, status);
|
|
@@ -1877,13 +2044,26 @@ static int __init atmci_init_slot(struct atmel_mci *host,
|
|
|
mmc->caps |= MMC_CAP_SDIO_IRQ;
|
|
mmc->caps |= MMC_CAP_SDIO_IRQ;
|
|
|
if (host->caps.has_highspeed)
|
|
if (host->caps.has_highspeed)
|
|
|
mmc->caps |= MMC_CAP_SD_HIGHSPEED;
|
|
mmc->caps |= MMC_CAP_SD_HIGHSPEED;
|
|
|
- if (slot_data->bus_width >= 4)
|
|
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Without the read/write proof capability, it is strongly suggested to
|
|
|
|
|
+ * use only one bit for data to prevent fifo underruns and overruns
|
|
|
|
|
+ * which will corrupt data.
|
|
|
|
|
+ */
|
|
|
|
|
+ if ((slot_data->bus_width >= 4) && host->caps.has_rwproof)
|
|
|
mmc->caps |= MMC_CAP_4_BIT_DATA;
|
|
mmc->caps |= MMC_CAP_4_BIT_DATA;
|
|
|
|
|
|
|
|
- mmc->max_segs = 64;
|
|
|
|
|
- mmc->max_req_size = 32768 * 512;
|
|
|
|
|
- mmc->max_blk_size = 32768;
|
|
|
|
|
- mmc->max_blk_count = 512;
|
|
|
|
|
|
|
+ if (atmci_get_version(host) < 0x200) {
|
|
|
|
|
+ mmc->max_segs = 256;
|
|
|
|
|
+ mmc->max_blk_size = 4095;
|
|
|
|
|
+ mmc->max_blk_count = 256;
|
|
|
|
|
+ mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
|
|
|
|
|
+ mmc->max_seg_size = mmc->max_blk_size * mmc->max_segs;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ mmc->max_segs = 64;
|
|
|
|
|
+ mmc->max_req_size = 32768 * 512;
|
|
|
|
|
+ mmc->max_blk_size = 32768;
|
|
|
|
|
+ mmc->max_blk_count = 512;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
/* Assume card is present initially */
|
|
/* Assume card is present initially */
|
|
|
set_bit(ATMCI_CARD_PRESENT, &slot->flags);
|
|
set_bit(ATMCI_CARD_PRESENT, &slot->flags);
|
|
@@ -2007,11 +2187,6 @@ static bool atmci_configure_dma(struct atmel_mci *host)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-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 (High Speed MCI) module is not fully compatible with MCI module.
|
|
|
* HSMCI provides DMA support and a new config register but no more supports
|
|
* HSMCI provides DMA support and a new config register but no more supports
|
|
@@ -2032,6 +2207,9 @@ static void __init atmci_get_cap(struct atmel_mci *host)
|
|
|
host->caps.has_highspeed = 0;
|
|
host->caps.has_highspeed = 0;
|
|
|
host->caps.has_rwproof = 0;
|
|
host->caps.has_rwproof = 0;
|
|
|
host->caps.has_odd_clk_div = 0;
|
|
host->caps.has_odd_clk_div = 0;
|
|
|
|
|
+ host->caps.has_bad_data_ordering = 1;
|
|
|
|
|
+ host->caps.need_reset_after_xfer = 1;
|
|
|
|
|
+ host->caps.need_blksz_mul_4 = 1;
|
|
|
|
|
|
|
|
/* keep only major version number */
|
|
/* keep only major version number */
|
|
|
switch (version & 0xf00) {
|
|
switch (version & 0xf00) {
|
|
@@ -2051,7 +2229,11 @@ static void __init atmci_get_cap(struct atmel_mci *host)
|
|
|
host->caps.has_highspeed = 1;
|
|
host->caps.has_highspeed = 1;
|
|
|
case 0x200:
|
|
case 0x200:
|
|
|
host->caps.has_rwproof = 1;
|
|
host->caps.has_rwproof = 1;
|
|
|
|
|
+ host->caps.need_blksz_mul_4 = 0;
|
|
|
case 0x100:
|
|
case 0x100:
|
|
|
|
|
+ host->caps.has_bad_data_ordering = 0;
|
|
|
|
|
+ host->caps.need_reset_after_xfer = 0;
|
|
|
|
|
+ case 0x0:
|
|
|
break;
|
|
break;
|
|
|
default:
|
|
default:
|
|
|
host->caps.has_pdc = 0;
|
|
host->caps.has_pdc = 0;
|
|
@@ -2138,14 +2320,20 @@ static int __init atmci_probe(struct platform_device *pdev)
|
|
|
if (pdata->slot[0].bus_width) {
|
|
if (pdata->slot[0].bus_width) {
|
|
|
ret = atmci_init_slot(host, &pdata->slot[0],
|
|
ret = atmci_init_slot(host, &pdata->slot[0],
|
|
|
0, ATMCI_SDCSEL_SLOT_A, ATMCI_SDIOIRQA);
|
|
0, ATMCI_SDCSEL_SLOT_A, ATMCI_SDIOIRQA);
|
|
|
- if (!ret)
|
|
|
|
|
|
|
+ if (!ret) {
|
|
|
nr_slots++;
|
|
nr_slots++;
|
|
|
|
|
+ host->buf_size = host->slot[0]->mmc->max_req_size;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
if (pdata->slot[1].bus_width) {
|
|
if (pdata->slot[1].bus_width) {
|
|
|
ret = atmci_init_slot(host, &pdata->slot[1],
|
|
ret = atmci_init_slot(host, &pdata->slot[1],
|
|
|
1, ATMCI_SDCSEL_SLOT_B, ATMCI_SDIOIRQB);
|
|
1, ATMCI_SDCSEL_SLOT_B, ATMCI_SDIOIRQB);
|
|
|
- if (!ret)
|
|
|
|
|
|
|
+ if (!ret) {
|
|
|
nr_slots++;
|
|
nr_slots++;
|
|
|
|
|
+ if (host->slot[1]->mmc->max_req_size > host->buf_size)
|
|
|
|
|
+ host->buf_size =
|
|
|
|
|
+ host->slot[1]->mmc->max_req_size;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (!nr_slots) {
|
|
if (!nr_slots) {
|
|
@@ -2153,6 +2341,19 @@ static int __init atmci_probe(struct platform_device *pdev)
|
|
|
goto err_init_slot;
|
|
goto err_init_slot;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ if (!host->caps.has_rwproof) {
|
|
|
|
|
+ host->buffer = dma_alloc_coherent(&pdev->dev, host->buf_size,
|
|
|
|
|
+ &host->buf_phys_addr,
|
|
|
|
|
+ GFP_KERNEL);
|
|
|
|
|
+ if (!host->buffer) {
|
|
|
|
|
+ ret = -ENOMEM;
|
|
|
|
|
+ dev_err(&pdev->dev, "buffer allocation failed\n");
|
|
|
|
|
+ goto err_init_slot;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ setup_timer(&host->timer, atmci_timeout_timer, (unsigned long)host);
|
|
|
|
|
+
|
|
|
dev_info(&pdev->dev,
|
|
dev_info(&pdev->dev,
|
|
|
"Atmel MCI controller at 0x%08lx irq %d, %u slots\n",
|
|
"Atmel MCI controller at 0x%08lx irq %d, %u slots\n",
|
|
|
host->mapbase, irq, nr_slots);
|
|
host->mapbase, irq, nr_slots);
|
|
@@ -2179,6 +2380,10 @@ static int __exit atmci_remove(struct platform_device *pdev)
|
|
|
|
|
|
|
|
platform_set_drvdata(pdev, NULL);
|
|
platform_set_drvdata(pdev, NULL);
|
|
|
|
|
|
|
|
|
|
+ if (host->buffer)
|
|
|
|
|
+ dma_free_coherent(&pdev->dev, host->buf_size,
|
|
|
|
|
+ host->buffer, host->buf_phys_addr);
|
|
|
|
|
+
|
|
|
for (i = 0; i < ATMCI_MAX_NR_SLOTS; i++) {
|
|
for (i = 0; i < ATMCI_MAX_NR_SLOTS; i++) {
|
|
|
if (host->slot[i])
|
|
if (host->slot[i])
|
|
|
atmci_cleanup_slot(host->slot[i], i);
|
|
atmci_cleanup_slot(host->slot[i], i);
|