lkml.org 
[lkml]   [2008]   [Oct]   [5]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH v2 4/7] atmel-mci: support multiple mmc slots
    Date
    The Atmel MCI controller can drive multiple cards through separate sets
    of pins, but only one at a time. This patch adds support for
    multiplexing access to the controller so that multiple card slots can be
    used as if they were hooked up to separate mmc controllers.

    The atmel-mci driver registers each slot as a separate mmc_host. Both
    access the same common controller state, but they also have some state
    on their own for card detection/write protect handling, and separate
    shadows of the MR and SDCR registers.

    When one of the slots receives a request from the mmc core, the common
    controller state is checked. If it's idle, the request is submitted
    immediately. If not, the request is added to a queue. When a request is
    done, the queue is checked and if there is a queued request, it is
    submitted before the completion callback is called.

    This patch also includes a few cleanups and fixes, including a locking
    overhaul. I had to change the locking extensively in any case, so I
    might as well try to get it right. The driver no longer takes any
    irq-safe locks, which may or may not improve the overall system
    performance.

    This patch also adds a bit of documentation of the internal data
    structures.

    Signed-off-by: Haavard Skinnemoen <haavard.skinnemoen@atmel.com>
    ---
    arch/avr32/include/asm/atmel-mci.h | 8 +-
    drivers/mmc/host/atmel-mci.c | 922 ++++++++++++++++++++++++------------
    2 files changed, 621 insertions(+), 309 deletions(-)

    diff --git a/arch/avr32/include/asm/atmel-mci.h b/arch/avr32/include/asm/atmel-mci.h
    index d38c64c..5d5ae12 100644
    --- a/arch/avr32/include/asm/atmel-mci.h
    +++ b/arch/avr32/include/asm/atmel-mci.h
    @@ -1,6 +1,8 @@
    #ifndef __ASM_AVR32_ATMEL_MCI_H
    #define __ASM_AVR32_ATMEL_MCI_H

    +#define ATMEL_MCI_MAX_NR_SLOTS 2
    +
    /**
    * struct mci_slot_pdata - board-specific per-slot configuration
    * @bus_width: Number of data lines wired up the slot
    @@ -11,6 +13,10 @@
    * set to 0. The other fields are ignored in this case.
    *
    * Any pins that aren't available should be set to a negative value.
    + *
    + * Note that support for multiple slots is experimental -- some cards
    + * might get upset if we don't get the clock management exactly right.
    + * But in most cases, it should work just fine.
    */
    struct mci_slot_pdata {
    unsigned int bus_width;
    @@ -23,7 +29,7 @@ struct mci_slot_pdata {
    * @slot: Per-slot configuration data.
    */
    struct mci_platform_data {
    - struct mci_slot_pdata slot[2];
    + struct mci_slot_pdata slot[ATMEL_MCI_MAX_NR_SLOTS];
    };

    #endif /* __ASM_AVR32_ATMEL_MCI_H */
    diff --git a/drivers/mmc/host/atmel-mci.c b/drivers/mmc/host/atmel-mci.c
    index 8170905..d8ab351 100644
    --- a/drivers/mmc/host/atmel-mci.c
    +++ b/drivers/mmc/host/atmel-mci.c
    @@ -42,20 +42,86 @@ enum {
    };

    enum atmel_mci_state {
    - STATE_SENDING_CMD = 0,
    + STATE_IDLE = 0,
    + STATE_SENDING_CMD,
    STATE_SENDING_DATA,
    STATE_DATA_BUSY,
    STATE_SENDING_STOP,
    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 mmc_host *mmc;
    + spinlock_t lock;
    void __iomem *regs;

    struct scatterlist *sg;
    unsigned int pio_offset;

    + struct atmel_mci_slot *cur_slot;
    struct mmc_request *mrq;
    struct mmc_command *cmd;
    struct mmc_data *data;
    @@ -64,25 +130,59 @@ struct atmel_mci {
    u32 data_status;
    u32 stop_cmdr;

    - u32 mode_reg;
    - u32 sdc_reg;
    -
    struct tasklet_struct tasklet;
    unsigned long pending_events;
    unsigned long completed_events;
    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 mapbase;
    struct clk *mck;
    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) \
    @@ -98,14 +198,15 @@ struct atmel_mci {
    */
    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 *stop;
    struct mmc_data *data;

    /* Make sure we get a consistent snapshot */
    - spin_lock_irq(&host->mmc->lock);
    + spin_lock_bh(&slot->host->lock);
    + mrq = slot->mrq;

    if (mrq) {
    cmd = mrq->cmd;
    @@ -130,7 +231,7 @@ static int atmci_req_show(struct seq_file *s, void *v)
    stop->resp[2], stop->error);
    }

    - spin_unlock_irq(&host->mmc->lock);
    + spin_unlock_bh(&slot->host->lock);

    return 0;
    }
    @@ -193,12 +294,16 @@ static int atmci_regs_show(struct seq_file *s, void *v)
    if (!buf)
    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);
    memcpy_fromio(buf, host->regs, MCI_REGS_SIZE);
    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",
    buf[MCI_MR / 4],
    @@ -236,13 +341,13 @@ static const struct file_operations atmci_regs_fops = {
    .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;
    if (!root)
    return;
    @@ -254,7 +359,7 @@ static void atmci_init_debugfs(struct atmel_mci *host)
    if (!node)
    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)
    goto err;

    @@ -275,8 +380,7 @@ static void atmci_init_debugfs(struct atmel_mci *host)
    return;

    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,
    @@ -286,7 +390,7 @@ static inline unsigned int ns_to_clocks(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[] = {
    0, 4, 7, 8, 10, 12, 16, 20
    @@ -309,7 +413,7 @@ static void atmci_set_timeout(struct atmel_mci *host,
    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]);
    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,
    - struct mmc_command *cmd,
    - u32 cmd_flags)
    + struct mmc_command *cmd, u32 cmd_flags)
    {
    WARN_ON(host->cmd);
    host->cmd = cmd;

    - dev_vdbg(&host->mmc->class_dev,
    + dev_vdbg(&host->pdev->dev,
    "start command: ARGR=0x%08x CMDR=0x%08x\n",
    cmd->arg, cmd_flags);

    @@ -376,32 +479,19 @@ static void atmci_start_command(struct atmel_mci *host,
    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);
    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
    * 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;

    @@ -409,10 +499,19 @@ static u32 atmci_submit_data(struct mmc_host *mmc, struct mmc_data *data)
    host->sg = NULL;
    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;
    +
    + /*
    + * 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->pio_offset = 0;
    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;
    }

    -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_data *data;
    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->pending_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;
    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 */
    mci_writel(host, BLKR, MCI_BCNT(data->blocks)
    | 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;
    cmd = mrq->cmd;
    - cmdflags = atmci_prepare_command(mmc, cmd);
    + cmdflags = atmci_prepare_command(slot->mmc, cmd);
    atmci_start_command(host, cmd, cmdflags);

    if (data)
    - iflags |= atmci_submit_data(mmc, data);
    + iflags |= atmci_submit_data(host, data);

    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;
    if (!(data->flags & MMC_DATA_WRITE))
    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.)
    */
    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)
    {
    - 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) {
    case MMC_BUS_WIDTH_1:
    - host->sdc_reg |= MCI_SDCBUS_1BIT;
    + slot->sdc_reg |= MCI_SDCBUS_1BIT;
    break;
    case MMC_BUS_WIDTH_4:
    - host->sdc_reg = MCI_SDCBUS_4BIT;
    + slot->sdc_reg = MCI_SDCBUS_4BIT;
    break;
    }

    if (ios->clock) {
    + unsigned int clock_min = ~0U;
    u32 clkdiv;

    - if (!host->mode_reg)
    + spin_lock_bh(&host->lock);
    + if (!host->mode_reg) {
    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) {
    dev_warn(&mmc->class_dev,
    "clock %u too slow; using %lu\n",
    - ios->clock, host->bus_hz / (2 * 256));
    + clock_min, host->bus_hz / (2 * 256));
    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
    | 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 {
    - 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) {
    + case MMC_POWER_UP:
    + set_bit(ATMCI_CARD_NEED_INIT, &slot->flags);
    + break;
    default:
    /*
    * TODO: None of the currently available AVR32-based
    * boards allow MMC power to be turned off. Implement
    * 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;
    }
    @@ -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)
    {
    - 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",
    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;
    }

    -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,
    .set_ios = atmci_set_ios,
    .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,
    struct mmc_command *cmd)
    {
    @@ -604,7 +843,7 @@ static void atmci_command_complete(struct atmel_mci *host,
    cmd->error = 0;

    if (cmd->error) {
    - dev_dbg(&host->mmc->class_dev,
    + dev_dbg(&host->pdev->dev,
    "command error: status=0x%08x\n", status);

    if (cmd->data) {
    @@ -618,81 +857,102 @@ static void atmci_command_complete(struct atmel_mci *host,

    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();
    - if (!gpio_is_valid(host->detect_pin))
    + if (test_bit(ATMCI_SHUTDOWN, &slot->flags))
    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");
    - 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 */
    + mrq = slot->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;
    - /* 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;
    - if (!mrq->stop)
    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)
    {
    - 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_data *data = host->data;
    struct mmc_command *cmd = host->cmd;
    @@ -700,9 +960,11 @@ static void atmci_tasklet_func(unsigned long priv)
    enum atmel_mci_state prev_state;
    u32 status;

    + spin_lock(&host->lock);
    +
    state = host->state;

    - dev_vdbg(&mmc->class_dev,
    + dev_vdbg(&host->pdev->dev,
    "tasklet: state %u pending/completed/mask %lx/%lx/%x\n",
    state, host->pending_events, host->completed_events,
    mci_readl(host, IMR));
    @@ -711,6 +973,9 @@ static void atmci_tasklet_func(unsigned long priv)
    prev_state = state;

    switch (state) {
    + case STATE_IDLE:
    + break;
    +
    case STATE_SENDING_CMD:
    if (!atmci_test_and_clear_pending(host,
    EVENT_CMD_COMPLETE))
    @@ -720,8 +985,8 @@ static void atmci_tasklet_func(unsigned long priv)
    atmci_set_completed(host, EVENT_CMD_COMPLETE);
    atmci_command_complete(host, mrq->cmd);
    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;
    @@ -731,7 +996,7 @@ static void atmci_tasklet_func(unsigned long priv)
    if (atmci_test_and_clear_pending(host,
    EVENT_DATA_ERROR)) {
    if (data->stop)
    - send_stop_cmd(host->mmc, data);
    + send_stop_cmd(host, data);
    state = STATE_DATA_ERROR;
    break;
    }
    @@ -754,15 +1019,15 @@ static void atmci_tasklet_func(unsigned long priv)
    status = host->data_status;
    if (unlikely(status & ATMCI_DATA_ERROR_FLAGS)) {
    if (status & MCI_DTOE) {
    - dev_dbg(&mmc->class_dev,
    + dev_dbg(&host->pdev->dev,
    "data timeout error\n");
    data->error = -ETIMEDOUT;
    } else if (status & MCI_DCRCE) {
    - dev_dbg(&mmc->class_dev,
    + dev_dbg(&host->pdev->dev,
    "data CRC error\n");
    data->error = -EILSEQ;
    } else {
    - dev_dbg(&mmc->class_dev,
    + dev_dbg(&host->pdev->dev,
    "data FIFO error (status=%08x)\n",
    status);
    data->error = -EIO;
    @@ -773,14 +1038,13 @@ static void atmci_tasklet_func(unsigned long priv)
    }

    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;
    if (!data->error)
    - send_stop_cmd(host->mmc, data);
    + send_stop_cmd(host, data);
    /* fall through */

    case STATE_SENDING_STOP:
    @@ -790,9 +1054,8 @@ static void atmci_tasklet_func(unsigned long priv)

    host->cmd = NULL;
    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:
    if (!atmci_test_and_clear_pending(host,
    @@ -805,6 +1068,9 @@ static void atmci_tasklet_func(unsigned long priv)
    } while (state != prev_state);

    host->state = state;
    +
    +unlock:
    + spin_unlock(&host->lock);
    }

    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
    | ATMCI_DATA_ERROR_FLAGS));
    host->data_status = status;
    + data->bytes_xfered += nbytes;
    + smp_wmb();
    atmci_set_pending(host, EVENT_DATA_ERROR);
    tasklet_schedule(&host->tasklet);
    - break;
    + return;
    }
    } while (status & MCI_RXRDY);

    @@ -869,6 +1137,7 @@ done:
    mci_writel(host, IDR, MCI_RXRDY);
    mci_writel(host, IER, MCI_NOTBUSY);
    data->bytes_xfered += nbytes;
    + smp_wmb();
    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
    | ATMCI_DATA_ERROR_FLAGS));
    host->data_status = status;
    + data->bytes_xfered += nbytes;
    + smp_wmb();
    atmci_set_pending(host, EVENT_DATA_ERROR);
    tasklet_schedule(&host->tasklet);
    - break;
    + return;
    }
    } while (status & MCI_TXRDY);

    @@ -937,29 +1208,26 @@ done:
    mci_writel(host, IDR, MCI_TXRDY);
    mci_writel(host, IER, MCI_NOTBUSY);
    data->bytes_xfered += nbytes;
    + smp_wmb();
    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);

    host->cmd_status = status;
    + smp_wmb();
    atmci_set_pending(host, EVENT_CMD_COMPLETE);
    tasklet_schedule(&host->tasklet);
    }

    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;
    unsigned int pass_count = 0;

    - spin_lock(&mmc->lock);
    -
    do {
    status = mci_readl(host, SR);
    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_RXRDY | MCI_TXRDY);
    pending &= mci_readl(host, IMR);
    +
    host->data_status = status;
    + smp_wmb();
    atmci_set_pending(host, EVENT_DATA_ERROR);
    tasklet_schedule(&host->tasklet);
    }
    @@ -979,6 +1249,7 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id)
    mci_writel(host, IDR,
    ATMCI_DATA_ERROR_FLAGS | MCI_NOTBUSY);
    host->data_status = status;
    + smp_wmb();
    atmci_set_pending(host, EVENT_DATA_COMPLETE);
    tasklet_schedule(&host->tasklet);
    }
    @@ -988,18 +1259,15 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id)
    atmci_write_data_pio(host);

    if (pending & MCI_CMDRDY)
    - atmci_cmd_interrupt(mmc, status);
    + atmci_cmd_interrupt(host, status);
    } while (pass_count++ < 5);

    - spin_unlock(&mmc->lock);
    -
    return pass_count ? IRQ_HANDLED : IRQ_NONE;
    }

    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
    @@ -1007,21 +1275,122 @@ static irqreturn_t atmci_detect_interrupt(int irq, void *dev_id)
    * middle of the timer routine when this interrupt triggers.
    */
    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;
    }

    +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)
    {
    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);
    if (!regs)
    @@ -1033,27 +1402,13 @@ static int __init atmci_probe(struct platform_device *pdev)
    if (irq < 0)
    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;

    - host = mmc_priv(mmc);
    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");
    if (IS_ERR(host->mck)) {
    @@ -1073,123 +1428,74 @@ static int __init atmci_probe(struct platform_device *pdev)

    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)
    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);

    - 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;

    +err_init_slot:
    + free_irq(irq, host);
    err_request_irq:
    iounmap(host->regs);
    err_ioremap:
    clk_put(host->mck);
    err_clk_get:
    - mmc_free_host(mmc);
    + kfree(host);
    return ret;
    }

    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);

    - 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;
    }

    --
    1.5.6.5


    \
     
     \ /
      Last update: 2008-10-05 18:33    [W:0.099 / U:123.100 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site