|
@@ -42,20 +42,86 @@ enum {
|
|
};
|
|
};
|
|
|
|
|
|
enum atmel_mci_state {
|
|
enum atmel_mci_state {
|
|
- STATE_SENDING_CMD = 0,
|
|
|
|
|
|
+ STATE_IDLE = 0,
|
|
|
|
+ STATE_SENDING_CMD,
|
|
STATE_SENDING_DATA,
|
|
STATE_SENDING_DATA,
|
|
STATE_DATA_BUSY,
|
|
STATE_DATA_BUSY,
|
|
STATE_SENDING_STOP,
|
|
STATE_SENDING_STOP,
|
|
STATE_DATA_ERROR,
|
|
STATE_DATA_ERROR,
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+/**
|
|
|
|
+ * struct atmel_mci - MMC controller state shared between all slots
|
|
|
|
+ * @lock: Spinlock protecting the queue and associated data.
|
|
|
|
+ * @regs: Pointer to MMIO registers.
|
|
|
|
+ * @sg: Scatterlist entry currently being processed by PIO code, if any.
|
|
|
|
+ * @pio_offset: Offset into the current scatterlist entry.
|
|
|
|
+ * @cur_slot: The slot which is currently using the controller.
|
|
|
|
+ * @mrq: The request currently being processed on @cur_slot,
|
|
|
|
+ * or NULL if the controller is idle.
|
|
|
|
+ * @cmd: The command currently being sent to the card, or NULL.
|
|
|
|
+ * @data: The data currently being transferred, or NULL if no data
|
|
|
|
+ * transfer is in progress.
|
|
|
|
+ * @cmd_status: Snapshot of SR taken upon completion of the current
|
|
|
|
+ * command. Only valid when EVENT_CMD_COMPLETE is pending.
|
|
|
|
+ * @data_status: Snapshot of SR taken upon completion of the current
|
|
|
|
+ * data transfer. Only valid when EVENT_DATA_COMPLETE or
|
|
|
|
+ * EVENT_DATA_ERROR is pending.
|
|
|
|
+ * @stop_cmdr: Value to be loaded into CMDR when the stop command is
|
|
|
|
+ * to be sent.
|
|
|
|
+ * @tasklet: Tasklet running the request state machine.
|
|
|
|
+ * @pending_events: Bitmask of events flagged by the interrupt handler
|
|
|
|
+ * to be processed by the tasklet.
|
|
|
|
+ * @completed_events: Bitmask of events which the state machine has
|
|
|
|
+ * processed.
|
|
|
|
+ * @state: Tasklet state.
|
|
|
|
+ * @queue: List of slots waiting for access to the controller.
|
|
|
|
+ * @need_clock_update: Update the clock rate before the next request.
|
|
|
|
+ * @need_reset: Reset controller before next request.
|
|
|
|
+ * @mode_reg: Value of the MR register.
|
|
|
|
+ * @bus_hz: The rate of @mck in Hz. This forms the basis for MMC bus
|
|
|
|
+ * rate and timeout calculations.
|
|
|
|
+ * @mapbase: Physical address of the MMIO registers.
|
|
|
|
+ * @mck: The peripheral bus clock hooked up to the MMC controller.
|
|
|
|
+ * @pdev: Platform device associated with the MMC controller.
|
|
|
|
+ * @slot: Slots sharing this MMC controller.
|
|
|
|
+ *
|
|
|
|
+ * Locking
|
|
|
|
+ * =======
|
|
|
|
+ *
|
|
|
|
+ * @lock is a softirq-safe spinlock protecting @queue as well as
|
|
|
|
+ * @cur_slot, @mrq and @state. These must always be updated
|
|
|
|
+ * at the same time while holding @lock.
|
|
|
|
+ *
|
|
|
|
+ * @lock also protects mode_reg and need_clock_update since these are
|
|
|
|
+ * used to synchronize mode register updates with the queue
|
|
|
|
+ * processing.
|
|
|
|
+ *
|
|
|
|
+ * The @mrq field of struct atmel_mci_slot is also protected by @lock,
|
|
|
|
+ * and must always be written at the same time as the slot is added to
|
|
|
|
+ * @queue.
|
|
|
|
+ *
|
|
|
|
+ * @pending_events and @completed_events are accessed using atomic bit
|
|
|
|
+ * operations, so they don't need any locking.
|
|
|
|
+ *
|
|
|
|
+ * None of the fields touched by the interrupt handler need any
|
|
|
|
+ * locking. However, ordering is important: Before EVENT_DATA_ERROR or
|
|
|
|
+ * EVENT_DATA_COMPLETE is set in @pending_events, all data-related
|
|
|
|
+ * interrupts must be disabled and @data_status updated with a
|
|
|
|
+ * snapshot of SR. Similarly, before EVENT_CMD_COMPLETE is set, the
|
|
|
|
+ * CMDRDY interupt must be disabled and @cmd_status updated with a
|
|
|
|
+ * snapshot of SR, and before EVENT_XFER_COMPLETE can be set, the
|
|
|
|
+ * bytes_xfered field of @data must be written. This is ensured by
|
|
|
|
+ * using barriers.
|
|
|
|
+ */
|
|
struct atmel_mci {
|
|
struct atmel_mci {
|
|
- struct mmc_host *mmc;
|
|
|
|
|
|
+ spinlock_t lock;
|
|
void __iomem *regs;
|
|
void __iomem *regs;
|
|
|
|
|
|
struct scatterlist *sg;
|
|
struct scatterlist *sg;
|
|
unsigned int pio_offset;
|
|
unsigned int pio_offset;
|
|
|
|
|
|
|
|
+ struct atmel_mci_slot *cur_slot;
|
|
struct mmc_request *mrq;
|
|
struct mmc_request *mrq;
|
|
struct mmc_command *cmd;
|
|
struct mmc_command *cmd;
|
|
struct mmc_data *data;
|
|
struct mmc_data *data;
|
|
@@ -64,25 +130,59 @@ struct atmel_mci {
|
|
u32 data_status;
|
|
u32 data_status;
|
|
u32 stop_cmdr;
|
|
u32 stop_cmdr;
|
|
|
|
|
|
- u32 mode_reg;
|
|
|
|
- u32 sdc_reg;
|
|
|
|
-
|
|
|
|
struct tasklet_struct tasklet;
|
|
struct tasklet_struct tasklet;
|
|
unsigned long pending_events;
|
|
unsigned long pending_events;
|
|
unsigned long completed_events;
|
|
unsigned long completed_events;
|
|
enum atmel_mci_state state;
|
|
enum atmel_mci_state state;
|
|
|
|
+ struct list_head queue;
|
|
|
|
|
|
- int present;
|
|
|
|
- int detect_pin;
|
|
|
|
- int wp_pin;
|
|
|
|
-
|
|
|
|
- /* For detect pin debouncing */
|
|
|
|
- struct timer_list detect_timer;
|
|
|
|
-
|
|
|
|
|
|
+ bool need_clock_update;
|
|
|
|
+ bool need_reset;
|
|
|
|
+ u32 mode_reg;
|
|
unsigned long bus_hz;
|
|
unsigned long bus_hz;
|
|
unsigned long mapbase;
|
|
unsigned long mapbase;
|
|
struct clk *mck;
|
|
struct clk *mck;
|
|
struct platform_device *pdev;
|
|
struct platform_device *pdev;
|
|
|
|
+
|
|
|
|
+ struct atmel_mci_slot *slot[ATMEL_MCI_MAX_NR_SLOTS];
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * struct atmel_mci_slot - MMC slot state
|
|
|
|
+ * @mmc: The mmc_host representing this slot.
|
|
|
|
+ * @host: The MMC controller this slot is using.
|
|
|
|
+ * @sdc_reg: Value of SDCR to be written before using this slot.
|
|
|
|
+ * @mrq: mmc_request currently being processed or waiting to be
|
|
|
|
+ * processed, or NULL when the slot is idle.
|
|
|
|
+ * @queue_node: List node for placing this node in the @queue list of
|
|
|
|
+ * &struct atmel_mci.
|
|
|
|
+ * @clock: Clock rate configured by set_ios(). Protected by host->lock.
|
|
|
|
+ * @flags: Random state bits associated with the slot.
|
|
|
|
+ * @detect_pin: GPIO pin used for card detection, or negative if not
|
|
|
|
+ * available.
|
|
|
|
+ * @wp_pin: GPIO pin used for card write protect sending, or negative
|
|
|
|
+ * if not available.
|
|
|
|
+ * @detect_timer: Timer used for debouncing @detect_pin interrupts.
|
|
|
|
+ */
|
|
|
|
+struct atmel_mci_slot {
|
|
|
|
+ struct mmc_host *mmc;
|
|
|
|
+ struct atmel_mci *host;
|
|
|
|
+
|
|
|
|
+ u32 sdc_reg;
|
|
|
|
+
|
|
|
|
+ struct mmc_request *mrq;
|
|
|
|
+ struct list_head queue_node;
|
|
|
|
+
|
|
|
|
+ unsigned int clock;
|
|
|
|
+ unsigned long flags;
|
|
|
|
+#define ATMCI_CARD_PRESENT 0
|
|
|
|
+#define ATMCI_CARD_NEED_INIT 1
|
|
|
|
+#define ATMCI_SHUTDOWN 2
|
|
|
|
+
|
|
|
|
+ int detect_pin;
|
|
|
|
+ int wp_pin;
|
|
|
|
+
|
|
|
|
+ struct timer_list detect_timer;
|
|
};
|
|
};
|
|
|
|
|
|
#define atmci_test_and_clear_pending(host, event) \
|
|
#define atmci_test_and_clear_pending(host, event) \
|
|
@@ -98,14 +198,15 @@ struct atmel_mci {
|
|
*/
|
|
*/
|
|
static int atmci_req_show(struct seq_file *s, void *v)
|
|
static int atmci_req_show(struct seq_file *s, void *v)
|
|
{
|
|
{
|
|
- struct atmel_mci *host = s->private;
|
|
|
|
- struct mmc_request *mrq = host->mrq;
|
|
|
|
|
|
+ struct atmel_mci_slot *slot = s->private;
|
|
|
|
+ struct mmc_request *mrq;
|
|
struct mmc_command *cmd;
|
|
struct mmc_command *cmd;
|
|
struct mmc_command *stop;
|
|
struct mmc_command *stop;
|
|
struct mmc_data *data;
|
|
struct mmc_data *data;
|
|
|
|
|
|
/* Make sure we get a consistent snapshot */
|
|
/* Make sure we get a consistent snapshot */
|
|
- spin_lock_irq(&host->mmc->lock);
|
|
|
|
|
|
+ spin_lock_bh(&slot->host->lock);
|
|
|
|
+ mrq = slot->mrq;
|
|
|
|
|
|
if (mrq) {
|
|
if (mrq) {
|
|
cmd = mrq->cmd;
|
|
cmd = mrq->cmd;
|
|
@@ -130,7 +231,7 @@ static int atmci_req_show(struct seq_file *s, void *v)
|
|
stop->resp[2], stop->error);
|
|
stop->resp[2], stop->error);
|
|
}
|
|
}
|
|
|
|
|
|
- spin_unlock_irq(&host->mmc->lock);
|
|
|
|
|
|
+ spin_unlock_bh(&slot->host->lock);
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
@@ -193,12 +294,16 @@ static int atmci_regs_show(struct seq_file *s, void *v)
|
|
if (!buf)
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
return -ENOMEM;
|
|
|
|
|
|
- /* Grab a more or less consistent snapshot */
|
|
|
|
- spin_lock_irq(&host->mmc->lock);
|
|
|
|
|
|
+ /*
|
|
|
|
+ * Grab a more or less consistent snapshot. Note that we're
|
|
|
|
+ * not disabling interrupts, so IMR and SR may not be
|
|
|
|
+ * consistent.
|
|
|
|
+ */
|
|
|
|
+ spin_lock_bh(&host->lock);
|
|
clk_enable(host->mck);
|
|
clk_enable(host->mck);
|
|
memcpy_fromio(buf, host->regs, MCI_REGS_SIZE);
|
|
memcpy_fromio(buf, host->regs, MCI_REGS_SIZE);
|
|
clk_disable(host->mck);
|
|
clk_disable(host->mck);
|
|
- spin_unlock_irq(&host->mmc->lock);
|
|
|
|
|
|
+ spin_unlock_bh(&host->lock);
|
|
|
|
|
|
seq_printf(s, "MR:\t0x%08x%s%s CLKDIV=%u\n",
|
|
seq_printf(s, "MR:\t0x%08x%s%s CLKDIV=%u\n",
|
|
buf[MCI_MR / 4],
|
|
buf[MCI_MR / 4],
|
|
@@ -236,13 +341,13 @@ static const struct file_operations atmci_regs_fops = {
|
|
.release = single_release,
|
|
.release = single_release,
|
|
};
|
|
};
|
|
|
|
|
|
-static void atmci_init_debugfs(struct atmel_mci *host)
|
|
|
|
|
|
+static void atmci_init_debugfs(struct atmel_mci_slot *slot)
|
|
{
|
|
{
|
|
- struct mmc_host *mmc;
|
|
|
|
- struct dentry *root;
|
|
|
|
- struct dentry *node;
|
|
|
|
|
|
+ struct mmc_host *mmc = slot->mmc;
|
|
|
|
+ struct atmel_mci *host = slot->host;
|
|
|
|
+ struct dentry *root;
|
|
|
|
+ struct dentry *node;
|
|
|
|
|
|
- mmc = host->mmc;
|
|
|
|
root = mmc->debugfs_root;
|
|
root = mmc->debugfs_root;
|
|
if (!root)
|
|
if (!root)
|
|
return;
|
|
return;
|
|
@@ -254,7 +359,7 @@ static void atmci_init_debugfs(struct atmel_mci *host)
|
|
if (!node)
|
|
if (!node)
|
|
goto err;
|
|
goto err;
|
|
|
|
|
|
- node = debugfs_create_file("req", S_IRUSR, root, host, &atmci_req_fops);
|
|
|
|
|
|
+ node = debugfs_create_file("req", S_IRUSR, root, slot, &atmci_req_fops);
|
|
if (!node)
|
|
if (!node)
|
|
goto err;
|
|
goto err;
|
|
|
|
|
|
@@ -275,8 +380,7 @@ static void atmci_init_debugfs(struct atmel_mci *host)
|
|
return;
|
|
return;
|
|
|
|
|
|
err:
|
|
err:
|
|
- dev_err(&host->pdev->dev,
|
|
|
|
- "failed to initialize debugfs for controller\n");
|
|
|
|
|
|
+ dev_err(&mmc->class_dev, "failed to initialize debugfs for slot\n");
|
|
}
|
|
}
|
|
|
|
|
|
static inline unsigned int ns_to_clocks(struct atmel_mci *host,
|
|
static inline unsigned int ns_to_clocks(struct atmel_mci *host,
|
|
@@ -286,7 +390,7 @@ static inline unsigned int ns_to_clocks(struct atmel_mci *host,
|
|
}
|
|
}
|
|
|
|
|
|
static void atmci_set_timeout(struct atmel_mci *host,
|
|
static void atmci_set_timeout(struct atmel_mci *host,
|
|
- struct mmc_data *data)
|
|
|
|
|
|
+ struct atmel_mci_slot *slot, struct mmc_data *data)
|
|
{
|
|
{
|
|
static unsigned dtomul_to_shift[] = {
|
|
static unsigned dtomul_to_shift[] = {
|
|
0, 4, 7, 8, 10, 12, 16, 20
|
|
0, 4, 7, 8, 10, 12, 16, 20
|
|
@@ -309,7 +413,7 @@ static void atmci_set_timeout(struct atmel_mci *host,
|
|
dtocyc = 15;
|
|
dtocyc = 15;
|
|
}
|
|
}
|
|
|
|
|
|
- dev_vdbg(&host->mmc->class_dev, "setting timeout to %u cycles\n",
|
|
|
|
|
|
+ dev_vdbg(&slot->mmc->class_dev, "setting timeout to %u cycles\n",
|
|
dtocyc << dtomul_to_shift[dtomul]);
|
|
dtocyc << dtomul_to_shift[dtomul]);
|
|
mci_writel(host, DTOR, (MCI_DTOMUL(dtomul) | MCI_DTOCYC(dtocyc)));
|
|
mci_writel(host, DTOR, (MCI_DTOMUL(dtomul) | MCI_DTOCYC(dtocyc)));
|
|
}
|
|
}
|
|
@@ -362,13 +466,12 @@ static u32 atmci_prepare_command(struct mmc_host *mmc,
|
|
}
|
|
}
|
|
|
|
|
|
static void atmci_start_command(struct atmel_mci *host,
|
|
static void atmci_start_command(struct atmel_mci *host,
|
|
- struct mmc_command *cmd,
|
|
|
|
- u32 cmd_flags)
|
|
|
|
|
|
+ struct mmc_command *cmd, u32 cmd_flags)
|
|
{
|
|
{
|
|
WARN_ON(host->cmd);
|
|
WARN_ON(host->cmd);
|
|
host->cmd = cmd;
|
|
host->cmd = cmd;
|
|
|
|
|
|
- dev_vdbg(&host->mmc->class_dev,
|
|
|
|
|
|
+ dev_vdbg(&host->pdev->dev,
|
|
"start command: ARGR=0x%08x CMDR=0x%08x\n",
|
|
"start command: ARGR=0x%08x CMDR=0x%08x\n",
|
|
cmd->arg, cmd_flags);
|
|
cmd->arg, cmd_flags);
|
|
|
|
|
|
@@ -376,32 +479,19 @@ static void atmci_start_command(struct atmel_mci *host,
|
|
mci_writel(host, CMDR, cmd_flags);
|
|
mci_writel(host, CMDR, cmd_flags);
|
|
}
|
|
}
|
|
|
|
|
|
-static void send_stop_cmd(struct mmc_host *mmc, struct mmc_data *data)
|
|
|
|
|
|
+static void send_stop_cmd(struct atmel_mci *host, struct mmc_data *data)
|
|
{
|
|
{
|
|
- struct atmel_mci *host = mmc_priv(mmc);
|
|
|
|
-
|
|
|
|
atmci_start_command(host, data->stop, host->stop_cmdr);
|
|
atmci_start_command(host, data->stop, host->stop_cmdr);
|
|
mci_writel(host, IER, MCI_CMDRDY);
|
|
mci_writel(host, IER, MCI_CMDRDY);
|
|
}
|
|
}
|
|
|
|
|
|
-static void atmci_request_end(struct mmc_host *mmc, struct mmc_request *mrq)
|
|
|
|
-{
|
|
|
|
- struct atmel_mci *host = mmc_priv(mmc);
|
|
|
|
-
|
|
|
|
- WARN_ON(host->cmd || host->data);
|
|
|
|
- host->mrq = NULL;
|
|
|
|
-
|
|
|
|
- mmc_request_done(mmc, mrq);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
/*
|
|
/*
|
|
* Returns a mask of interrupt flags to be enabled after the whole
|
|
* Returns a mask of interrupt flags to be enabled after the whole
|
|
* request has been prepared.
|
|
* request has been prepared.
|
|
*/
|
|
*/
|
|
-static u32 atmci_submit_data(struct mmc_host *mmc, struct mmc_data *data)
|
|
|
|
|
|
+static u32 atmci_submit_data(struct atmel_mci *host, struct mmc_data *data)
|
|
{
|
|
{
|
|
- struct atmel_mci *host = mmc_priv(mmc);
|
|
|
|
- u32 iflags;
|
|
|
|
|
|
+ u32 iflags;
|
|
|
|
|
|
data->error = -EINPROGRESS;
|
|
data->error = -EINPROGRESS;
|
|
|
|
|
|
@@ -409,10 +499,19 @@ static u32 atmci_submit_data(struct mmc_host *mmc, struct mmc_data *data)
|
|
host->sg = NULL;
|
|
host->sg = NULL;
|
|
host->data = data;
|
|
host->data = data;
|
|
|
|
|
|
- dev_vdbg(&mmc->class_dev, "BLKR=0x%08x\n",
|
|
|
|
- MCI_BCNT(data->blocks) | MCI_BLKLEN(data->blksz));
|
|
|
|
-
|
|
|
|
iflags = ATMCI_DATA_ERROR_FLAGS;
|
|
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->sg = data->sg;
|
|
host->sg = data->sg;
|
|
host->pio_offset = 0;
|
|
host->pio_offset = 0;
|
|
if (data->flags & MMC_DATA_READ)
|
|
if (data->flags & MMC_DATA_READ)
|
|
@@ -423,62 +522,62 @@ static u32 atmci_submit_data(struct mmc_host *mmc, struct mmc_data *data)
|
|
return iflags;
|
|
return iflags;
|
|
}
|
|
}
|
|
|
|
|
|
-static void atmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
|
|
|
|
|
+static void atmci_start_request(struct atmel_mci *host,
|
|
|
|
+ struct atmel_mci_slot *slot)
|
|
{
|
|
{
|
|
- struct atmel_mci *host = mmc_priv(mmc);
|
|
|
|
- struct mmc_data *data;
|
|
|
|
|
|
+ struct mmc_request *mrq;
|
|
struct mmc_command *cmd;
|
|
struct mmc_command *cmd;
|
|
|
|
+ struct mmc_data *data;
|
|
u32 iflags;
|
|
u32 iflags;
|
|
- u32 cmdflags = 0;
|
|
|
|
-
|
|
|
|
- iflags = mci_readl(host, IMR);
|
|
|
|
- if (iflags)
|
|
|
|
- dev_warn(&mmc->class_dev, "WARNING: IMR=0x%08x\n",
|
|
|
|
- mci_readl(host, IMR));
|
|
|
|
-
|
|
|
|
- WARN_ON(host->mrq != NULL);
|
|
|
|
-
|
|
|
|
- /*
|
|
|
|
- * We may "know" the card is gone even though there's still an
|
|
|
|
- * electrical connection. If so, we really need to communicate
|
|
|
|
- * this to the MMC core since there won't be any more
|
|
|
|
- * interrupts as the card is completely removed. Otherwise,
|
|
|
|
- * the MMC core might believe the card is still there even
|
|
|
|
- * though the card was just removed very slowly.
|
|
|
|
- */
|
|
|
|
- if (!host->present) {
|
|
|
|
- mrq->cmd->error = -ENOMEDIUM;
|
|
|
|
- mmc_request_done(mmc, mrq);
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
|
|
+ u32 cmdflags;
|
|
|
|
|
|
|
|
+ mrq = slot->mrq;
|
|
|
|
+ host->cur_slot = slot;
|
|
host->mrq = mrq;
|
|
host->mrq = mrq;
|
|
|
|
+
|
|
host->pending_events = 0;
|
|
host->pending_events = 0;
|
|
host->completed_events = 0;
|
|
host->completed_events = 0;
|
|
- host->state = STATE_SENDING_CMD;
|
|
|
|
|
|
|
|
- /* We don't support multiple blocks of weird lengths. */
|
|
|
|
|
|
+ if (host->need_reset) {
|
|
|
|
+ mci_writel(host, CR, MCI_CR_SWRST);
|
|
|
|
+ mci_writel(host, CR, MCI_CR_MCIEN);
|
|
|
|
+ mci_writel(host, MR, host->mode_reg);
|
|
|
|
+ host->need_reset = false;
|
|
|
|
+ }
|
|
|
|
+ mci_writel(host, SDCR, slot->sdc_reg);
|
|
|
|
+
|
|
|
|
+ iflags = mci_readl(host, IMR);
|
|
|
|
+ if (iflags)
|
|
|
|
+ dev_warn(&slot->mmc->class_dev, "WARNING: IMR=0x%08x\n",
|
|
|
|
+ iflags);
|
|
|
|
+
|
|
|
|
+ if (unlikely(test_and_clear_bit(ATMCI_CARD_NEED_INIT, &slot->flags))) {
|
|
|
|
+ /* Send init sequence (74 clock cycles) */
|
|
|
|
+ mci_writel(host, CMDR, MCI_CMDR_SPCMD_INIT);
|
|
|
|
+ while (!(mci_readl(host, SR) & MCI_CMDRDY))
|
|
|
|
+ cpu_relax();
|
|
|
|
+ }
|
|
data = mrq->data;
|
|
data = mrq->data;
|
|
if (data) {
|
|
if (data) {
|
|
- if (data->blocks > 1 && data->blksz & 3)
|
|
|
|
- goto fail;
|
|
|
|
- atmci_set_timeout(host, data);
|
|
|
|
|
|
+ atmci_set_timeout(host, slot, data);
|
|
|
|
|
|
/* Must set block count/size before sending command */
|
|
/* Must set block count/size before sending command */
|
|
mci_writel(host, BLKR, MCI_BCNT(data->blocks)
|
|
mci_writel(host, BLKR, MCI_BCNT(data->blocks)
|
|
| MCI_BLKLEN(data->blksz));
|
|
| MCI_BLKLEN(data->blksz));
|
|
|
|
+ dev_vdbg(&slot->mmc->class_dev, "BLKR=0x%08x\n",
|
|
|
|
+ MCI_BCNT(data->blocks) | MCI_BLKLEN(data->blksz));
|
|
}
|
|
}
|
|
|
|
|
|
iflags = MCI_CMDRDY;
|
|
iflags = MCI_CMDRDY;
|
|
cmd = mrq->cmd;
|
|
cmd = mrq->cmd;
|
|
- cmdflags = atmci_prepare_command(mmc, cmd);
|
|
|
|
|
|
+ cmdflags = atmci_prepare_command(slot->mmc, cmd);
|
|
atmci_start_command(host, cmd, cmdflags);
|
|
atmci_start_command(host, cmd, cmdflags);
|
|
|
|
|
|
if (data)
|
|
if (data)
|
|
- iflags |= atmci_submit_data(mmc, data);
|
|
|
|
|
|
+ iflags |= atmci_submit_data(host, data);
|
|
|
|
|
|
if (mrq->stop) {
|
|
if (mrq->stop) {
|
|
- host->stop_cmdr = atmci_prepare_command(mmc, mrq->stop);
|
|
|
|
|
|
+ host->stop_cmdr = atmci_prepare_command(slot->mmc, mrq->stop);
|
|
host->stop_cmdr |= MCI_CMDR_STOP_XFER;
|
|
host->stop_cmdr |= MCI_CMDR_STOP_XFER;
|
|
if (!(data->flags & MMC_DATA_WRITE))
|
|
if (!(data->flags & MMC_DATA_WRITE))
|
|
host->stop_cmdr |= MCI_CMDR_TRDIR_READ;
|
|
host->stop_cmdr |= MCI_CMDR_TRDIR_READ;
|
|
@@ -495,65 +594,156 @@ static void atmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
|
* prepared yet.)
|
|
* prepared yet.)
|
|
*/
|
|
*/
|
|
mci_writel(host, IER, iflags);
|
|
mci_writel(host, IER, iflags);
|
|
|
|
+}
|
|
|
|
|
|
- return;
|
|
|
|
|
|
+static void atmci_queue_request(struct atmel_mci *host,
|
|
|
|
+ struct atmel_mci_slot *slot, struct mmc_request *mrq)
|
|
|
|
+{
|
|
|
|
+ dev_vdbg(&slot->mmc->class_dev, "queue request: state=%d\n",
|
|
|
|
+ host->state);
|
|
|
|
+
|
|
|
|
+ spin_lock_bh(&host->lock);
|
|
|
|
+ slot->mrq = mrq;
|
|
|
|
+ if (host->state == STATE_IDLE) {
|
|
|
|
+ host->state = STATE_SENDING_CMD;
|
|
|
|
+ atmci_start_request(host, slot);
|
|
|
|
+ } else {
|
|
|
|
+ list_add_tail(&slot->queue_node, &host->queue);
|
|
|
|
+ }
|
|
|
|
+ spin_unlock_bh(&host->lock);
|
|
|
|
+}
|
|
|
|
|
|
-fail:
|
|
|
|
- host->mrq = NULL;
|
|
|
|
- mrq->cmd->error = -EINVAL;
|
|
|
|
- mmc_request_done(mmc, mrq);
|
|
|
|
|
|
+static void atmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
|
|
|
+{
|
|
|
|
+ struct atmel_mci_slot *slot = mmc_priv(mmc);
|
|
|
|
+ struct atmel_mci *host = slot->host;
|
|
|
|
+ struct mmc_data *data;
|
|
|
|
+
|
|
|
|
+ WARN_ON(slot->mrq);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * We may "know" the card is gone even though there's still an
|
|
|
|
+ * electrical connection. If so, we really need to communicate
|
|
|
|
+ * this to the MMC core since there won't be any more
|
|
|
|
+ * interrupts as the card is completely removed. Otherwise,
|
|
|
|
+ * the MMC core might believe the card is still there even
|
|
|
|
+ * though the card was just removed very slowly.
|
|
|
|
+ */
|
|
|
|
+ if (!test_bit(ATMCI_CARD_PRESENT, &slot->flags)) {
|
|
|
|
+ mrq->cmd->error = -ENOMEDIUM;
|
|
|
|
+ mmc_request_done(mmc, mrq);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* We don't support multiple blocks of weird lengths. */
|
|
|
|
+ data = mrq->data;
|
|
|
|
+ if (data && data->blocks > 1 && data->blksz & 3) {
|
|
|
|
+ mrq->cmd->error = -EINVAL;
|
|
|
|
+ mmc_request_done(mmc, mrq);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ atmci_queue_request(host, slot, mrq);
|
|
}
|
|
}
|
|
|
|
|
|
static void atmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
|
static void atmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
|
{
|
|
{
|
|
- struct atmel_mci *host = mmc_priv(mmc);
|
|
|
|
|
|
+ struct atmel_mci_slot *slot = mmc_priv(mmc);
|
|
|
|
+ struct atmel_mci *host = slot->host;
|
|
|
|
+ unsigned int i;
|
|
|
|
|
|
- host->sdc_reg &= ~MCI_SDCBUS_MASK;
|
|
|
|
|
|
+ slot->sdc_reg &= ~MCI_SDCBUS_MASK;
|
|
switch (ios->bus_width) {
|
|
switch (ios->bus_width) {
|
|
case MMC_BUS_WIDTH_1:
|
|
case MMC_BUS_WIDTH_1:
|
|
- host->sdc_reg |= MCI_SDCBUS_1BIT;
|
|
|
|
|
|
+ slot->sdc_reg |= MCI_SDCBUS_1BIT;
|
|
break;
|
|
break;
|
|
case MMC_BUS_WIDTH_4:
|
|
case MMC_BUS_WIDTH_4:
|
|
- host->sdc_reg = MCI_SDCBUS_4BIT;
|
|
|
|
|
|
+ slot->sdc_reg = MCI_SDCBUS_4BIT;
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
if (ios->clock) {
|
|
if (ios->clock) {
|
|
|
|
+ unsigned int clock_min = ~0U;
|
|
u32 clkdiv;
|
|
u32 clkdiv;
|
|
|
|
|
|
- if (!host->mode_reg)
|
|
|
|
|
|
+ spin_lock_bh(&host->lock);
|
|
|
|
+ if (!host->mode_reg) {
|
|
clk_enable(host->mck);
|
|
clk_enable(host->mck);
|
|
|
|
+ mci_writel(host, CR, MCI_CR_SWRST);
|
|
|
|
+ mci_writel(host, CR, MCI_CR_MCIEN);
|
|
|
|
+ }
|
|
|
|
|
|
- /* Set clock rate */
|
|
|
|
- clkdiv = DIV_ROUND_UP(host->bus_hz, 2 * ios->clock) - 1;
|
|
|
|
|
|
+ /*
|
|
|
|
+ * Use mirror of ios->clock to prevent race with mmc
|
|
|
|
+ * core ios update when finding the minimum.
|
|
|
|
+ */
|
|
|
|
+ slot->clock = ios->clock;
|
|
|
|
+ for (i = 0; i < ATMEL_MCI_MAX_NR_SLOTS; i++) {
|
|
|
|
+ if (host->slot[i] && host->slot[i]->clock
|
|
|
|
+ && host->slot[i]->clock < clock_min)
|
|
|
|
+ clock_min = host->slot[i]->clock;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Calculate clock divider */
|
|
|
|
+ clkdiv = DIV_ROUND_UP(host->bus_hz, 2 * clock_min) - 1;
|
|
if (clkdiv > 255) {
|
|
if (clkdiv > 255) {
|
|
dev_warn(&mmc->class_dev,
|
|
dev_warn(&mmc->class_dev,
|
|
"clock %u too slow; using %lu\n",
|
|
"clock %u too slow; using %lu\n",
|
|
- ios->clock, host->bus_hz / (2 * 256));
|
|
|
|
|
|
+ clock_min, host->bus_hz / (2 * 256));
|
|
clkdiv = 255;
|
|
clkdiv = 255;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /*
|
|
|
|
+ * WRPROOF and RDPROOF prevent overruns/underruns by
|
|
|
|
+ * stopping the clock when the FIFO is full/empty.
|
|
|
|
+ * This state is not expected to last for long.
|
|
|
|
+ */
|
|
host->mode_reg = MCI_MR_CLKDIV(clkdiv) | MCI_MR_WRPROOF
|
|
host->mode_reg = MCI_MR_CLKDIV(clkdiv) | MCI_MR_WRPROOF
|
|
| MCI_MR_RDPROOF;
|
|
| MCI_MR_RDPROOF;
|
|
|
|
|
|
- mci_writel(host, CR, MCI_CR_MCIEN);
|
|
|
|
- mci_writel(host, MR, host->mode_reg);
|
|
|
|
- mci_writel(host, SDCR, host->sdc_reg);
|
|
|
|
|
|
+ if (list_empty(&host->queue))
|
|
|
|
+ mci_writel(host, MR, host->mode_reg);
|
|
|
|
+ else
|
|
|
|
+ host->need_clock_update = true;
|
|
|
|
+
|
|
|
|
+ spin_unlock_bh(&host->lock);
|
|
} else {
|
|
} else {
|
|
- mci_writel(host, CR, MCI_CR_MCIDIS);
|
|
|
|
- if (host->mode_reg) {
|
|
|
|
- mci_readl(host, MR);
|
|
|
|
- clk_disable(host->mck);
|
|
|
|
|
|
+ bool any_slot_active = false;
|
|
|
|
+
|
|
|
|
+ spin_lock_bh(&host->lock);
|
|
|
|
+ slot->clock = 0;
|
|
|
|
+ for (i = 0; i < ATMEL_MCI_MAX_NR_SLOTS; i++) {
|
|
|
|
+ if (host->slot[i] && host->slot[i]->clock) {
|
|
|
|
+ any_slot_active = true;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- host->mode_reg = 0;
|
|
|
|
|
|
+ if (!any_slot_active) {
|
|
|
|
+ mci_writel(host, CR, MCI_CR_MCIDIS);
|
|
|
|
+ if (host->mode_reg) {
|
|
|
|
+ mci_readl(host, MR);
|
|
|
|
+ clk_disable(host->mck);
|
|
|
|
+ }
|
|
|
|
+ host->mode_reg = 0;
|
|
|
|
+ }
|
|
|
|
+ spin_unlock_bh(&host->lock);
|
|
}
|
|
}
|
|
|
|
|
|
switch (ios->power_mode) {
|
|
switch (ios->power_mode) {
|
|
|
|
+ case MMC_POWER_UP:
|
|
|
|
+ set_bit(ATMCI_CARD_NEED_INIT, &slot->flags);
|
|
|
|
+ break;
|
|
default:
|
|
default:
|
|
/*
|
|
/*
|
|
* TODO: None of the currently available AVR32-based
|
|
* TODO: None of the currently available AVR32-based
|
|
* boards allow MMC power to be turned off. Implement
|
|
* boards allow MMC power to be turned off. Implement
|
|
* power control when this can be tested properly.
|
|
* power control when this can be tested properly.
|
|
|
|
+ *
|
|
|
|
+ * We also need to hook this into the clock management
|
|
|
|
+ * somehow so that newly inserted cards aren't
|
|
|
|
+ * subjected to a fast clock before we have a chance
|
|
|
|
+ * to figure out what the maximum rate is. Currently,
|
|
|
|
+ * there's no way to avoid this, and there never will
|
|
|
|
+ * be for boards that don't support power control.
|
|
*/
|
|
*/
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
@@ -561,28 +751,77 @@ static void atmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
|
|
|
|
|
static int atmci_get_ro(struct mmc_host *mmc)
|
|
static int atmci_get_ro(struct mmc_host *mmc)
|
|
{
|
|
{
|
|
- int read_only = 0;
|
|
|
|
- struct atmel_mci *host = mmc_priv(mmc);
|
|
|
|
|
|
+ int read_only = -ENOSYS;
|
|
|
|
+ struct atmel_mci_slot *slot = mmc_priv(mmc);
|
|
|
|
|
|
- if (gpio_is_valid(host->wp_pin)) {
|
|
|
|
- read_only = gpio_get_value(host->wp_pin);
|
|
|
|
|
|
+ if (gpio_is_valid(slot->wp_pin)) {
|
|
|
|
+ read_only = gpio_get_value(slot->wp_pin);
|
|
dev_dbg(&mmc->class_dev, "card is %s\n",
|
|
dev_dbg(&mmc->class_dev, "card is %s\n",
|
|
read_only ? "read-only" : "read-write");
|
|
read_only ? "read-only" : "read-write");
|
|
- } else {
|
|
|
|
- dev_dbg(&mmc->class_dev,
|
|
|
|
- "no pin for checking read-only switch."
|
|
|
|
- " Assuming write-enable.\n");
|
|
|
|
}
|
|
}
|
|
|
|
|
|
return read_only;
|
|
return read_only;
|
|
}
|
|
}
|
|
|
|
|
|
-static struct mmc_host_ops atmci_ops = {
|
|
|
|
|
|
+static int atmci_get_cd(struct mmc_host *mmc)
|
|
|
|
+{
|
|
|
|
+ int present = -ENOSYS;
|
|
|
|
+ struct atmel_mci_slot *slot = mmc_priv(mmc);
|
|
|
|
+
|
|
|
|
+ if (gpio_is_valid(slot->detect_pin)) {
|
|
|
|
+ present = !gpio_get_value(slot->detect_pin);
|
|
|
|
+ dev_dbg(&mmc->class_dev, "card is %spresent\n",
|
|
|
|
+ present ? "" : "not ");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return present;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static const struct mmc_host_ops atmci_ops = {
|
|
.request = atmci_request,
|
|
.request = atmci_request,
|
|
.set_ios = atmci_set_ios,
|
|
.set_ios = atmci_set_ios,
|
|
.get_ro = atmci_get_ro,
|
|
.get_ro = atmci_get_ro,
|
|
|
|
+ .get_cd = atmci_get_cd,
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+/* Called with host->lock held */
|
|
|
|
+static void atmci_request_end(struct atmel_mci *host, struct mmc_request *mrq)
|
|
|
|
+ __releases(&host->lock)
|
|
|
|
+ __acquires(&host->lock)
|
|
|
|
+{
|
|
|
|
+ struct atmel_mci_slot *slot = NULL;
|
|
|
|
+ struct mmc_host *prev_mmc = host->cur_slot->mmc;
|
|
|
|
+
|
|
|
|
+ WARN_ON(host->cmd || host->data);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Update the MMC clock rate if necessary. This may be
|
|
|
|
+ * necessary if set_ios() is called when a different slot is
|
|
|
|
+ * busy transfering data.
|
|
|
|
+ */
|
|
|
|
+ if (host->need_clock_update)
|
|
|
|
+ mci_writel(host, MR, host->mode_reg);
|
|
|
|
+
|
|
|
|
+ host->cur_slot->mrq = NULL;
|
|
|
|
+ host->mrq = NULL;
|
|
|
|
+ if (!list_empty(&host->queue)) {
|
|
|
|
+ slot = list_entry(host->queue.next,
|
|
|
|
+ struct atmel_mci_slot, queue_node);
|
|
|
|
+ list_del(&slot->queue_node);
|
|
|
|
+ dev_vdbg(&host->pdev->dev, "list not empty: %s is next\n",
|
|
|
|
+ mmc_hostname(slot->mmc));
|
|
|
|
+ host->state = STATE_SENDING_CMD;
|
|
|
|
+ atmci_start_request(host, slot);
|
|
|
|
+ } else {
|
|
|
|
+ dev_vdbg(&host->pdev->dev, "list empty\n");
|
|
|
|
+ host->state = STATE_IDLE;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ spin_unlock(&host->lock);
|
|
|
|
+ mmc_request_done(prev_mmc, mrq);
|
|
|
|
+ spin_lock(&host->lock);
|
|
|
|
+}
|
|
|
|
+
|
|
static void atmci_command_complete(struct atmel_mci *host,
|
|
static void atmci_command_complete(struct atmel_mci *host,
|
|
struct mmc_command *cmd)
|
|
struct mmc_command *cmd)
|
|
{
|
|
{
|
|
@@ -604,7 +843,7 @@ static void atmci_command_complete(struct atmel_mci *host,
|
|
cmd->error = 0;
|
|
cmd->error = 0;
|
|
|
|
|
|
if (cmd->error) {
|
|
if (cmd->error) {
|
|
- dev_dbg(&host->mmc->class_dev,
|
|
|
|
|
|
+ dev_dbg(&host->pdev->dev,
|
|
"command error: status=0x%08x\n", status);
|
|
"command error: status=0x%08x\n", status);
|
|
|
|
|
|
if (cmd->data) {
|
|
if (cmd->data) {
|
|
@@ -618,81 +857,102 @@ static void atmci_command_complete(struct atmel_mci *host,
|
|
|
|
|
|
static void atmci_detect_change(unsigned long data)
|
|
static void atmci_detect_change(unsigned long data)
|
|
{
|
|
{
|
|
- struct atmel_mci *host = (struct atmel_mci *)data;
|
|
|
|
- struct mmc_request *mrq = host->mrq;
|
|
|
|
- int present;
|
|
|
|
|
|
+ struct atmel_mci_slot *slot = (struct atmel_mci_slot *)data;
|
|
|
|
+ bool present;
|
|
|
|
+ bool present_old;
|
|
|
|
|
|
/*
|
|
/*
|
|
- * atmci_remove() sets detect_pin to -1 before freeing the
|
|
|
|
- * interrupt. We must not re-enable the interrupt if it has
|
|
|
|
- * been freed.
|
|
|
|
|
|
+ * atmci_cleanup_slot() sets the ATMCI_SHUTDOWN flag before
|
|
|
|
+ * freeing the interrupt. We must not re-enable the interrupt
|
|
|
|
+ * if it has been freed, and if we're shutting down, it
|
|
|
|
+ * doesn't really matter whether the card is present or not.
|
|
*/
|
|
*/
|
|
smp_rmb();
|
|
smp_rmb();
|
|
- if (!gpio_is_valid(host->detect_pin))
|
|
|
|
|
|
+ if (test_bit(ATMCI_SHUTDOWN, &slot->flags))
|
|
return;
|
|
return;
|
|
|
|
|
|
- enable_irq(gpio_to_irq(host->detect_pin));
|
|
|
|
- present = !gpio_get_value(host->detect_pin);
|
|
|
|
|
|
+ enable_irq(gpio_to_irq(slot->detect_pin));
|
|
|
|
+ present = !gpio_get_value(slot->detect_pin);
|
|
|
|
+ present_old = test_bit(ATMCI_CARD_PRESENT, &slot->flags);
|
|
|
|
|
|
- dev_vdbg(&host->pdev->dev, "detect change: %d (was %d)\n",
|
|
|
|
- present, host->present);
|
|
|
|
|
|
+ dev_vdbg(&slot->mmc->class_dev, "detect change: %d (was %d)\n",
|
|
|
|
+ present, present_old);
|
|
|
|
|
|
- if (present != host->present) {
|
|
|
|
- dev_dbg(&host->mmc->class_dev, "card %s\n",
|
|
|
|
|
|
+ if (present != present_old) {
|
|
|
|
+ struct atmel_mci *host = slot->host;
|
|
|
|
+ struct mmc_request *mrq;
|
|
|
|
+
|
|
|
|
+ dev_dbg(&slot->mmc->class_dev, "card %s\n",
|
|
present ? "inserted" : "removed");
|
|
present ? "inserted" : "removed");
|
|
- host->present = present;
|
|
|
|
|
|
|
|
- /* Reset controller if card is gone */
|
|
|
|
- if (!present) {
|
|
|
|
- mci_writel(host, CR, MCI_CR_SWRST);
|
|
|
|
- mci_writel(host, IDR, ~0UL);
|
|
|
|
- mci_writel(host, CR, MCI_CR_MCIEN);
|
|
|
|
- }
|
|
|
|
|
|
+ spin_lock(&host->lock);
|
|
|
|
+
|
|
|
|
+ if (!present)
|
|
|
|
+ clear_bit(ATMCI_CARD_PRESENT, &slot->flags);
|
|
|
|
+ else
|
|
|
|
+ set_bit(ATMCI_CARD_PRESENT, &slot->flags);
|
|
|
|
|
|
/* Clean up queue if present */
|
|
/* Clean up queue if present */
|
|
|
|
+ mrq = slot->mrq;
|
|
if (mrq) {
|
|
if (mrq) {
|
|
- /*
|
|
|
|
- * Reset controller to terminate any ongoing
|
|
|
|
- * commands or data transfers.
|
|
|
|
- */
|
|
|
|
- mci_writel(host, CR, MCI_CR_SWRST);
|
|
|
|
- mci_readl(host, SR);
|
|
|
|
-
|
|
|
|
- host->data = NULL;
|
|
|
|
- host->cmd = NULL;
|
|
|
|
-
|
|
|
|
- switch (host->state) {
|
|
|
|
- case STATE_SENDING_CMD:
|
|
|
|
- mrq->cmd->error = -ENOMEDIUM;
|
|
|
|
- if (!mrq->data)
|
|
|
|
|
|
+ if (mrq == host->mrq) {
|
|
|
|
+ /*
|
|
|
|
+ * Reset controller to terminate any ongoing
|
|
|
|
+ * commands or data transfers.
|
|
|
|
+ */
|
|
|
|
+ mci_writel(host, CR, MCI_CR_SWRST);
|
|
|
|
+ mci_writel(host, CR, MCI_CR_MCIEN);
|
|
|
|
+ mci_writel(host, MR, host->mode_reg);
|
|
|
|
+
|
|
|
|
+ host->data = NULL;
|
|
|
|
+ host->cmd = NULL;
|
|
|
|
+
|
|
|
|
+ switch (host->state) {
|
|
|
|
+ case STATE_IDLE:
|
|
break;
|
|
break;
|
|
- /* fall through */
|
|
|
|
- case STATE_SENDING_DATA:
|
|
|
|
- mrq->data->error = -ENOMEDIUM;
|
|
|
|
- break;
|
|
|
|
- case STATE_DATA_BUSY:
|
|
|
|
- case STATE_DATA_ERROR:
|
|
|
|
- if (mrq->data->error == -EINPROGRESS)
|
|
|
|
|
|
+ case STATE_SENDING_CMD:
|
|
|
|
+ mrq->cmd->error = -ENOMEDIUM;
|
|
|
|
+ if (!mrq->data)
|
|
|
|
+ break;
|
|
|
|
+ /* fall through */
|
|
|
|
+ case STATE_SENDING_DATA:
|
|
mrq->data->error = -ENOMEDIUM;
|
|
mrq->data->error = -ENOMEDIUM;
|
|
- if (!mrq->stop)
|
|
|
|
break;
|
|
break;
|
|
- /* fall through */
|
|
|
|
- case STATE_SENDING_STOP:
|
|
|
|
- mrq->stop->error = -ENOMEDIUM;
|
|
|
|
- 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_SENDING_STOP:
|
|
|
|
+ mrq->stop->error = -ENOMEDIUM;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
|
|
- atmci_request_end(host->mmc, mrq);
|
|
|
|
|
|
+ atmci_request_end(host, mrq);
|
|
|
|
+ } else {
|
|
|
|
+ list_del(&slot->queue_node);
|
|
|
|
+ mrq->cmd->error = -ENOMEDIUM;
|
|
|
|
+ if (mrq->data)
|
|
|
|
+ mrq->data->error = -ENOMEDIUM;
|
|
|
|
+ if (mrq->stop)
|
|
|
|
+ mrq->stop->error = -ENOMEDIUM;
|
|
|
|
+
|
|
|
|
+ spin_unlock(&host->lock);
|
|
|
|
+ mmc_request_done(slot->mmc, mrq);
|
|
|
|
+ spin_lock(&host->lock);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
+ spin_unlock(&host->lock);
|
|
|
|
|
|
- mmc_detect_change(host->mmc, 0);
|
|
|
|
|
|
+ mmc_detect_change(slot->mmc, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void atmci_tasklet_func(unsigned long priv)
|
|
static void atmci_tasklet_func(unsigned long priv)
|
|
{
|
|
{
|
|
- struct mmc_host *mmc = (struct mmc_host *)priv;
|
|
|
|
- struct atmel_mci *host = mmc_priv(mmc);
|
|
|
|
|
|
+ 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;
|
|
struct mmc_command *cmd = host->cmd;
|
|
@@ -700,9 +960,11 @@ static void atmci_tasklet_func(unsigned long priv)
|
|
enum atmel_mci_state prev_state;
|
|
enum atmel_mci_state prev_state;
|
|
u32 status;
|
|
u32 status;
|
|
|
|
|
|
|
|
+ spin_lock(&host->lock);
|
|
|
|
+
|
|
state = host->state;
|
|
state = host->state;
|
|
|
|
|
|
- dev_vdbg(&mmc->class_dev,
|
|
|
|
|
|
+ dev_vdbg(&host->pdev->dev,
|
|
"tasklet: state %u pending/completed/mask %lx/%lx/%x\n",
|
|
"tasklet: state %u pending/completed/mask %lx/%lx/%x\n",
|
|
state, host->pending_events, host->completed_events,
|
|
state, host->pending_events, host->completed_events,
|
|
mci_readl(host, IMR));
|
|
mci_readl(host, IMR));
|
|
@@ -711,6 +973,9 @@ static void atmci_tasklet_func(unsigned long priv)
|
|
prev_state = state;
|
|
prev_state = state;
|
|
|
|
|
|
switch (state) {
|
|
switch (state) {
|
|
|
|
+ case STATE_IDLE:
|
|
|
|
+ break;
|
|
|
|
+
|
|
case STATE_SENDING_CMD:
|
|
case STATE_SENDING_CMD:
|
|
if (!atmci_test_and_clear_pending(host,
|
|
if (!atmci_test_and_clear_pending(host,
|
|
EVENT_CMD_COMPLETE))
|
|
EVENT_CMD_COMPLETE))
|
|
@@ -720,8 +985,8 @@ static void atmci_tasklet_func(unsigned long priv)
|
|
atmci_set_completed(host, EVENT_CMD_COMPLETE);
|
|
atmci_set_completed(host, EVENT_CMD_COMPLETE);
|
|
atmci_command_complete(host, mrq->cmd);
|
|
atmci_command_complete(host, mrq->cmd);
|
|
if (!mrq->data || cmd->error) {
|
|
if (!mrq->data || cmd->error) {
|
|
- atmci_request_end(mmc, host->mrq);
|
|
|
|
- break;
|
|
|
|
|
|
+ atmci_request_end(host, host->mrq);
|
|
|
|
+ goto unlock;
|
|
}
|
|
}
|
|
|
|
|
|
prev_state = state = STATE_SENDING_DATA;
|
|
prev_state = state = STATE_SENDING_DATA;
|
|
@@ -731,7 +996,7 @@ static void atmci_tasklet_func(unsigned long priv)
|
|
if (atmci_test_and_clear_pending(host,
|
|
if (atmci_test_and_clear_pending(host,
|
|
EVENT_DATA_ERROR)) {
|
|
EVENT_DATA_ERROR)) {
|
|
if (data->stop)
|
|
if (data->stop)
|
|
- send_stop_cmd(host->mmc, data);
|
|
|
|
|
|
+ send_stop_cmd(host, data);
|
|
state = STATE_DATA_ERROR;
|
|
state = STATE_DATA_ERROR;
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
@@ -754,15 +1019,15 @@ static void atmci_tasklet_func(unsigned long priv)
|
|
status = host->data_status;
|
|
status = host->data_status;
|
|
if (unlikely(status & ATMCI_DATA_ERROR_FLAGS)) {
|
|
if (unlikely(status & ATMCI_DATA_ERROR_FLAGS)) {
|
|
if (status & MCI_DTOE) {
|
|
if (status & MCI_DTOE) {
|
|
- dev_dbg(&mmc->class_dev,
|
|
|
|
|
|
+ dev_dbg(&host->pdev->dev,
|
|
"data timeout error\n");
|
|
"data timeout error\n");
|
|
data->error = -ETIMEDOUT;
|
|
data->error = -ETIMEDOUT;
|
|
} else if (status & MCI_DCRCE) {
|
|
} else if (status & MCI_DCRCE) {
|
|
- dev_dbg(&mmc->class_dev,
|
|
|
|
|
|
+ dev_dbg(&host->pdev->dev,
|
|
"data CRC error\n");
|
|
"data CRC error\n");
|
|
data->error = -EILSEQ;
|
|
data->error = -EILSEQ;
|
|
} else {
|
|
} else {
|
|
- dev_dbg(&mmc->class_dev,
|
|
|
|
|
|
+ dev_dbg(&host->pdev->dev,
|
|
"data FIFO error (status=%08x)\n",
|
|
"data FIFO error (status=%08x)\n",
|
|
status);
|
|
status);
|
|
data->error = -EIO;
|
|
data->error = -EIO;
|
|
@@ -773,14 +1038,13 @@ static void atmci_tasklet_func(unsigned long priv)
|
|
}
|
|
}
|
|
|
|
|
|
if (!data->stop) {
|
|
if (!data->stop) {
|
|
- atmci_request_end(mmc, host->mrq);
|
|
|
|
- prev_state = state;
|
|
|
|
- break;
|
|
|
|
|
|
+ atmci_request_end(host, host->mrq);
|
|
|
|
+ goto unlock;
|
|
}
|
|
}
|
|
|
|
|
|
prev_state = state = STATE_SENDING_STOP;
|
|
prev_state = state = STATE_SENDING_STOP;
|
|
if (!data->error)
|
|
if (!data->error)
|
|
- send_stop_cmd(host->mmc, data);
|
|
|
|
|
|
+ send_stop_cmd(host, data);
|
|
/* fall through */
|
|
/* fall through */
|
|
|
|
|
|
case STATE_SENDING_STOP:
|
|
case STATE_SENDING_STOP:
|
|
@@ -790,9 +1054,8 @@ static void atmci_tasklet_func(unsigned long priv)
|
|
|
|
|
|
host->cmd = NULL;
|
|
host->cmd = NULL;
|
|
atmci_command_complete(host, mrq->stop);
|
|
atmci_command_complete(host, mrq->stop);
|
|
- atmci_request_end(mmc, host->mrq);
|
|
|
|
- prev_state = state;
|
|
|
|
- break;
|
|
|
|
|
|
+ atmci_request_end(host, host->mrq);
|
|
|
|
+ goto unlock;
|
|
|
|
|
|
case STATE_DATA_ERROR:
|
|
case STATE_DATA_ERROR:
|
|
if (!atmci_test_and_clear_pending(host,
|
|
if (!atmci_test_and_clear_pending(host,
|
|
@@ -805,6 +1068,9 @@ static void atmci_tasklet_func(unsigned long priv)
|
|
} while (state != prev_state);
|
|
} while (state != prev_state);
|
|
|
|
|
|
host->state = state;
|
|
host->state = state;
|
|
|
|
+
|
|
|
|
+unlock:
|
|
|
|
+ spin_unlock(&host->lock);
|
|
}
|
|
}
|
|
|
|
|
|
static void atmci_read_data_pio(struct atmel_mci *host)
|
|
static void atmci_read_data_pio(struct atmel_mci *host)
|
|
@@ -854,9 +1120,11 @@ static void atmci_read_data_pio(struct atmel_mci *host)
|
|
mci_writel(host, IDR, (MCI_NOTBUSY | MCI_RXRDY
|
|
mci_writel(host, IDR, (MCI_NOTBUSY | MCI_RXRDY
|
|
| ATMCI_DATA_ERROR_FLAGS));
|
|
| ATMCI_DATA_ERROR_FLAGS));
|
|
host->data_status = status;
|
|
host->data_status = status;
|
|
|
|
+ data->bytes_xfered += nbytes;
|
|
|
|
+ smp_wmb();
|
|
atmci_set_pending(host, EVENT_DATA_ERROR);
|
|
atmci_set_pending(host, EVENT_DATA_ERROR);
|
|
tasklet_schedule(&host->tasklet);
|
|
tasklet_schedule(&host->tasklet);
|
|
- break;
|
|
|
|
|
|
+ return;
|
|
}
|
|
}
|
|
} while (status & MCI_RXRDY);
|
|
} while (status & MCI_RXRDY);
|
|
|
|
|
|
@@ -869,6 +1137,7 @@ done:
|
|
mci_writel(host, IDR, MCI_RXRDY);
|
|
mci_writel(host, IDR, MCI_RXRDY);
|
|
mci_writel(host, IER, MCI_NOTBUSY);
|
|
mci_writel(host, IER, MCI_NOTBUSY);
|
|
data->bytes_xfered += nbytes;
|
|
data->bytes_xfered += nbytes;
|
|
|
|
+ smp_wmb();
|
|
atmci_set_pending(host, EVENT_XFER_COMPLETE);
|
|
atmci_set_pending(host, EVENT_XFER_COMPLETE);
|
|
}
|
|
}
|
|
|
|
|
|
@@ -922,9 +1191,11 @@ static void atmci_write_data_pio(struct atmel_mci *host)
|
|
mci_writel(host, IDR, (MCI_NOTBUSY | MCI_TXRDY
|
|
mci_writel(host, IDR, (MCI_NOTBUSY | MCI_TXRDY
|
|
| ATMCI_DATA_ERROR_FLAGS));
|
|
| ATMCI_DATA_ERROR_FLAGS));
|
|
host->data_status = status;
|
|
host->data_status = status;
|
|
|
|
+ data->bytes_xfered += nbytes;
|
|
|
|
+ smp_wmb();
|
|
atmci_set_pending(host, EVENT_DATA_ERROR);
|
|
atmci_set_pending(host, EVENT_DATA_ERROR);
|
|
tasklet_schedule(&host->tasklet);
|
|
tasklet_schedule(&host->tasklet);
|
|
- break;
|
|
|
|
|
|
+ return;
|
|
}
|
|
}
|
|
} while (status & MCI_TXRDY);
|
|
} while (status & MCI_TXRDY);
|
|
|
|
|
|
@@ -937,29 +1208,26 @@ done:
|
|
mci_writel(host, IDR, MCI_TXRDY);
|
|
mci_writel(host, IDR, MCI_TXRDY);
|
|
mci_writel(host, IER, MCI_NOTBUSY);
|
|
mci_writel(host, IER, MCI_NOTBUSY);
|
|
data->bytes_xfered += nbytes;
|
|
data->bytes_xfered += nbytes;
|
|
|
|
+ smp_wmb();
|
|
atmci_set_pending(host, EVENT_XFER_COMPLETE);
|
|
atmci_set_pending(host, EVENT_XFER_COMPLETE);
|
|
}
|
|
}
|
|
|
|
|
|
-static void atmci_cmd_interrupt(struct mmc_host *mmc, u32 status)
|
|
|
|
|
|
+static void atmci_cmd_interrupt(struct atmel_mci *host, u32 status)
|
|
{
|
|
{
|
|
- struct atmel_mci *host = mmc_priv(mmc);
|
|
|
|
-
|
|
|
|
mci_writel(host, IDR, MCI_CMDRDY);
|
|
mci_writel(host, IDR, MCI_CMDRDY);
|
|
|
|
|
|
host->cmd_status = status;
|
|
host->cmd_status = status;
|
|
|
|
+ smp_wmb();
|
|
atmci_set_pending(host, EVENT_CMD_COMPLETE);
|
|
atmci_set_pending(host, EVENT_CMD_COMPLETE);
|
|
tasklet_schedule(&host->tasklet);
|
|
tasklet_schedule(&host->tasklet);
|
|
}
|
|
}
|
|
|
|
|
|
static irqreturn_t atmci_interrupt(int irq, void *dev_id)
|
|
static irqreturn_t atmci_interrupt(int irq, void *dev_id)
|
|
{
|
|
{
|
|
- struct mmc_host *mmc = dev_id;
|
|
|
|
- struct atmel_mci *host = mmc_priv(mmc);
|
|
|
|
|
|
+ struct atmel_mci *host = dev_id;
|
|
u32 status, mask, pending;
|
|
u32 status, mask, pending;
|
|
unsigned int pass_count = 0;
|
|
unsigned int pass_count = 0;
|
|
|
|
|
|
- spin_lock(&mmc->lock);
|
|
|
|
-
|
|
|
|
do {
|
|
do {
|
|
status = mci_readl(host, SR);
|
|
status = mci_readl(host, SR);
|
|
mask = mci_readl(host, IMR);
|
|
mask = mci_readl(host, IMR);
|
|
@@ -971,7 +1239,9 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id)
|
|
mci_writel(host, IDR, ATMCI_DATA_ERROR_FLAGS
|
|
mci_writel(host, IDR, ATMCI_DATA_ERROR_FLAGS
|
|
| MCI_RXRDY | MCI_TXRDY);
|
|
| MCI_RXRDY | MCI_TXRDY);
|
|
pending &= mci_readl(host, IMR);
|
|
pending &= mci_readl(host, IMR);
|
|
|
|
+
|
|
host->data_status = status;
|
|
host->data_status = status;
|
|
|
|
+ smp_wmb();
|
|
atmci_set_pending(host, EVENT_DATA_ERROR);
|
|
atmci_set_pending(host, EVENT_DATA_ERROR);
|
|
tasklet_schedule(&host->tasklet);
|
|
tasklet_schedule(&host->tasklet);
|
|
}
|
|
}
|
|
@@ -979,6 +1249,7 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id)
|
|
mci_writel(host, IDR,
|
|
mci_writel(host, IDR,
|
|
ATMCI_DATA_ERROR_FLAGS | MCI_NOTBUSY);
|
|
ATMCI_DATA_ERROR_FLAGS | MCI_NOTBUSY);
|
|
host->data_status = status;
|
|
host->data_status = status;
|
|
|
|
+ smp_wmb();
|
|
atmci_set_pending(host, EVENT_DATA_COMPLETE);
|
|
atmci_set_pending(host, EVENT_DATA_COMPLETE);
|
|
tasklet_schedule(&host->tasklet);
|
|
tasklet_schedule(&host->tasklet);
|
|
}
|
|
}
|
|
@@ -988,18 +1259,15 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id)
|
|
atmci_write_data_pio(host);
|
|
atmci_write_data_pio(host);
|
|
|
|
|
|
if (pending & MCI_CMDRDY)
|
|
if (pending & MCI_CMDRDY)
|
|
- atmci_cmd_interrupt(mmc, status);
|
|
|
|
|
|
+ atmci_cmd_interrupt(host, status);
|
|
} while (pass_count++ < 5);
|
|
} while (pass_count++ < 5);
|
|
|
|
|
|
- spin_unlock(&mmc->lock);
|
|
|
|
-
|
|
|
|
return pass_count ? IRQ_HANDLED : IRQ_NONE;
|
|
return pass_count ? IRQ_HANDLED : IRQ_NONE;
|
|
}
|
|
}
|
|
|
|
|
|
static irqreturn_t atmci_detect_interrupt(int irq, void *dev_id)
|
|
static irqreturn_t atmci_detect_interrupt(int irq, void *dev_id)
|
|
{
|
|
{
|
|
- struct mmc_host *mmc = dev_id;
|
|
|
|
- struct atmel_mci *host = mmc_priv(mmc);
|
|
|
|
|
|
+ struct atmel_mci_slot *slot = dev_id;
|
|
|
|
|
|
/*
|
|
/*
|
|
* Disable interrupts until the pin has stabilized and check
|
|
* Disable interrupts until the pin has stabilized and check
|
|
@@ -1007,21 +1275,122 @@ static irqreturn_t atmci_detect_interrupt(int irq, void *dev_id)
|
|
* middle of the timer routine when this interrupt triggers.
|
|
* middle of the timer routine when this interrupt triggers.
|
|
*/
|
|
*/
|
|
disable_irq_nosync(irq);
|
|
disable_irq_nosync(irq);
|
|
- mod_timer(&host->detect_timer, jiffies + msecs_to_jiffies(20));
|
|
|
|
|
|
+ mod_timer(&slot->detect_timer, jiffies + msecs_to_jiffies(20));
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
return IRQ_HANDLED;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static int __init atmci_init_slot(struct atmel_mci *host,
|
|
|
|
+ struct mci_slot_pdata *slot_data, unsigned int id,
|
|
|
|
+ u32 sdc_reg)
|
|
|
|
+{
|
|
|
|
+ struct mmc_host *mmc;
|
|
|
|
+ struct atmel_mci_slot *slot;
|
|
|
|
+
|
|
|
|
+ mmc = mmc_alloc_host(sizeof(struct atmel_mci_slot), &host->pdev->dev);
|
|
|
|
+ if (!mmc)
|
|
|
|
+ return -ENOMEM;
|
|
|
|
+
|
|
|
|
+ slot = mmc_priv(mmc);
|
|
|
|
+ slot->mmc = mmc;
|
|
|
|
+ slot->host = host;
|
|
|
|
+ slot->detect_pin = slot_data->detect_pin;
|
|
|
|
+ slot->wp_pin = slot_data->wp_pin;
|
|
|
|
+ slot->sdc_reg = sdc_reg;
|
|
|
|
+
|
|
|
|
+ mmc->ops = &atmci_ops;
|
|
|
|
+ mmc->f_min = DIV_ROUND_UP(host->bus_hz, 512);
|
|
|
|
+ mmc->f_max = host->bus_hz / 2;
|
|
|
|
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
|
|
|
|
+ if (slot_data->bus_width >= 4)
|
|
|
|
+ mmc->caps |= MMC_CAP_4_BIT_DATA;
|
|
|
|
+
|
|
|
|
+ mmc->max_hw_segs = 64;
|
|
|
|
+ mmc->max_phys_segs = 64;
|
|
|
|
+ mmc->max_req_size = 32768 * 512;
|
|
|
|
+ mmc->max_blk_size = 32768;
|
|
|
|
+ mmc->max_blk_count = 512;
|
|
|
|
+
|
|
|
|
+ /* Assume card is present initially */
|
|
|
|
+ set_bit(ATMCI_CARD_PRESENT, &slot->flags);
|
|
|
|
+ if (gpio_is_valid(slot->detect_pin)) {
|
|
|
|
+ if (gpio_request(slot->detect_pin, "mmc_detect")) {
|
|
|
|
+ dev_dbg(&mmc->class_dev, "no detect pin available\n");
|
|
|
|
+ slot->detect_pin = -EBUSY;
|
|
|
|
+ } else if (gpio_get_value(slot->detect_pin)) {
|
|
|
|
+ clear_bit(ATMCI_CARD_PRESENT, &slot->flags);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!gpio_is_valid(slot->detect_pin))
|
|
|
|
+ mmc->caps |= MMC_CAP_NEEDS_POLL;
|
|
|
|
+
|
|
|
|
+ if (gpio_is_valid(slot->wp_pin)) {
|
|
|
|
+ if (gpio_request(slot->wp_pin, "mmc_wp")) {
|
|
|
|
+ dev_dbg(&mmc->class_dev, "no WP pin available\n");
|
|
|
|
+ slot->wp_pin = -EBUSY;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ host->slot[id] = slot;
|
|
|
|
+ mmc_add_host(mmc);
|
|
|
|
+
|
|
|
|
+ if (gpio_is_valid(slot->detect_pin)) {
|
|
|
|
+ int ret;
|
|
|
|
+
|
|
|
|
+ setup_timer(&slot->detect_timer, atmci_detect_change,
|
|
|
|
+ (unsigned long)slot);
|
|
|
|
+
|
|
|
|
+ ret = request_irq(gpio_to_irq(slot->detect_pin),
|
|
|
|
+ atmci_detect_interrupt,
|
|
|
|
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
|
|
|
|
+ "mmc-detect", slot);
|
|
|
|
+ if (ret) {
|
|
|
|
+ dev_dbg(&mmc->class_dev,
|
|
|
|
+ "could not request IRQ %d for detect pin\n",
|
|
|
|
+ gpio_to_irq(slot->detect_pin));
|
|
|
|
+ gpio_free(slot->detect_pin);
|
|
|
|
+ slot->detect_pin = -EBUSY;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ atmci_init_debugfs(slot);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void __exit atmci_cleanup_slot(struct atmel_mci_slot *slot,
|
|
|
|
+ unsigned int id)
|
|
|
|
+{
|
|
|
|
+ /* Debugfs stuff is cleaned up by mmc core */
|
|
|
|
+
|
|
|
|
+ set_bit(ATMCI_SHUTDOWN, &slot->flags);
|
|
|
|
+ smp_wmb();
|
|
|
|
+
|
|
|
|
+ mmc_remove_host(slot->mmc);
|
|
|
|
+
|
|
|
|
+ if (gpio_is_valid(slot->detect_pin)) {
|
|
|
|
+ int pin = slot->detect_pin;
|
|
|
|
+
|
|
|
|
+ free_irq(gpio_to_irq(pin), slot);
|
|
|
|
+ del_timer_sync(&slot->detect_timer);
|
|
|
|
+ gpio_free(pin);
|
|
|
|
+ }
|
|
|
|
+ if (gpio_is_valid(slot->wp_pin))
|
|
|
|
+ gpio_free(slot->wp_pin);
|
|
|
|
+
|
|
|
|
+ slot->host->slot[id] = NULL;
|
|
|
|
+ mmc_free_host(slot->mmc);
|
|
|
|
+}
|
|
|
|
+
|
|
static int __init atmci_probe(struct platform_device *pdev)
|
|
static int __init atmci_probe(struct platform_device *pdev)
|
|
{
|
|
{
|
|
struct mci_platform_data *pdata;
|
|
struct mci_platform_data *pdata;
|
|
- struct mci_slot_pdata *slot;
|
|
|
|
- struct atmel_mci *host;
|
|
|
|
- struct mmc_host *mmc;
|
|
|
|
- struct resource *regs;
|
|
|
|
- u32 sdc_reg;
|
|
|
|
- int irq;
|
|
|
|
- int ret;
|
|
|
|
|
|
+ struct atmel_mci *host;
|
|
|
|
+ struct resource *regs;
|
|
|
|
+ unsigned int nr_slots;
|
|
|
|
+ int irq;
|
|
|
|
+ int ret;
|
|
|
|
|
|
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!regs)
|
|
if (!regs)
|
|
@@ -1033,27 +1402,13 @@ static int __init atmci_probe(struct platform_device *pdev)
|
|
if (irq < 0)
|
|
if (irq < 0)
|
|
return irq;
|
|
return irq;
|
|
|
|
|
|
- /* TODO: Allow using several slots at once */
|
|
|
|
- if (pdata->slot[0].bus_width) {
|
|
|
|
- sdc_reg = MCI_SDCSEL_SLOT_A;
|
|
|
|
- slot = &pdata->slot[0];
|
|
|
|
- } else if (pdata->slot[1].bus_width) {
|
|
|
|
- sdc_reg = MCI_SDCSEL_SLOT_B;
|
|
|
|
- slot = &pdata->slot[1];
|
|
|
|
- } else {
|
|
|
|
- return -EINVAL;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- mmc = mmc_alloc_host(sizeof(struct atmel_mci), &pdev->dev);
|
|
|
|
- if (!mmc)
|
|
|
|
|
|
+ host = kzalloc(sizeof(struct atmel_mci), GFP_KERNEL);
|
|
|
|
+ if (!host)
|
|
return -ENOMEM;
|
|
return -ENOMEM;
|
|
|
|
|
|
- host = mmc_priv(mmc);
|
|
|
|
host->pdev = pdev;
|
|
host->pdev = pdev;
|
|
- host->mmc = mmc;
|
|
|
|
- host->detect_pin = slot->detect_pin;
|
|
|
|
- host->wp_pin = slot->wp_pin;
|
|
|
|
- host->sdc_reg = sdc_reg;
|
|
|
|
|
|
+ spin_lock_init(&host->lock);
|
|
|
|
+ INIT_LIST_HEAD(&host->queue);
|
|
|
|
|
|
host->mck = clk_get(&pdev->dev, "mci_clk");
|
|
host->mck = clk_get(&pdev->dev, "mci_clk");
|
|
if (IS_ERR(host->mck)) {
|
|
if (IS_ERR(host->mck)) {
|
|
@@ -1073,123 +1428,74 @@ static int __init atmci_probe(struct platform_device *pdev)
|
|
|
|
|
|
host->mapbase = regs->start;
|
|
host->mapbase = regs->start;
|
|
|
|
|
|
- mmc->ops = &atmci_ops;
|
|
|
|
- mmc->f_min = (host->bus_hz + 511) / 512;
|
|
|
|
- mmc->f_max = host->bus_hz / 2;
|
|
|
|
- mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
|
|
|
|
- if (slot->bus_width >= 4)
|
|
|
|
- mmc->caps |= MMC_CAP_4_BIT_DATA;
|
|
|
|
-
|
|
|
|
- mmc->max_hw_segs = 64;
|
|
|
|
- mmc->max_phys_segs = 64;
|
|
|
|
- mmc->max_req_size = 32768 * 512;
|
|
|
|
- mmc->max_blk_size = 32768;
|
|
|
|
- mmc->max_blk_count = 512;
|
|
|
|
-
|
|
|
|
- tasklet_init(&host->tasklet, atmci_tasklet_func, (unsigned long)mmc);
|
|
|
|
|
|
+ tasklet_init(&host->tasklet, atmci_tasklet_func, (unsigned long)host);
|
|
|
|
|
|
- ret = request_irq(irq, atmci_interrupt, 0, pdev->dev.bus_id, mmc);
|
|
|
|
|
|
+ ret = request_irq(irq, atmci_interrupt, 0, pdev->dev.bus_id, host);
|
|
if (ret)
|
|
if (ret)
|
|
goto err_request_irq;
|
|
goto err_request_irq;
|
|
|
|
|
|
- /* Assume card is present if we don't have a detect pin */
|
|
|
|
- host->present = 1;
|
|
|
|
- if (gpio_is_valid(host->detect_pin)) {
|
|
|
|
- if (gpio_request(host->detect_pin, "mmc_detect")) {
|
|
|
|
- dev_dbg(&mmc->class_dev, "no detect pin available\n");
|
|
|
|
- host->detect_pin = -1;
|
|
|
|
- } else {
|
|
|
|
- host->present = !gpio_get_value(host->detect_pin);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (!gpio_is_valid(host->detect_pin))
|
|
|
|
- mmc->caps |= MMC_CAP_NEEDS_POLL;
|
|
|
|
-
|
|
|
|
- if (gpio_is_valid(host->wp_pin)) {
|
|
|
|
- if (gpio_request(host->wp_pin, "mmc_wp")) {
|
|
|
|
- dev_dbg(&mmc->class_dev, "no WP pin available\n");
|
|
|
|
- host->wp_pin = -1;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
platform_set_drvdata(pdev, host);
|
|
platform_set_drvdata(pdev, host);
|
|
|
|
|
|
- mmc_add_host(mmc);
|
|
|
|
-
|
|
|
|
- if (gpio_is_valid(host->detect_pin)) {
|
|
|
|
- setup_timer(&host->detect_timer, atmci_detect_change,
|
|
|
|
- (unsigned long)host);
|
|
|
|
-
|
|
|
|
- ret = request_irq(gpio_to_irq(host->detect_pin),
|
|
|
|
- atmci_detect_interrupt,
|
|
|
|
- IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
|
|
|
|
- "mmc-detect", mmc);
|
|
|
|
- if (ret) {
|
|
|
|
- dev_dbg(&mmc->class_dev,
|
|
|
|
- "could not request IRQ %d for detect pin\n",
|
|
|
|
- gpio_to_irq(host->detect_pin));
|
|
|
|
- gpio_free(host->detect_pin);
|
|
|
|
- host->detect_pin = -1;
|
|
|
|
- }
|
|
|
|
|
|
+ /* We need at least one slot to succeed */
|
|
|
|
+ nr_slots = 0;
|
|
|
|
+ ret = -ENODEV;
|
|
|
|
+ if (pdata->slot[0].bus_width) {
|
|
|
|
+ ret = atmci_init_slot(host, &pdata->slot[0],
|
|
|
|
+ MCI_SDCSEL_SLOT_A, 0);
|
|
|
|
+ if (!ret)
|
|
|
|
+ nr_slots++;
|
|
|
|
+ }
|
|
|
|
+ if (pdata->slot[1].bus_width) {
|
|
|
|
+ ret = atmci_init_slot(host, &pdata->slot[1],
|
|
|
|
+ MCI_SDCSEL_SLOT_B, 1);
|
|
|
|
+ if (!ret)
|
|
|
|
+ nr_slots++;
|
|
}
|
|
}
|
|
|
|
|
|
- dev_info(&mmc->class_dev,
|
|
|
|
- "Atmel MCI controller at 0x%08lx irq %d\n",
|
|
|
|
- host->mapbase, irq);
|
|
|
|
|
|
+ if (!nr_slots)
|
|
|
|
+ goto err_init_slot;
|
|
|
|
|
|
- atmci_init_debugfs(host);
|
|
|
|
|
|
+ dev_info(&pdev->dev,
|
|
|
|
+ "Atmel MCI controller at 0x%08lx irq %d, %u slots\n",
|
|
|
|
+ host->mapbase, irq, nr_slots);
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
|
|
+err_init_slot:
|
|
|
|
+ free_irq(irq, host);
|
|
err_request_irq:
|
|
err_request_irq:
|
|
iounmap(host->regs);
|
|
iounmap(host->regs);
|
|
err_ioremap:
|
|
err_ioremap:
|
|
clk_put(host->mck);
|
|
clk_put(host->mck);
|
|
err_clk_get:
|
|
err_clk_get:
|
|
- mmc_free_host(mmc);
|
|
|
|
|
|
+ kfree(host);
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
static int __exit atmci_remove(struct platform_device *pdev)
|
|
static int __exit atmci_remove(struct platform_device *pdev)
|
|
{
|
|
{
|
|
- struct atmel_mci *host = platform_get_drvdata(pdev);
|
|
|
|
|
|
+ struct atmel_mci *host = platform_get_drvdata(pdev);
|
|
|
|
+ unsigned int i;
|
|
|
|
|
|
platform_set_drvdata(pdev, NULL);
|
|
platform_set_drvdata(pdev, NULL);
|
|
|
|
|
|
- if (host) {
|
|
|
|
- /* Debugfs stuff is cleaned up by mmc core */
|
|
|
|
-
|
|
|
|
- if (gpio_is_valid(host->detect_pin)) {
|
|
|
|
- int pin = host->detect_pin;
|
|
|
|
-
|
|
|
|
- /* Make sure the timer doesn't enable the interrupt */
|
|
|
|
- host->detect_pin = -1;
|
|
|
|
- smp_wmb();
|
|
|
|
-
|
|
|
|
- free_irq(gpio_to_irq(pin), host->mmc);
|
|
|
|
- del_timer_sync(&host->detect_timer);
|
|
|
|
- gpio_free(pin);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- mmc_remove_host(host->mmc);
|
|
|
|
-
|
|
|
|
- clk_enable(host->mck);
|
|
|
|
- mci_writel(host, IDR, ~0UL);
|
|
|
|
- mci_writel(host, CR, MCI_CR_MCIDIS);
|
|
|
|
- mci_readl(host, SR);
|
|
|
|
- clk_disable(host->mck);
|
|
|
|
|
|
+ for (i = 0; i < ATMEL_MCI_MAX_NR_SLOTS; i++) {
|
|
|
|
+ if (host->slot[i])
|
|
|
|
+ atmci_cleanup_slot(host->slot[i], i);
|
|
|
|
+ }
|
|
|
|
|
|
- if (gpio_is_valid(host->wp_pin))
|
|
|
|
- gpio_free(host->wp_pin);
|
|
|
|
|
|
+ clk_enable(host->mck);
|
|
|
|
+ mci_writel(host, IDR, ~0UL);
|
|
|
|
+ mci_writel(host, CR, MCI_CR_MCIDIS);
|
|
|
|
+ mci_readl(host, SR);
|
|
|
|
+ clk_disable(host->mck);
|
|
|
|
|
|
- free_irq(platform_get_irq(pdev, 0), host->mmc);
|
|
|
|
- iounmap(host->regs);
|
|
|
|
|
|
+ free_irq(platform_get_irq(pdev, 0), host);
|
|
|
|
+ iounmap(host->regs);
|
|
|
|
|
|
- clk_put(host->mck);
|
|
|
|
|
|
+ clk_put(host->mck);
|
|
|
|
+ kfree(host);
|
|
|
|
|
|
- mmc_free_host(host->mmc);
|
|
|
|
- }
|
|
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|