/*
* Marvell MMC/SD/SDIO driver
*
* Authors: Maen Suleiman, Nicolas Pitre
* Copyright (C) 2008-2009 Marvell Ltd.
*
* 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/module.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/mbus.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/scatterlist.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/mmc/host.h>
#include <asm/sizes.h>
#include <asm/unaligned.h>
#include <plat/mvsdio.h>
#include "mvsdio.h"
#define DRIVER_NAME "mvsdio"
static int maxfreq = MVSD_CLOCKRATE_MAX;
static int nodma;
struct mvsd_host {
void __iomem *base;
struct mmc_request *mrq;
spinlock_t lock;
unsigned int xfer_mode;
unsigned int intr_en;
unsigned int ctrl;
unsigned int pio_size;
void *pio_ptr;
unsigned int sg_frags;
unsigned int ns_per_clk;
unsigned int clock;
unsigned int base_clock;
struct timer_list timer;
struct mmc_host *mmc;
struct device *dev;
struct resource *res;
int irq;
int gpio_card_detect;
int gpio_write_protect;
};
#define mvsd_write(offs, val) writel(val, iobase + (offs))
#define mvsd_read(offs) readl(iobase + (offs))
static int mvsd_setup_data(struct mvsd_host *host, struct mmc_data *data)
{
void __iomem *iobase = host->base;
unsigned int tmout;
int tmout_index;
/* If timeout=0 then maximum timeout index is used. */
tmout = DIV_ROUND_UP(data->timeout_ns, host->ns_per_clk);
tmout += data->timeout_clks;
tmout_index = fls(tmout - 1) - 12;
if (tmout_index < 0)
tmout_index = 0;
if (tmout_index > MVSD_HOST_CTRL_TMOUT_MAX)
tmout_index = MVSD_HOST_CTRL_TMOUT_MAX;
dev_dbg(host->dev, "data %s at 0x%08x: blocks=%d blksz=%d tmout=%u (%d)\n",
(data->flags & MMC_DATA_READ) ? "read" : "write",
(u32)sg_virt(data->sg), data->blocks, data->blksz,
tmout, tmout_index);
host->ctrl &= ~MVSD_HOST_CTRL_TMOUT_MASK;
host->ctrl |= MVSD_HOST_CTRL_TMOUT(tmout_index);
mvsd_write(MVSD_HOST_CTRL, host->ctrl);
mvsd_write(MVSD_BLK_COUNT, data->blocks);
mvsd_write(MVSD_BLK_SIZE, data->blksz);
if (nodma || (data->blksz | data->sg->offset) & 3) {
/*
* We cannot do DMA on a buffer which offset or size
* is not aligned on a 4-byte boundary.
*/
host->pio_size = data->blocks * data->blksz;
host->pio_ptr = sg_virt(data->sg);
if (!nodma)
printk(KERN_DEBUG "%s: fallback to PIO for data "
"at 0x%p size %d\n",
mmc_hostname(host->mmc),
host->pio_ptr, host->pio_size);
return 1;
} else {
dma_addr_t phys_addr;
int dma_dir = (data->flags & MMC_DATA_READ) ?
DMA_FROM_DEVICE : DMA_TO_DEVICE;
host->sg_frags = dma_map_sg(mmc_dev(host->mmc), data->sg,
data->sg_len, dma_dir);
phys_addr = sg_dma_address(data->sg);
mvsd_write(MVSD_SYS_ADDR_LOW, (u32)phys_addr & 0xffff);
mvsd_write(MVSD_SYS_ADDR_HI, (u32)phys_addr >> 16);
return 0;
}
}
static void mvsd_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct mvsd_host *host = mmc_priv(mmc);
void __iomem *iobase = host->base;
struct mmc_command *cmd = mrq->cmd;
u32 cmdreg = 0, xfer = 0, intr = 0;
unsigned long flags;
BUG_ON(host->mrq != NULL);
host->mrq = mrq;
dev_dbg(host->dev, "cmd %d (hw state 0x%04x)\n",
cmd->opcode, mvsd_read(MVSD_HW_STATE));
cmdreg = MVSD_CMD_INDEX(cmd->opcode);
if (cmd->flags & MMC_RSP_BUSY)
cmdreg |= MVSD_CMD_RSP_48BUSY;
else if (cmd->flags & MMC_RSP_136)
cmdreg |= MVSD_CMD_RSP_136;
else if (cmd->flags & MMC_RSP_PRESENT)
cmdreg |= MVSD_CMD_RSP_48;
else
cmdreg |= MVSD_CMD_RSP_NONE;
if (cmd->flags & MMC_RSP_CRC)
cmdreg |= MVSD_CMD_CHECK_CMDCRC;
if (cmd->flags & MMC_RSP_OPCODE)
cmdreg |= MVSD_CMD_INDX_CHECK;
if (cmd->flags & MMC_RSP_PRESENT) {
cmdreg |= MVSD_UNEXPECTED_RESP;
intr |= MVSD_NOR_UNEXP_RSP;
}
if (mrq->data) {
struct mmc_data *data = mrq->data;
int pio;