blob: 8170905a040149db94f8430074c037e659285400 [file] [log] [blame]
/*
* Atmel MultiMedia Card Interface driver
*
* Copyright (C) 2004-2008 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 <linux/blkdev.h>
#include <linux/clk.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/scatterlist.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/mmc/host.h>
#include <asm/atmel-mci.h>
#include <asm/io.h>
#include <asm/unaligned.h>
#include <mach/board.h>
#include "atmel-mci-regs.h"
#define ATMCI_DATA_ERROR_FLAGS (MCI_DCRCE | MCI_DTOE | MCI_OVRE | MCI_UNRE)
enum {
EVENT_CMD_COMPLETE = 0,
EVENT_XFER_COMPLETE,
EVENT_DATA_COMPLETE,
EVENT_DATA_ERROR,
};
enum atmel_mci_state {
STATE_SENDING_CMD = 0,
STATE_SENDING_DATA,
STATE_DATA_BUSY,
STATE_SENDING_STOP,
STATE_DATA_ERROR,
};
struct atmel_mci {
struct mmc_host *mmc;
void __iomem *regs;
struct scatterlist *sg;
unsigned int pio_offset;
struct mmc_request *mrq;
struct mmc_command *cmd;
struct mmc_data *data;
u32 cmd_status;
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;
int present;
int detect_pin;
int wp_pin;
/* For detect pin debouncing */
struct timer_list detect_timer;
unsigned long bus_hz;
unsigned long mapbase;
struct clk *mck;
struct platform_device *pdev;
};
#define atmci_test_and_clear_pending(host, event) \
test_and_clear_bit(event, &host->pending_events)
#define atmci_set_completed(host, event) \
set_bit(event, &host->completed_events)
#define atmci_set_pending(host, event) \
set_bit(event, &host->pending_events)
/*
* The debugfs stuff below is mostly optimized away when
* CONFIG_DEBUG_FS is not set.
*/
static int atmci_req_show(struct seq_file *s, void *v)
{
struct atmel_mci *host = s->private;
struct mmc_request *mrq = host->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);
if (mrq) {
cmd = mrq->cmd;
data = mrq->data;
stop = mrq->stop;
if (cmd)
seq_printf(s,
"CMD%u(0x%x) flg %x rsp %x %x %x %x err %d\n",
cmd->opcode, cmd->arg, cmd->flags,
cmd->resp[0], cmd->resp[1], cmd->resp[2],
cmd->resp[2], cmd->error);
if (data)
seq_printf(s, "DATA %u / %u * %u flg %x err %d\n",
data->bytes_xfered, data->blocks,
data->blksz, data->flags, data->error);
if (stop)
seq_printf(s,
"CMD%u(0x%x) flg %x rsp %x %x %x %x err %d\n",
stop->opcode, stop->arg, stop->flags,
stop->resp[0], stop->resp[1], stop->resp[2],
stop->resp[2], stop->error);
}
spin_unlock_irq(&host->mmc->lock);
return 0;
}
static int atmci_req_open(struct inode *inode, struct file *file)
{
return single_open(file, atmci_req_show, inode->i_private);
}
static const struct file_operations atmci_req_fops = {
.owner = THIS_MODULE,
.open = atmci_req_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static void atmci_show_status_reg(struct seq_file *s,
const char *regname, u32 value)
{
static const char *sr_bit[] = {
[0] = "CMDRDY",
[1] = "RXRDY",
[2] = "TXRDY",
[3] = "BLKE",
[4] = "DTIP",
[5] = "NOTBUSY",
[8] = "SDIOIRQA",
[9] = "SDIOIRQB",
[16] = "RINDE",
[17] = "RDIRE",
[18] = "RCRCE",
[19] = "RENDE",
[20] = "RTOE",
[21] = "DCRCE",
[22] = "DTOE",
[30] = "OVRE",
[31] = "UNRE",
};
unsigned int i;
seq_printf(s, "%s:\t0x%08x", regname, value);
for (i = 0; i < ARRAY_SIZE(sr_bit); i++) {
if (value & (1 << i)) {
if (sr_bit[i])
seq_printf(s, " %s", sr_bit[i]);
else
seq_puts(s, " UNKNOWN");
}
}
seq_putc(s, '\n');
}
static int atmci_regs_show(struct seq_file *s, void *v)
{
struct atmel_mci *host = s->private;
u32 *buf;
buf = kmalloc(MCI_REGS_SIZE, GFP_KERNEL);
if (!buf)
return -ENOMEM;
/* Grab a more or less consistent snapshot */
spin_lock_irq(&host->mmc->lock);
clk_enable(host->mck);
memcpy_fromio(buf, host->regs, MCI_REGS_SIZE);
clk_disable(host->mck);
spin_unlock_irq(&host->mmc->lock);
seq_printf(s, "MR:\t0x%08x%s%s CLKDIV=%u\n",
buf[MCI_MR / 4],
buf[MCI_MR / 4] & MCI_MR_RDPROOF ? " RDPROOF" : "",
buf[MCI_MR / 4] & MCI_MR_WRPROOF ? " WRPROOF" : "",
buf[MCI_MR / 4] & 0xff);
seq_printf(s, "DTOR:\t0x%08x\n", buf[MCI_DTOR / 4]);
seq_printf(s, "SDCR:\t0x%08x\n", buf[MCI_SDCR / 4]);
seq_printf(s, "ARGR:\t0x%08x\n", buf[MCI_ARGR / 4]);
seq_printf(s, "BLKR:\t0x%08x BCNT=%u BLKLEN=%u\n",
buf[MCI_BLKR / 4],
buf[MCI_BLKR / 4] & 0xffff,
(buf[MCI_BLKR / 4] >> 16) & 0xffff);
/* Don't read RSPR and RDR; it will consume the data there */
atmci_show_status_reg(s, "SR", buf[MCI_SR / 4]);
atmci_show_status_reg(s, "IMR", buf[MCI_IMR / 4]);
kfree(buf);
return 0;
}
static int atmci_regs_open(struct inode *inode, struct file *file)
{
return single_open(file, atmci_regs_show, inode->i_private);
}
static const struct file_operations atmci_regs_fops = {
.owner = THIS_MODULE,
.open = atmci_regs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static void atmci_init_debugfs(struct atmel_mci *host)
{
struct mmc_host *mmc;
struct dentry *root;
struct dentry *node;
mmc = host->mmc;
root = mmc->debugfs_root;
if (!root)
return;
node = debugfs_create_file("regs", S_IRUSR, root, host,
&atmci_regs_fops);
if (IS_ERR(node))
return;
if (!node)
goto err;
node = debugfs_create_file("req", S_IRUSR, root, host, &atmci_req_fops);
if (!node)
goto err;
node = debugfs_create_u32("state", S_IRUSR, root, (u32 *)&host->state);
if (!node)
goto err;
node = debugfs_create_x32("pending_events", S_IRUSR, root,
(u32 *)&host->pending_events);
if (!node)
goto err;
node = debugfs_create_x32("completed_events", S_IRUSR, root,
(u32 *)&host->completed_events);
if (!node)
goto err;
return;
err:
dev_err(&host->pdev->dev,
"failed to initialize debugfs for controller\n");
}
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;
unsigned 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;
}
dev_vdbg(&host->mmc->class_dev, "setting timeout to %u cycles\n",
dtocyc << dtomul_to_shift[dtomul]);
mci_writel(host, DTOR, (MCI_DTOMUL(dtomul) | MCI_DTOCYC(dtocyc)));
}
/*
* Return mask with command flags to be enabled for this command.
*/
static u32 atmci_prepare_command(struct mmc_host *mmc,
struct mmc_command *cmd)
{
struct mmc_data *data;
u32 cmdr;
cmd->error = -EINPROGRESS;
cmdr = MCI_CMDR_CMDNB(cmd->opcode);
if (cmd->flags & MMC_RSP_PRESENT) {
if (cmd->flags & MMC_RSP_136)
cmdr |= MCI_CMDR_RSPTYP_136BIT;
else
cmdr |= MCI_CMDR_RSPTYP_48BIT;
}
/*
* 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_CMDR_MAXLAT_64CYC;
if (mmc->ios.bus_mode == MMC_BUSMODE_OPENDRAIN)
cmdr |= MCI_CMDR_OPDCMD;
data = cmd->data;
if (data) {
cmdr |= MCI_CMDR_START_XFER;
if (data->flags & MMC_DATA_STREAM)
cmdr |= MCI_CMDR_STREAM;
else if (data->blocks > 1)
cmdr |= MCI_CMDR_MULTI_BLOCK;
else
cmdr |= MCI_CMDR_BLOCK;
if (data->flags & MMC_DATA_READ)
cmdr |= MCI_CMDR_TRDIR_READ;
}
return cmdr;
}
static void atmci_start_command(struct atmel_mci *host,
struct mmc_command *cmd,
u32 cmd_flags)
{
WARN_ON(host->cmd);
host->cmd = cmd;
dev_vdbg(&host->mmc->class_dev,
"start command: ARGR=0x%08x CMDR=0x%08x\n",
cmd->arg, cmd_flags);
mci_writel(host, ARGR, cmd->arg);
mci_writel(host, CMDR, cmd_flags);
}
static void send_stop_cmd(struct mmc_host *mmc, 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)
{
struct atmel_mci *host = mmc_priv(mmc);
u32 iflags;
data->error = -EINPROGRESS;
WARN_ON(host->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;
host->sg = data->sg;
host->pio_offset = 0;
if (data->flags & MMC_DATA_READ)
iflags |= MCI_RXRDY;
else
iflags |= MCI_TXRDY;
return iflags;
}
static void atmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct atmel_mci *host = mmc_priv(mmc);
struct mmc_data *data;
struct mmc_command *cmd;
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;
}
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. */
data = mrq->data;
if (data) {
if (data->blocks > 1 && data->blksz & 3)
goto fail;
atmci_set_timeout(host, data);
/* Must set block count/size before sending command */
mci_writel(host, BLKR, MCI_BCNT(data->blocks)
| MCI_BLKLEN(data->blksz));
}
iflags = MCI_CMDRDY;
cmd = mrq->cmd;
cmdflags = atmci_prepare_command(mmc, cmd);
atmci_start_command(host, cmd, cmdflags);
if (data)
iflags |= atmci_submit_data(mmc, data);
if (mrq->stop) {
host->stop_cmdr = atmci_prepare_command(mmc, mrq->stop);
host->stop_cmdr |= MCI_CMDR_STOP_XFER;
if (!(data->flags & MMC_DATA_WRITE))
host->stop_cmdr |= MCI_CMDR_TRDIR_READ;
if (data->flags & MMC_DATA_STREAM)
host->stop_cmdr |= MCI_CMDR_STREAM;
else
host->stop_cmdr |= MCI_CMDR_MULTI_BLOCK;
}
/*
* We could have enabled interrupts earlier, but I suspect
* that would open up a nice can of interesting race
* conditions (e.g. command and data complete, but stop not
* prepared yet.)
*/
mci_writel(host, IER, iflags);
return;
fail:
host->mrq = NULL;
mrq->cmd->error = -EINVAL;
mmc_request_done(mmc, mrq);
}
static void atmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct atmel_mci *host = mmc_priv(mmc);
host->sdc_reg &= ~MCI_SDCBUS_MASK;
switch (ios->bus_width) {
case MMC_BUS_WIDTH_1:
host->sdc_reg |= MCI_SDCBUS_1BIT;
break;
case MMC_BUS_WIDTH_4:
host->sdc_reg = MCI_SDCBUS_4BIT;
break;
}
if (ios->clock) {
u32 clkdiv;
if (!host->mode_reg)
clk_enable(host->mck);
/* Set clock rate */
clkdiv = DIV_ROUND_UP(host->bus_hz, 2 * ios->clock) - 1;
if (clkdiv > 255) {
dev_warn(&mmc->class_dev,
"clock %u too slow; using %lu\n",
ios->clock, host->bus_hz / (2 * 256));
clkdiv = 255;
}
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);
} else {
mci_writel(host, CR, MCI_CR_MCIDIS);
if (host->mode_reg) {
mci_readl(host, MR);
clk_disable(host->mck);
}
host->mode_reg = 0;
}
switch (ios->power_mode) {
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.
*/
break;
}
}
static int atmci_get_ro(struct mmc_host *mmc)
{
int read_only = 0;
struct atmel_mci *host = mmc_priv(mmc);
if (gpio_is_valid(host->wp_pin)) {
read_only = gpio_get_value(host->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 = {
.request = atmci_request,
.set_ios = atmci_set_ios,
.get_ro = atmci_get_ro,
};
static void atmci_command_complete(struct atmel_mci *host,
struct mmc_command *cmd)
{
u32 status = host->cmd_status;
/* Read the response from the card (up to 16 bytes) */
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);
if (status & MCI_RTOE)
cmd->error = -ETIMEDOUT;
else if ((cmd->flags & MMC_RSP_CRC) && (status & MCI_RCRCE))
cmd->error = -EILSEQ;
else if (status & (MCI_RINDE | MCI_RDIRE | MCI_RENDE))
cmd->error = -EIO;
else
cmd->error = 0;
if (cmd->error) {
dev_dbg(&host->mmc->class_dev,
"command error: status=0x%08x\n", status);
if (cmd->data) {
host->data = NULL;
mci_writel(host, IDR, MCI_NOTBUSY
| MCI_TXRDY | MCI_RXRDY
| ATMCI_DATA_ERROR_FLAGS);
}
}
}
static void atmci_detect_change(unsigned long data)
{
struct atmel_mci *host = (struct atmel_mci *)data;
struct mmc_request *mrq = host->mrq;
int present;
/*
* atmci_remove() sets detect_pin to -1 before freeing the
* interrupt. We must not re-enable the interrupt if it has
* been freed.
*/
smp_rmb();
if (!gpio_is_valid(host->detect_pin))
return;
enable_irq(gpio_to_irq(host->detect_pin));
present = !gpio_get_value(host->detect_pin);
dev_vdbg(&host->pdev->dev, "detect change: %d (was %d)\n",
present, host->present);
if (present != host->present) {
dev_dbg(&host->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);
}
/* Clean up queue if present */
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)
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)
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);
}
mmc_detect_change(host->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 mmc_request *mrq = host->mrq;
struct mmc_data *data = host->data;
struct mmc_command *cmd = host->cmd;
enum atmel_mci_state state = host->state;
enum atmel_mci_state prev_state;
u32 status;
state = host->state;
dev_vdbg(&mmc->class_dev,
"tasklet: state %u pending/completed/mask %lx/%lx/%x\n",
state, host->pending_events, host->completed_events,
mci_readl(host, IMR));
do {
prev_state = state;
switch (state) {
case STATE_SENDING_CMD:
if (!atmci_test_and_clear_pending(host,
EVENT_CMD_COMPLETE))
break;
host->cmd = NULL;
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;
}
prev_state = state = STATE_SENDING_DATA;
/* fall through */
case STATE_SENDING_DATA:
if (atmci_test_and_clear_pending(host,
EVENT_DATA_ERROR)) {
if (data->stop)
send_stop_cmd(host->mmc, data);
state = STATE_DATA_ERROR;
break;
}
if (!atmci_test_and_clear_pending(host,
EVENT_XFER_COMPLETE))
break;
atmci_set_completed(host, EVENT_XFER_COMPLETE);
prev_state = state = STATE_DATA_BUSY;
/* fall through */
case STATE_DATA_BUSY:
if (!atmci_test_and_clear_pending(host,
EVENT_DATA_COMPLETE))
break;
host->data = NULL;
atmci_set_completed(host, EVENT_DATA_COMPLETE);
status = host->data_status;
if (unlikely(status & ATMCI_DATA_ERROR_FLAGS)) {
if (status & MCI_DTOE) {
dev_dbg(&mmc->class_dev,
"data timeout error\n");
data->error = -ETIMEDOUT;
} else if (status & MCI_DCRCE) {
dev_dbg(&mmc->class_dev,
"data CRC error\n");
data->error = -EILSEQ;
} else {
dev_dbg(&mmc->class_dev,
"data FIFO error (status=%08x)\n",
status);
data->error = -EIO;
}
} else {
data->bytes_xfered = data->blocks * data->blksz;
data->error = 0;
}
if (!data->stop) {
atmci_request_end(mmc, host->mrq);
prev_state = state;
break;
}
prev_state = state = STATE_SENDING_STOP;
if (!data->error)
send_stop_cmd(host->mmc, data);
/* fall through */
case STATE_SENDING_STOP:
if (!atmci_test_and_clear_pending(host,
EVENT_CMD_COMPLETE))
break;
host->cmd = NULL;
atmci_command_complete(host, mrq->stop);
atmci_request_end(mmc, host->mrq);
prev_state = state;
break;
case STATE_DATA_ERROR:
if (!atmci_test_and_clear_pending(host,
EVENT_XFER_COMPLETE))
break;
state = STATE_DATA_BUSY;
break;
}
} while (state != prev_state);
host->state = state;
}
static void atmci_read_data_pio(struct atmel_mci *host)
{
struct scatterlist *sg = host->sg;
void *buf = sg_virt(sg);
unsigned int offset = host->pio_offset;
struct mmc_data *data = host->data;
u32 value;
u32 status;
unsigned int nbytes = 0;
do {
value = mci_readl(host, RDR);
if (likely(offset + 4 <= sg->length)) {
put_unaligned(value, (u32 *)(buf + offset));
offset += 4;
nbytes += 4;
if (offset == sg->length) {
host->sg = sg = sg_next(sg);
if (!sg)
goto done;
offset = 0;
buf = sg_virt(sg);
}
} else {
unsigned int remaining = sg->length - offset;
memcpy(buf + offset, &value, remaining);
nbytes += remaining;
flush_dcache_page(sg_page(sg));
host->sg = sg = sg_next(sg);
if (!sg)
goto done;
offset = 4 - remaining;
buf = sg_virt(sg);
memcpy(buf, (u8 *)&value + remaining, offset);
nbytes += offset;
}
status = mci_readl(host, SR);
if (status & ATMCI_DATA_ERROR_FLAGS) {
mci_writel(host, IDR, (MCI_NOTBUSY | MCI_RXRDY
| ATMCI_DATA_ERROR_FLAGS));
host->data_status = status;
atmci_set_pending(host, EVENT_DATA_ERROR);
tasklet_schedule(&host->tasklet);
break;
}
} while (status & MCI_RXRDY);
host->pio_offset = offset;
data->bytes_xfered += nbytes;
return;
done:
mci_writel(host, IDR, MCI_RXRDY);
mci_writel(host, IER, MCI_NOTBUSY);
data->bytes_xfered += nbytes;
atmci_set_pending(host, EVENT_XFER_COMPLETE);
}
static void atmci_write_data_pio(struct atmel_mci *host)
{
struct scatterlist *sg = host->sg;
void *buf = sg_virt(sg);
unsigned int offset = host->pio_offset;
struct mmc_data *data = host->data;
u32 value;
u32 status;
unsigned int nbytes = 0;
do {
if (likely(offset + 4 <= sg->length)) {
value = get_unaligned((u32 *)(buf + offset));
mci_writel(host, TDR, value);
offset += 4;
nbytes += 4;
if (offset == sg->length) {
host->sg = sg = sg_next(sg);
if (!sg)
goto done;
offset = 0;
buf = sg_virt(sg);
}
} else {
unsigned int remaining = sg->length - offset;
value = 0;
memcpy(&value, buf + offset, remaining);
nbytes += remaining;
host->sg = sg = sg_next(sg);
if (!sg) {
mci_writel(host, TDR, value);
goto done;
}
offset = 4 - remaining;
buf = sg_virt(sg);
memcpy((u8 *)&value + remaining, buf, offset);
mci_writel(host, TDR, value);
nbytes += offset;
}
status = mci_readl(host, SR);
if (status & ATMCI_DATA_ERROR_FLAGS) {
mci_writel(host, IDR, (MCI_NOTBUSY | MCI_TXRDY
| ATMCI_DATA_ERROR_FLAGS));
host->data_status = status;
atmci_set_pending(host, EVENT_DATA_ERROR);
tasklet_schedule(&host->tasklet);
break;
}
} while (status & MCI_TXRDY);
host->pio_offset = offset;
data->bytes_xfered += nbytes;
return;
done:
mci_writel(host, IDR, MCI_TXRDY);
mci_writel(host, IER, MCI_NOTBUSY);
data->bytes_xfered += nbytes;
atmci_set_pending(host, EVENT_XFER_COMPLETE);
}
static void atmci_cmd_interrupt(struct mmc_host *mmc, u32 status)
{
struct atmel_mci *host = mmc_priv(mmc);
mci_writel(host, IDR, MCI_CMDRDY);
host->cmd_status = status;
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);
u32 status, mask, pending;
unsigned int pass_count = 0;
spin_lock(&mmc->lock);
do {
status = mci_readl(host, SR);
mask = mci_readl(host, IMR);
pending = status & mask;
if (!pending)
break;
if (pending & ATMCI_DATA_ERROR_FLAGS) {
mci_writel(host, IDR, ATMCI_DATA_ERROR_FLAGS
| MCI_RXRDY | MCI_TXRDY);
pending &= mci_readl(host, IMR);
host->data_status = status;
atmci_set_pending(host, EVENT_DATA_ERROR);
tasklet_schedule(&host->tasklet);
}
if (pending & MCI_NOTBUSY) {
mci_writel(host, IDR,
ATMCI_DATA_ERROR_FLAGS | MCI_NOTBUSY);
host->data_status = status;
atmci_set_pending(host, EVENT_DATA_COMPLETE);
tasklet_schedule(&host->tasklet);
}
if (pending & MCI_RXRDY)
atmci_read_data_pio(host);
if (pending & MCI_TXRDY)
atmci_write_data_pio(host);
if (pending & MCI_CMDRDY)
atmci_cmd_interrupt(mmc, 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);
/*
* Disable interrupts until the pin has stabilized and check
* the state then. Use mod_timer() since we may be in the
* middle of the timer routine when this interrupt triggers.
*/
disable_irq_nosync(irq);
mod_timer(&host->detect_timer, jiffies + msecs_to_jiffies(20));
return IRQ_HANDLED;
}
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;
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!regs)
return -ENXIO;
pdata = pdev->dev.platform_data;
if (!pdata)
return -ENXIO;
irq = platform_get_irq(pdev, 0);
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)
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;
host->mck = clk_get(&pdev->dev, "mci_clk");
if (IS_ERR(host->mck)) {
ret = PTR_ERR(host->mck);
goto err_clk_get;
}
ret = -ENOMEM;
host->regs = ioremap(regs->start, regs->end - regs->start + 1);
if (!host->regs)
goto err_ioremap;
clk_enable(host->mck);
mci_writel(host, CR, MCI_CR_SWRST);
host->bus_hz = clk_get_rate(host->mck);
clk_disable(host->mck);
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);
ret = request_irq(irq, atmci_interrupt, 0, pdev->dev.bus_id, mmc);
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;
}
}
dev_info(&mmc->class_dev,
"Atmel MCI controller at 0x%08lx irq %d\n",
host->mapbase, irq);
atmci_init_debugfs(host);
return 0;
err_request_irq:
iounmap(host->regs);
err_ioremap:
clk_put(host->mck);
err_clk_get:
mmc_free_host(mmc);
return ret;
}
static int __exit atmci_remove(struct platform_device *pdev)
{
struct atmel_mci *host = platform_get_drvdata(pdev);
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);
if (gpio_is_valid(host->wp_pin))
gpio_free(host->wp_pin);
free_irq(platform_get_irq(pdev, 0), host->mmc);
iounmap(host->regs);
clk_put(host->mck);
mmc_free_host(host->mmc);
}
return 0;
}
static struct platform_driver atmci_driver = {
.remove = __exit_p(atmci_remove),
.driver = {
.name = "atmel_mci",
},
};
static int __init atmci_init(void)
{
return platform_driver_probe(&atmci_driver, atmci_probe);
}
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_AUTHOR("Haavard Skinnemoen <haavard.skinnemoen@atmel.com>");
MODULE_LICENSE("GPL v2");