/*
* linux/drivers/media/mmc/omap.c
*
* Copyright (C) 2004 Nokia Corporation
* Written by Tuukka Tikkanen and Juha Yrjölä<juha.yrjola@nokia.com>
* Misc hacks here and there by Tony Lindgren <tony@atomide.com>
* Other hacks (DMA, SD, etc) by David Brownell
*
* 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/config.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/timer.h>
#include <linux/mmc/host.h>
#include <linux/mmc/protocol.h>
#include <linux/mmc/card.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/scatterlist.h>
#include <asm/mach-types.h>
#include <asm/arch/board.h>
#include <asm/arch/gpio.h>
#include <asm/arch/dma.h>
#include <asm/arch/mux.h>
#include <asm/arch/fpga.h>
#include <asm/arch/tps65010.h>
#include "omap.h"
#define DRIVER_NAME "mmci-omap"
#define RSP_TYPE(x) ((x) & ~(MMC_RSP_BUSY|MMC_RSP_OPCODE))
/* Specifies how often in millisecs to poll for card status changes
* when the cover switch is open */
#define OMAP_MMC_SWITCH_POLL_DELAY 500
static int mmc_omap_enable_poll = 1;
struct mmc_omap_host {
int initialized;
int suspended;
struct mmc_request * mrq;
struct mmc_command * cmd;
struct mmc_data * data;
struct mmc_host * mmc;
struct device * dev;
unsigned char id; /* 16xx chips have 2 MMC blocks */
struct clk * iclk;
struct clk * fclk;
void __iomem *base;
int irq;
unsigned char bus_mode;
unsigned char hw_bus_mode;
unsigned int sg_len;
int sg_idx;
u16 * buffer;
u32 buffer_bytes_left;
u32 total_bytes_left;
unsigned use_dma:1;
unsigned brs_received:1, dma_done:1;
unsigned dma_is_read:1;
unsigned dma_in_use:1;
int dma_ch;
spinlock_t dma_lock;
struct timer_list dma_timer;
unsigned dma_len;
short power_pin;
short wp_pin;
int switch_pin;
struct work_struct switch_work;
struct timer_list switch_timer;
int switch_last_state;
};
static inline int
mmc_omap_cover_is_open(struct mmc_omap_host *host)
{
if (host->switch_pin < 0)
return 0;
return omap_get_gpio_datain(host->switch_pin);
}
static ssize_t
mmc_omap_show_cover_switch(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct mmc_omap_host *host = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", mmc_omap_cover_is_open(host) ? "open" :
"closed");
}
static DEVICE_ATTR(cover_switch, S_IRUGO, mmc_omap_show_cover_switch, NULL);
static ssize_t
mmc_omap_show_enable_poll(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", mmc_omap_enable_poll);
}
static ssize_t
mmc_omap_store_enable_poll(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t size)
{
int enable_poll;
if (sscanf(buf, "%10d", &enable_poll) != 1)
return -EINVAL;
if (enable_poll != mmc_omap_enable_poll) {
struct mmc_omap_host *host = dev_get_drvdata(dev);
mmc_omap_enable_poll = enable_poll;
if (enable_poll && host->switch_pin >= 0)
schedule_work(&host->switch_work);
}
return size;
}
static DEVICE_ATTR(enable_poll, 0664,
mmc_omap_show_enable_poll, mmc_omap_store_enable_poll);
static void
mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd)
{
u32 cmdreg;
u32 resptype;
u32 cmdtype;
host->cmd = cmd;
resptype = 0;
cmdtype = 0;
/* Our hardware needs to know exact type */
switch (RSP_TYPE(mmc_resp_type(cmd))) {
case RSP_TYPE(MMC_RSP_R1):
/* resp 1, resp 1b */
resptype = 1;
break;
case RSP_TYPE(MMC_RSP_R2):
resptype = 2;
break;
case RSP_TYPE(MMC_RSP_R3):
resptype = 3;
break;
default:
break;
}
if (mmc_cmd_type(cmd) == MMC_CMD_ADTC) {
cmdtype = OMAP_MMC_CMDTYPE_ADTC;
} else if (mmc_cmd_type(cmd) == MMC_CMD_BC) {
cmdtype = OMAP_MMC_CMDTYPE_BC;
} else if (mmc_cmd_type(cmd) == MMC_CMD_BCR) {
cmdtype = OMAP_MMC_CMDTYPE_BCR;
} else {
cmdtype = OMAP_MMC_CMDTYPE_AC;
}
cmdreg = cmd->opcode | (resptype << 8) | (cmdtype << 12);
if (host->bus_mode == MMC_BUSMODE_OPENDRAIN)
cmdreg |= 1 << 6;
if (cmd->flags & MMC_RSP_BUSY)
cmdreg |= 1 << 11;
if (host->data && !(host->data->flags & MMC_DATA_WRITE))
cmdreg |= 1 << 15;
clk_enable(host->fclk);
OMAP_MMC_WRITE(host->base, CTO, 200);
OMAP_MMC_WRITE(host->base, ARGL, cmd->arg & 0xffff);
OMAP_MMC_WRITE(host->base, ARGH, cmd->arg >> 16);
OMAP_MMC_WRITE(host->base, IE,
OMAP_MMC_STAT_A_EMPTY | OMAP_MMC_STAT_A_FULL |
OMAP_MMC_STAT_CMD_CRC | OMAP_MMC_STAT_CMD_TOUT |
OMAP_MMC_STAT_DATA_CRC | OMAP_MMC_STAT_DATA_TOUT |
OMAP_MMC_STAT_END_OF_CMD | OMAP_MMC_STAT_CARD_ERR |
OMAP_MMC_STAT_END_OF_DATA);
OMAP_MMC_WRITE(host->base, CMD, cmdreg);
}
static void
mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data)
{
if (host->dma_in_use) {
|