From: Dirk Behme <dirk.behme@googlemail.com>
To: John Rigby <jcrigby@gmail.com>
Cc: Madhusudhan <madhu.cr@ti.com>,
linux-mmc@vger.kernel.org, linux-omap@vger.kernel.org,
Steve Sakoman <sakoman@gmail.com>
Subject: Re: MMC_CAP_SDIO_IRQ for omap 3430
Date: Sun, 18 Oct 2009 18:44:52 +0200 [thread overview]
Message-ID: <4ADB4604.2090704@googlemail.com> (raw)
In-Reply-To: <4b73d43f0910161426l7600f424w5b8345d16790dd21@mail.gmail.com>
[-- Attachment #1: Type: text/plain, Size: 8302 bytes --]
John Rigby wrote:
> It appears to never get cleared in the status register.
>
> I added some printks to sdio_irq.c to print the pending interrupts in
> the SDIO_CCCR_INTx register for the card and there are no pending
> interrupts so I don't think it is a card driver or card issue.
>
> It would be funny if the TRM was wrong and the CIRQ bit is really
> cleared by writing 1 to it. I'll try that.
2.6.22.18 TI kernel for Beagle from
http://www.beagleboard.org/uploads/2.6_kernel_revb-v2.tar.gz
seems to have SDIO support in omap_hsmmc.c/h. Both in attachment.
Having a short look to it:
- enable_sdio_irq()/int_enable() seems to be done the same way
- interrupt acknowledge in irq handler seems to be done the same way
as we do it, too
but
- IBG bit doesn't seem to be used (?)
- SDIO interrupt is additionally enabled in mmc_omap_start_command()
Anything more?
Cheers
Dirk
> On Fri, Oct 16, 2009 at 3:14 PM, Madhusudhan <madhu.cr@ti.com> wrote:
>>
>>> -----Original Message-----
>>> From: Dirk Behme [mailto:dirk.behme@googlemail.com]
>>> Sent: Friday, October 16, 2009 2:28 PM
>>> To: Madhusudhan Chikkature
>>> Cc: linux-mmc@vger.kernel.org; John Rigby; linux-omap@vger.kernel.org;
>>> Steve Sakoman
>>> Subject: Re: MMC_CAP_SDIO_IRQ for omap 3430
>>>
>>> Madhusudhan Chikkature wrote:
>>>> Hi Dirk,
>>>>
>>>> I am inlining the patch so that it helps review.
>>> ...
>>>> @@ -1165,8 +1178,15 @@ static void omap_hsmmc_set_ios(struct mm
>>>> break;
>>>> case MMC_BUS_WIDTH_4:
>>>> OMAP_HSMMC_WRITE(host->base, CON, con & ~DW8);
>>>> - OMAP_HSMMC_WRITE(host->base, HCTL,
>>>> - OMAP_HSMMC_READ(host->base, HCTL) | FOUR_BIT);
>>>> + if (mmc_card_sdio(host->mmc->card)) {
>>>>
>>>> I wish it could be moved to "enable_sdio_irq" so that we can avoid
>>> inclusion of
>>>> card.h and checking the type of card in the host controller driver.
>>> Yes, this would be the real clean way. But ...
>>>
>>>> But the
>>>> dependancy on 4-bit seems to be a problem here.
>>> ... most probably we have to find a workaround until (if ever?) above
>>> clean implementation is available.
>>>
>>> What we need is after SDIO mode and bus width is known, and before the
>>> first interrupt comes, to set IBG.
>>>
>>>> On the problems being discussed on testing is the interrupt source
>>> geting
>>>> cleared at the SDIO card level upon genaration of the CIRQ? If not it
>>> remains
>>>> asserted.
>>> Yes, this seems to be exactly the problem John reports in his follow
>>> up mail.
>>>
>>> Any hint how to clear SDIO interrupt?
>>>
>> On the controller side I guess it is cleared when you pass "disable" in the
>> enable_sdio_irq" fn. This happens when you call mmc_signal_sdio_irq.
>>
>> I am not too sure about how it gets disabled from the card side. I see that
>> SDIO core has a function "sdio_release_irq" which is used by the sdio uart
>> driver. The usage of this could give a clue.
>>
>> Regards,
>> Madhu
>>
>>> Many thanks
>>>
>>> Dirk
>>>
>>>> + OMAP_HSMMC_WRITE(host->base, HCTL,
>>>> + OMAP_HSMMC_READ(host->base, HCTL)
>>>> + | IBG | FOUR_BIT);
>>>> + } else {
>>>> + OMAP_HSMMC_WRITE(host->base, HCTL,
>>>> + OMAP_HSMMC_READ(host->base, HCTL)
>>>> + | FOUR_BIT);
>>>> + }
>>>> break;
>>>> case MMC_BUS_WIDTH_1:
>>>> OMAP_HSMMC_WRITE(host->base, CON, con & ~DW8);
>>>> @@ -1512,6 +1532,24 @@ static int omap_hsmmc_disable_fclk(struc
>>>> return 0;
>>>> }
>>>>
>>>> +static void omap_hsmmc_enable_sdio_irq(struct mmc_host *mmc, int
>>> enable)
>>>> +{
>>>> + struct omap_hsmmc_host *host = mmc_priv(mmc);
>>>> + u32 ie, ise;
>>>> +
>>>> + ise = OMAP_HSMMC_READ(host->base, ISE);
>>>> + ie = OMAP_HSMMC_READ(host->base, IE);
>>>> +
>>>> + if (enable) {
>>>> + OMAP_HSMMC_WRITE(host->base, ISE, ise | CIRQ_ENABLE);
>>>> + OMAP_HSMMC_WRITE(host->base, IE, ie | CIRQ_ENABLE);
>>>> + } else {
>>>> + OMAP_HSMMC_WRITE(host->base, ISE, ise & ~CIRQ_ENABLE);
>>>> + OMAP_HSMMC_WRITE(host->base, IE, ie & ~CIRQ_ENABLE);
>>>> + }
>>>> +
>>>> +}
>>>> +
>>>> static const struct mmc_host_ops omap_hsmmc_ops = {
>>>> .enable = omap_hsmmc_enable_fclk,
>>>> .disable = omap_hsmmc_disable_fclk,
>>>> @@ -1519,7 +1557,7 @@ static const struct mmc_host_ops omap_hs
>>>> .set_ios = omap_hsmmc_set_ios,
>>>> .get_cd = omap_hsmmc_get_cd,
>>>> .get_ro = omap_hsmmc_get_ro,
>>>> - /* NYET -- enable_sdio_irq */
>>>> + .enable_sdio_irq = omap_hsmmc_enable_sdio_irq,
>>>> };
>>>>
>>>> static const struct mmc_host_ops omap_hsmmc_ps_ops = {
>>>> @@ -1529,7 +1567,7 @@ static const struct mmc_host_ops omap_hs
>>>> .set_ios = omap_hsmmc_set_ios,
>>>> .get_cd = omap_hsmmc_get_cd,
>>>> .get_ro = omap_hsmmc_get_ro,
>>>> - /* NYET -- enable_sdio_irq */
>>>> + .enable_sdio_irq = omap_hsmmc_enable_sdio_irq,
>>>> };
>>>>
>>>> #ifdef CONFIG_DEBUG_FS
>>>> @@ -1734,7 +1772,7 @@ static int __init omap_hsmmc_probe(struc
>>>> mmc->max_seg_size = mmc->max_req_size;
>>>>
>>>> mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED |
>>>> - MMC_CAP_WAIT_WHILE_BUSY;
>>>> + MMC_CAP_WAIT_WHILE_BUSY | MMC_CAP_SDIO_IRQ;
>>>>
>>>> if (mmc_slot(host).wires >= 8)
>>>> mmc->caps |= MMC_CAP_8_BIT_DATA;
>>>>
>>>>
>>>>
>>>>
>>>>
>>>>> John Rigby wrote:
>>>>>> I have seen several discussions about lack of sdio irq support in the
>>>>>> hsmmc driver but no patches. Has anyone on this list implemented this
>>>>>> and/or can anyone point me to patches?
>>>>> What a coincidence ;)
>>>>>
>>>>> I'm currently working on this. See attachment what I currently have.
>>>>> It is compile tested only against recent omap linux head. I don't have
>>>>> a board using SDIO at the moment, so no real testing possible :(
>>>>>
>>>>> Some background, maybe it helps people to step in:
>>>>>
>>>>> Gumstix OMAP3 based Overo air board connects Marvell 88W8686 wifi by
>>>>> MMC port 2 in 4 bit configuration [1]. The wifi performance is quite
>>>>> bad (~100kB/s). There is some rumor that this might be SDIO irq
>>>>> related [2]. There was an attempt to fix this [3] already, but this
>>>>> doesn't work [4]. Having this, I started to look into it.
>>>>>
>>>>> I used [3], the TI Davinci driver [5] (supporting SDIO irq), the SDIO
>>>>> Simplified Specification [6] and the OMAP35x TRM [7] as starting
>>> points.
>>>>> Unfortunately, the Davinci MMC registers and irqs are different
>>>>> (Davinci has a dedicated SDIO irq). But combining [3] and [5] helps to
>>>>> get an idea what has to be done.
>>>>>
>>>>> I think the main issues of [3] were that it doesn't enable IBG for 4
>>>>> bit mode ([6] chapter 8.1.2) and that mmc_omap_irq() doesn't reset the
>>>>> irq bits.
>>>>>
>>>>> Topics I still open:
>>>>>
>>>>> - Is it always necessary to deal with IE _and_ ISE register? I'm not
>>>>> totally clear what the difference between these two registers are ;)
>>>>> And in which order they have to be set.
>>>>>
>>>>> - Davinci driver [5] in line 1115 checks for data line to call
>>>>> mmc_signal_sdio_irq() for irq enable.
>>>>>
>>>>> - Davinci driver deals with SDIO in xfer_done() (line 873)
>>>>>
>>>>> - Davinci driver sets block size to 64 if SDIO in line 701
>>>>>
>>>>> It would be quite nice if anybody likes to comment on attachment and
>>>>> help testing.
>>>>>
>>>>> Many thanks and best regards
>>>>>
>>>>> Dirk
>>>>>
>>>>> [1] http://gumstix.net/wiki/index.php?title=Overo_Wifi
>>>>>
>>>>> [2] http://groups.google.com/group/beagleboard/msg/14e822778c5eeb56
>>>>>
>>>>> [3] http://groups.google.com/group/beagleboard/msg/d0eb69f4c20673be
>>>>>
>>>>> [4] http://groups.google.com/group/beagleboard/msg/5cdfe2a319531937
>>>>>
>>>>> [5]
>>>>> http://arago-project.org/git/projects/?p=linux-
>>> davinci.git;a=blob;f=drivers/mmc/host/davinci_mmc.c;h=1bf0587250614c6d8abf
>>> e02028b96e0e47148ac8;hb=HEAD
>>>>> [6] http://www.sdcard.org/developers/tech/sdio/sd_bluetooth_spec/
>>>>>
>>>>> [7] http://focus.ti.com/lit/ug/spruf98c/spruf98c.pdf
>>>>>
>>>>>
>>>>
>>>>
>>
>>
>
[-- Attachment #2: omap_hsmmc.c --]
[-- Type: text/x-csrc, Size: 78180 bytes --]
/*
* drivers/mmc/omap_hsmmc.c
*
* Driver for OMAP2430/3430 MMC controller.
*
* Copyright (C) 2006-2007 Texas Instruments, Inc
* Author: Texas Instruments
*
* 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.
*
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/sd.h>
#include <linux/mmc/sdio.h>
#include <linux/mmc/host.h>
#include <linux/mmc/core.h>
#include <linux/mmc/card.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/dma.h>
#include <asm/mach-types.h>
#include <asm/arch/twl4030.h>
#include <asm/hardware.h>
#include <asm/arch/board.h>
#include <asm/arch/cpu.h>
#include <asm/arch/clock.h>
#include <asm/semaphore.h>
#include "omap_hsmmc.h"
#include <asm/scatterlist.h>
#ifdef CONFIG_PM
#include <linux/notifier.h>
#include <linux/pm.h>
#endif
#ifdef CONFIG_DPM
#include <linux/dpm.h>
#endif
/* TAG for Aggressive Power changes in MMC */
//#define AGGR_PM_CAP 1
#undef AGGR_PM_CAP
#ifdef AGGR_PM_CAP
#define mmc_clk_enable_aggressive(host) mmc_clk_enable(host)
#define mmc_clk_disable_aggressive(host) mmc_clk_disable(host)
#else
#define mmc_clk_enable_aggressive(host) /* NULL */
#define mmc_clk_disable_aggressive(host) /* NULL */
#endif
#ifdef AGGR_PM_CAP
/* SYSCONFIG bit values */
#define OMAP_MMC_SYSCONFIG_CLKACT_IOFF_FOFF 0x0
#define OMAP_MMC_SYSCONFIG_CLKACT_ION_FOFF 0x1
#define OMAP_MMC_SYSCONFIG_CLKACT_IOFF_FON 0x2
#define OMAP_MMC_SYSCONFIG_CLKACT_ION_FON 0x3
#define OMAP_MMC_SYSCONFIG_SIDLE_FORCEIDLE 0x0
#define OMAP_MMC_SYSCONFIG_SIDLE_NOIDLE 0x1
#define OMAP_MMC_SYSCONFIG_SIDLE_SMARTIDLE 0x2
#define OMAP_MMC_SYSCONFIG_ENAWAKEUP 0x1
#define OMAP_MMC_SYSCONFIG_AUTOIDLE 0x1
/* SYSCONFIG bit Masks */
#define OMAP_MMC_SYSCONFIG_CLKACT_SHIFT 0x8
#define OMAP_MMC_SYSCONFIG_SIDLE_SHIFT 0x3
#define OMAP_MMC_SYSCONFIG_ENAWAKEUP_SHIFT 0x2
#define OMAP_MMC_SYSCONFIG_LVL1 0x1
#define OMAP_MMC_SYSCONFIG_LVL2 0x2
#endif /* #ifdef AGGR_PM_CAP */
#if defined(CONFIG_MACH_OMAP_2430SDP) || \
defined(CONFIG_MACH_OMAP_3430SDP) || \
defined(CONFIG_MACH_OMAP_3430LABRADOR) || \
defined(CONFIG_MACH_OMAP3EVM) || defined(CONFIG_MACH_OMAP3_BEAGLE)
extern int enable_mmc_power(int slot);
extern int disable_mmc_power(int slot);
extern int mask_carddetect_int(int slot);
extern int unmask_carddetect_int(int slot);
extern int setup_mmc_carddetect_irq(int irq);
extern int switch_power_mode(int power_mode);
extern ssize_t mmc_omap_show_cover_switch(struct device *dev, struct
device_attribute *attr, char *buf);
extern ssize_t set_mmc_carddetect(struct device *dev, struct device_attribute
*attr, const char *buf, size_t count);
DEVICE_ATTR(mmc_cover_switch, S_IRUGO, mmc_omap_show_cover_switch, NULL);
DEVICE_ATTR(mmc_card_detect, S_IWUSR, NULL, set_mmc_carddetect);
#endif
#define MMC_CARD_NONE 4
#define MAX_CRC_RETRY 1
struct mmc_omap_host *saved_host1, *saved_host2;
struct mmc_omap_host {
int suspended;
struct mmc_host *mmc;
struct mmc_request *mrq;
struct mmc_command *cmd;
struct mmc_data *data;
#ifdef CONFIG_OMAP_SDIO
struct mmc_data *sdiodata;
int sdio_card_intr;
#endif
struct timer_list detect_timer;
struct resource *mem_res;
void __iomem *base;
void *mapbase;
struct clk *fclk, *iclk, *dbclk;
/* Required for a 3430 ES1.0 Sil errata fix */
struct clk *gptfck;
unsigned int id;
int irq;
int card_detect_irq;
unsigned char bus_mode;
struct semaphore sem;
unsigned char datadir;
u32 *buffer;
u32 bytesleft;
int use_dma, dma_ch;
unsigned int dma_len;
unsigned int sg_dma_len;
unsigned int dma_dir;
int chain_id;
struct omap_dma_channel_params params;
u32 chains_requested;/* Number of chains to be requested */
u32 extra_chain_reqd;/* if there is a need of last chaining*/
u32 no_of_chain_reqd;/*No of times callback called*/
u32 current_cb_cnt;
int brs_received;
int dma_done;
int dma_is_read;
spinlock_t dma_lock;
unsigned int sg_len;
int sg_idx;
u32 buffer_bytes_left;
u32 total_bytes_left;
struct work_struct mmc_carddetect_work;
int initstream;
/*Added for CRC retry*/
bool card_detected;
u32 rca, mod_addr, org_addr;
int is_high_capacity;
int flag_err, cmd_12,cmd_13, crc_retry;
};
#ifdef CONFIG_OMAP34XX_OFFMODE
struct omap_hsmmc_regs {
u32 hctl;
u32 capa;
u32 sysconfig;
u32 ise;
u32 ie;
u32 con;
u32 sysctl;
};
static struct omap_hsmmc_regs hsmmc_ctx[2];
#endif /* #ifdef CONFIG_OMAP34XX_OFFMODE */
#ifdef CONFIG_OMAP_SDIO
static int blkmode_bytecount[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
static int polling_mode = 0;
static void mmc_omap_polling_command(struct mmc_omap_host *host,
struct mmc_command *cmd, u32 cmdreg);
#endif /* ifdef CONFIG_OMAP_SDIO */
#ifdef CONFIG_MMC_OMAP3430
static spinlock_t mmc_gpt_lock;
static int gptfclk_counter;
#endif /* #ifdef CONFIG_MMC_OMAP3430 */
static int mmc_clk_counter [NO_OF_MMC_HOSTS];
#define OMAP_MMC_STAT_BRR 1 << 5
#define OMAP_MMC_STAT_BWR 1 << 4
#define NO_OF_DMA_CHAINS_USED 2//This will work with 2 chains currently
static void mmc_chain_dma(struct mmc_omap_host *host, struct mmc_data *data);
static int mmc_omap_get_dma_channel(struct mmc_omap_host *host, struct mmc_data *data);
#ifdef AGGR_PM_CAP
static int omap_mmc_sysconfig (struct mmc_omap_host *host, int level)
{
u32 sysconfig_val;
switch (level) {
case OMAP_MMC_SYSCONFIG_LVL1:
/*
* All features of SYSCONFIG enabled
* Note: MMC has Wakeup capability enabled at reset.
* If this capability is required then care needs to be taken
* for other wakeup related register such as HCTL(IWE bit) and
* interrupts in IE and ISE registers
*
* Clock activity has only FCLK ON
*
* Enabling SmartIdle in ES1.0, pervents CORE from going to
* retention. Hence even Wakeup capability is disabled.
*/
sysconfig_val = (
(OMAP_MMC_SYSCONFIG_CLKACT_IOFF_FON <<
OMAP_MMC_SYSCONFIG_CLKACT_SHIFT) |
(OMAP_MMC_SYSCONFIG_SIDLE_SMARTIDLE <<
OMAP_MMC_SYSCONFIG_SIDLE_SHIFT) |
OMAP_MMC_SYSCONFIG_AUTOIDLE
);
OMAP_HSMMC_WRITE(host->base, SYSCONFIG, sysconfig_val);
break;
case OMAP_MMC_SYSCONFIG_LVL2:
/*
* Clock activity has ICLK and FCLK OFF
*/
sysconfig_val = (
(OMAP_MMC_SYSCONFIG_CLKACT_IOFF_FOFF <<
OMAP_MMC_SYSCONFIG_CLKACT_SHIFT) |
OMAP_MMC_SYSCONFIG_AUTOIDLE
);
OMAP_HSMMC_WRITE(host->base, SYSCONFIG, sysconfig_val);
break;
}
return 0;
}
#endif /* #ifdef AGGR_PM_CAP */
#ifdef CONFIG_OMAP34XX_OFFMODE
static void omap2_hsmmc_save_ctx(struct mmc_omap_host *host)
{
/* MMC : context save */
hsmmc_ctx[host->id - 1].hctl = OMAP_HSMMC_READ(host->base, HCTL);
hsmmc_ctx[host->id - 1].capa = OMAP_HSMMC_READ(host->base, CAPA);
hsmmc_ctx[host->id - 1].sysconfig = OMAP_HSMMC_READ(host->base,
SYSCONFIG);
hsmmc_ctx[host->id - 1].ise = OMAP_HSMMC_READ(host->base, ISE);
hsmmc_ctx[host->id - 1].ie = OMAP_HSMMC_READ(host->base, IE);
hsmmc_ctx[host->id - 1].con = OMAP_HSMMC_READ(host->base, CON);
hsmmc_ctx[host->id - 1].sysctl = OMAP_HSMMC_READ(host->base, SYSCTL);
}
static void omap2_hsmmc_restore_ctx(struct mmc_omap_host *host)
{
/* MMC : context restore */
OMAP_HSMMC_WRITE(host->base, HCTL, hsmmc_ctx[host->id - 1].hctl);
OMAP_HSMMC_WRITE(host->base, CAPA, hsmmc_ctx[host->id - 1].capa);
OMAP_HSMMC_WRITE(host->base, CON, hsmmc_ctx[host->id - 1].con);
OMAP_HSMMC_WRITE(host->base, SYSCONFIG,
hsmmc_ctx[host->id - 1].sysconfig);
OMAP_HSMMC_WRITE(host->base, ISE, hsmmc_ctx[host->id - 1].ise);
OMAP_HSMMC_WRITE(host->base, IE, hsmmc_ctx[host->id - 1].ie);
OMAP_HSMMC_WRITE(host->base, SYSCTL, hsmmc_ctx[host->id - 1].sysctl);
OMAP_HSMMC_WRITE(host->base, HCTL, OMAP_HSMMC_READ(host->base,
HCTL) | SDBP);
}
#endif /* #ifdef CONFIG_OMAP34XX_OFFMODE */
static int mmc_clk_enable (struct mmc_omap_host *host)
{
int hostid = host->id - 1;
#ifdef CONFIG_MMC_OMAP3430
/* 3430-ES1.0 Sil errata fix */
if (is_sil_rev_less_than(OMAP3430_REV_ES2_0)) {
spin_lock(&mmc_gpt_lock);
if (!gptfclk_counter) {
if (clk_enable(host->gptfck) != 0) {
dev_dbg(mmc_dev(host->mmc),
"Unable to enable gptfck clock \n");
goto clk_en_err;
}
}
gptfclk_counter ++;
spin_unlock(&mmc_gpt_lock);
}
#endif /* #ifdef CONFIG_MMC_OMAP3430 */
if (!mmc_clk_counter[hostid]) {
if (clk_enable(host->iclk) != 0)
goto clk_en_err1;
if (clk_enable(host->fclk) != 0)
goto clk_en_err2;
#ifdef AGGR_PM_CAP
omap_mmc_sysconfig (host, OMAP_MMC_SYSCONFIG_LVL1);
#endif /* #ifdef AGGR_PM_CAP */
#ifdef CONFIG_OMAP34XX_OFFMODE
if (context_restore_required(host->fclk))
omap2_hsmmc_restore_ctx(host);
#endif /* #ifdef CONFIG_OMAP34XX_OFFMODE */
}
mmc_clk_counter[hostid] ++;
return 0;
clk_en_err2:
/* On fclk failure */
clk_disable(host->iclk);
clk_en_err1:
/* On iclk failure */
#ifdef CONFIG_MMC_OMAP3430
if (is_sil_rev_less_than(OMAP3430_REV_ES2_0)) {
spin_lock(&mmc_gpt_lock);
gptfclk_counter --;
if (!gptfclk_counter)
clk_disable(host->gptfck);
spin_unlock(&mmc_gpt_lock);
}
#endif /* #ifdef CONFIG_MMC_OMAP3430 */
clk_en_err:
dev_dbg(mmc_dev(host->mmc),
"Unable to enable MMC clocks \n");
#ifdef CONFIG_MMC_OMAP3430
spin_unlock(&mmc_gpt_lock);
#endif /* #ifdef CONFIG_MMC_OMAP3430 */
return -1;
}
static int mmc_clk_disable (struct mmc_omap_host *host)
{
int hostid = host->id - 1;
mmc_clk_counter[hostid] --;
if (!mmc_clk_counter[hostid]) {
#ifdef CONFIG_OMAP34XX_OFFMODE
omap2_hsmmc_save_ctx(host);
#endif /* #ifdef CONFIG_OMAP34XX_OFFMODE */
#ifdef AGGR_PM_CAP
omap_mmc_sysconfig (host, OMAP_MMC_SYSCONFIG_LVL2);
#endif /* #ifdef AGGR_PM_CAP */
clk_disable(host->fclk);
clk_disable(host->iclk);
}
#ifdef CONFIG_MMC_OMAP3430
/* 3430-ES1.0 Sil errata fix */
if (is_sil_rev_less_than(OMAP3430_REV_ES2_0)) {
spin_lock(&mmc_gpt_lock);
gptfclk_counter --;
if (!gptfclk_counter)
clk_disable(host->gptfck);
spin_unlock(&mmc_gpt_lock);
}
#endif /* #ifdef CONFIG_MMC_OMAP3430 */
return 0;
}
/*
* Stop clock to the card
*/
static void omap_mmc_stop_clock(struct mmc_omap_host *host)
{
/* Stop clock to the card */
OMAP_HSMMC_WRITE(host->base, SYSCTL,
OMAP_HSMMC_READ(host->base, SYSCTL) & ~CEN);
if ((OMAP_HSMMC_READ(host->base, SYSCTL) & CEN) != 0x0)
dev_dbg(mmc_dev(host->mmc), "MMC clock not stoped,"
"clock freq can not be altered\n");
}
/*
* Send init stream sequence to the card before sending IDLE command
*/
static void send_init_stream(struct mmc_omap_host *host)
{
int reg = 0, status;
typeof(jiffies) timeout;
disable_irq(host->irq);
OMAP_HSMMC_WRITE(host->base, ISE, INT_CLEAR);
OMAP_HSMMC_WRITE(host->base, IE, INT_CLEAR);
OMAP_HSMMC_WRITE(host->base, CON,
OMAP_HSMMC_READ(host->base, CON) | INIT_STREAM);
OMAP_HSMMC_WRITE(host->base, CMD, INIT_STREAM_CMD);
timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
while ((reg != CC) && time_before(jiffies, timeout)) {
reg = OMAP_HSMMC_READ(host->base, STAT) & CC;
}
OMAP_HSMMC_WRITE(host->base, CON,
OMAP_HSMMC_READ(host->base, CON) & ~INIT_STREAM);
status = OMAP_HSMMC_READ(host->base, STAT);
OMAP_HSMMC_WRITE(host->base, STAT, status);
enable_irq(host->irq);
}
/*
* Configure the resptype, cmdtype and send the given command to the card
*/
static void
mmc_omap_start_command18(struct mmc_omap_host *host, struct mmc_command *cmd)
{
int cmdreg = 0, resptype = 0, cmdtype = 0;
mmc_clk_enable_aggressive(host);
/* Clear status bits and enable interrupts */
OMAP_HSMMC_WRITE(host->base, STAT, OMAP_HSMMC_STAT_CLEAR);
resptype = 2;
cmdreg = (MMC_READ_MULTIPLE_BLOCK << 24) | (resptype << 16) | (cmdtype << 22);
cmdreg |= DP_SELECT | DDIR | MSBS | BCE | DMA_EN;
OMAP_HSMMC_WRITE(host->base, ISE, INT_EN_MASK);
OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK);
OMAP_HSMMC_WRITE(host->base, ARG, host->mod_addr);
OMAP_HSMMC_WRITE(host->base, CMD, cmdreg);
}
/*
* Configure the resptype, cmdtype and send the given command to the card
*/
static void
mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd)
{
int cmdreg = 0, resptype = 0, cmdtype = 0;
#ifdef CONFIG_OMAP_SDIO
int func = 0, new_size = 0;
#endif /* ifdef CONFIG_OMAP_SDIO */
dev_dbg(mmc_dev(host->mmc), "%s: CMD%d, argument 0x%08x\n",
mmc_hostname(host->mmc), cmd->opcode, cmd->arg);
host->cmd = cmd;
#ifdef CONFIG_OMAP_SDIO
if (cmd->opcode == IO_RW_DIRECT) {
if ((cmd->arg & IO_RW_DIRECT_MASK) == IO_RW_DIRECT_ARG_MASK)
cmdtype = 0x2;
}
#endif /* ifdef CONFIG_OMAP_SDIO */
mmc_clk_enable_aggressive(host);
/* Clear status bits and enable interrupts */
OMAP_HSMMC_WRITE(host->base, STAT, OMAP_HSMMC_STAT_CLEAR);
switch (RSP_TYPE(mmc_resp_type(cmd))) {
case RSP_TYPE(MMC_RSP_R1):
case RSP_TYPE(MMC_RSP_R3):
/* resp 1, resp 1b */
resptype = 2;
break;
case RSP_TYPE(MMC_RSP_R2):
resptype = 1;
break;
default:
break;
}
cmdreg = (cmd->opcode << 24) | (resptype << 16) | (cmdtype << 22);
if (cmd->opcode == MMC_SEND_CSD)
host->rca = cmd->arg;
if (cmd->opcode == MMC_READ_MULTIPLE_BLOCK)
host->org_addr = cmd->arg;
if (cmd->opcode == MMC_READ_SINGLE_BLOCK
|| cmd->opcode == MMC_READ_MULTIPLE_BLOCK
|| cmd->opcode == SD_APP_SEND_SCR
|| (cmd->opcode == SD_SWITCH && cmd->arg == 0xfffff1)
|| (cmd->opcode == SD_SWITCH && cmd->arg == 0x80fffff1)
|| (cmd->opcode == MMC_SEND_EXT_CSD && cmd->arg == 0)) {
if (host->use_dma)
cmdreg |= DP_SELECT | DDIR | MSBS | BCE | DMA_EN;
else
cmdreg |= DP_SELECT | DDIR | MSBS | BCE;
} else if (cmd->opcode == MMC_WRITE_BLOCK
|| cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK) {
if (host->use_dma)
cmdreg |= DP_SELECT | MSBS | BCE | DMA_EN;
else
cmdreg |= DP_SELECT | MSBS | BCE;
cmdreg &= ~(DDIR);
}
#ifdef CONFIG_OMAP_SDIO
/* Handle I/O related command settings */
if (cmd->opcode == IO_RW_EXTENDED) {
if (polling_mode == 1) {
return mmc_omap_polling_command(host, cmd, cmdreg);
}
if (cmd->arg & OMAP_SDIO_READ) {
#ifdef CONFIG_OMAP_SDIO_NON_DMA_MODE
cmdreg |= DP_SELECT;
#else
cmdreg |= DP_SELECT | DMA_EN | BCE | MSBS;
#endif
cmdreg &= ~(DDIR);
} else {
#ifdef CONFIG_OMAP_SDIO_NON_DMA_MODE
cmdreg |= DP_SELECT | DDIR;
#else
cmdreg |= DP_SELECT | DDIR | DMA_EN | BCE | MSBS;
#endif
}
}
if (cmd->opcode == IO_RW_DIRECT) {
if (cmd->arg & OMAP_SDIO_READ) {
cmdreg &= ~(DDIR);
if ((cmd->arg & sdio_blkmode_mask) ==
sdio_blkmode_regaddr1) {
func = ((cmd->arg & sdio_rw_function_mask)
>> 17);
new_size = (cmd->arg & 0xFF);
blkmode_bytecount[func] =
blkmode_bytecount[func] & 0xFF00;
blkmode_bytecount[func] =
blkmode_bytecount[func] | new_size;
} else if ((cmd->arg & sdio_blkmode_mask) ==
sdio_blkmode_regaddr2) {
func = ((cmd->arg & sdio_rw_function_mask)
>> 17);
new_size = ((cmd->arg & 0xFF) << 8);
blkmode_bytecount[func] =
blkmode_bytecount[func] & 0x00FF;
blkmode_bytecount[func] =
blkmode_bytecount[func] | new_size;
} else
cmdreg |= DDIR;
}
}
#endif /* ifdef CONFIG_OMAP_SDIO */
if (cmd->opcode == MMC_GO_IDLE_STATE || cmd->opcode == MMC_SEND_OP_COND
|| cmd->opcode == MMC_ALL_SEND_CID)
OMAP_HSMMC_WRITE(host->base, CON,
OMAP_HSMMC_READ(host->base, CON) | OD);
if (cmd->opcode == MMC_GO_IDLE_STATE) {
if (host->initstream == 0) {
send_init_stream(host);
host->initstream = 1;
}
}
#ifdef CONFIG_OMAP_SDIO
if (host->sdio_card_intr) {
OMAP_HSMMC_WRITE(host->base, ISE, SDIO_CARD_INT_EN_MASK);
OMAP_HSMMC_WRITE(host->base, IE, SDIO_CARD_INT_EN_MASK);
} else {
#endif
OMAP_HSMMC_WRITE(host->base, ISE, INT_EN_MASK);
OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK);
#ifdef CONFIG_OMAP_SDIO
}
#endif
OMAP_HSMMC_WRITE(host->base, ARG, cmd->arg);
OMAP_HSMMC_WRITE(host->base, CMD, cmdreg);
}
#ifdef CONFIG_OMAP_SDIO
/*
* Notify the xfer done on SDIO card to the core
*/
static void
sdio_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *sdiodata)
{
if (!sdiodata)
return;
#ifndef CONFIG_OMAP_SDIO_NON_DMA_MODE
if (polling_mode == 0)
dma_unmap_sg(mmc_dev(host->mmc), host->mrq->data->sg,
host->dma_len, host->dma_dir);
#endif
host->data = NULL;
host->sdiodata = NULL;
host->datadir = OMAP_MMC_DATADIR_NONE;
mmc_clk_disable_aggressive(host);
if (!host->cmd) {
host->mrq = NULL;
mmc_request_done(host->mmc, sdiodata->mrq);
}
return;
}
#endif /* ifdef CONFIG_OMAP_SDIO */
/*
* Notify the xfer done on MMC/SD cards to the core
*/
static void
mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data)
{
if(host->use_dma)
{
/* Un-map the memory required for DMA */
dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->sg_len,
host->dma_dir);
}
/* Reset the variables as transfer is complete */
host->data = NULL;
host->sg_len = 0;
host->sg_dma_len = 0;
host->datadir = OMAP_MMC_DATADIR_NONE;
if (data->error == MMC_ERR_NONE)
data->bytes_xfered += data->blocks * (data->blksz);
else
data->bytes_xfered = 0;
mmc_clk_disable_aggressive(host);
if (!data->stop) {
host->mrq = NULL;
mmc_request_done(host->mmc, data->mrq);
return;
}
/* Send the stop command. Remember FCLK is not stopped before this call */
mmc_omap_start_command(host, data->stop);
}
static void
mmc_omap_end_of_data(struct mmc_omap_host *host, struct mmc_data *data)
{
unsigned long flags;
int done;
if (!host->use_dma) {
mmc_omap_xfer_done(host, data);
return;
}
done = 0;
spin_lock_irqsave(&host->dma_lock, flags);
if (host->dma_done)
done = 1;
else
host->brs_received = 1;
spin_unlock_irqrestore(&host->dma_lock, flags);
if (done)
mmc_omap_xfer_done(host, data);
}
static void
mmc_omap_dma_done(struct mmc_omap_host *host, struct mmc_data *data)
{
unsigned long flags;
int done;
done = 0;
spin_lock_irqsave(&host->dma_lock, flags);
if (host->brs_received)
done = 1;
else
host->dma_done = 1;
spin_unlock_irqrestore(&host->dma_lock, flags);
if (done)
mmc_omap_xfer_done(host, data);
}
/*
* Notify the core about command completion
*/
static void
mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd)
{
host->cmd = NULL;
if (cmd->flags & MMC_RSP_PRESENT) {
if (cmd->flags & MMC_RSP_136) {
/* response type 2 */
cmd->resp[3] = OMAP_HSMMC_READ(host->base, RSP10);
cmd->resp[2] = OMAP_HSMMC_READ(host->base, RSP32);
cmd->resp[1] = OMAP_HSMMC_READ(host->base, RSP54);
cmd->resp[0] = OMAP_HSMMC_READ(host->base, RSP76);
} else {
/* response types 1, 1b, 3, 4, 5, 6 */
cmd->resp[0] = OMAP_HSMMC_READ(host->base, RSP10);
}
}
if(cmd->opcode == SD_APP_OP_COND)
{
if(cmd->resp[0] & 0x40000000)
{
host->is_high_capacity = 1;
}
else
{
host->is_high_capacity = 0;
}
}
if(cmd->opcode == MMC_SEND_OP_COND)
{
if(cmd->resp[0] & 0x40000000)
{
host->is_high_capacity = 1;
}
else
{
host->is_high_capacity = 0;
}
}
#ifdef CONFIG_OMAP_SDIO
if (host->mmc->mode == MMC_MODE_SDIO) {
if (host->sdiodata == NULL || cmd->error != MMC_ERR_NONE) {
dev_dbg(mmc_dev(host->mmc), "%s: End request, err %x\n",
mmc_hostname(host->mmc), cmd->error);
host->mrq = NULL;
mmc_clk_disable_aggressive(host);
mmc_request_done(host->mmc, cmd->mrq);
}
} else {
#endif
if (host->data == NULL || cmd->error != MMC_ERR_NONE) {
dev_dbg(mmc_dev(host->mmc), "%s: End request, err %x\n",
mmc_hostname(host->mmc), cmd->error);
host->mrq = NULL;
mmc_clk_disable_aggressive(host);
mmc_request_done(host->mmc, cmd->mrq);
}
#ifdef CONFIG_OMAP_SDIO
}
#endif
}
/*
* Dma cleaning in case of command errors
*/
static void mmc_dma_cleanup(struct mmc_omap_host *host)
{
int dma_ch;
host->data->error |= MMC_ERR_TIMEOUT;
if(host->mmc->mode == MMC_MODE_SDIO)
{
if (host->use_dma && host->dma_ch != -1) {
dma_unmap_sg(mmc_dev(host->mmc), host->data->sg, host->dma_len,
host->dma_dir);
dma_ch = host->dma_ch;
host->dma_ch = -1;
omap_free_dma(dma_ch);
}
}
else
{
if (host->use_dma) {
omap_stop_dma_chain_transfers(host->chain_id);
dma_unmap_sg(mmc_dev(host->mmc), host->data->sg, host->sg_len,
host->dma_dir);
omap_free_dma_chain(host->chain_id);
mmc_omap_dma_done(host, host->data);
host->chain_id = -1;
}
}
host->data = NULL;
#ifdef CONFIG_OMAP_SDIO
host->sdiodata = NULL;
#endif
host->datadir = OMAP_MMC_DATADIR_NONE;
}
#if defined(CONFIG_OMAP_SDIO) && defined(CONFIG_OMAP_SDIO_NON_DMA_MODE)
/*
* Sdio non dma mode data transfer function
*/
static void sdio_non_dma_xfer(struct mmc_omap_host *host)
{
int i, readCnt, bytec;
typeof(jiffies) timeout;
if (host->cmd) {
timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
if (host->cmd->opcode == IO_RW_EXTENDED) {
bytec = (host->cmd->arg & 0x1FF);
if (bytec == 0)
bytec = 0x200;
readCnt = (bytec / 4);
if (bytec % 4)
readCnt++;
if (host->cmd->arg & OMAP_SDIO_READ) {
while (((OMAP_HSMMC_READ(host->base, PSTATE)
& BWE) != BRW)
&& time_before(jiffies, timeout));
for (i = 0; i < readCnt; i++)
OMAP_HSMMC_WRITE(host->base, DATA,
*host->buffer++);
} else {
while (((OMAP_HSMMC_READ(host->base, PSTATE)
& BRE) != BRR)
&& time_before(jiffies, timeout));
for (i = 0; i < readCnt; i++)
*host->buffer++ =
OMAP_HSMMC_READ(host->base, DATA);
}
}
}
}
#endif
/* PIO only */
static void
mmc_omap_sg_to_buf(struct mmc_omap_host *host)
{
struct scatterlist *sg;
sg = host->data->sg + host->sg_idx;
host->buffer_bytes_left = sg->length;
host->buffer = page_address(sg->page) + sg->offset;
if (host->buffer_bytes_left > host->total_bytes_left)
host->buffer_bytes_left = host->total_bytes_left;
}
/* PIO only */
static void
mmc_omap_xfer_data(struct mmc_omap_host *host, int write)
{
int n;
if (host->buffer_bytes_left == 0) {
host->sg_idx++;
BUG_ON(host->sg_idx == host->sg_len);
mmc_omap_sg_to_buf(host);
}
n = 64;
if (n > host->buffer_bytes_left)
n = host->buffer_bytes_left;
host->buffer_bytes_left -= n;
host->total_bytes_left -= n;
host->data->bytes_xfered += n;
if (write) {
__raw_writesw(host->base + OMAP_HSMMC_DATA, host->buffer, n);
} else {
__raw_readsw(host->base + OMAP_HSMMC_DATA, host->buffer, n);
}
}
/*
* Configure the resptype, cmdtype and send the given command to the card
*/
static void
mmc_omap_start_command13(struct mmc_omap_host *host, struct mmc_command *cmd)
{
int cmdreg = 0, resptype = 0, cmdtype = 0;
unsigned long flags;
mmc_clk_enable_aggressive(host);
/* Clear status bits and enable interrupts */
OMAP_HSMMC_WRITE(host->base, STAT, OMAP_HSMMC_STAT_CLEAR);
resptype = 2;
cmdreg = (MMC_SEND_STATUS << 24) | (resptype << 16) | (cmdtype << 22);
OMAP_HSMMC_WRITE(host->base, ISE, INT_EN_MASK);
OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK);
OMAP_HSMMC_WRITE(host->base, ARG, host->rca);
OMAP_HSMMC_WRITE(host->base, CMD, cmdreg);
spin_lock_irqsave(&host->dma_lock, flags);
host->cmd_13 = 1;
spin_unlock_irqrestore(&host->dma_lock, flags);
}
/*
* Notify the core about command completion
*/
static void
mmc_omap_err_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd)
{
cmd->resp[0] = OMAP_HSMMC_READ(host->base, RSP10);
mmc_clk_disable_aggressive(host);
if(host->cmd_13 == 1)
{
if(cmd->resp[0] != 0x900)
dev_dbg(mmc_dev(host->mmc),
"%s:CMD response error cmd->resp[0]%x\n",
mmc_hostname(host->mmc),cmd->resp[0]);
}
}
static int
mmc_omap_err_calc(struct mmc_omap_host *host)
{
unsigned sg_len_remain;
struct mmc_data *data = host->data;
unsigned long flags;
if(unlikely(host == NULL))
{
return -1;
}
/* Enable DMA */
host->use_dma = 1;
spin_lock_irqsave(&host->dma_lock, flags);
if(host->extra_chain_reqd == 0)
{
if(host->data->sg_len <= NO_OF_DMA_CHAINS_USED)
host->sg_idx = 0;
else
host->sg_idx = host->sg_idx-NO_OF_DMA_CHAINS_USED;
}
else
{
if(host->sg_idx == host->data->sg_len)
host->sg_idx = host->data->sg_len;
else
host->sg_idx = host->sg_idx-NO_OF_DMA_CHAINS_USED;
}
if(host->sg_idx == host->data->sg_len)
sg_len_remain =1;
else
sg_len_remain = data->sg_len - host->sg_idx;
if(sg_len_remain >= NO_OF_DMA_CHAINS_USED)
{
host->chains_requested = NO_OF_DMA_CHAINS_USED;
}
else
{
host->chains_requested = 1;
}
if(host->data->sg_len <= NO_OF_DMA_CHAINS_USED)
{
host->extra_chain_reqd = 0;
host->no_of_chain_reqd = 0;
host->current_cb_cnt = 0;
}
else
{
host->no_of_chain_reqd = sg_len_remain/NO_OF_DMA_CHAINS_USED;
host->extra_chain_reqd= sg_len_remain%NO_OF_DMA_CHAINS_USED;
host->current_cb_cnt = 1;
}
spin_unlock_irqrestore(&host->dma_lock, flags);
return 0;
}
/*
* Routine to configure block leangth for MMC/SD/SDIO cards
* and intiate the transfer.
*/
static int
mmc_omap_err_prepare_data(struct mmc_omap_host *host)
{
int i;
unsigned count=0, count1=0;
struct mmc_data *data = host->data;
if(unlikely(host == NULL))
{
return -1;
}
mmc_clk_enable_aggressive(host);
if (host->use_dma && host->chain_id != -1) {
omap_stop_dma_chain_transfers(host->chain_id);
/* dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->sg_len,
host->dma_dir);*/
omap_free_dma_chain(host->chain_id);
host->chain_id = -1;
}
for(i = 0 ;i < host->sg_idx; i++)
{
count1 += (sg_dma_len(&data->sg[i])/data->blksz);
}
for(i = host->sg_idx ;i < (host->chains_requested+host->sg_idx); i++)
{
count += (sg_dma_len(&data->sg[i])/data->blksz);
}
OMAP_HSMMC_WRITE(host->base, BLK, (host->data->blksz));
OMAP_HSMMC_WRITE(host->base, BLK,
OMAP_HSMMC_READ(host->base,
BLK) | (count << 16));
host->mod_addr = host->org_addr;
if(host->is_high_capacity ==1)
host->mod_addr += count1;//as card is high capacity
else
host->mod_addr += (count1*512);//as card is not high capacity
host->datadir = (host->data->flags & MMC_DATA_WRITE) ?
OMAP_MMC_DATADIR_WRITE : OMAP_MMC_DATADIR_READ;
if (mmc_omap_get_dma_channel(host, data) == 0)
{
enum dma_data_direction dma_data_dir;
if (data->flags & MMC_DATA_WRITE)
dma_data_dir = DMA_TO_DEVICE;
else
dma_data_dir = DMA_FROM_DEVICE;
host->total_bytes_left = 0;
mmc_chain_dma(host, host->data);
host->brs_received = 0;
host->dma_done = 0;
/* Enable DMA */
host->use_dma = 1;
}
mmc_clk_disable_aggressive(host);
return 0;
}
/*
* Notify the xfer done on MMC/SD cards to the core
*/
static void
mmc_omap_read_err_done(struct mmc_omap_host *host, struct mmc_data *data)
{
unsigned long flags;
spin_lock_irqsave(&host->dma_lock, flags);
host->flag_err = 1;
host->cmd_12 = 1;
spin_unlock_irqrestore(&host->dma_lock, flags);
mmc_clk_disable_aggressive(host);
mmc_omap_start_command(host, data->stop);
}
/*
* Notify the xfer done on MMC/SD cards to the core
*/
static void
mmc_omap_err_done(struct mmc_omap_host *host, struct mmc_data *data)
{
host->data = NULL;
if (host->use_dma && host->chain_id != -1) {
omap_stop_dma_chain_transfers(host->chain_id);
dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->sg_len,
host->dma_dir);
omap_free_dma_chain(host->chain_id);
host->chain_id = -1;
}
host->datadir = OMAP_MMC_DATADIR_NONE;
host->sg_dma_len = 0;
mmc_clk_disable_aggressive(host);
if (!data->stop) {
host->mrq = NULL;
mmc_request_done(host->mmc, data->mrq);
return;
}
mmc_omap_start_command(host, data->stop);
}
/*
* The MMC controller IRQ handler
*/
static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
{
struct mmc_omap_host *host = (struct mmc_omap_host *)dev_id;
int end_command, end_transfer, status, read_crc =0;
unsigned long flags;
typeof(jiffies) timeout;
if (unlikely(host == NULL))
{
return IRQ_HANDLED;
}
if(unlikely(host->cmd == NULL && host->data == NULL)) {
status = OMAP_HSMMC_READ(host->base, STAT);
if (status != 0) {
OMAP_HSMMC_WRITE(host->base, STAT, status);
}
mmc_clk_disable_aggressive(host);
return IRQ_HANDLED;
}
end_command = 0;
end_transfer = 0;
if (host->cmd) {
if (host->cmd->opcode == MMC_SELECT_CARD
|| host->cmd->opcode == MMC_SWITCH) {
timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
while (time_before(jiffies, timeout)) {
if ((OMAP_HSMMC_READ(host->base, STAT)
& CC) == CC)
break;
}
}
}
status = OMAP_HSMMC_READ(host->base, STAT);
dev_dbg(mmc_dev(host->mmc), "Status in IRQ %x\n", status);
if(unlikely(host->use_dma == 0))
{
if (host->total_bytes_left != 0) {
if ((status & OMAP_MMC_STAT_BRR) ||
(status & TC))
mmc_omap_xfer_data(host, 0);
if (status & OMAP_MMC_STAT_BWR)
mmc_omap_xfer_data(host, 1);
}
}
if (status & (OMAP_HSMMC_ERR)) {
if (status & (OMAP_HSMMC_CMD_TIMEOUT) ||
status & (OMAP_HSMMC_CMD_CRC)) {
if (host->cmd) {
if (status & (OMAP_HSMMC_CMD_TIMEOUT)) {
/*
* Timeouts are normal in case of
* MMC_SEND_STATUS
*/
if (host->cmd->opcode !=
MMC_ALL_SEND_CID)
dev_dbg(mmc_dev(host->mmc),
"CMD Timeout CMD%d\n",
host->cmd->opcode);
host->cmd->error |= MMC_ERR_TIMEOUT;
} else {
dev_dbg(mmc_dev(host->mmc),
"%s: Command CRC error CMD%d\n",
mmc_hostname(host->mmc),
host->cmd->opcode);
host->cmd->error |= MMC_ERR_BADCRC;
}
end_command = 1;
}
if (host->data)
mmc_dma_cleanup(host);
}
if (status & (OMAP_HSMMC_DATA_TIMEOUT) ||
status & (OMAP_HSMMC_DATA_CRC)) {
if (host->data) {
if (status & (OMAP_HSMMC_DATA_TIMEOUT)) {
dev_dbg(mmc_dev(host->mmc),
"%s:Data timeout\n",
mmc_hostname(host->mmc));
mmc_dma_cleanup(host);
} else {
if(host->data->flags & MMC_DATA_READ)
{
read_crc = 1;
if(host->flag_err == 1)
{
dev_dbg(mmc_dev(host->mmc),
"%s:Data CRC for the second time\n",
mmc_hostname(host->mmc));
spin_lock_irqsave(&host->dma_lock, flags);
host->crc_retry++;
host->flag_err = 0;
spin_unlock_irqrestore(&host->dma_lock, flags);
if(host->crc_retry == MAX_CRC_RETRY)
{
spin_lock_irqsave(&host->dma_lock, flags);
host->mod_addr = host->org_addr = 0;
host->flag_err = host->cmd_12 = host->cmd_13 = 0;
spin_unlock_irqrestore(&host->dma_lock, flags);
host->data->error |= MMC_ERR_BADCRC;
mmc_omap_err_done(host, host->data);
}
}
}
else
host->data->error |= MMC_ERR_BADCRC;
dev_dbg(mmc_dev(host->mmc),
"%s: Data CRC error,"
" bytes left %d\n",
mmc_hostname(host->mmc),
host->bytesleft);
}
end_transfer = 1;
}
}
if (status & OMAP_HSMMC_CARD_ERR) {
dev_dbg(mmc_dev(host->mmc),
"MMC%d: Card status error (CMD%d)\n",
host->id, host->cmd->opcode);
if (host->cmd) {
host->cmd->error |= MMC_ERR_FAILED;
end_command = 1;
}
if (host->data) {
host->data->error |= MMC_ERR_FAILED;
end_transfer = 1;
}
}
}
#ifdef CONFIG_OMAP_SDIO
if (host->mmc->mode == MMC_MODE_SDIO && host->sdio_card_intr) {
if (status & OMAP_HSMMC_CARD_INT) {
dev_dbg(mmc_dev(host->mmc),
"MMC%d: SDIO CARD interrupt status %x\n",
host->id, status);
OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK &
(~OMAP_HSMMC_CARD_INT));
host->sdio_card_intr = 0;
/*
* SDIO Card interrupt notifier code should go here,
* it should clear the source of interrupt and then
* call again the interrupt enable API.
*/
return IRQ_HANDLED;
}
}
#endif
#if defined(CONFIG_OMAP_SDIO) && defined(CONFIG_OMAP_SDIO_NON_DMA_MODE)
sdio_non_dma_xfer(host);
#endif /* ifdef CONFIG_OMAP_SDIO */
OMAP_HSMMC_WRITE(host->base, STAT, status);
if(host->flag_err == 1)
{
if(host->cmd_12 == 1)
{
if (status & CC) {
mmc_omap_err_cmd_done(host, host->cmd);
spin_lock_irqsave(&host->dma_lock, flags);
host->cmd_12 =0;
spin_unlock_irqrestore(&host->dma_lock, flags);
mmc_omap_start_command13(host, host->cmd);
return IRQ_HANDLED;
}
}
if(host->cmd_13 == 1)
{
if (status & CC) {
mmc_omap_err_cmd_done(host, host->cmd);
spin_lock_irqsave(&host->dma_lock, flags);
host->cmd_13 =0;
spin_unlock_irqrestore(&host->dma_lock, flags);
mmc_omap_err_prepare_data(host);
omap_start_dma_chain_transfers(host->chain_id);
mmc_omap_start_command18(host, host->cmd);
return IRQ_HANDLED;
}
}
return IRQ_HANDLED;
}
if (end_command || (status & CC)) {
mmc_omap_cmd_done(host, host->cmd);
}
if (host->mmc->mode == MMC_MODE_MMC
|| host->mmc->mode == MMC_MODE_SD){
if (end_transfer){
if(read_crc)
{
mmc_omap_err_calc(host);
mmc_omap_read_err_done(host, host->data);
}
else
mmc_omap_err_done(host, host->data);
}
else if (status & TC)
mmc_omap_end_of_data(host, host->data);
}
#ifdef CONFIG_OMAP_SDIO
if(host->mmc->mode == MMC_MODE_SDIO){
if (end_transfer || (status & TC))
sdio_omap_xfer_done(host, host->sdiodata);
}
#endif /* ifdef CONFIG_OMAP_SDIO */
return IRQ_HANDLED;
}
#ifdef CONFIG_OMAP_SDIO
/*
* Function for polling mode read write for SDIO cards
*/
static void mmc_omap_polling_command(struct mmc_omap_host *host,
struct mmc_command *cmd, u32 cmdreg)
{
int i, readCnt, bytec, status = 0;
typeof(jiffies) timeout;
if (cmd->arg & OMAP_SDIO_READ) {
cmdreg |= DP_SELECT;
cmdreg &= ~(DDIR);
} else {
cmdreg |= DP_SELECT | DDIR;
}
OMAP_HSMMC_WRITE(host->base, STAT, OMAP_HSMMC_STAT_CLEAR);
OMAP_HSMMC_WRITE(host->base, ISE, INT_CLEAR);
OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK);
OMAP_HSMMC_WRITE(host->base, ARG, cmd->arg);
OMAP_HSMMC_WRITE(host->base, CMD, cmdreg);
timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
while (time_before(jiffies, timeout)) {
status = OMAP_HSMMC_READ(host->base, STAT);
if ((status & CC))
break;
}
if (!(status & CC)) {
dev_dbg(mmc_dev(host->mmc),
"SDIO Command error CMD IO_RW_extd\n");
host->cmd->error |= MMC_ERR_TIMEOUT;
mmc_omap_cmd_done(host, host->cmd);
return;
}
timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
bytec = (host->cmd->arg & 0x1FF);
readCnt = (bytec / 4);
if (bytec % 4)
readCnt++;
if (host->cmd->arg & OMAP_SDIO_READ) {
while (((OMAP_HSMMC_READ(host->base, PSTATE)
& BWE) != BRW)
&& time_before(jiffies, timeout)) ;
for (i = 0; i < readCnt; i++)
OMAP_HSMMC_WRITE(host->base, DATA, *host->buffer++);
} else {
while (((OMAP_HSMMC_READ(host->base, PSTATE) & BRE) != BRR)
&& time_before(jiffies, timeout)) ;
for (i = 0; i < readCnt; i++)
*host->buffer++ = OMAP_HSMMC_READ(host->base, DATA);
}
status = 0;
mmc_omap_cmd_done(host, host->cmd);
timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
while (time_before(jiffies, timeout)) {
status = OMAP_HSMMC_READ(host->base, STAT);
if (status & TC)
break;
}
if (!(status & TC)) {
dev_dbg(mmc_dev(host->mmc), "SDIO data sending error \n");
host->data->error = MMC_ERR_TIMEOUT;
return;
}
sdio_omap_xfer_done(host, host->sdiodata);
return;
}
#endif
/*
* Turn the socket power ON/OFF
*/
static int mmc_omap_power(struct mmc_omap_host *host, int on)
{
int ret = 0;
if (on){
if (machine_is_omap_2430sdp() ||
machine_is_omap_3430sdp() ||
machine_is_omap_3430labrador() ||
machine_is_omap3evm() || machine_is_omap3_beagle() )
ret = enable_mmc_power(host->id);
} else {
if (machine_is_omap_2430sdp() ||
machine_is_omap_3430sdp() ||
machine_is_omap_3430labrador() ||
machine_is_omap3evm() || machine_is_omap3_beagle() )
ret = disable_mmc_power(host->id);
}
return ret;
}
/*
* power switching module for mmc slot 1
* power_mode=0 switches to 1.8V
* power_mode=1 switches to 3V
* Caller makes sure that it calls on slot 1 with correct cpu revision
*/
static int omap_mmc_switch_opcond(struct mmc_omap_host *host, int power_mode)
{
int ret = 0;
mmc_clk_disable(host);
if (cpu_is_omap2430())
clk_disable(host->dbclk);
ret = mmc_omap_power(host,0);
if (ret != 0)
dev_dbg(mmc_dev(host->mmc),"Unable to disable power to MMC1\n");
if (machine_is_omap_2430sdp() ||
machine_is_omap_3430sdp() ||
machine_is_omap_3430labrador() ||
machine_is_omap3evm() || machine_is_omap3_beagle() ) {
if (switch_power_mode(power_mode))
dev_dbg(mmc_dev(host->mmc), "Unable to switch operating"
"voltage to the card\n");
}
mmc_clk_enable(host);
if (cpu_is_omap2430()) {
if (clk_enable(host->dbclk) != 0)
dev_dbg(mmc_dev(host->mmc),
"Unable to enable MMC1 debounce clock"
"while switching power\n");
}
OMAP_HSMMC_WRITE(host->base, HCTL,
OMAP_HSMMC_READ(host->base, HCTL) & SDVSCLR);
if (power_mode == 0) {
OMAP_HSMMC_WRITE(host->base, HCTL,
OMAP_HSMMC_READ(host->base, HCTL) | SDVS18);
} else {
OMAP_HSMMC_WRITE(host->base, HCTL,
OMAP_HSMMC_READ(host->base, HCTL) | SDVS30);
host->initstream = 0;
}
OMAP_HSMMC_WRITE(host->base, HCTL,
OMAP_HSMMC_READ(host->base, HCTL) | SDBP);
return 0;
}
/*
* Work Item to notify the core about card insertion/removal
*/
static void mmc_omap_detect(struct work_struct *work)
{
struct mmc_omap_host *host = container_of(work, struct mmc_omap_host,
mmc_carddetect_work);
mmc_clk_enable_aggressive(host);
if (cpu_is_omap34xx() || (cpu_is_omap2430()
&& omap2_cpu_rev() == 2)) {
if (host->id == OMAP_MMC1_DEVID) {
if (!(OMAP_HSMMC_READ(host->base, HCTL)
& SDVSDET)) {
if (omap_mmc_switch_opcond(host, 1) != 0)
dev_dbg(mmc_dev(host->mmc),
"mmc_omap_detect:switch"
"command operation failed\n");
host->mmc->ios.vdd =
fls(host->mmc->ocr_avail) - 1;
}
}
}
mmc_detect_change(host->mmc, (HZ * 200) / 1000);
mmc_clk_disable_aggressive(host);
}
/*
* Interrupt service routine for handling card insertion and removal
*/
static irqreturn_t mmc_omap_irq_cd(int irq, void *dev_id)
{
struct mmc_omap_host *host = (struct mmc_omap_host *)dev_id;
unsigned long flags;
if (machine_is_omap_2430sdp() ||
machine_is_omap_3430sdp() ||
machine_is_omap_3430labrador() ||
machine_is_omap3evm() || machine_is_omap3_beagle() )
{
spin_lock_irqsave(&host->dma_lock, flags);
host->card_detected = 1;
host->mmc->mode = MMC_CARD_NONE;
host->rca = host->mod_addr = host->org_addr = 0;
host->is_high_capacity = 0;
host->flag_err = host->cmd_12 = host->cmd_13 = 0;
spin_unlock_irqrestore(&host->dma_lock, flags);
schedule_work(&host->mmc_carddetect_work);
}
else
dev_dbg(mmc_dev(host->mmc), "Place to implement MMC hotplug"
"implementation based on what the other"
"board can support\n");
return IRQ_HANDLED;
}
/*
* DMA call back function
lch is chain id in case of chaining
*/
static void mmc_omap_dma_cb(int lch, u16 ch_status, void *data)
{
struct mmc_omap_host *host = (struct mmc_omap_host *)data;
int chainid = host->chain_id;
int dma_ch;
unsigned long flags;
if(host->flag_err)
{
spin_lock_irqsave(&host->dma_lock, flags);
host->crc_retry =0;
spin_unlock_irqrestore(&host->dma_lock, flags);
if(host->no_of_chain_reqd > host->current_cb_cnt)
{
spin_lock_irqsave(&host->dma_lock, flags);
host->current_cb_cnt++;
spin_unlock_irqrestore(&host->dma_lock, flags);
mmc_omap_read_err_done(host, host->data);
}
else if(host->no_of_chain_reqd == host->current_cb_cnt)
{
if(host->extra_chain_reqd == 0)
{
/*cleanup and go away*/
spin_lock_irqsave(&host->dma_lock, flags);
host->flag_err = 0;
spin_unlock_irqrestore(&host->dma_lock, flags);
omap_stop_dma_chain_transfers(chainid);
omap_free_dma_chain(chainid);
mmc_omap_dma_done(host, host->data);
host->chain_id = -1;
}
else
{
/*do the last transfer*/
spin_lock_irqsave(&host->dma_lock, flags);
host->chains_requested = host->extra_chain_reqd;
host->extra_chain_reqd = 0;
spin_unlock_irqrestore(&host->dma_lock, flags);
mmc_omap_read_err_done(host, host->data);
}
}
return;
}
if(host->mmc->mode == MMC_MODE_SDIO)
{
/*
* Only report the error for the time being, until the error handling
* for these type of errors is supported from the core
*/
if (ch_status & (1 << 11))
dev_dbg(mmc_dev(host->mmc), " %s :MISALIGNED_ADRS_ERR\n",
mmc_hostname(host->mmc));
if (host->dma_ch < 0) {
dev_dbg(mmc_dev(host->mmc), "%s:"
"DMA callback while DMA not enabled?\n",
mmc_hostname(host->mmc));
return;
}
dma_ch = host->dma_ch;
host->dma_ch = -1;
omap_free_dma(dma_ch);
}
else
{
/* If we are at the last transfer, Shut down the reciever */
if (omap_dma_chain_status(chainid) == OMAP_DMA_CHAIN_INACTIVE) {
if(host->no_of_chain_reqd > host->current_cb_cnt)
{
mmc_chain_dma(host, host->data);
omap_dma_set_interrupt_ch(host->chain_id, OMAP_DMA_DROP_IRQ |
OMAP2_DMA_MISALIGNED_ERR_IRQ |
OMAP2_DMA_TRANS_ERR_IRQ,
OMAP_DMA_DROP_IRQ |
OMAP_DMA_BLOCK_IRQ |
OMAP2_DMA_MISALIGNED_ERR_IRQ |
OMAP2_DMA_TRANS_ERR_IRQ);
omap_start_dma_chain_transfers(host->chain_id);
spin_lock_irqsave(&host->dma_lock, flags);
host->current_cb_cnt++;
spin_unlock_irqrestore(&host->dma_lock, flags);
}
else if(host->no_of_chain_reqd == host->current_cb_cnt)
{
if(host->extra_chain_reqd == 0)
{
omap_stop_dma_chain_transfers(chainid);
omap_free_dma_chain(chainid);
mmc_omap_dma_done(host, host->data);
host->chain_id = -1;
}
else
{
omap_stop_dma_chain_transfers(chainid);
omap_free_dma_chain(chainid);
host->chain_id = -1;
spin_lock_irqsave(&host->dma_lock, flags);
host->chains_requested = host->extra_chain_reqd;
spin_unlock_irqrestore(&host->dma_lock, flags);
mmc_omap_get_dma_channel(host, host->data);
mmc_chain_dma(host, host->data);
omap_start_dma_chain_transfers(host->chain_id);
spin_lock_irqsave(&host->dma_lock, flags);
host->extra_chain_reqd = 0;
spin_unlock_irqrestore(&host->dma_lock, flags);
}
}
else
{
dev_dbg(mmc_dev(host->mmc), "%s:"
"DMA callback ERROR\n",
mmc_hostname(host->mmc));
}
}
else
{
dev_dbg(mmc_dev(host->mmc), "%s:"
"DMA callback Channel active?\n",
mmc_hostname(host->mmc));
}
}
}
#ifdef CONFIG_OMAP_SDIO
#ifndef CONFIG_OMAP_SDIO_NON_DMA_MODE
/*
* Configure dma src and destination parameters
*/
static int mmc_omap_config_dma_param(int sync_dir, struct mmc_omap_host *host,
struct mmc_data *data)
{
if (sync_dir == OMAP_DMA_DST_SYNC) {
omap_set_dma_dest_params(host->dma_ch,
0, // dest_port required only for OMAP1
OMAP_DMA_AMODE_CONSTANT,
(dma_addr_t) (host-> mapbase + OMAP_HSMMC_DATA),0, 0);
omap_set_dma_src_params(host->dma_ch,
0, // src_port required only for OMAP1
OMAP_DMA_AMODE_POST_INC,
sg_dma_address(&data-> sg[0]), 0, 0);
} else {
omap_set_dma_src_params(host->dma_ch,
0, // src_port required only for OMAP1
OMAP_DMA_AMODE_CONSTANT,
(dma_addr_t) (host->mapbase +OMAP_HSMMC_DATA),0, 0);
omap_set_dma_dest_params(host->dma_ch,
0, // dest_port required only for OMAP1
OMAP_DMA_AMODE_POST_INC,
sg_dma_address(&data->sg[0]), 0,0);
}
return 0;
}
/*
* Routine to configure and start dma for SDIO card
*/
static int
sdio_omap_start_dma_transfer(struct mmc_omap_host *host,
struct mmc_request *req)
{
int sync_dev, sync_dir, dma_ch, ret, readCnt, bytecount;
int nob = 1, func = 0;
struct mmc_data *data = req->data;
/*
* If for some reason the DMA transfer is still active,
* we wait for timeout period and free the dma
*/
if (host->dma_ch != -1) {
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(100);
if (down_trylock(&host->sem)) {
dma_ch = host->dma_ch;
host->dma_ch = -1;
omap_free_dma(dma_ch);
up(&host->sem);
return 1;
}
} else {
if (down_trylock(&host->sem)) {
dev_dbg(mmc_dev(host->mmc),
"Semaphore was not initialized \n");
BUG();
}
}
if (req->cmd->opcode == IO_RW_EXTENDED) {
if (req->cmd->arg & OMAP_SDIO_READ) {
if (host->id == OMAP_MMC1_DEVID)
sync_dev = OMAP24XX_DMA_MMC1_TX;
else
sync_dev = OMAP24XX_DMA_MMC2_TX;
} else {
if (host->id == OMAP_MMC1_DEVID)
sync_dev = OMAP24XX_DMA_MMC1_RX;
else
sync_dev = OMAP24XX_DMA_MMC2_RX;
}
ret = omap_request_dma(sync_dev, "SDIO", mmc_omap_dma_cb,
host,&dma_ch);
if (ret != 0) {
dev_dbg(mmc_dev(host->mmc),
"%s: omap_request_dma() failed with %d\n",
mmc_hostname(host->mmc), ret);
return ret;
}
host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg,
data->sg_len, host->dma_dir);
host->dma_ch = dma_ch;
if (req->cmd->arg & OMAP_SDIO_READ) {
sync_dir = OMAP_DMA_DST_SYNC;
mmc_omap_config_dma_param(sync_dir, host, data);
} else {
sync_dir = OMAP_DMA_SRC_SYNC;
mmc_omap_config_dma_param(sync_dir, host, data);
}
if (req->cmd->arg & SDIO_BLKMODE) {
nob = req->cmd->arg & 0x1FF;
if (nob == 0)
nob = 0x200;
func = ((req->cmd->arg & sdio_function_mask) >> 28);
bytecount = blkmode_bytecount[func];
readCnt = (bytecount / 4);
if (bytecount % 4)
readCnt++;
} else {
bytecount = req->cmd->arg & 0x1FF;
if (bytecount == 0)
bytecount = 0x200;
readCnt = (bytecount / 4);
if (bytecount % 4)
readCnt++;
}
omap_set_dma_transfer_params(dma_ch,
OMAP_DMA_DATA_TYPE_S32, readCnt,
nob,OMAP_DMA_SYNC_FRAME, sync_dev,sync_dir);
omap_start_dma(dma_ch);
}
return 0;
}
#endif
#endif /* ifdef CONFIG_OMAP_SDIO */
static int mmc_omap_get_dma_channel(struct mmc_omap_host *host, struct mmc_data *data)
{
int ret = 0;
int dma_chid;
u16 frame;
u32 count;
struct scatterlist *sg = &data->sg[host->sg_idx];
int sync_dev = 0;
frame = data->blksz;/*blk size*/
count = sg_dma_len(sg)/frame;/*No of blocks*/
/*
* If for some reason the DMA transfer is still active,
* we wait for timeout period and free the dma
*/
if(host->chain_id != -1)
dev_dbg(mmc_dev(host->mmc),
"%s: chain is not free\n",
mmc_hostname(host->mmc));
/*Common params*/
//host->params.burst_mode =
host->params.data_type = OMAP_DMA_DATA_TYPE_S32;
host->params.dst_ei = 0;
host->params.dst_fi = 0;
host->params.dst_port = 0;
host->params.elem_count = (data->blksz / 4);
//host->params.ie =
host->params.read_prio = DMA_CH_PRIO_HIGH;
host->params.src_ei = 0;
host->params.src_fi = 0;
host->params.src_port = 0;
host->params.sync_mode = OMAP_DMA_SYNC_FRAME;
host->params.write_prio = DMA_CH_PRIO_HIGH;
if (!(data->flags & MMC_DATA_WRITE)) {
host->dma_dir = DMA_FROM_DEVICE;
if (host->id == OMAP_MMC1_DEVID)
sync_dev = OMAP24XX_DMA_MMC1_RX;
else
sync_dev = OMAP24XX_DMA_MMC2_RX;
host->params.dst_amode = OMAP_DMA_AMODE_POST_INC;
host->params.dst_start = sg_dma_address(&data->sg[host->sg_idx]);
host->params.frame_count = count;
host->params.src_amode = OMAP_DMA_AMODE_CONSTANT;
host->params.src_or_dst_synch = OMAP_DMA_SRC_SYNC;
host->params.src_start = (dma_addr_t) (host->mapbase +OMAP_HSMMC_DATA);
host->params.trigger = sync_dev;
} else {
host->dma_dir = DMA_TO_DEVICE;
if (host->id == OMAP_MMC1_DEVID)
sync_dev = OMAP24XX_DMA_MMC1_TX;
else
sync_dev = OMAP24XX_DMA_MMC2_TX;
host->params.dst_amode = OMAP_DMA_AMODE_CONSTANT;
host->params.dst_start = (dma_addr_t) (host->mapbase + OMAP_HSMMC_DATA);
host->params.frame_count = count;
host->params.src_amode = OMAP_DMA_AMODE_POST_INC;
host->params.src_or_dst_synch = OMAP_DMA_DST_SYNC;
host->params.src_start = sg_dma_address(&data->sg[host->sg_idx]);
host->params.trigger = sync_dev;
}
/* Request a DMA chain for transfer
* A chain is requested before each transfer to avoid
* locking of DMA resources
*/
ret = omap_request_dma_chain(sync_dev, "MMC/SD", mmc_omap_dma_cb,
&dma_chid, host->chains_requested,
OMAP_DMA_DYNAMIC_CHAIN, host->params);
if (ret != 0) {
dev_dbg(mmc_dev(host->mmc),
"%s: omap_request_dma_chain() failed with %d\n",
mmc_hostname(host->mmc), ret);
return ret;
}
else
{
if(host->chains_requested > 1)
omap_dma_set_interrupt_ch(dma_chid, OMAP_DMA_DROP_IRQ |
OMAP2_DMA_MISALIGNED_ERR_IRQ |
OMAP2_DMA_TRANS_ERR_IRQ,
OMAP_DMA_DROP_IRQ |
OMAP_DMA_BLOCK_IRQ |
OMAP2_DMA_MISALIGNED_ERR_IRQ |
OMAP2_DMA_TRANS_ERR_IRQ);
}
host->chain_id = dma_chid;
return 0;
}
static void mmc_chain_dma(struct mmc_omap_host *host, struct mmc_data *data)
{
u16 frame;
u32 count,i,dma_chain_status, sg_idx = host->sg_idx;
struct scatterlist *sg;
frame = data->blksz;
for(i = host->sg_idx ;i < (host->chains_requested + sg_idx); i++)
{
sg = &data->sg[i];
count = sg_dma_len(sg)/frame;
host->sg_dma_len += (frame * count);
if(!(data->flags & MMC_DATA_WRITE))
{
dma_chain_status = omap_dma_chain_a_transfer(host->chain_id,
(dma_addr_t) (host->mapbase +OMAP_HSMMC_DATA),
sg_dma_address(&data->sg[i]), (data->blksz / 4),
count, host);
if(dma_chain_status != 0)
dev_dbg(mmc_dev(host->mmc),
"%s: omap_dma_chain_a_transfer() failed during read with %d\n",
mmc_hostname(host->mmc), dma_chain_status);
}
else
{
dma_chain_status = omap_dma_chain_a_transfer(host->chain_id,
sg_dma_address(&data->sg[i]),
(dma_addr_t) (host->mapbase + OMAP_HSMMC_DATA),
(data->blksz / 4) ,count ,host);
if(dma_chain_status != 0)
dev_dbg(mmc_dev(host->mmc),
"%s: omap_dma_chain_a_transfer() failed during write with %d\n",
mmc_hostname(host->mmc), dma_chain_status);
}
host->sg_idx++;
}
}
/*
* Routine to configure block leangth for MMC/SD/SDIO cards
* and intiate the transfer.
*/
static int
mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req)
{
int use_dma;
int i, block_size;
unsigned sg_len;
struct mmc_data *data = req->data;
unsigned long flags;
#ifdef CONFIG_OMAP_SDIO
int byte_count = 0, func = 0;
int ret = 0;
#endif
if(unlikely(host == NULL))
{
return -1;
}
/* Store the pointer for request */
host->data = req->data;
#ifdef CONFIG_OMAP_SDIO
host->sdiodata = req->data;
if (req->cmd->opcode == IO_RW_EXTENDED) {
if (req->cmd->arg & OMAP_SDIO_READ)
host->datadir = OMAP_MMC_DATADIR_WRITE;
else
host->datadir = OMAP_MMC_DATADIR_READ;
if ((req->cmd->arg & 0x1FF) == 0)
byte_count = 0x200;
else
byte_count = req->cmd->arg & 0x1FF;
func = ((req->cmd->arg & sdio_function_mask) >> 28);
if (req->cmd->arg & SDIO_BLKMODE) {
OMAP_HSMMC_WRITE(host->base, BLK,
blkmode_bytecount[func]);
OMAP_HSMMC_WRITE(host->base, BLK,
OMAP_HSMMC_READ(host->base,
BLK) | (byte_count << 16));
} else {
OMAP_HSMMC_WRITE(host->base, BLK, byte_count);
OMAP_HSMMC_WRITE(host->base, BLK,
OMAP_HSMMC_READ(host->base,BLK)
| (1 << 16));
}
#ifdef CONFIG_OMAP_SDIO_NON_DMA_MODE
host->buffer = (u32 *) req->data->sdio_buffer_virt;
#else
if (polling_mode == 0) {
ret = sdio_omap_start_dma_transfer(host, req);
if (ret != 0) {
dev_dbg(mmc_dev(host->mmc),
"Sdio start dma failure\n");
return ret;
} else {
host->buffer = NULL;
host->bytesleft = 0;
}
} else {
host->buffer = (u32 *) req->data->sdio_buffer_virt;
}
#endif
return 0;
}
#endif /* ifdef CONFIG_OMAP_SDIO */
/* Enable DMA */
host->use_dma = 1;
if (req->data == NULL) {
host->datadir = OMAP_MMC_DATADIR_NONE;
OMAP_HSMMC_WRITE(host->base, BLK, BLK_CLEAR);
/* Since there is nothing to DMA, clear the flag */
host->use_dma = 0;
return 0;
}
OMAP_HSMMC_WRITE(host->base, BLK, (req->data->blksz));
OMAP_HSMMC_WRITE(host->base, BLK,
OMAP_HSMMC_READ(host->base,
BLK) | (req->data->blocks << 16));
/* Copy the Block Size information */
block_size = data->blksz;
/* Cope with calling layer confusion; it issues "single
* block" writes using multi-block scatterlists.
*/
sg_len = (data->blocks == 1) ? 1 : data->sg_len;
spin_lock_irqsave(&host->dma_lock, flags);
if(sg_len > NO_OF_DMA_CHAINS_USED)
{
host->extra_chain_reqd = sg_len % NO_OF_DMA_CHAINS_USED;
host->no_of_chain_reqd = sg_len / NO_OF_DMA_CHAINS_USED;
host->chains_requested = NO_OF_DMA_CHAINS_USED;
host->current_cb_cnt = 1;
}
else
{
host->extra_chain_reqd = 0;
host->no_of_chain_reqd = 0;
host->chains_requested = data->sg_len;
host->current_cb_cnt = 0;
}
spin_unlock_irqrestore(&host->dma_lock, flags);
/* Only do DMA for entire blocks */
use_dma = host->use_dma;
if (use_dma) {
for (i = 0; i < sg_len; i++) {
if ((data->sg[i].length % block_size) != 0) {
use_dma = 0;
break;
}
}
}
host->datadir = (req->data-> flags & MMC_DATA_WRITE) ?
OMAP_MMC_DATADIR_WRITE : OMAP_MMC_DATADIR_READ;
/* Initialize the internal scatter list count */
host->sg_idx = 0;
if (use_dma) {
if (mmc_omap_get_dma_channel(host, data) == 0) {
enum dma_data_direction dma_data_dir;
if (data->flags & MMC_DATA_WRITE)
dma_data_dir = DMA_TO_DEVICE;
else
dma_data_dir = DMA_FROM_DEVICE;
host->sg_len = dma_map_sg(mmc_dev(host->mmc), data->sg,
sg_len, dma_data_dir);
host->total_bytes_left = 0;
mmc_chain_dma(host, req->data);
host->brs_received = 0;
host->dma_done = 0;
/* Enable DMA */
host->use_dma = 1;
}
else
{
host->use_dma = 0;
}
} else {
/* Revert to CPU copy */
host->buffer =
(u32 *) (page_address(req->data->sg->page) +
req->data->sg->offset);
host->bytesleft = req->data->blocks * (req->data->blksz);
host->dma_ch = -1;
host->use_dma = 0;
}
return 0;
}
/*
* Request function. Exposed API to core for read/write operation
*/
static void omap_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
{
struct mmc_omap_host *host = mmc_priv(mmc);
WARN_ON(host->mrq != NULL);
host->mrq = req;
mmc_clk_enable_aggressive(host);
/* Reset MMC Controller's Data FSM */
if (req->cmd->opcode == MMC_GO_IDLE_STATE) {
OMAP_HSMMC_WRITE(host->base, SYSCTL,
OMAP_HSMMC_READ(host->base, SYSCTL) | 1 << 26);
while (OMAP_HSMMC_READ(host->base, SYSCTL) & (1 << 26)) ;
}
if (req->cmd->opcode == SD_APP_SEND_SCR
|| req->cmd->opcode == MMC_SEND_EXT_CSD)
mmc->ios.bus_width = MMC_BUS_WIDTH_1;
if (mmc_omap_prepare_data(host, req))
dev_dbg(mmc_dev(host->mmc),
"MMC host %s failed to initiate data transfer\n",
mmc_hostname(host->mmc));
/* Start the DMA if DMA is needed */
if (host->use_dma && (host->mmc->mode == MMC_MODE_MMC
|| host->mmc->mode == MMC_MODE_SD))
{
omap_start_dma_chain_transfers(host->chain_id);
}
if(host->card_detected == 1)
{
if (host->mmc->mode == MMC_MODE_MMC ||
host->mmc->mode == MMC_MODE_SD)
{
host->mmc->max_hw_segs = 128;
host->mmc->max_phys_segs = 128;
host->mmc->max_blk_size = 512;
host->mmc->max_blk_count = 0xFFFF;
host->mmc->max_req_size = host->mmc->max_blk_size * host->mmc->max_blk_count;
host->mmc->max_seg_size = host->mmc->max_req_size;
host->card_detected = 0;
}
else if(host->mmc->mode == MMC_MODE_SDIO)
{
host->mmc->max_hw_segs = 1;
host->mmc->max_phys_segs = 1;
host->mmc->max_seg_size = 1<<12;
host->mmc->max_req_size = 1<<12;
host->mmc->max_blk_size = 512;
host->mmc->max_blk_count = 1<<12 / 512;
host->card_detected = 0;
}
}
mmc_clk_disable_aggressive(host);
mmc_omap_start_command(host, req->cmd);
}
/*
* Routine to configure clock values. Exposed API to core
*/
static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct mmc_omap_host *host = mmc_priv(mmc);
u16 dsor = 0;
unsigned long regVal;
typeof(jiffies) timeout;
int *addr;
dev_dbg(mmc_dev(host->mmc), "%s: set_ios: clock %dHz busmode %d"
"powermode %d Vdd %x Bus Width %d\n",
mmc_hostname(host->mmc), ios->clock, ios->bus_mode,
ios->power_mode, ios->vdd, ios->bus_width);
switch (ios->power_mode) {
case MMC_POWER_OFF:
host->initstream = 0;
#ifdef CONFIG_MMC_OMAP3430
if (host->id == OMAP_MMC1_DEVID) {
addr = (int *)&OMAP2_CONTROL_PBIAS_1;
*addr &= ~(1 << 1);
if (is_sil_rev_greater_than(OMAP3430_REV_ES1_0))
*addr &= ~(1 << 9);
}
#endif
if (mmc_omap_power(host, 0))
dev_dbg(mmc_dev(host->mmc),
"Could not disable power to MMC%d\n",host->id);
break;
case MMC_POWER_UP:
if (mmc_omap_power(host, 1))
dev_dbg(mmc_dev(host->mmc),
"Could not enable power to MMC%d\n",host->id);
#ifdef CONFIG_MMC_OMAP3430
if (host->id == OMAP_MMC1_DEVID) {
addr = (int *)&OMAP2_CONTROL_PBIAS_1;
*addr |= (1 << 1);
if (is_sil_rev_greater_than(OMAP3430_REV_ES1_0))
*addr |= (1 << 9);
}
#endif
break;
}
mmc_clk_enable_aggressive(host);
switch (mmc->ios.bus_width) {
case MMC_BUS_WIDTH_8:
OMAP_HSMMC_WRITE(host->base, CON,
OMAP_HSMMC_READ(host->base,CON)
| EIGHT_BIT);
break;
case MMC_BUS_WIDTH_4:
OMAP_HSMMC_WRITE(host->base, HCTL,
OMAP_HSMMC_READ(host->base,HCTL)
| FOUR_BIT);
break;
case MMC_BUS_WIDTH_1:
OMAP_HSMMC_WRITE(host->base, CON,
OMAP_HSMMC_READ(host->base,CON) & ~EIGHT_BIT);
OMAP_HSMMC_WRITE(host->base, HCTL,
OMAP_HSMMC_READ(host->base,HCTL) & ~FOUR_BIT);
break;
}
if (host->id == OMAP_MMC1_DEVID) {
if ((cpu_is_omap34xx() && is_sil_rev_less_than(OMAP3430_REV_ES2_0))
|| (cpu_is_omap2430() && omap2_cpu_rev() == 2)) {
if ((OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET) &&
((ios->vdd == 7) || (ios->vdd == 8))) {
if (omap_mmc_switch_opcond(host, 0) != 0)
dev_dbg(mmc_dev(host->mmc),
"omap_mmc_set_ios:"
"switch operation failed\n");
host->initstream = 0;
}
}
}
if (ios->clock) {
/* Enable MMC_SD_CLK */
dsor = OMAP_MMC_MASTER_CLOCK / ios->clock;
if (dsor < 1)
dsor = 1;
if (OMAP_MMC_MASTER_CLOCK / dsor > ios->clock)
dsor++;
if (dsor > 250)
dsor = 250;
}
omap_mmc_stop_clock(host);
regVal = OMAP_HSMMC_READ(host->base, SYSCTL);
regVal = regVal & ~(CLKD_MASK);
regVal = regVal | (dsor << 6);
regVal = regVal | (DTO << 16);
OMAP_HSMMC_WRITE(host->base, SYSCTL, regVal);
OMAP_HSMMC_WRITE(host->base, SYSCTL,
OMAP_HSMMC_READ(host->base, SYSCTL) | ICE);
/* wait till the ICS bit is set */
timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
while ((OMAP_HSMMC_READ(host->base, SYSCTL) & ICS) != 0x2
&& time_before(jiffies, timeout)) ;
/* Enable clock to the card */
OMAP_HSMMC_WRITE(host->base, SYSCTL,
OMAP_HSMMC_READ(host->base, SYSCTL) | CEN);
mmc_clk_disable_aggressive(host);
}
static struct mmc_host_ops mmc_omap_ops = {
.request = omap_mmc_request,
.set_ios = omap_mmc_set_ios,
};
#ifdef CONFIG_OMAP_SDIO
/*
* Routine implementing SDIO polling mode sysfs entry
*/
static ssize_t sdio_polling_switch(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
char cmd[25];
int i = 0;
if (count < 6) {
dev_dbg(dev, "Invalid string\n");
return count;
}
while (buf[i] != ' ' && buf[i] != '\n' && i < count) {
cmd[i] = buf[i];
i++;
}
cmd[i] = '\0';
i++;
if (!strcmp(cmd, "Enable")) {
polling_mode = 1;
} else if (!strcmp(cmd, "Disable")) {
polling_mode = 0;
} else {
dev_dbg(dev, "Unrecognized string\n");
dev_dbg(dev, "Usage:\n");
dev_dbg(dev, "echo Enable >"
"/sys/devices/platform/hsmmc-omap/sdio_polling_switch\n");
dev_dbg(dev, "echo Disable >"
"/sys/devices/platform/hsmmc-omap/sdio_polling_switch\n");
}
return count;
}
static DEVICE_ATTR(sdio_polling_switch, S_IWUSR, NULL, sdio_polling_switch);
#endif
/*
* Routine implementing the driver probe method
*/
static int __init omap_mmc_probe(struct platform_device *pdev)
{
struct omap_mmc_conf *minfo = pdev->dev.platform_data;
struct mmc_host *mmc;
struct mmc_omap_host *host = NULL;
struct resource *res;
int ret = 0, irq, *addr;
if (minfo == NULL) {
dev_err(&pdev->dev, "platform data missing\n");
return -ENXIO;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
irq = platform_get_irq(pdev, 0);
if (res == NULL || irq < 0)
return -ENXIO;
res = request_mem_region(res->start, res->end - res->start + 1,
pdev->name);
if (res == NULL)
return -EBUSY;
mmc = mmc_alloc_host(sizeof(struct mmc_omap_host), &pdev->dev);
if (!mmc) {
ret = -ENOMEM;
goto mmc_alloc_err;
}
host = mmc_priv(mmc);
host->mmc = mmc;
sema_init(&host->sem, 1);
host->use_dma = OMAP_USE_DMA;
host->dma_ch = -1;
host->initstream = 0;
host->mem_res = res;
host->irq = irq;
host->id = pdev->id;
host->mapbase = (void *)host->mem_res->start;
host->base = (void __iomem *)IO_ADDRESS(host->mapbase);
mmc->ops = &mmc_omap_ops;
mmc->f_min = 400000;
mmc->f_max = 52000000;
mmc->mode = MMC_CARD_NONE;
host->card_detected = 1;
host->is_high_capacity = 0;
host->flag_err = 0;
host->cmd_12 = 0;
host->cmd_13 = 0;
host->crc_retry = 0;
#ifdef CONFIG_OMAP_SDIO
host->sdio_card_intr = 0;
#endif
spin_lock_init(&host->dma_lock);
host->chain_id = -1;
host->sg_dma_len = 0;
if (cpu_is_omap2430()) {
if (host->id == OMAP_MMC1_DEVID) {
host->fclk = clk_get(&pdev->dev, "mmchs1_fck");
if (IS_ERR(host->fclk)) {
ret = PTR_ERR(host->fclk);
host->fclk = NULL;
goto clk_get_err;
}
host->iclk = clk_get(&pdev->dev, "mmchs1_ick");
if (IS_ERR(host->iclk)) {
ret = PTR_ERR(host->iclk);
host->iclk = NULL;
clk_put(host->fclk);
goto clk_get_err;
}
host->dbclk = clk_get(&pdev->dev, "mmchsdb1_fck");
/*
* Only through a error message, MMC can still work
* without debounce clock.
*/
if (IS_ERR(host->dbclk))
dev_dbg(mmc_dev(host->mmc),
"Failed to get debounce"
"clock for MMC1\n");
} else {
host->fclk = clk_get(&pdev->dev, "mmchs2_fck");
if (IS_ERR(host->fclk)) {
ret = PTR_ERR(host->fclk);
host->fclk = NULL;
goto clk_get_err;
}
host->iclk = clk_get(&pdev->dev, "mmchs2_ick");
if (IS_ERR(host->iclk)) {
ret = PTR_ERR(host->iclk);
host->iclk = NULL;
clk_put(host->fclk);
goto clk_get_err;
}
host->dbclk = clk_get(&pdev->dev, "mmchsdb2_fck");
/*
* Only through a error message, MMC can still work
* without debounce clock.
*/
if (IS_ERR(host->dbclk))
dev_dbg(mmc_dev(host->mmc),
"Failed to get"
"debounce clock for MMC2\n");
}
}
if (cpu_is_omap34xx()) {
/* 3430-ES1.0 Sil errata fix */
if (is_sil_rev_less_than(OMAP3430_REV_ES2_0)) {
host->gptfck = clk_get(&pdev->dev, "gpt10_fck");
if (IS_ERR(host->gptfck)) {
ret = PTR_ERR(host->gptfck);
host->gptfck = NULL;
goto clk_get_err;
}
}
host->fclk = clk_get(&pdev->dev, "mmc_fck");
if (IS_ERR(host->fclk)) {
ret = PTR_ERR(host->fclk);
host->fclk = NULL;
goto clk_get_err;
}
host->iclk = clk_get(&pdev->dev, "mmc_ick");
if (IS_ERR(host->iclk)) {
ret = PTR_ERR(host->iclk);
clk_put(host->fclk);
host->iclk = NULL;
goto clk_get_err;
}
}
#ifdef CONFIG_OMAP34XX_OFFMODE
modify_timeout_value(host->fclk, 500);
#endif /* #ifdef CONFIG_OMAP34XX_OFFMODE */
mmc_clk_enable(host);
if (cpu_is_omap2430()) {
if (clk_enable(host->dbclk) != 0)
dev_dbg(mmc_dev(host->mmc),
"Failed to enable debounce clock for MMC%d\n",
host->id);
omap_writel(omap_readl(OMAP2_CONTROL_DEVCONF1) | MMC1_ACTIVE_OVERWRITE,
OMAP2_CONTROL_DEVCONF1);
if (minfo->wire4)
/* OMAP2430 ES2.0 and onwards can support 4-bit */
if (omap2_cpu_rev() >= 1)
mmc->caps = MMC_CAP_4_BIT_DATA;
}
if (host->id == OMAP_MMC1_DEVID) {
if (cpu_is_omap34xx()) {
#ifdef CONFIG_MMC_OMAP3430
addr = (int *)&OMAP2_CONTROL_PBIAS_1;
*addr |= (1 << 2);
addr = (int *)&OMAP2_CONTROL_DEVCONF0;
*addr |= (1 << 24);
#endif
/* There is no 8-bit field in the structure yet */
if (minfo->wire4) {
if (cpu_is_omap3410()) {
mmc->caps = MMC_CAP_4_BIT_DATA;
}
else
mmc->caps = MMC_CAP_8_BIT_DATA;
}
OMAP_HSMMC_WRITE(host->base, HCTL,
OMAP_HSMMC_READ(host->base,
HCTL) | SDVS30);
} else if (cpu_is_omap2430()) {
/* OMAP2430 MMC1 on ES1.0 and ES2.1 can support 3V */
if (omap2_cpu_rev() == 0 || omap2_cpu_rev() > 1) {
OMAP_HSMMC_WRITE(host->base, HCTL,
OMAP_HSMMC_READ(host->base,
HCTL) | SDVS30);
} else {
/* OMAP2430 MMC1 ES2.0 - 1.8V only */
OMAP_HSMMC_WRITE(host->base, HCTL,
OMAP_HSMMC_READ(host->base,
HCTL) | SDVS18);
}
}
} else if (host->id == OMAP_MMC2_DEVID) {
if (cpu_is_omap34xx()) {
#ifdef CONFIG_MMC_OMAP3430
addr = (int *)&OMAP2_CONTROL_DEVCONF1;
*addr |= (1 << 6);
#endif
if (minfo->wire4)
mmc->caps = MMC_CAP_4_BIT_DATA;
}
OMAP_HSMMC_WRITE(host->base, HCTL,
OMAP_HSMMC_READ(host->base,
HCTL) | SDVS18);
}
/* Use scatterlist DMA to reduce per-transfer costs.
* NOTE max_seg_size assumption that small blocks aren't
* normally used (except e.g. for reading SD registers).
*/
mmc->max_phys_segs = 128; /* Largest sized scatter list
* the driver could handle. Since this is
* managed by us in software, we can tune
* this value */
mmc->max_hw_segs = 128; /* Largest number of address/length
* pairs the host adapter can actually
* give at once to the device. This value
* should be kept same as scatter list */
mmc->max_blk_size = 512; /* Block Length at max can be 1024 */
mmc->max_blk_count = 0xFFFF; /* No. of Blocks is 16 bits */
mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
mmc->max_seg_size = mmc->max_req_size;
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
OMAP_HSMMC_WRITE(host->base, CAPA,OMAP_HSMMC_READ(host->base,
CAPA) | VS30 | VS18);
mmc->caps |= MMC_CAP_MULTIWRITE | MMC_CAP_BYTEBLOCK | MMC_CAP_MMC_HIGHSPEED |
MMC_CAP_SD_HIGHSPEED;
/* Set the controller to AUTO IDLE mode */
OMAP_HSMMC_WRITE(host->base, SYSCONFIG,
OMAP_HSMMC_READ(host->base, SYSCONFIG) | AUTOIDLE);
/* Set SD bus power bit */
OMAP_HSMMC_WRITE(host->base, HCTL,
OMAP_HSMMC_READ(host->base, HCTL) | SDBP);
if (machine_is_omap_2430sdp() ||
machine_is_omap_3430sdp() ||
machine_is_omap_3430labrador() ||
machine_is_omap3evm() || machine_is_omap3_beagle() ) {
/*
* Create sysfs entries for enabling/disabling hotplug
* support for MMC cards
*/
if (device_create_file(&pdev->dev,
&dev_attr_mmc_cover_switch) < 0) {
dev_dbg(mmc_dev(host->mmc),
"Unable to create sysfs"
"attribute for MMC1 cover switch\n");
}
if (device_create_file(&pdev->dev,
&dev_attr_mmc_card_detect) < 0) {
dev_dbg(mmc_dev(host->mmc),
"Unable to create sysfs"
"attribute for MMC1 card detect\n");
}
}
#ifdef CONFIG_OMAP_SDIO
if (device_create_file(&pdev->dev, &dev_attr_sdio_polling_switch) < 0) {
dev_dbg(mmc_dev(host->mmc),
"Unable to create sysfs"
"attribute for SDIO 1 polling switch\n");
}
#endif
/* Request IRQ for MMC operations */
ret = request_irq(host->irq, mmc_omap_irq, 0, pdev->name,
host);
if (ret) {
dev_dbg(mmc_dev(host->mmc), "Unable to grab HSMMC IRQ");
goto irq_err;
}
host->card_detect_irq = minfo->switch_pin;
if (minfo->switch_pin >= 0) {
if (machine_is_omap_2430sdp() ||
machine_is_omap_3430sdp() ||
machine_is_omap_3430labrador() ||
machine_is_omap3evm() || machine_is_omap3_beagle() ) {
host->card_detect_irq =
TWL4030_GPIO_IRQ_NO(minfo->switch_pin);
INIT_WORK(&host->mmc_carddetect_work, mmc_omap_detect);
if (setup_mmc_carddetect_irq(minfo->switch_pin)) {
free_irq(host->irq, host);
goto irq_err;
}
}
}
if (minfo->switch_pin >= 0) {
ret = request_irq(host->card_detect_irq,
mmc_omap_irq_cd, IRQF_DISABLED, pdev->name,host);
if (ret < 0) {
dev_dbg(mmc_dev(host->mmc),
"Unable to grab T2 GPIO IRQ");
free_irq(host->irq, host);
goto irq_err;
}
}
if (host->id == OMAP_MMC1_DEVID)
saved_host1 = host;
else
saved_host2 = host;
platform_set_drvdata(pdev, host);
mmc_clk_disable_aggressive(host);
mmc_add_host(mmc);
return 0;
clk_get_err:
dev_dbg(mmc_dev(host->mmc),
"Error getting clock for MMC\n");
if (host) {
mmc_free_host(mmc);
}
return ret;
mmc_alloc_err:
if (host)
mmc_free_host(mmc);
return ret;
irq_err:
mmc_clk_disable(host);
if (cpu_is_omap2430())
clk_disable(host->dbclk);
clk_put(host->fclk);
clk_put(host->iclk);
if (cpu_is_omap2430())
clk_put(host->dbclk);
if (host)
mmc_free_host(mmc);
return ret;
}
/*
* Routine implementing the driver remove method
*/
static int omap_mmc_remove(struct platform_device *pdev)
{
struct mmc_omap_host *host = platform_get_drvdata(pdev);
platform_set_drvdata(pdev, NULL);
if (host) {
free_irq(host->irq, host);
free_irq(host->card_detect_irq, host);
flush_scheduled_work();
/* Free the clks */
#ifndef AGGR_PM_CAP
mmc_clk_disable(host);
#endif /* #ifndef AGGR_PM_CAP */
if (cpu_is_omap2430())
clk_disable(host->dbclk);
clk_put(host->fclk);
clk_put(host->iclk);
if (cpu_is_omap2430())
clk_put(host->dbclk);
#ifdef CONFIG_OMAP_SDIO
device_remove_file(&pdev->dev,
&dev_attr_sdio_polling_switch);
#endif
mmc_free_host(host->mmc);
}
return 0;
}
#ifdef CONFIG_PM
/*
* Routine to suspend the MMC device
*/
static int omap_mmc_suspend(struct platform_device *pdev, pm_message_t state)
{
int ret = 0;
int status;
struct mmc_omap_host *host = platform_get_drvdata(pdev);
if (host && host->suspended)
return 0;
if (host) {
/* Notify the core to suspend the host */
ret = mmc_suspend_host(host->mmc, state);
if (ret == 0) {
host->suspended = 1;
/* Temporarily enabling the clocks for configuration */
mmc_clk_enable_aggressive(host);
if (machine_is_omap_2430sdp() ||
machine_is_omap_3430sdp() ||
machine_is_omap_3430labrador() ||
machine_is_omap3evm() || machine_is_omap3_beagle() ) {
disable_irq(host->card_detect_irq);
ret = mask_carddetect_int(host->id);
if (ret)
dev_dbg(mmc_dev(host->mmc),
"Unable to mask the card detect"
"interrupt in suspend\n");
}
if (cpu_is_omap34xx() || (cpu_is_omap2430()
&& omap2_cpu_rev() == 2)) {
if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) {
OMAP_HSMMC_WRITE(host->base,HCTL,
OMAP_HSMMC_READ(host->base,HCTL)
& SDVSCLR);
OMAP_HSMMC_WRITE(host->base,HCTL,
OMAP_HSMMC_READ(host->base,HCTL)
|SDVS30);
OMAP_HSMMC_WRITE(host->base,HCTL,
OMAP_HSMMC_READ(host->base,HCTL)
| SDBP);
}
}
/* Disable Interrupts */
OMAP_HSMMC_WRITE(host->base, ISE, INT_CLEAR);
OMAP_HSMMC_WRITE(host->base, IE, INT_CLEAR);
/* Clearing the STAT register*/
status = OMAP_HSMMC_READ(host->base, STAT);
OMAP_HSMMC_WRITE(host->base, STAT, status);
/* disable clks for MMC1 */
mmc_clk_disable(host);
if (cpu_is_omap2430())
clk_disable(host->dbclk);
if (cpu_is_omap2430()) {
if (host->id == OMAP_MMC1_DEVID) {
if (omap2_cpu_rev() == 2) {
omap_writel(omap_readl(OMAP2_CONTROL_DEVCONF1)
& ~MMC1_ACTIVE_OVERWRITE,
OMAP2_CONTROL_DEVCONF1);
}
}
}
ret = mmc_omap_power(host,0);
if (ret != 0)
dev_dbg(mmc_dev(host->mmc),
"Unable to disable power to MMC1\n");
host->initstream = 0;
}
}
return ret;
}
/*
* Routine to resume the MMC device
*/
static int omap_mmc_resume(struct platform_device *pdev)
{
int ret = 0;
struct mmc_omap_host *host = platform_get_drvdata(pdev);
if (host && !host->suspended)
return 0;
if (host) {
if (cpu_is_omap2430()) {
if (host->id == OMAP_MMC1_DEVID) {
if (omap2_cpu_rev() == 2)
omap_writel(omap_readl(OMAP2_CONTROL_DEVCONF1)
| MMC1_ACTIVE_OVERWRITE,
OMAP2_CONTROL_DEVCONF1);
}
}
ret = mmc_omap_power(host,1);
if (ret != 0) {
dev_dbg(mmc_dev(host->mmc),
"Unable to enable power to MMC1\n");
return ret;
}
#ifndef AGGR_PM_CAP
mmc_clk_enable(host);
#endif /* #ifndef AGGR_PM_CAP */
if (cpu_is_omap2430()) {
if (clk_enable(host->dbclk) != 0)
dev_dbg(mmc_dev(host->mmc),
"Unable to enable debounce"
"clock for MMC1\n");
}
if (machine_is_omap_2430sdp() ||
machine_is_omap_3430sdp() ||
machine_is_omap_3430labrador() ||
machine_is_omap3evm() || machine_is_omap3_beagle() ) {
enable_irq(host->card_detect_irq);
ret = unmask_carddetect_int(host->id);
if (ret)
dev_dbg(mmc_dev(host->mmc),
"Unable to unmask the card"
"detect interrupt\n");
}
/* Notify the core to resume the host */
ret = mmc_resume_host(host->mmc);
if (ret == 0)
host->suspended = 0;
}
return ret;
#ifndef AGGR_PM_CAP
clk_en_err:
dev_dbg(mmc_dev(host->mmc),
"Unable to enable MMC clocks during resume\n");
return -1;
#endif
}
#else
#define omap_mmc_suspend NULL
#define omap_mmc_resume NULL
#endif
#ifdef CONFIG_DPM
static int
omap_mmc_pre_scale(int slot, struct notifier_block *op, unsigned long level,
void *ptr)
{
int i = 0, timeout = 20;
struct mmc_omap_host *host = (slot == MMC1) ? saved_host1 : saved_host2;
switch (level) {
case SCALE_PRECHANGE:
/* If DMA is active then enable the stop at block gap event */
if (host->dma_ch) {
OMAP_HSMMC_WRITE(host->base, HCTL,
OMAP_HSMMC_READ(host->base,HCTL) | SBGR);
while (((OMAP_HSMMC_READ(host->base, STAT) & TC) != 0x2)
|| (i < timeout)) {
i++;
/*
* Wait for 5 Micro seconds before reading the
* Block gap status
*/
udelay(5);
}
host->dma_ch = -1;
}
break;
}
return 0;
}
static int
omap_mmc_post_scale(int slot, struct notifier_block *op, unsigned long level,
void *ptr)
{
struct mmc_omap_host *host = (slot == MMC1) ? saved_host1 : saved_host2;
switch (level) {
case SCALE_POSTCHANGE:
if (host->dma_ch == -1) {
/*
* Reset the stop at block gap event before re-starting the
* transmission
*/
OMAP_HSMMC_WRITE(host->base, HCTL,
OMAP_HSMMC_READ(host->base,HCTL) & ~(SBGR));
/* Restart the transmision from the previously left block */
OMAP_HSMMC_WRITE(host->base, HCTL,
OMAP_HSMMC_READ(host->base,HCTL) | CT);
/* 1ms delay reuired after re-starting transfer */
mdelay(1);
}
break;
}
return 0;
}
#if defined(CONFIG_OMAP2430_MMC1) || defined(CONFIG_OMAP3430_MMC1)
/*
* Prescale function for MMC1 controller
*/
static int
omap_mmc1_scale_prechange(struct notifier_block *op, unsigned long level,
void *ptr)
{
return omap_mmc_pre_scale(MMC1, op, level, ptr);
}
/*
* Post scale function for MMC1 controller
*/
static int
omap_mmc1_scale_postchange(struct notifier_block *op, unsigned long level,
void *ptr)
{
return omap_mmc_post_scale(MMC1, op, level, ptr);
}
static struct notifier_block omap_mmc1_pre_scale = {
.notifier_call = omap_mmc1_scale_prechange,
};
static struct notifier_block omap_mmc1_post_scale = {
.notifier_call = omap_mmc1_scale_postchange,
};
#endif
#if defined(CONFIG_OMAP2430_MMC2) || defined(CONFIG_OMAP3430_MMC2)
/*
* Prescale function for MMC2 controller
*/
static int
omap_mmc2_scale_prechange(struct notifier_block *op, unsigned long level,
void *ptr)
{
return omap_mmc_pre_scale(MMC2, op, level, ptr);
}
/*
* Post scale function for MMC2 controller
*/
static int
omap_mmc2_scale_postchange(struct notifier_block *op, unsigned long level,
void *ptr)
{
return omap_mmc_post_scale(MMC2, op, level, ptr);
}
static struct notifier_block omap_mmc2_pre_scale = {
.notifier_call = omap_mmc2_scale_prechange,
};
static struct notifier_block omap_mmc2_post_scale = {
.notifier_call = omap_mmc2_scale_postchange,
};
#endif
#endif
static struct platform_driver omap_mmc_driver = {
.probe = omap_mmc_probe,
.remove = omap_mmc_remove,
.suspend = omap_mmc_suspend,
.resume = omap_mmc_resume,
.driver = {
.name = "hsmmc-omap",
},
};
#ifdef CONFIG_OMAP_SDIO
/* API for Enable/Disable SDIO card interrupt */
int sdio_card_int_enable(int enable, int slot)
{
int intr_mask, intr_ena;
struct mmc_omap_host *host = (slot == MMC1) ? saved_host1 : saved_host2;
intr_ena = OMAP_HSMMC_READ(host->base, ISE);
intr_mask = OMAP_HSMMC_READ(host->base, IE);
host->sdio_card_intr = enable;
if (enable == SDIO_CARD_INT_DISABLE) {
intr_ena = intr_ena & ~(OMAP_HSMMC_CARD_INT);
intr_mask = intr_mask & ~(OMAP_HSMMC_CARD_INT);
} else {
intr_ena = intr_ena | OMAP_HSMMC_CARD_INT;
intr_mask = intr_mask | OMAP_HSMMC_CARD_INT;
}
OMAP_HSMMC_WRITE(host->base, ISE, intr_ena);
OMAP_HSMMC_WRITE(host->base, IE, intr_mask);
return 0;
}
EXPORT_SYMBOL(sdio_card_int_enable);
#endif
/*
* Driver init method
*/
static int __init omap_mmc_init(void)
{
/* Register the MMC driver */
if (platform_driver_register(&omap_mmc_driver)) {
printk(KERN_ERR ":failed to register MMC driver\n");
return -ENODEV;
}
#ifdef CONFIG_DPM
#if defined(CONFIG_OMAP2430_MMC1) || defined(CONFIG_OMAP3430_MMC1)
/* DPM scale registration for MMC1 controller */
dpm_register_scale(&omap_mmc1_pre_scale, SCALE_PRECHANGE);
dpm_register_scale(&omap_mmc1_post_scale, SCALE_POSTCHANGE);
#endif
#if defined(CONFIG_OMAP2430_MMC2) || defined(CONFIG_OMAP3430_MMC2)
/* DPM scale registration for MMC2 controller */
dpm_register_scale(&omap_mmc2_pre_scale, SCALE_PRECHANGE);
dpm_register_scale(&omap_mmc2_post_scale, SCALE_POSTCHANGE);
#endif
#endif
return 0;
}
/*
* Driver exit method
*/
static void __exit omap_mmc_cleanup(void)
{
/* Unregister MMC driver */
platform_driver_unregister(&omap_mmc_driver);
#ifdef CONFIG_DPM
#if defined(CONFIG_OMAP2430_MMC1) || defined(CONFIG_OMAP3430_MMC1)
/* Unregister DPM scale functions for MMC1 controller */
dpm_unregister_scale(&omap_mmc1_pre_scale, SCALE_PRECHANGE);
dpm_unregister_scale(&omap_mmc1_post_scale, SCALE_POSTCHANGE);
#endif
#if defined(CONFIG_OMAP2430_MMC2) || defined(CONFIG_OMAP3430_MMC2)
/* Unregister DPM scale functions for MMC2 controller */
dpm_unregister_scale(&omap_mmc2_pre_scale, SCALE_PRECHANGE);
dpm_unregister_scale(&omap_mmc2_post_scale, SCALE_POSTCHANGE);
#endif
#endif
}
module_init(omap_mmc_init);
module_exit(omap_mmc_cleanup);
MODULE_DESCRIPTION("OMAP 2430/3430 Multimedia Card driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Texas Instruments");
[-- Attachment #3: omap_hsmmc.h --]
[-- Type: text/x-chdr, Size: 3945 bytes --]
/*
* drivers/mmc/omap_hsmmc.h
*
* Header file for OMAP2430/3430 HSMMC controller.
*
* Copyright (C) 2007 Texas Instruments.
* Author: Texas Instruments
*
* 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.
*
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#ifndef DRIVERS_MEDIA_MMC_OMAP_H
#define DRIVERS_MEDIA_MMC_OMAP_H
#define RSP_TYPE(x) ((x) & ~(MMC_RSP_BUSY|MMC_RSP_OPCODE))
#define OMAP_MMC1_DEVID 1
#define OMAP_MMC2_DEVID 2
#define OMAP_MMC_DATADIR_NONE 0
#define OMAP_MMC_DATADIR_READ 1
#define OMAP_MMC_DATADIR_WRITE 2
#define MMC1_ACTIVE_OVERWRITE (1<<31)
#define OMAP_MMC_MASTER_CLOCK 96000000
#define OMAP_USE_DMA 1
#define MMC1 1
#define MMC2 2
/* HSMMC Host Controller Registers */
#define OMAP_HSMMC_SYSCONFIG 0x0010
#define OMAP_HSMMC_SYSSTATUS 0x0014
#define OMAP_HSMMC_CSRE 0x0024
#define OMAP_HSMMC_SYSTEST 0x0028
#define OMAP_HSMMC_CON 0x002C
#define OMAP_HSMMC_BLK 0x0104
#define OMAP_HSMMC_ARG 0x0108
#define OMAP_HSMMC_CMD 0x010C
#define OMAP_HSMMC_RSP10 0x0110
#define OMAP_HSMMC_RSP32 0x0114
#define OMAP_HSMMC_RSP54 0x0118
#define OMAP_HSMMC_RSP76 0x011C
#define OMAP_HSMMC_DATA 0x0120
#define OMAP_HSMMC_PSTATE 0x0124
#define OMAP_HSMMC_HCTL 0x0128
#define OMAP_HSMMC_SYSCTL 0x012C
#define OMAP_HSMMC_STAT 0x0130
#define OMAP_HSMMC_IE 0x0134
#define OMAP_HSMMC_ISE 0x0138
#define OMAP_HSMMC_AC12 0x013C
#define OMAP_HSMMC_CAPA 0x0140
#define OMAP_HSMMC_CUR_CAPA 0x0148
#define OMAP_HSMMC_REV 0x01FC
/* HSMMC controller bit definitions */
#define VS18 (1<<26)
#define VS30 (1<<25)
#define SDVS18 (0x5<<9)
#define SDVS30 (0x6<<9)
#define SDVSCLR 0xFFFFF1FF
#define SDVSDET 0x00000400
#define SIDLE_MODE (0x2<<3)
#define AUTOIDLE 0x1
#define SDBP (1<<8)
#define DTO 0xe
#define ICE 0x1
#define ICS 0x2
#define CEN (1<<2)
#define CLKD_MASK 0x0000FFC0
#define INT_EN_MASK 0x307F0033
#define SDIO_CARD_INT_EN_MASK 0x307F0133
#define INIT_STREAM (1<<1)
#define DP_SELECT (1<<21)
#define DDIR (1<<4)
#define DMA_EN 0x1
#define MSBS 1<<5
#define BCE 1<<1
#define EIGHT_BIT 1 << 5
#define FOUR_BIT 1 << 1
#define CC 0x1
#define TC 0x02
#define OD 0x1
#define BRW 0x400
#define BRR 0x800
#define BRE (1<<11)
#define BWE (1<<10)
#define SBGR (1<<16)
#define CT (1<<17)
#define OMAP_SDIO_READ (1<<31)
#define SDIO_BLKMODE (1<<27)
#define OMAP_HSMMC_ERR (1 << 15) /* Any error */
#define OMAP_HSMMC_CMD_TIMEOUT (1 << 16) /* Command response time-out */
#define OMAP_HSMMC_DATA_TIMEOUT (1 << 20) /* Data response time-out */
#define OMAP_HSMMC_CMD_CRC (1 << 17) /* Command CRC error */
#define OMAP_HSMMC_DATA_CRC (1 << 21) /* Date CRC error */
#define OMAP_HSMMC_CARD_ERR (1 << 28) /* Card ERR */
#define OMAP_HSMMC_CARD_INT (1 << 8) /* SDIO Card INT */
#define OMAP_HSMMC_STAT_CLEAR 0xFFFFFFFF
#define INIT_STREAM_CMD 0x00000000
#define INT_CLEAR 0x00000000
#define BLK_CLEAR 0x00000000
#define sdio_blkmode_regaddr1 0x2000
#define sdio_blkmode_regaddr2 0x2200
#define sdio_blkmode_mask 0x03F1FE00
#define sdio_function_mask 0x70000000
#define sdio_rw_function_mask 0x000E0000
#define IO_RW_DIRECT_MASK 0xF000FF00
#define IO_RW_DIRECT_ARG_MASK 0x80001A00
#define SDIO_CARD_INT_ENABLE 1
#define SDIO_CARD_INT_DISABLE 0
#define MMC_TIMEOUT_MS 20
#define NO_OF_MMC_HOSTS 2
/* MMC Host controller read/write API's */
#define OMAP_HSMMC_READ(base, reg) __raw_readl((base) + OMAP_HSMMC_##reg)
#define OMAP_HSMMC_WRITE(base, reg, val) __raw_writel((val), (base) + OMAP_HSMMC_##reg)
#ifdef CONFIG_OMAP34XX_OFFMODE
extern int context_restore_required(struct clk *clk);
extern void modify_timeout_value(struct clk *clk, u32 value);
#endif /* #ifdef CONFIG_OMAP34XX_OFFMODE */
#endif
next prev parent reply other threads:[~2009-10-18 16:45 UTC|newest]
Thread overview: 34+ messages / expand[flat|nested] mbox.gz Atom feed top
2009-10-15 20:30 MMC_CAP_SDIO_IRQ for omap 3430 John Rigby
2009-10-16 7:16 ` Dirk Behme
2009-10-16 13:43 ` John Rigby
2009-10-16 15:02 ` Dirk Behme
2009-10-16 15:59 ` John Rigby
2009-10-16 16:06 ` Dirk Behme
2009-10-16 18:51 ` John Rigby
2009-10-16 19:55 ` Dirk Behme
[not found] ` <4b73d43f0910161337k24908c7bjf5d84a90efb27bef@mail.gmail.com>
2009-10-16 20:39 ` John Rigby
2009-10-16 17:43 ` Madhusudhan Chikkature
2009-10-16 19:28 ` Dirk Behme
2009-10-16 21:14 ` Madhusudhan
2009-10-16 21:26 ` John Rigby
2009-10-17 6:30 ` Dirk Behme
2009-10-17 15:12 ` John Rigby
2009-10-17 17:36 ` Dirk Behme
2009-10-17 18:08 ` Dirk Behme
2009-10-18 16:44 ` Dirk Behme [this message]
2009-10-18 23:12 ` Woodruff, Richard
2009-10-19 0:17 ` John Rigby
[not found] ` <4b73d43f0910181724q11d40851wb2aed801d7ae85f6@mail.gmail.com>
2009-10-19 17:27 ` Madhusudhan
[not found] ` <005101ca50e1$11ef2770$544ff780@am.dhcp.ti.com>
2009-10-19 18:10 ` Dirk Behme
2009-10-19 18:16 ` Dirk Behme
2009-10-20 22:47 ` Madhusudhan
2009-10-20 22:59 ` John Rigby
2009-10-21 17:46 ` Dirk Behme
2009-10-21 17:44 ` Dirk Behme
2009-10-20 1:13 ` John Rigby
2009-10-20 2:53 ` Woodruff, Richard
[not found] ` <4b73d43f0910191439o30fd1de6odab8ccd5e5430760@mail.gmail.com>
2009-10-20 4:47 ` Dirk Behme
2009-10-20 18:45 ` John Rigby
2009-10-20 18:55 ` Steve Sakoman
2009-10-19 14:52 ` Dirk Behme
2009-10-19 15:29 ` Woodruff, Richard
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=4ADB4604.2090704@googlemail.com \
--to=dirk.behme@googlemail.com \
--cc=jcrigby@gmail.com \
--cc=linux-mmc@vger.kernel.org \
--cc=linux-omap@vger.kernel.org \
--cc=madhu.cr@ti.com \
--cc=sakoman@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox