From nobody Mon Sep 17 00:00:00 2001 From: HÃ¥vard Skinnemoen Date: Fri Nov 18 17:20:29 2005 +0100 Subject: [PATCH] AVR32: MMC Host Driver --- drivers/mmc/Kconfig | 10 drivers/mmc/Makefile | 1 drivers/mmc/atmel-mci.c | 738 ++++++++++++++++++++++++++++++++++++++++++++++++ drivers/mmc/atmel-mci.h | 192 ++++++++++++ 4 files changed, 941 insertions(+) Index: linux-2.6.18-avr32/drivers/mmc/Kconfig =================================================================== --- linux-2.6.18-avr32.orig/drivers/mmc/Kconfig 2007-01-15 10:14:40.000000000 +0100 +++ linux-2.6.18-avr32/drivers/mmc/Kconfig 2007-01-15 10:14:46.000000000 +0100 @@ -71,6 +71,16 @@ config MMC_OMAP If unsure, say N. +config MMC_ATMELMCI + tristate "Atmel Multimedia Card Interface support" + depends on AVR32 && MMC + help + This selects the Atmel Multimedia Card Interface. If you have + a AT91 (ARM) or AT32 (AVR32) platform with a Multimedia Card + slot, say Y or M here. + + If unsure, say N. + config MMC_WBSD tristate "Winbond W83L51xD SD/MMC Card Interface support" depends on MMC && ISA_DMA_API Index: linux-2.6.18-avr32/drivers/mmc/Makefile =================================================================== --- linux-2.6.18-avr32.orig/drivers/mmc/Makefile 2007-01-15 10:14:40.000000000 +0100 +++ linux-2.6.18-avr32/drivers/mmc/Makefile 2007-01-15 10:14:46.000000000 +0100 @@ -23,6 +23,7 @@ obj-$(CONFIG_MMC_WBSD) += wbsd.o obj-$(CONFIG_MMC_AU1X) += au1xmmc.o obj-$(CONFIG_MMC_OMAP) += omap.o obj-$(CONFIG_MMC_AT91RM9200) += at91_mci.o +obj-$(CONFIG_MMC_ATMELMCI) += atmel-mci.o mmc_core-y := mmc.o mmc_queue.o mmc_sysfs.o Index: linux-2.6.18-avr32/drivers/mmc/atmel-mci.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.18-avr32/drivers/mmc/atmel-mci.c 2007-01-15 10:31:55.000000000 +0100 @@ -0,0 +1,738 @@ +/* + * Atmel MultiMedia Card Interface driver + * + * Copyright (C) 2004-2006 Atmel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "atmel-mci.h" + +#define DRIVER_NAME "mmci" + +#define MCI_CMD_ERROR_FLAGS (MCI_BIT(RINDE) | MCI_BIT(RDIRE) | \ + MCI_BIT(RCRCE) | MCI_BIT(RENDE) | \ + MCI_BIT(RTOE)) +#define MCI_DATA_ERROR_FLAGS (MCI_BIT(DCRCE) | MCI_BIT(DTOE) | \ + MCI_BIT(OVRE) | MCI_BIT(UNRE)) + +enum { + EVENT_CMD_COMPLETE = 0, + EVENT_CMD_ERROR, + EVENT_DATA_COMPLETE, + EVENT_DATA_ERROR, + EVENT_STOP_SENT, + EVENT_STOP_COMPLETE, + EVENT_STOP_ERROR, + EVENT_DMA_ERROR, +}; + +struct atmel_mci_dma { + struct dma_request_sg req; + unsigned short rx_periph_id; + unsigned short tx_periph_id; + int blocks_left; +}; + +struct atmel_mci { + struct mmc_host *mmc; + void __iomem *regs; + struct atmel_mci_dma dma; + + struct mmc_request *mrq; + struct mmc_command *cmd; + struct mmc_data *data; + + u32 stop_cmdr; + u32 stop_iflags; + + struct tasklet_struct tasklet; + unsigned long pending_events; + unsigned long completed_events; + u32 error_status; + + unsigned long bus_hz; + unsigned long mapbase; + struct clk *mck; + struct platform_device *pdev; +}; + +/* Those printks take an awful lot of time... */ +#ifndef DEBUG +static unsigned int fmax = 15000000U; +#else +static unsigned int fmax = 1000000U; +#endif +module_param(fmax, uint, 0444); +MODULE_PARM_DESC(fmax, "Max frequency in Hz of the MMC bus clock"); + +static inline unsigned int ns_to_clocks(struct atmel_mci *host, + unsigned int ns) +{ + return (ns * (host->bus_hz / 1000000) + 999) / 1000; +} + +static void atmci_set_timeout(struct atmel_mci *host, + struct mmc_data *data) +{ + static unsigned dtomul_to_shift[] = { + 0, 4, 7, 8, 10, 12, 16, 20 + }; + unsigned timeout; + unsigned dtocyc, dtomul; + + timeout = ns_to_clocks(host, data->timeout_ns) + data->timeout_clks; + + for (dtomul = 0; dtomul < 8; dtomul++) { + unsigned shift = dtomul_to_shift[dtomul]; + dtocyc = (timeout + (1 << shift) - 1) >> shift; + if (dtocyc < 15) + break; + } + + if (dtomul >= 8) { + dtomul = 7; + dtocyc = 15; + } + + pr_debug("%s: setting timeout to %u cycles\n", + mmc_hostname(host->mmc), + dtocyc << dtomul_to_shift[dtomul]); + mci_writel(host, DTOR, (MCI_BF(DTOMUL, dtomul) + | MCI_BF(DTOCYC, dtocyc))); +} + +/* + * Return mask with interrupt flags to be handled for this command. + */ +static u32 atmci_prepare_command(struct mmc_host *mmc, + struct mmc_command *cmd, + u32 *cmd_flags) +{ + u32 cmdr; + u32 iflags; + + cmd->error = MMC_ERR_NONE; + + cmdr = 0; + BUG_ON(MCI_BFEXT(CMDNB, cmdr) != 0); + cmdr = MCI_BFINS(CMDNB, cmd->opcode, cmdr); + + if (cmd->flags & MMC_RSP_PRESENT) { + if (cmd->flags & MMC_RSP_136) + cmdr |= MCI_BF(RSPTYP, MCI_RSPTYP_136_BIT); + else + cmdr |= MCI_BF(RSPTYP, MCI_RSPTYP_48_BIT); + } + + /* + * This should really be MAXLAT_5 for CMD2 and ACMD41, but + * it's too difficult to determine whether this is an ACMD or + * not. Better make it 64. + */ + cmdr |= MCI_BIT(MAXLAT); + + if (mmc->ios.bus_mode == MMC_BUSMODE_OPENDRAIN) + cmdr |= MCI_BIT(OPDCMD); + + iflags = MCI_BIT(CMDRDY) | MCI_CMD_ERROR_FLAGS; + if (!(cmd->flags & MMC_RSP_CRC)) + iflags &= ~MCI_BIT(RCRCE); + + pr_debug("%s: cmd: op %02x arg %08x flags %08x, cmdflags %08lx\n", + mmc_hostname(mmc), cmd->opcode, cmd->arg, cmd->flags, + (unsigned long)cmdr); + + *cmd_flags = cmdr; + return iflags; +} + +static void atmci_start_command(struct atmel_mci *host, + struct mmc_command *cmd, + u32 cmd_flags) +{ + WARN_ON(host->cmd); + host->cmd = cmd; + + mci_writel(host, ARGR, cmd->arg); + mci_writel(host, CMDR, cmd_flags); + + if (cmd->data) + dma_start_request(host->dma.req.req.dmac, + host->dma.req.req.channel); +} + +/* + * Returns a mask of flags to be set in the command register when the + * command to start the transfer is to be sent. + */ +static u32 atmci_prepare_data(struct mmc_host *mmc, struct mmc_data *data) +{ + struct atmel_mci *host = mmc_priv(mmc); + u32 cmd_flags; + + WARN_ON(host->data); + host->data = data; + + atmci_set_timeout(host, data); + mci_writel(host, BLKR, (MCI_BF(BCNT, data->blocks) + | MCI_BF(BLKLEN, data->blksz))); + host->dma.req.block_size = data->blksz; + + cmd_flags = MCI_BF(TRCMD, MCI_TRCMD_START_TRANS); + if (data->flags & MMC_DATA_STREAM) + cmd_flags |= MCI_BF(TRTYP, MCI_TRTYP_STREAM); + else if (data->blocks > 1) + cmd_flags |= MCI_BF(TRTYP, MCI_TRTYP_MULTI_BLOCK); + else + cmd_flags |= MCI_BF(TRTYP, MCI_TRTYP_BLOCK); + + if (data->flags & MMC_DATA_READ) { + cmd_flags |= MCI_BIT(TRDIR); + host->dma.req.nr_sg + = dma_map_sg(&host->pdev->dev, data->sg, + data->sg_len, DMA_FROM_DEVICE); + host->dma.req.periph_id = host->dma.rx_periph_id; + host->dma.req.direction = DMA_DIR_PERIPH_TO_MEM; + host->dma.req.data_reg = host->mapbase + MCI_RDR; + } else { + host->dma.req.nr_sg + = dma_map_sg(&host->pdev->dev, data->sg, + data->sg_len, DMA_TO_DEVICE); + host->dma.req.periph_id = host->dma.tx_periph_id; + host->dma.req.direction = DMA_DIR_MEM_TO_PERIPH; + host->dma.req.data_reg = host->mapbase + MCI_TDR; + } + host->dma.req.sg = data->sg; + host->dma.blocks_left = data->blocks; + + dma_prepare_request_sg(host->dma.req.req.dmac, &host->dma.req); + + return cmd_flags; +} + +static void atmci_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct atmel_mci *host = mmc_priv(mmc); + struct mmc_data *data = mrq->data; + u32 iflags; + u32 cmdflags = 0; + + iflags = mci_readl(host, IMR); + if (iflags) + printk("WARNING: IMR=0x%08x\n", mci_readl(host, IMR)); + + WARN_ON(host->mrq != NULL); + host->mrq = mrq; + host->pending_events = 0; + host->completed_events = 0; + + iflags = atmci_prepare_command(mmc, mrq->cmd, &cmdflags); + + if (mrq->stop) { + BUG_ON(!data); + + host->stop_iflags = atmci_prepare_command(mmc, mrq->stop, + &host->stop_cmdr); + host->stop_cmdr |= MCI_BF(TRCMD, MCI_TRCMD_STOP_TRANS); + if (!(data->flags & MMC_DATA_WRITE)) + host->stop_cmdr |= MCI_BIT(TRDIR); + if (data->flags & MMC_DATA_STREAM) + host->stop_cmdr |= MCI_BF(TRTYP, MCI_TRTYP_STREAM); + else + host->stop_cmdr |= MCI_BF(TRTYP, MCI_TRTYP_MULTI_BLOCK); + } + if (data) { + cmdflags |= atmci_prepare_data(mmc, data); + iflags |= MCI_DATA_ERROR_FLAGS; + } + + atmci_start_command(host, mrq->cmd, cmdflags); + mci_writel(host, IER, iflags); +} + +static void atmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct atmel_mci *host = mmc_priv(mmc); + + if (ios->clock) { + u32 clkdiv; + + clkdiv = host->bus_hz / (2 * ios->clock) - 1; + if (clkdiv > 255) + clkdiv = 255; + mci_writel(host, MR, (clkdiv + | MCI_BIT(WRPROOF) + | MCI_BIT(RDPROOF))); + } + + switch (ios->bus_width) { + case MMC_BUS_WIDTH_1: + mci_writel(host, SDCR, 0); + break; + case MMC_BUS_WIDTH_4: + mci_writel(host, SDCR, MCI_BIT(SDCBUS)); + break; + } + + switch (ios->power_mode) { + case MMC_POWER_OFF: + mci_writel(host, CR, MCI_BIT(MCIDIS)); + break; + case MMC_POWER_UP: + mci_writel(host, CR, MCI_BIT(SWRST)); + break; + case MMC_POWER_ON: + mci_writel(host, CR, MCI_BIT(MCIEN)); + break; + } +} + +static struct mmc_host_ops atmci_ops = { + .request = atmci_request, + .set_ios = atmci_set_ios, +}; + +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); +} + +static void send_stop_cmd(struct mmc_host *mmc, struct mmc_data *data, + u32 flags) +{ + struct atmel_mci *host = mmc_priv(mmc); + + atmci_start_command(host, data->stop, host->stop_cmdr | flags); + mci_writel(host, IER, host->stop_iflags); +} + +static void atmci_data_complete(struct atmel_mci *host, struct mmc_data *data) +{ + host->data = NULL; + dma_unmap_sg(&host->pdev->dev, data->sg, host->dma.req.nr_sg, + ((data->flags & MMC_DATA_WRITE) + ? DMA_TO_DEVICE : DMA_FROM_DEVICE)); + + /* + * Data might complete before command for very short transfers + * (like READ_SCR) + */ + if (test_bit(EVENT_CMD_COMPLETE, &host->completed_events) + && (!data->stop + || test_bit(EVENT_STOP_COMPLETE, &host->completed_events))) + atmci_request_end(host->mmc, data->mrq); +} + +static void atmci_command_error(struct mmc_host *mmc, + struct mmc_command *cmd, + u32 status) +{ + pr_debug("%s: command error: status=0x%08x\n", + mmc_hostname(mmc), status); + + if (status & MCI_BIT(RTOE)) + cmd->error = MMC_ERR_TIMEOUT; + else if (status & MCI_BIT(RCRCE)) + cmd->error = MMC_ERR_BADCRC; + else + cmd->error = MMC_ERR_FAILED; +} + +static void atmci_tasklet_func(unsigned long priv) +{ + struct mmc_host *mmc = (struct mmc_host *)priv; + struct atmel_mci *host = mmc_priv(mmc); + struct mmc_request *mrq = host->mrq; + struct mmc_data *data = host->data; + + pr_debug("atmci_tasklet: pending/completed/mask %lx/%lx/%x\n", + host->pending_events, host->completed_events, + mci_readl(host, IMR)); + + if (test_and_clear_bit(EVENT_CMD_ERROR, &host->pending_events)) { + struct mmc_command *cmd; + + set_bit(EVENT_CMD_ERROR, &host->completed_events); + clear_bit(EVENT_CMD_COMPLETE, &host->pending_events); + cmd = host->mrq->cmd; + + if (cmd->data) { + dma_stop_request(host->dma.req.req.dmac, + host->dma.req.req.channel); + host->data = NULL; + } + + atmci_command_error(mmc, cmd, host->error_status); + atmci_request_end(mmc, cmd->mrq); + } + if (test_and_clear_bit(EVENT_STOP_ERROR, &host->pending_events)) { + set_bit(EVENT_STOP_ERROR, &host->completed_events); + clear_bit(EVENT_STOP_COMPLETE, &host->pending_events); + atmci_command_error(mmc, host->mrq->stop, + host->error_status); + if (!host->data) + atmci_request_end(mmc, host->mrq); + } + if (test_and_clear_bit(EVENT_CMD_COMPLETE, &host->pending_events)) { + set_bit(EVENT_CMD_COMPLETE, &host->completed_events); + if (!mrq->data + || test_bit(EVENT_DATA_COMPLETE, &host->completed_events)) + atmci_request_end(mmc, mrq); + } + if (test_and_clear_bit(EVENT_STOP_COMPLETE, &host->pending_events)) { + set_bit(EVENT_STOP_COMPLETE, &host->completed_events); + if (test_bit(EVENT_DATA_COMPLETE, &host->completed_events)) + atmci_request_end(mmc, mrq); + } + if (test_and_clear_bit(EVENT_DMA_ERROR, &host->pending_events)) { + set_bit(EVENT_DMA_ERROR, &host->completed_events); + clear_bit(EVENT_DATA_COMPLETE, &host->pending_events); + + /* DMA controller got bus error => invalid address */ + data->error = MMC_ERR_INVALID; + + printk(KERN_DEBUG "%s: dma error after %u bytes xfered\n", + mmc_hostname(mmc), host->data->bytes_xfered); + + if (data->stop + && !test_and_set_bit(EVENT_STOP_SENT, + &host->completed_events)) + /* TODO: Check if card is still present */ + send_stop_cmd(host->mmc, data, 0); + + atmci_data_complete(host, data); + } + if (test_and_clear_bit(EVENT_DATA_ERROR, &host->pending_events)) { + u32 status = host->error_status; + + set_bit(EVENT_DATA_ERROR, &host->completed_events); + clear_bit(EVENT_DATA_COMPLETE, &host->pending_events); + + dma_stop_request(host->dma.req.req.dmac, + host->dma.req.req.channel); + + printk(KERN_DEBUG "%s: data error: status=0x%08x\n", + mmc_hostname(host->mmc), status); + + if (status & MCI_BIT(DCRCE)) { + printk(KERN_DEBUG "%s: Data CRC error\n", + mmc_hostname(host->mmc)); + data->error = MMC_ERR_BADCRC; + } else if (status & MCI_BIT(DTOE)) { + printk(KERN_DEBUG "%s: Data Timeout error\n", + mmc_hostname(host->mmc)); + data->error = MMC_ERR_TIMEOUT; + } else { + printk(KERN_DEBUG "%s: Data FIFO error\n", + mmc_hostname(host->mmc)); + data->error = MMC_ERR_FIFO; + } + printk(KERN_DEBUG "%s: Bytes xfered: %u\n", + mmc_hostname(host->mmc), data->bytes_xfered); + + if (data->stop + && !test_and_set_bit(EVENT_STOP_SENT, &host->completed_events)) + /* TODO: Check if card is still present */ + send_stop_cmd(host->mmc, data, 0); + + atmci_data_complete(host, data); + } + if (test_and_clear_bit(EVENT_DATA_COMPLETE, &host->pending_events)) { + set_bit(EVENT_DATA_COMPLETE, &host->completed_events); + data->bytes_xfered = data->blocks * data->blksz; + atmci_data_complete(host, data); + } +} + +static void atmci_cmd_interrupt(struct mmc_host *mmc, u32 status) +{ + struct atmel_mci *host = mmc_priv(mmc); + struct mmc_command *cmd = host->cmd; + + /* + * Read the response now so that we're free to send a new + * command immediately. + */ + cmd->resp[0] = mci_readl(host, RSPR); + cmd->resp[1] = mci_readl(host, RSPR); + cmd->resp[2] = mci_readl(host, RSPR); + cmd->resp[3] = mci_readl(host, RSPR); + + mci_writel(host, IDR, MCI_BIT(CMDRDY) | MCI_CMD_ERROR_FLAGS); + host->cmd = NULL; + + if (test_bit(EVENT_STOP_SENT, &host->completed_events)) + set_bit(EVENT_STOP_COMPLETE, &host->pending_events); + else + set_bit(EVENT_CMD_COMPLETE, &host->pending_events); + + tasklet_schedule(&host->tasklet); +} + +static void atmci_xfer_complete(struct dma_request *_req) +{ + struct dma_request_sg *req = to_dma_request_sg(_req); + struct atmel_mci_dma *dma; + struct atmel_mci *host; + struct mmc_data *data; + + dma = container_of(req, struct atmel_mci_dma, req); + host = container_of(dma, struct atmel_mci, dma); + data = host->data; + + if (data->stop && !test_and_set_bit(EVENT_STOP_SENT, + &host->completed_events)) + send_stop_cmd(host->mmc, data, 0); + + if (data->flags & MMC_DATA_READ) { + mci_writel(host, IDR, MCI_DATA_ERROR_FLAGS); + set_bit(EVENT_DATA_COMPLETE, &host->pending_events); + tasklet_schedule(&host->tasklet); + } else { + /* + * For the WRITE case, wait for NOTBUSY. This function + * is called when everything has been written to the + * controller, not when the card is done programming. + */ + mci_writel(host, IER, MCI_BIT(NOTBUSY)); + } +} + +static void atmci_dma_error(struct dma_request *_req) +{ + struct dma_request_sg *req = to_dma_request_sg(_req); + struct atmel_mci_dma *dma; + struct atmel_mci *host; + + dma = container_of(req, struct atmel_mci_dma, req); + host = container_of(dma, struct atmel_mci, dma); + + mci_writel(host, IDR, (MCI_BIT(NOTBUSY) + | MCI_DATA_ERROR_FLAGS)); + + set_bit(EVENT_DMA_ERROR, &host->pending_events); + tasklet_schedule(&host->tasklet); +} + +static irqreturn_t atmci_interrupt(int irq, void *dev_id, + struct pt_regs *regs) +{ + struct mmc_host *mmc = dev_id; + struct atmel_mci *host = mmc_priv(mmc); + u32 status, mask, pending; + + spin_lock(&mmc->lock); + + status = mci_readl(host, SR); + mask = mci_readl(host, IMR); + pending = status & mask; + + do { + if (pending & MCI_CMD_ERROR_FLAGS) { + mci_writel(host, IDR, (MCI_BIT(CMDRDY) + | MCI_BIT(NOTBUSY) + | MCI_CMD_ERROR_FLAGS + | MCI_DATA_ERROR_FLAGS)); + host->error_status = status; + host->cmd = NULL; + if (test_bit(EVENT_STOP_SENT, &host->completed_events)) + set_bit(EVENT_STOP_ERROR, &host->pending_events); + else + set_bit(EVENT_CMD_ERROR, &host->pending_events); + tasklet_schedule(&host->tasklet); + break; + } + if (pending & MCI_DATA_ERROR_FLAGS) { + mci_writel(host, IDR, (MCI_BIT(NOTBUSY) + | MCI_DATA_ERROR_FLAGS)); + host->error_status = status; + set_bit(EVENT_DATA_ERROR, &host->pending_events); + tasklet_schedule(&host->tasklet); + break; + } + if (pending & MCI_BIT(CMDRDY)) + atmci_cmd_interrupt(mmc, status); + if (pending & MCI_BIT(NOTBUSY)) { + mci_writel(host, IDR, (MCI_BIT(NOTBUSY) + | MCI_DATA_ERROR_FLAGS)); + set_bit(EVENT_DATA_COMPLETE, &host->pending_events); + tasklet_schedule(&host->tasklet); + } + + status = mci_readl(host, SR); + mask = mci_readl(host, IMR); + pending = status & mask; + } while (pending); + + spin_unlock(&mmc->lock); + + return IRQ_HANDLED; +} + +static int __devinit atmci_probe(struct platform_device *pdev) +{ + struct atmel_mci *host; + struct mmc_host *mmc; + struct resource *regs; + int irq; + int ret; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) + return -ENXIO; + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + mmc = mmc_alloc_host(sizeof(struct atmel_mci), &pdev->dev); + if (!mmc) + return -ENOMEM; + + host = mmc_priv(mmc); + host->pdev = pdev; + host->mmc = mmc; + + host->mck = clk_get(&pdev->dev, "mck"); + if (IS_ERR(host->mck)) { + ret = PTR_ERR(host->mck); + goto out_free_host; + } + clk_enable(host->mck); + + ret = -ENOMEM; + host->regs = ioremap(regs->start, regs->end - regs->start + 1); + if (!host->regs) + goto out_disable_clk; + + host->bus_hz = clk_get_rate(host->mck); + host->mapbase = regs->start; + + mmc->ops = &atmci_ops; + mmc->f_min = (host->bus_hz + 511) / 512; + mmc->f_max = min((unsigned int)(host->bus_hz / 2), fmax); + mmc->ocr_avail = 0x00100000; + mmc->caps |= MMC_CAP_4_BIT_DATA; + + tasklet_init(&host->tasklet, atmci_tasklet_func, (unsigned long)mmc); + + ret = request_irq(irq, atmci_interrupt, 0, "mmci", mmc); + if (ret) + goto out_unmap; + + /* TODO: Get this information from platform data */ + ret = -ENOMEM; + host->dma.req.req.dmac = find_dma_controller(0); + if (!host->dma.req.req.dmac) { + printk(KERN_ERR + "mmci: No DMA controller available, aborting\n"); + goto out_free_irq; + } + ret = dma_alloc_channel(host->dma.req.req.dmac); + if (ret < 0) { + printk(KERN_ERR + "mmci: Unable to allocate DMA channel, aborting\n"); + goto out_free_irq; + } + host->dma.req.req.channel = ret; + host->dma.req.width = DMA_WIDTH_32BIT; + host->dma.req.req.xfer_complete = atmci_xfer_complete; + host->dma.req.req.block_complete = NULL; // atmci_block_complete; + host->dma.req.req.error = atmci_dma_error; + host->dma.rx_periph_id = 0; + host->dma.tx_periph_id = 1; + + mci_writel(host, CR, MCI_BIT(SWRST)); + mci_writel(host, IDR, ~0UL); + mci_writel(host, CR, MCI_BIT(MCIEN)); + + platform_set_drvdata(pdev, host); + + mmc_add_host(mmc); + + printk(KERN_INFO "%s: Atmel MCI controller at 0x%08lx irq %d\n", + mmc_hostname(mmc), host->mapbase, irq); + + return 0; + +out_free_irq: + free_irq(irq, mmc); +out_unmap: + iounmap(host->regs); +out_disable_clk: + clk_disable(host->mck); + clk_put(host->mck); +out_free_host: + mmc_free_host(mmc); + return ret; +} + +static int __devexit atmci_remove(struct platform_device *pdev) +{ + struct atmel_mci *host = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + + if (host) { + mmc_remove_host(host->mmc); + + mci_writel(host, IDR, ~0UL); + mci_writel(host, CR, MCI_BIT(MCIDIS)); + mci_readl(host, SR); + + free_irq(platform_get_irq(pdev, 0), host->mmc); + iounmap(host->regs); + + clk_disable(host->mck); + clk_put(host->mck); + + mmc_free_host(host->mmc); + } + return 0; +} + +static struct platform_driver atmci_driver = { + .probe = atmci_probe, + .remove = __devexit_p(atmci_remove), + .driver = { + .name = DRIVER_NAME, + }, +}; + +static int __init atmci_init(void) +{ + return platform_driver_register(&atmci_driver); +} + +static void __exit atmci_exit(void) +{ + platform_driver_unregister(&atmci_driver); +} + +module_init(atmci_init); +module_exit(atmci_exit); + +MODULE_DESCRIPTION("Atmel Multimedia Card Interface driver"); +MODULE_LICENSE("GPL"); Index: linux-2.6.18-avr32/drivers/mmc/atmel-mci.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.18-avr32/drivers/mmc/atmel-mci.h 2007-01-15 10:31:36.000000000 +0100 @@ -0,0 +1,192 @@ +/* + * Atmel MultiMedia Card Interface driver + * + * Copyright (C) 2004-2006 Atmel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __DRIVERS_MMC_ATMEL_MCI_H__ +#define __DRIVERS_MMC_ATMEL_MCI_H__ + +/* MCI register offsets */ +#define MCI_CR 0x0000 +#define MCI_MR 0x0004 +#define MCI_DTOR 0x0008 +#define MCI_SDCR 0x000c +#define MCI_ARGR 0x0010 +#define MCI_CMDR 0x0014 +#define MCI_BLKR 0x0018 +#define MCI_RSPR 0x0020 +#define MCI_RSPR1 0x0024 +#define MCI_RSPR2 0x0028 +#define MCI_RSPR3 0x002c +#define MCI_RDR 0x0030 +#define MCI_TDR 0x0034 +#define MCI_SR 0x0040 +#define MCI_IER 0x0044 +#define MCI_IDR 0x0048 +#define MCI_IMR 0x004c + +/* Bitfields in CR */ +#define MCI_MCIEN_OFFSET 0 +#define MCI_MCIEN_SIZE 1 +#define MCI_MCIDIS_OFFSET 1 +#define MCI_MCIDIS_SIZE 1 +#define MCI_PWSEN_OFFSET 2 +#define MCI_PWSEN_SIZE 1 +#define MCI_PWSDIS_OFFSET 3 +#define MCI_PWSDIS_SIZE 1 +#define MCI_SWRST_OFFSET 7 +#define MCI_SWRST_SIZE 1 + +/* Bitfields in MR */ +#define MCI_CLKDIV_OFFSET 0 +#define MCI_CLKDIV_SIZE 8 +#define MCI_PWSDIV_OFFSET 8 +#define MCI_PWSDIV_SIZE 3 +#define MCI_RDPROOF_OFFSET 11 +#define MCI_RDPROOF_SIZE 1 +#define MCI_WRPROOF_OFFSET 12 +#define MCI_WRPROOF_SIZE 1 +#define MCI_DMAPADV_OFFSET 14 +#define MCI_DMAPADV_SIZE 1 +#define MCI_BLKLEN_OFFSET 16 +#define MCI_BLKLEN_SIZE 16 + +/* Bitfields in DTOR */ +#define MCI_DTOCYC_OFFSET 0 +#define MCI_DTOCYC_SIZE 4 +#define MCI_DTOMUL_OFFSET 4 +#define MCI_DTOMUL_SIZE 3 + +/* Bitfields in SDCR */ +#define MCI_SDCSEL_OFFSET 0 +#define MCI_SDCSEL_SIZE 4 +#define MCI_SDCBUS_OFFSET 7 +#define MCI_SDCBUS_SIZE 1 + +/* Bitfields in ARGR */ +#define MCI_ARG_OFFSET 0 +#define MCI_ARG_SIZE 32 + +/* Bitfields in CMDR */ +#define MCI_CMDNB_OFFSET 0 +#define MCI_CMDNB_SIZE 6 +#define MCI_RSPTYP_OFFSET 6 +#define MCI_RSPTYP_SIZE 2 +#define MCI_SPCMD_OFFSET 8 +#define MCI_SPCMD_SIZE 3 +#define MCI_OPDCMD_OFFSET 11 +#define MCI_OPDCMD_SIZE 1 +#define MCI_MAXLAT_OFFSET 12 +#define MCI_MAXLAT_SIZE 1 +#define MCI_TRCMD_OFFSET 16 +#define MCI_TRCMD_SIZE 2 +#define MCI_TRDIR_OFFSET 18 +#define MCI_TRDIR_SIZE 1 +#define MCI_TRTYP_OFFSET 19 +#define MCI_TRTYP_SIZE 2 + +/* Bitfields in BLKR */ +#define MCI_BCNT_OFFSET 0 +#define MCI_BCNT_SIZE 16 + +/* Bitfields in RSPRn */ +#define MCI_RSP_OFFSET 0 +#define MCI_RSP_SIZE 32 + +/* Bitfields in SR/IER/IDR/IMR */ +#define MCI_CMDRDY_OFFSET 0 +#define MCI_CMDRDY_SIZE 1 +#define MCI_RXRDY_OFFSET 1 +#define MCI_RXRDY_SIZE 1 +#define MCI_TXRDY_OFFSET 2 +#define MCI_TXRDY_SIZE 1 +#define MCI_BLKE_OFFSET 3 +#define MCI_BLKE_SIZE 1 +#define MCI_DTIP_OFFSET 4 +#define MCI_DTIP_SIZE 1 +#define MCI_NOTBUSY_OFFSET 5 +#define MCI_NOTBUSY_SIZE 1 +#define MCI_ENDRX_OFFSET 6 +#define MCI_ENDRX_SIZE 1 +#define MCI_ENDTX_OFFSET 7 +#define MCI_ENDTX_SIZE 1 +#define MCI_RXBUFF_OFFSET 14 +#define MCI_RXBUFF_SIZE 1 +#define MCI_TXBUFE_OFFSET 15 +#define MCI_TXBUFE_SIZE 1 +#define MCI_RINDE_OFFSET 16 +#define MCI_RINDE_SIZE 1 +#define MCI_RDIRE_OFFSET 17 +#define MCI_RDIRE_SIZE 1 +#define MCI_RCRCE_OFFSET 18 +#define MCI_RCRCE_SIZE 1 +#define MCI_RENDE_OFFSET 19 +#define MCI_RENDE_SIZE 1 +#define MCI_RTOE_OFFSET 20 +#define MCI_RTOE_SIZE 1 +#define MCI_DCRCE_OFFSET 21 +#define MCI_DCRCE_SIZE 1 +#define MCI_DTOE_OFFSET 22 +#define MCI_DTOE_SIZE 1 +#define MCI_OVRE_OFFSET 30 +#define MCI_OVRE_SIZE 1 +#define MCI_UNRE_OFFSET 31 +#define MCI_UNRE_SIZE 1 + +/* Constants for DTOMUL */ +#define MCI_DTOMUL_1_CYCLE 0 +#define MCI_DTOMUL_16_CYCLES 1 +#define MCI_DTOMUL_128_CYCLES 2 +#define MCI_DTOMUL_256_CYCLES 3 +#define MCI_DTOMUL_1024_CYCLES 4 +#define MCI_DTOMUL_4096_CYCLES 5 +#define MCI_DTOMUL_65536_CYCLES 6 +#define MCI_DTOMUL_1048576_CYCLES 7 + +/* Constants for RSPTYP */ +#define MCI_RSPTYP_NO_RESP 0 +#define MCI_RSPTYP_48_BIT 1 +#define MCI_RSPTYP_136_BIT 2 + +/* Constants for SPCMD */ +#define MCI_SPCMD_NO_SPEC_CMD 0 +#define MCI_SPCMD_INIT_CMD 1 +#define MCI_SPCMD_SYNC_CMD 2 +#define MCI_SPCMD_INT_CMD 4 +#define MCI_SPCMD_INT_RESP 5 + +/* Constants for TRCMD */ +#define MCI_TRCMD_NO_TRANS 0 +#define MCI_TRCMD_START_TRANS 1 +#define MCI_TRCMD_STOP_TRANS 2 + +/* Constants for TRTYP */ +#define MCI_TRTYP_BLOCK 0 +#define MCI_TRTYP_MULTI_BLOCK 1 +#define MCI_TRTYP_STREAM 2 + +/* Bit manipulation macros */ +#define MCI_BIT(name) \ + (1 << MCI_##name##_OFFSET) +#define MCI_BF(name,value) \ + (((value) & ((1 << MCI_##name##_SIZE) - 1)) \ + << MCI_##name##_OFFSET) +#define MCI_BFEXT(name,value) \ + (((value) >> MCI_##name##_OFFSET) \ + & ((1 << MCI_##name##_SIZE) - 1)) +#define MCI_BFINS(name,value,old) \ + (((old) & ~(((1 << MCI_##name##_SIZE) - 1) \ + << MCI_##name##_OFFSET)) \ + | MCI_BF(name,value)) + +/* Register access macros */ +#define mci_readl(port,reg) \ + __raw_readl((port)->regs + MCI_##reg) +#define mci_writel(port,reg,value) \ + __raw_writel((value), (port)->regs + MCI_##reg) + +#endif /* __DRIVERS_MMC_ATMEL_MCI_H__ */