/*
* 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/device.h>
#include <linux/err.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/mmc/host.h>
#include <asm/atmel-mci.h>
#include <asm/io.h>
#include <asm/unaligned.h>
#include <asm/arch/board.h>
#include <asm/arch/gpio.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_DATA_ERROR,
EVENT_DATA_COMPLETE,
EVENT_STOP_SENT,
EVENT_STOP_COMPLETE,
EVENT_XFER_COMPLETE,
};
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_status;
u32 stop_cmdr;
u32 mode_reg;
u32 sdc_reg;
struct tasklet_struct tasklet;
unsigned long pending_events;
unsigned long completed_events;
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_is_completed(host, event) \
test_bit(event, &host->completed_events)
#define atmci_test_and_clear_pending(host, event) \
test_and_clear_bit(event, &host->pending_events)
#define atmci_test_and_set_completed(host, event) \
test_and_set_bit(event, &host->completed_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)
#define atmci_clear_pending(host, event) \
clear_bit(event, &host->pending_events)
static void atmci_enable(struct atmel_mci *host)
{
clk_enable(host->mck);
mci_writel(host, CR, MCI_CR_MCIEN);
mci_writel(host, MR, host->mode_reg);
mci_writel(host, SDCR, host->sdc_reg);
}
static void atmci_disable(struct atmel_mci *host)
{
mci_writel(host, CR, MCI_CR_SWRST);
/* Stall until write is complete, then disable the bus clock */
mci_readl(host, SR);
clk_disable(host->mck);
}
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