|
@@ -29,6 +29,7 @@
|
|
#include <linux/timer.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of.h>
|
|
|
|
+#include <linux/of_irq.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/omap-dmaengine.h>
|
|
#include <linux/omap-dmaengine.h>
|
|
@@ -36,6 +37,7 @@
|
|
#include <linux/mmc/core.h>
|
|
#include <linux/mmc/core.h>
|
|
#include <linux/mmc/mmc.h>
|
|
#include <linux/mmc/mmc.h>
|
|
#include <linux/io.h>
|
|
#include <linux/io.h>
|
|
|
|
+#include <linux/irq.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
@@ -54,6 +56,7 @@
|
|
#define OMAP_HSMMC_RSP54 0x0118
|
|
#define OMAP_HSMMC_RSP54 0x0118
|
|
#define OMAP_HSMMC_RSP76 0x011C
|
|
#define OMAP_HSMMC_RSP76 0x011C
|
|
#define OMAP_HSMMC_DATA 0x0120
|
|
#define OMAP_HSMMC_DATA 0x0120
|
|
|
|
+#define OMAP_HSMMC_PSTATE 0x0124
|
|
#define OMAP_HSMMC_HCTL 0x0128
|
|
#define OMAP_HSMMC_HCTL 0x0128
|
|
#define OMAP_HSMMC_SYSCTL 0x012C
|
|
#define OMAP_HSMMC_SYSCTL 0x012C
|
|
#define OMAP_HSMMC_STAT 0x0130
|
|
#define OMAP_HSMMC_STAT 0x0130
|
|
@@ -91,7 +94,10 @@
|
|
#define BCE (1 << 1)
|
|
#define BCE (1 << 1)
|
|
#define FOUR_BIT (1 << 1)
|
|
#define FOUR_BIT (1 << 1)
|
|
#define HSPE (1 << 2)
|
|
#define HSPE (1 << 2)
|
|
|
|
+#define IWE (1 << 24)
|
|
#define DDR (1 << 19)
|
|
#define DDR (1 << 19)
|
|
|
|
+#define CLKEXTFREE (1 << 16)
|
|
|
|
+#define CTPL (1 << 11)
|
|
#define DW8 (1 << 5)
|
|
#define DW8 (1 << 5)
|
|
#define OD 0x1
|
|
#define OD 0x1
|
|
#define STAT_CLEAR 0xFFFFFFFF
|
|
#define STAT_CLEAR 0xFFFFFFFF
|
|
@@ -101,11 +107,15 @@
|
|
#define SRD (1 << 26)
|
|
#define SRD (1 << 26)
|
|
#define SOFTRESET (1 << 1)
|
|
#define SOFTRESET (1 << 1)
|
|
|
|
|
|
|
|
+/* PSTATE */
|
|
|
|
+#define DLEV_DAT(x) (1 << (20 + (x)))
|
|
|
|
+
|
|
/* Interrupt masks for IE and ISE register */
|
|
/* Interrupt masks for IE and ISE register */
|
|
#define CC_EN (1 << 0)
|
|
#define CC_EN (1 << 0)
|
|
#define TC_EN (1 << 1)
|
|
#define TC_EN (1 << 1)
|
|
#define BWR_EN (1 << 4)
|
|
#define BWR_EN (1 << 4)
|
|
#define BRR_EN (1 << 5)
|
|
#define BRR_EN (1 << 5)
|
|
|
|
+#define CIRQ_EN (1 << 8)
|
|
#define ERR_EN (1 << 15)
|
|
#define ERR_EN (1 << 15)
|
|
#define CTO_EN (1 << 16)
|
|
#define CTO_EN (1 << 16)
|
|
#define CCRC_EN (1 << 17)
|
|
#define CCRC_EN (1 << 17)
|
|
@@ -140,7 +150,6 @@
|
|
#define VDD_3V0 3000000 /* 300000 uV */
|
|
#define VDD_3V0 3000000 /* 300000 uV */
|
|
#define VDD_165_195 (ffs(MMC_VDD_165_195) - 1)
|
|
#define VDD_165_195 (ffs(MMC_VDD_165_195) - 1)
|
|
|
|
|
|
-#define AUTO_CMD23 (1 << 1) /* Auto CMD23 support */
|
|
|
|
/*
|
|
/*
|
|
* One controller can have multiple slots, like on some omap boards using
|
|
* One controller can have multiple slots, like on some omap boards using
|
|
* omap.c controller driver. Luckily this is not currently done on any known
|
|
* omap.c controller driver. Luckily this is not currently done on any known
|
|
@@ -194,6 +203,7 @@ struct omap_hsmmc_host {
|
|
u32 sysctl;
|
|
u32 sysctl;
|
|
u32 capa;
|
|
u32 capa;
|
|
int irq;
|
|
int irq;
|
|
|
|
+ int wake_irq;
|
|
int use_dma, dma_ch;
|
|
int use_dma, dma_ch;
|
|
struct dma_chan *tx_chan;
|
|
struct dma_chan *tx_chan;
|
|
struct dma_chan *rx_chan;
|
|
struct dma_chan *rx_chan;
|
|
@@ -206,6 +216,9 @@ struct omap_hsmmc_host {
|
|
int req_in_progress;
|
|
int req_in_progress;
|
|
unsigned long clk_rate;
|
|
unsigned long clk_rate;
|
|
unsigned int flags;
|
|
unsigned int flags;
|
|
|
|
+#define AUTO_CMD23 (1 << 0) /* Auto CMD23 support */
|
|
|
|
+#define HSMMC_SDIO_IRQ_ENABLED (1 << 1) /* SDIO irq enabled */
|
|
|
|
+#define HSMMC_WAKE_IRQ_ENABLED (1 << 2)
|
|
struct omap_hsmmc_next next_data;
|
|
struct omap_hsmmc_next next_data;
|
|
struct omap_mmc_platform_data *pdata;
|
|
struct omap_mmc_platform_data *pdata;
|
|
};
|
|
};
|
|
@@ -510,27 +523,40 @@ static void omap_hsmmc_stop_clock(struct omap_hsmmc_host *host)
|
|
static void omap_hsmmc_enable_irq(struct omap_hsmmc_host *host,
|
|
static void omap_hsmmc_enable_irq(struct omap_hsmmc_host *host,
|
|
struct mmc_command *cmd)
|
|
struct mmc_command *cmd)
|
|
{
|
|
{
|
|
- unsigned int irq_mask;
|
|
|
|
|
|
+ u32 irq_mask = INT_EN_MASK;
|
|
|
|
+ unsigned long flags;
|
|
|
|
|
|
if (host->use_dma)
|
|
if (host->use_dma)
|
|
- irq_mask = INT_EN_MASK & ~(BRR_EN | BWR_EN);
|
|
|
|
- else
|
|
|
|
- irq_mask = INT_EN_MASK;
|
|
|
|
|
|
+ irq_mask &= ~(BRR_EN | BWR_EN);
|
|
|
|
|
|
/* Disable timeout for erases */
|
|
/* Disable timeout for erases */
|
|
if (cmd->opcode == MMC_ERASE)
|
|
if (cmd->opcode == MMC_ERASE)
|
|
irq_mask &= ~DTO_EN;
|
|
irq_mask &= ~DTO_EN;
|
|
|
|
|
|
|
|
+ spin_lock_irqsave(&host->irq_lock, flags);
|
|
OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
|
|
OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
|
|
OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
|
|
OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
|
|
|
|
+
|
|
|
|
+ /* latch pending CIRQ, but don't signal MMC core */
|
|
|
|
+ if (host->flags & HSMMC_SDIO_IRQ_ENABLED)
|
|
|
|
+ irq_mask |= CIRQ_EN;
|
|
OMAP_HSMMC_WRITE(host->base, IE, irq_mask);
|
|
OMAP_HSMMC_WRITE(host->base, IE, irq_mask);
|
|
|
|
+ spin_unlock_irqrestore(&host->irq_lock, flags);
|
|
}
|
|
}
|
|
|
|
|
|
static void omap_hsmmc_disable_irq(struct omap_hsmmc_host *host)
|
|
static void omap_hsmmc_disable_irq(struct omap_hsmmc_host *host)
|
|
{
|
|
{
|
|
- OMAP_HSMMC_WRITE(host->base, ISE, 0);
|
|
|
|
- OMAP_HSMMC_WRITE(host->base, IE, 0);
|
|
|
|
|
|
+ u32 irq_mask = 0;
|
|
|
|
+ unsigned long flags;
|
|
|
|
+
|
|
|
|
+ spin_lock_irqsave(&host->irq_lock, flags);
|
|
|
|
+ /* no transfer running but need to keep cirq if enabled */
|
|
|
|
+ if (host->flags & HSMMC_SDIO_IRQ_ENABLED)
|
|
|
|
+ irq_mask |= CIRQ_EN;
|
|
|
|
+ OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
|
|
|
|
+ OMAP_HSMMC_WRITE(host->base, IE, irq_mask);
|
|
OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
|
|
OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
|
|
|
|
+ spin_unlock_irqrestore(&host->irq_lock, flags);
|
|
}
|
|
}
|
|
|
|
|
|
/* Calculate divisor for the given clock frequency */
|
|
/* Calculate divisor for the given clock frequency */
|
|
@@ -667,6 +693,9 @@ static int omap_hsmmc_context_restore(struct omap_hsmmc_host *host)
|
|
capa = VS18;
|
|
capa = VS18;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ if (host->mmc->caps & MMC_CAP_SDIO_IRQ)
|
|
|
|
+ hctl |= IWE;
|
|
|
|
+
|
|
OMAP_HSMMC_WRITE(host->base, HCTL,
|
|
OMAP_HSMMC_WRITE(host->base, HCTL,
|
|
OMAP_HSMMC_READ(host->base, HCTL) | hctl);
|
|
OMAP_HSMMC_READ(host->base, HCTL) | hctl);
|
|
|
|
|
|
@@ -681,7 +710,9 @@ static int omap_hsmmc_context_restore(struct omap_hsmmc_host *host)
|
|
&& time_before(jiffies, timeout))
|
|
&& time_before(jiffies, timeout))
|
|
;
|
|
;
|
|
|
|
|
|
- omap_hsmmc_disable_irq(host);
|
|
|
|
|
|
+ OMAP_HSMMC_WRITE(host->base, ISE, 0);
|
|
|
|
+ OMAP_HSMMC_WRITE(host->base, IE, 0);
|
|
|
|
+ OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
|
|
|
|
|
|
/* Do not initialize card-specific things if the power is off */
|
|
/* Do not initialize card-specific things if the power is off */
|
|
if (host->power_mode == MMC_POWER_OFF)
|
|
if (host->power_mode == MMC_POWER_OFF)
|
|
@@ -1118,8 +1149,12 @@ static irqreturn_t omap_hsmmc_irq(int irq, void *dev_id)
|
|
int status;
|
|
int status;
|
|
|
|
|
|
status = OMAP_HSMMC_READ(host->base, STAT);
|
|
status = OMAP_HSMMC_READ(host->base, STAT);
|
|
- while (status & INT_EN_MASK && host->req_in_progress) {
|
|
|
|
- omap_hsmmc_do_irq(host, status);
|
|
|
|
|
|
+ while (status & (INT_EN_MASK | CIRQ_EN)) {
|
|
|
|
+ if (host->req_in_progress)
|
|
|
|
+ omap_hsmmc_do_irq(host, status);
|
|
|
|
+
|
|
|
|
+ if (status & CIRQ_EN)
|
|
|
|
+ mmc_signal_sdio_irq(host->mmc);
|
|
|
|
|
|
/* Flush posted write */
|
|
/* Flush posted write */
|
|
status = OMAP_HSMMC_READ(host->base, STAT);
|
|
status = OMAP_HSMMC_READ(host->base, STAT);
|
|
@@ -1128,6 +1163,22 @@ static irqreturn_t omap_hsmmc_irq(int irq, void *dev_id)
|
|
return IRQ_HANDLED;
|
|
return IRQ_HANDLED;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static irqreturn_t omap_hsmmc_wake_irq(int irq, void *dev_id)
|
|
|
|
+{
|
|
|
|
+ struct omap_hsmmc_host *host = dev_id;
|
|
|
|
+
|
|
|
|
+ /* cirq is level triggered, disable to avoid infinite loop */
|
|
|
|
+ spin_lock(&host->irq_lock);
|
|
|
|
+ if (host->flags & HSMMC_WAKE_IRQ_ENABLED) {
|
|
|
|
+ disable_irq_nosync(host->wake_irq);
|
|
|
|
+ host->flags &= ~HSMMC_WAKE_IRQ_ENABLED;
|
|
|
|
+ }
|
|
|
|
+ spin_unlock(&host->irq_lock);
|
|
|
|
+ pm_request_resume(host->dev); /* no use counter */
|
|
|
|
+
|
|
|
|
+ return IRQ_HANDLED;
|
|
|
|
+}
|
|
|
|
+
|
|
static void set_sd_bus_power(struct omap_hsmmc_host *host)
|
|
static void set_sd_bus_power(struct omap_hsmmc_host *host)
|
|
{
|
|
{
|
|
unsigned long i;
|
|
unsigned long i;
|
|
@@ -1639,6 +1690,103 @@ static void omap_hsmmc_init_card(struct mmc_host *mmc, struct mmc_card *card)
|
|
mmc_slot(host).init_card(card);
|
|
mmc_slot(host).init_card(card);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static void omap_hsmmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
|
|
|
|
+{
|
|
|
|
+ struct omap_hsmmc_host *host = mmc_priv(mmc);
|
|
|
|
+ u32 irq_mask, con;
|
|
|
|
+ unsigned long flags;
|
|
|
|
+
|
|
|
|
+ spin_lock_irqsave(&host->irq_lock, flags);
|
|
|
|
+
|
|
|
|
+ con = OMAP_HSMMC_READ(host->base, CON);
|
|
|
|
+ irq_mask = OMAP_HSMMC_READ(host->base, ISE);
|
|
|
|
+ if (enable) {
|
|
|
|
+ host->flags |= HSMMC_SDIO_IRQ_ENABLED;
|
|
|
|
+ irq_mask |= CIRQ_EN;
|
|
|
|
+ con |= CTPL | CLKEXTFREE;
|
|
|
|
+ } else {
|
|
|
|
+ host->flags &= ~HSMMC_SDIO_IRQ_ENABLED;
|
|
|
|
+ irq_mask &= ~CIRQ_EN;
|
|
|
|
+ con &= ~(CTPL | CLKEXTFREE);
|
|
|
|
+ }
|
|
|
|
+ OMAP_HSMMC_WRITE(host->base, CON, con);
|
|
|
|
+ OMAP_HSMMC_WRITE(host->base, IE, irq_mask);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * if enable, piggy back detection on current request
|
|
|
|
+ * but always disable immediately
|
|
|
|
+ */
|
|
|
|
+ if (!host->req_in_progress || !enable)
|
|
|
|
+ OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
|
|
|
|
+
|
|
|
|
+ /* flush posted write */
|
|
|
|
+ OMAP_HSMMC_READ(host->base, IE);
|
|
|
|
+
|
|
|
|
+ spin_unlock_irqrestore(&host->irq_lock, flags);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int omap_hsmmc_configure_wake_irq(struct omap_hsmmc_host *host)
|
|
|
|
+{
|
|
|
|
+ struct mmc_host *mmc = host->mmc;
|
|
|
|
+ int ret;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * For omaps with wake-up path, wakeirq will be irq from pinctrl and
|
|
|
|
+ * for other omaps, wakeirq will be from GPIO (dat line remuxed to
|
|
|
|
+ * gpio). wakeirq is needed to detect sdio irq in runtime suspend state
|
|
|
|
+ * with functional clock disabled.
|
|
|
|
+ */
|
|
|
|
+ if (!host->dev->of_node || !host->wake_irq)
|
|
|
|
+ return -ENODEV;
|
|
|
|
+
|
|
|
|
+ /* Prevent auto-enabling of IRQ */
|
|
|
|
+ irq_set_status_flags(host->wake_irq, IRQ_NOAUTOEN);
|
|
|
|
+ ret = devm_request_irq(host->dev, host->wake_irq, omap_hsmmc_wake_irq,
|
|
|
|
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
|
|
|
|
+ mmc_hostname(mmc), host);
|
|
|
|
+ if (ret) {
|
|
|
|
+ dev_err(mmc_dev(host->mmc), "Unable to request wake IRQ\n");
|
|
|
|
+ goto err;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Some omaps don't have wake-up path from deeper idle states
|
|
|
|
+ * and need to remux SDIO DAT1 to GPIO for wake-up from idle.
|
|
|
|
+ */
|
|
|
|
+ if (host->pdata->controller_flags & OMAP_HSMMC_SWAKEUP_MISSING) {
|
|
|
|
+ struct pinctrl *p = devm_pinctrl_get(host->dev);
|
|
|
|
+ if (!p) {
|
|
|
|
+ ret = -ENODEV;
|
|
|
|
+ goto err_free_irq;
|
|
|
|
+ }
|
|
|
|
+ if (IS_ERR(pinctrl_lookup_state(p, PINCTRL_STATE_DEFAULT))) {
|
|
|
|
+ dev_info(host->dev, "missing default pinctrl state\n");
|
|
|
|
+ devm_pinctrl_put(p);
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ goto err_free_irq;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (IS_ERR(pinctrl_lookup_state(p, PINCTRL_STATE_IDLE))) {
|
|
|
|
+ dev_info(host->dev, "missing idle pinctrl state\n");
|
|
|
|
+ devm_pinctrl_put(p);
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ goto err_free_irq;
|
|
|
|
+ }
|
|
|
|
+ devm_pinctrl_put(p);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ OMAP_HSMMC_WRITE(host->base, HCTL,
|
|
|
|
+ OMAP_HSMMC_READ(host->base, HCTL) | IWE);
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+err_free_irq:
|
|
|
|
+ devm_free_irq(host->dev, host->wake_irq, host);
|
|
|
|
+err:
|
|
|
|
+ dev_warn(host->dev, "no SDIO IRQ support, falling back to polling\n");
|
|
|
|
+ host->wake_irq = 0;
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
static void omap_hsmmc_conf_bus_power(struct omap_hsmmc_host *host)
|
|
static void omap_hsmmc_conf_bus_power(struct omap_hsmmc_host *host)
|
|
{
|
|
{
|
|
u32 hctl, capa, value;
|
|
u32 hctl, capa, value;
|
|
@@ -1691,7 +1839,7 @@ static const struct mmc_host_ops omap_hsmmc_ops = {
|
|
.get_cd = omap_hsmmc_get_cd,
|
|
.get_cd = omap_hsmmc_get_cd,
|
|
.get_ro = omap_hsmmc_get_ro,
|
|
.get_ro = omap_hsmmc_get_ro,
|
|
.init_card = omap_hsmmc_init_card,
|
|
.init_card = omap_hsmmc_init_card,
|
|
- /* NYET -- enable_sdio_irq */
|
|
|
|
|
|
+ .enable_sdio_irq = omap_hsmmc_enable_sdio_irq,
|
|
};
|
|
};
|
|
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
#ifdef CONFIG_DEBUG_FS
|
|
@@ -1701,13 +1849,23 @@ static int omap_hsmmc_regs_show(struct seq_file *s, void *data)
|
|
struct mmc_host *mmc = s->private;
|
|
struct mmc_host *mmc = s->private;
|
|
struct omap_hsmmc_host *host = mmc_priv(mmc);
|
|
struct omap_hsmmc_host *host = mmc_priv(mmc);
|
|
|
|
|
|
- seq_printf(s, "mmc%d:\n ctx_loss:\t%d\n\nregs:\n",
|
|
|
|
- mmc->index, host->context_loss);
|
|
|
|
|
|
+ seq_printf(s, "mmc%d:\n", mmc->index);
|
|
|
|
+ seq_printf(s, "sdio irq mode\t%s\n",
|
|
|
|
+ (mmc->caps & MMC_CAP_SDIO_IRQ) ? "interrupt" : "polling");
|
|
|
|
|
|
- pm_runtime_get_sync(host->dev);
|
|
|
|
|
|
+ if (mmc->caps & MMC_CAP_SDIO_IRQ) {
|
|
|
|
+ seq_printf(s, "sdio irq \t%s\n",
|
|
|
|
+ (host->flags & HSMMC_SDIO_IRQ_ENABLED) ? "enabled"
|
|
|
|
+ : "disabled");
|
|
|
|
+ }
|
|
|
|
+ seq_printf(s, "ctx_loss:\t%d\n", host->context_loss);
|
|
|
|
|
|
|
|
+ pm_runtime_get_sync(host->dev);
|
|
|
|
+ seq_puts(s, "\nregs:\n");
|
|
seq_printf(s, "CON:\t\t0x%08x\n",
|
|
seq_printf(s, "CON:\t\t0x%08x\n",
|
|
OMAP_HSMMC_READ(host->base, CON));
|
|
OMAP_HSMMC_READ(host->base, CON));
|
|
|
|
+ seq_printf(s, "PSTATE:\t\t0x%08x\n",
|
|
|
|
+ OMAP_HSMMC_READ(host->base, PSTATE));
|
|
seq_printf(s, "HCTL:\t\t0x%08x\n",
|
|
seq_printf(s, "HCTL:\t\t0x%08x\n",
|
|
OMAP_HSMMC_READ(host->base, HCTL));
|
|
OMAP_HSMMC_READ(host->base, HCTL));
|
|
seq_printf(s, "SYSCTL:\t\t0x%08x\n",
|
|
seq_printf(s, "SYSCTL:\t\t0x%08x\n",
|
|
@@ -1761,6 +1919,10 @@ static const struct omap_mmc_of_data omap3_pre_es3_mmc_of_data = {
|
|
static const struct omap_mmc_of_data omap4_mmc_of_data = {
|
|
static const struct omap_mmc_of_data omap4_mmc_of_data = {
|
|
.reg_offset = 0x100,
|
|
.reg_offset = 0x100,
|
|
};
|
|
};
|
|
|
|
+static const struct omap_mmc_of_data am33xx_mmc_of_data = {
|
|
|
|
+ .reg_offset = 0x100,
|
|
|
|
+ .controller_flags = OMAP_HSMMC_SWAKEUP_MISSING,
|
|
|
|
+};
|
|
|
|
|
|
static const struct of_device_id omap_mmc_of_match[] = {
|
|
static const struct of_device_id omap_mmc_of_match[] = {
|
|
{
|
|
{
|
|
@@ -1777,6 +1939,10 @@ static const struct of_device_id omap_mmc_of_match[] = {
|
|
.compatible = "ti,omap4-hsmmc",
|
|
.compatible = "ti,omap4-hsmmc",
|
|
.data = &omap4_mmc_of_data,
|
|
.data = &omap4_mmc_of_data,
|
|
},
|
|
},
|
|
|
|
+ {
|
|
|
|
+ .compatible = "ti,am33xx-hsmmc",
|
|
|
|
+ .data = &am33xx_mmc_of_data,
|
|
|
|
+ },
|
|
{},
|
|
{},
|
|
};
|
|
};
|
|
MODULE_DEVICE_TABLE(of, omap_mmc_of_match);
|
|
MODULE_DEVICE_TABLE(of, omap_mmc_of_match);
|
|
@@ -1850,7 +2016,6 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
|
|
const struct of_device_id *match;
|
|
const struct of_device_id *match;
|
|
dma_cap_mask_t mask;
|
|
dma_cap_mask_t mask;
|
|
unsigned tx_req, rx_req;
|
|
unsigned tx_req, rx_req;
|
|
- struct pinctrl *pinctrl;
|
|
|
|
const struct omap_mmc_of_data *data;
|
|
const struct omap_mmc_of_data *data;
|
|
void __iomem *base;
|
|
void __iomem *base;
|
|
|
|
|
|
@@ -1913,6 +2078,9 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
|
|
|
|
|
|
platform_set_drvdata(pdev, host);
|
|
platform_set_drvdata(pdev, host);
|
|
|
|
|
|
|
|
+ if (pdev->dev.of_node)
|
|
|
|
+ host->wake_irq = irq_of_parse_and_map(pdev->dev.of_node, 1);
|
|
|
|
+
|
|
mmc->ops = &omap_hsmmc_ops;
|
|
mmc->ops = &omap_hsmmc_ops;
|
|
|
|
|
|
mmc->f_min = OMAP_MMC_MIN_CLOCK;
|
|
mmc->f_min = OMAP_MMC_MIN_CLOCK;
|
|
@@ -2061,10 +2229,17 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
|
|
|
|
|
|
omap_hsmmc_disable_irq(host);
|
|
omap_hsmmc_disable_irq(host);
|
|
|
|
|
|
- pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
|
|
|
|
- if (IS_ERR(pinctrl))
|
|
|
|
- dev_warn(&pdev->dev,
|
|
|
|
- "pins are not configured from the driver\n");
|
|
|
|
|
|
+ /*
|
|
|
|
+ * For now, only support SDIO interrupt if we have a separate
|
|
|
|
+ * wake-up interrupt configured from device tree. This is because
|
|
|
|
+ * the wake-up interrupt is needed for idle state and some
|
|
|
|
+ * platforms need special quirks. And we don't want to add new
|
|
|
|
+ * legacy mux platform init code callbacks any longer as we
|
|
|
|
+ * are moving to DT based booting anyways.
|
|
|
|
+ */
|
|
|
|
+ ret = omap_hsmmc_configure_wake_irq(host);
|
|
|
|
+ if (!ret)
|
|
|
|
+ mmc->caps |= MMC_CAP_SDIO_IRQ;
|
|
|
|
|
|
omap_hsmmc_protect_card(host);
|
|
omap_hsmmc_protect_card(host);
|
|
|
|
|
|
@@ -2170,11 +2345,18 @@ static int omap_hsmmc_suspend(struct device *dev)
|
|
pm_runtime_get_sync(host->dev);
|
|
pm_runtime_get_sync(host->dev);
|
|
|
|
|
|
if (!(host->mmc->pm_flags & MMC_PM_KEEP_POWER)) {
|
|
if (!(host->mmc->pm_flags & MMC_PM_KEEP_POWER)) {
|
|
- omap_hsmmc_disable_irq(host);
|
|
|
|
|
|
+ OMAP_HSMMC_WRITE(host->base, ISE, 0);
|
|
|
|
+ OMAP_HSMMC_WRITE(host->base, IE, 0);
|
|
|
|
+ OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
|
|
OMAP_HSMMC_WRITE(host->base, HCTL,
|
|
OMAP_HSMMC_WRITE(host->base, HCTL,
|
|
OMAP_HSMMC_READ(host->base, HCTL) & ~SDBP);
|
|
OMAP_HSMMC_READ(host->base, HCTL) & ~SDBP);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /* do not wake up due to sdio irq */
|
|
|
|
+ if ((host->mmc->caps & MMC_CAP_SDIO_IRQ) &&
|
|
|
|
+ !(host->mmc->pm_flags & MMC_PM_WAKE_SDIO_IRQ))
|
|
|
|
+ disable_irq(host->wake_irq);
|
|
|
|
+
|
|
if (host->dbclk)
|
|
if (host->dbclk)
|
|
clk_disable_unprepare(host->dbclk);
|
|
clk_disable_unprepare(host->dbclk);
|
|
|
|
|
|
@@ -2200,6 +2382,10 @@ static int omap_hsmmc_resume(struct device *dev)
|
|
|
|
|
|
omap_hsmmc_protect_card(host);
|
|
omap_hsmmc_protect_card(host);
|
|
|
|
|
|
|
|
+ if ((host->mmc->caps & MMC_CAP_SDIO_IRQ) &&
|
|
|
|
+ !(host->mmc->pm_flags & MMC_PM_WAKE_SDIO_IRQ))
|
|
|
|
+ enable_irq(host->wake_irq);
|
|
|
|
+
|
|
pm_runtime_mark_last_busy(host->dev);
|
|
pm_runtime_mark_last_busy(host->dev);
|
|
pm_runtime_put_autosuspend(host->dev);
|
|
pm_runtime_put_autosuspend(host->dev);
|
|
return 0;
|
|
return 0;
|
|
@@ -2215,22 +2401,77 @@ static int omap_hsmmc_resume(struct device *dev)
|
|
static int omap_hsmmc_runtime_suspend(struct device *dev)
|
|
static int omap_hsmmc_runtime_suspend(struct device *dev)
|
|
{
|
|
{
|
|
struct omap_hsmmc_host *host;
|
|
struct omap_hsmmc_host *host;
|
|
|
|
+ unsigned long flags;
|
|
|
|
+ int ret = 0;
|
|
|
|
|
|
host = platform_get_drvdata(to_platform_device(dev));
|
|
host = platform_get_drvdata(to_platform_device(dev));
|
|
omap_hsmmc_context_save(host);
|
|
omap_hsmmc_context_save(host);
|
|
dev_dbg(dev, "disabled\n");
|
|
dev_dbg(dev, "disabled\n");
|
|
|
|
|
|
- return 0;
|
|
|
|
|
|
+ spin_lock_irqsave(&host->irq_lock, flags);
|
|
|
|
+ if ((host->mmc->caps & MMC_CAP_SDIO_IRQ) &&
|
|
|
|
+ (host->flags & HSMMC_SDIO_IRQ_ENABLED)) {
|
|
|
|
+ /* disable sdio irq handling to prevent race */
|
|
|
|
+ OMAP_HSMMC_WRITE(host->base, ISE, 0);
|
|
|
|
+ OMAP_HSMMC_WRITE(host->base, IE, 0);
|
|
|
|
+
|
|
|
|
+ if (!(OMAP_HSMMC_READ(host->base, PSTATE) & DLEV_DAT(1))) {
|
|
|
|
+ /*
|
|
|
|
+ * dat1 line low, pending sdio irq
|
|
|
|
+ * race condition: possible irq handler running on
|
|
|
|
+ * multi-core, abort
|
|
|
|
+ */
|
|
|
|
+ dev_dbg(dev, "pending sdio irq, abort suspend\n");
|
|
|
|
+ OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
|
|
|
|
+ OMAP_HSMMC_WRITE(host->base, ISE, CIRQ_EN);
|
|
|
|
+ OMAP_HSMMC_WRITE(host->base, IE, CIRQ_EN);
|
|
|
|
+ pm_runtime_mark_last_busy(dev);
|
|
|
|
+ ret = -EBUSY;
|
|
|
|
+ goto abort;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ pinctrl_pm_select_idle_state(dev);
|
|
|
|
+
|
|
|
|
+ WARN_ON(host->flags & HSMMC_WAKE_IRQ_ENABLED);
|
|
|
|
+ enable_irq(host->wake_irq);
|
|
|
|
+ host->flags |= HSMMC_WAKE_IRQ_ENABLED;
|
|
|
|
+ } else {
|
|
|
|
+ pinctrl_pm_select_idle_state(dev);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+abort:
|
|
|
|
+ spin_unlock_irqrestore(&host->irq_lock, flags);
|
|
|
|
+ return ret;
|
|
}
|
|
}
|
|
|
|
|
|
static int omap_hsmmc_runtime_resume(struct device *dev)
|
|
static int omap_hsmmc_runtime_resume(struct device *dev)
|
|
{
|
|
{
|
|
struct omap_hsmmc_host *host;
|
|
struct omap_hsmmc_host *host;
|
|
|
|
+ unsigned long flags;
|
|
|
|
|
|
host = platform_get_drvdata(to_platform_device(dev));
|
|
host = platform_get_drvdata(to_platform_device(dev));
|
|
omap_hsmmc_context_restore(host);
|
|
omap_hsmmc_context_restore(host);
|
|
dev_dbg(dev, "enabled\n");
|
|
dev_dbg(dev, "enabled\n");
|
|
|
|
|
|
|
|
+ spin_lock_irqsave(&host->irq_lock, flags);
|
|
|
|
+ if ((host->mmc->caps & MMC_CAP_SDIO_IRQ) &&
|
|
|
|
+ (host->flags & HSMMC_SDIO_IRQ_ENABLED)) {
|
|
|
|
+ /* sdio irq flag can't change while in runtime suspend */
|
|
|
|
+ if (host->flags & HSMMC_WAKE_IRQ_ENABLED) {
|
|
|
|
+ disable_irq_nosync(host->wake_irq);
|
|
|
|
+ host->flags &= ~HSMMC_WAKE_IRQ_ENABLED;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ pinctrl_pm_select_default_state(host->dev);
|
|
|
|
+
|
|
|
|
+ /* irq lost, if pinmux incorrect */
|
|
|
|
+ OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
|
|
|
|
+ OMAP_HSMMC_WRITE(host->base, ISE, CIRQ_EN);
|
|
|
|
+ OMAP_HSMMC_WRITE(host->base, IE, CIRQ_EN);
|
|
|
|
+ } else {
|
|
|
|
+ pinctrl_pm_select_default_state(host->dev);
|
|
|
|
+ }
|
|
|
|
+ spin_unlock_irqrestore(&host->irq_lock, flags);
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|