All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/4] Updated omap_hsmmc SDIO and remuxing patches
@ 2013-06-07 21:49 ` Tony Lindgren
  0 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-07 21:49 UTC (permalink / raw)
  To: cjb; +Cc: linux-omap, linux-mmc, linux-arm-kernel

Hi all,

Here are the update SDIO remuxing patches that are based on Andreas'
earlier patches. The remuxing part is still in discussion, but that
can be merged later in case there's some way to do it automatically
at the drivers/base/pinctrl.c level.

I've used these patches successfully with wl12xx and mwifiex_sdio
on at least omap3 zoom, omap3 evm, omap4 sdp, and panda es.

Regards,

Tony

---

Andreas Fenkart (2):
      mmc: omap_hsmmc: Enable SDIO IRQ using a GPIO in idle mode
      mmc: omap_hsmmc: debugfs entries for GPIO and SDIO mode

Tony Lindgren (2):
      mmc: omap_hsmmc: Fix context save and restore for DT
      mmc: omap_hsmmc: Remux pins to support SDIO interrupt and PM runtime


 .../devicetree/bindings/mmc/ti-omap-hsmmc.txt      |   42 ++
 drivers/mmc/host/omap_hsmmc.c                      |  356 +++++++++++++++++---
 include/linux/platform_data/mmc-omap.h             |    4 
 3 files changed, 351 insertions(+), 51 deletions(-)

-- 
Signature

^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 0/4] Updated omap_hsmmc SDIO and remuxing patches
@ 2013-06-07 21:49 ` Tony Lindgren
  0 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-07 21:49 UTC (permalink / raw)
  To: linux-arm-kernel

Hi all,

Here are the update SDIO remuxing patches that are based on Andreas'
earlier patches. The remuxing part is still in discussion, but that
can be merged later in case there's some way to do it automatically
at the drivers/base/pinctrl.c level.

I've used these patches successfully with wl12xx and mwifiex_sdio
on at least omap3 zoom, omap3 evm, omap4 sdp, and panda es.

Regards,

Tony

---

Andreas Fenkart (2):
      mmc: omap_hsmmc: Enable SDIO IRQ using a GPIO in idle mode
      mmc: omap_hsmmc: debugfs entries for GPIO and SDIO mode

Tony Lindgren (2):
      mmc: omap_hsmmc: Fix context save and restore for DT
      mmc: omap_hsmmc: Remux pins to support SDIO interrupt and PM runtime


 .../devicetree/bindings/mmc/ti-omap-hsmmc.txt      |   42 ++
 drivers/mmc/host/omap_hsmmc.c                      |  356 +++++++++++++++++---
 include/linux/platform_data/mmc-omap.h             |    4 
 3 files changed, 351 insertions(+), 51 deletions(-)

-- 
Signature

^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 1/4] mmc: omap_hsmmc: Fix context save and restore for DT
  2013-06-07 21:49 ` Tony Lindgren
@ 2013-06-07 21:49   ` Tony Lindgren
  -1 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-07 21:49 UTC (permalink / raw)
  To: cjb; +Cc: linux-omap, linux-mmc, Balaji T K, linux-arm-kernel, Andreas Fenkart

We want to get rid of the omap specific platform init code
callbacks as they don't play nice with device tree.

Let's convert the context loss check to be based on a
register state detection instead.

Cc: Andreas Fenkart <afenkart@gmail.com>
Cc: Balaji T K <balajitk@ti.com>
Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 drivers/mmc/host/omap_hsmmc.c |   41 ++++++++---------------------------------
 1 file changed, 8 insertions(+), 33 deletions(-)

diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
index 6e44025..478849b 100644
--- a/drivers/mmc/host/omap_hsmmc.c
+++ b/drivers/mmc/host/omap_hsmmc.c
@@ -169,19 +169,19 @@ struct omap_hsmmc_host {
 	unsigned char		bus_mode;
 	unsigned char		power_mode;
 	int			suspended;
+	u32			hctl;
+	u32			capa;
 	int			irq;
 	int			use_dma, dma_ch;
 	struct dma_chan		*tx_chan;
 	struct dma_chan		*rx_chan;
 	int			slot_id;
 	int			response_busy;
-	int			context_loss;
 	int			protect_card;
 	int			reqs_blocked;
 	int			use_reg;
 	int			req_in_progress;
 	struct omap_hsmmc_next	next_data;
-
 	struct	omap_mmc_platform_data	*pdata;
 };
 
@@ -595,25 +595,16 @@ static void omap_hsmmc_set_bus_mode(struct omap_hsmmc_host *host)
 static int omap_hsmmc_context_restore(struct omap_hsmmc_host *host)
 {
 	struct mmc_ios *ios = &host->mmc->ios;
-	struct omap_mmc_platform_data *pdata = host->pdata;
-	int context_loss = 0;
 	u32 hctl, capa;
 	unsigned long timeout;
 
-	if (pdata->get_context_loss_count) {
-		context_loss = pdata->get_context_loss_count(host->dev);
-		if (context_loss < 0)
-			return 1;
-	}
-
-	dev_dbg(mmc_dev(host->mmc), "context was %slost\n",
-		context_loss == host->context_loss ? "not " : "");
-	if (host->context_loss == context_loss)
-		return 1;
-
 	if (!OMAP_HSMMC_READ(host->base, SYSSTATUS) & RESETDONE)
 		return 1;
 
+	if (host->hctl == OMAP_HSMMC_READ(host->base, HCTL) &&
+	    host->capa == OMAP_HSMMC_READ(host->base, CAPA))
+		return 0;
+
 	if (host->pdata->controller_flags & OMAP_HSMMC_SUPPORTS_DUAL_VOLT) {
 		if (host->power_mode != MMC_POWER_OFF &&
 		    (1 << ios->vdd) <= MMC_VDD_23_24)
@@ -653,8 +644,6 @@ static int omap_hsmmc_context_restore(struct omap_hsmmc_host *host)
 	omap_hsmmc_set_bus_mode(host);
 
 out:
-	host->context_loss = context_loss;
-
 	dev_dbg(mmc_dev(host->mmc), "context is restored\n");
 	return 0;
 }
@@ -664,15 +653,8 @@ out:
  */
 static void omap_hsmmc_context_save(struct omap_hsmmc_host *host)
 {
-	struct omap_mmc_platform_data *pdata = host->pdata;
-	int context_loss;
-
-	if (pdata->get_context_loss_count) {
-		context_loss = pdata->get_context_loss_count(host->dev);
-		if (context_loss < 0)
-			return;
-		host->context_loss = context_loss;
-	}
+	host->hctl = OMAP_HSMMC_READ(host->base, HCTL);
+	host->capa = OMAP_HSMMC_READ(host->base, CAPA);
 }
 
 #else
@@ -1633,13 +1615,6 @@ static int omap_hsmmc_regs_show(struct seq_file *s, void *data)
 {
 	struct mmc_host *mmc = s->private;
 	struct omap_hsmmc_host *host = mmc_priv(mmc);
-	int context_loss = 0;
-
-	if (host->pdata->get_context_loss_count)
-		context_loss = host->pdata->get_context_loss_count(host->dev);
-
-	seq_printf(s, "mmc%d:\n ctx_loss:\t%d:%d\n\nregs:\n",
-			mmc->index, host->context_loss, context_loss);
 
 	if (host->suspended) {
 		seq_printf(s, "host suspended, can't read registers\n");


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 1/4] mmc: omap_hsmmc: Fix context save and restore for DT
@ 2013-06-07 21:49   ` Tony Lindgren
  0 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-07 21:49 UTC (permalink / raw)
  To: linux-arm-kernel

We want to get rid of the omap specific platform init code
callbacks as they don't play nice with device tree.

Let's convert the context loss check to be based on a
register state detection instead.

Cc: Andreas Fenkart <afenkart@gmail.com>
Cc: Balaji T K <balajitk@ti.com>
Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 drivers/mmc/host/omap_hsmmc.c |   41 ++++++++---------------------------------
 1 file changed, 8 insertions(+), 33 deletions(-)

diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
index 6e44025..478849b 100644
--- a/drivers/mmc/host/omap_hsmmc.c
+++ b/drivers/mmc/host/omap_hsmmc.c
@@ -169,19 +169,19 @@ struct omap_hsmmc_host {
 	unsigned char		bus_mode;
 	unsigned char		power_mode;
 	int			suspended;
+	u32			hctl;
+	u32			capa;
 	int			irq;
 	int			use_dma, dma_ch;
 	struct dma_chan		*tx_chan;
 	struct dma_chan		*rx_chan;
 	int			slot_id;
 	int			response_busy;
-	int			context_loss;
 	int			protect_card;
 	int			reqs_blocked;
 	int			use_reg;
 	int			req_in_progress;
 	struct omap_hsmmc_next	next_data;
-
 	struct	omap_mmc_platform_data	*pdata;
 };
 
@@ -595,25 +595,16 @@ static void omap_hsmmc_set_bus_mode(struct omap_hsmmc_host *host)
 static int omap_hsmmc_context_restore(struct omap_hsmmc_host *host)
 {
 	struct mmc_ios *ios = &host->mmc->ios;
-	struct omap_mmc_platform_data *pdata = host->pdata;
-	int context_loss = 0;
 	u32 hctl, capa;
 	unsigned long timeout;
 
-	if (pdata->get_context_loss_count) {
-		context_loss = pdata->get_context_loss_count(host->dev);
-		if (context_loss < 0)
-			return 1;
-	}
-
-	dev_dbg(mmc_dev(host->mmc), "context was %slost\n",
-		context_loss == host->context_loss ? "not " : "");
-	if (host->context_loss == context_loss)
-		return 1;
-
 	if (!OMAP_HSMMC_READ(host->base, SYSSTATUS) & RESETDONE)
 		return 1;
 
+	if (host->hctl == OMAP_HSMMC_READ(host->base, HCTL) &&
+	    host->capa == OMAP_HSMMC_READ(host->base, CAPA))
+		return 0;
+
 	if (host->pdata->controller_flags & OMAP_HSMMC_SUPPORTS_DUAL_VOLT) {
 		if (host->power_mode != MMC_POWER_OFF &&
 		    (1 << ios->vdd) <= MMC_VDD_23_24)
@@ -653,8 +644,6 @@ static int omap_hsmmc_context_restore(struct omap_hsmmc_host *host)
 	omap_hsmmc_set_bus_mode(host);
 
 out:
-	host->context_loss = context_loss;
-
 	dev_dbg(mmc_dev(host->mmc), "context is restored\n");
 	return 0;
 }
@@ -664,15 +653,8 @@ out:
  */
 static void omap_hsmmc_context_save(struct omap_hsmmc_host *host)
 {
-	struct omap_mmc_platform_data *pdata = host->pdata;
-	int context_loss;
-
-	if (pdata->get_context_loss_count) {
-		context_loss = pdata->get_context_loss_count(host->dev);
-		if (context_loss < 0)
-			return;
-		host->context_loss = context_loss;
-	}
+	host->hctl = OMAP_HSMMC_READ(host->base, HCTL);
+	host->capa = OMAP_HSMMC_READ(host->base, CAPA);
 }
 
 #else
@@ -1633,13 +1615,6 @@ static int omap_hsmmc_regs_show(struct seq_file *s, void *data)
 {
 	struct mmc_host *mmc = s->private;
 	struct omap_hsmmc_host *host = mmc_priv(mmc);
-	int context_loss = 0;
-
-	if (host->pdata->get_context_loss_count)
-		context_loss = host->pdata->get_context_loss_count(host->dev);
-
-	seq_printf(s, "mmc%d:\n ctx_loss:\t%d:%d\n\nregs:\n",
-			mmc->index, host->context_loss, context_loss);
 
 	if (host->suspended) {
 		seq_printf(s, "host suspended, can't read registers\n");

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 2/4] mmc: omap_hsmmc: Enable SDIO IRQ using a GPIO in idle mode
  2013-06-07 21:49 ` Tony Lindgren
@ 2013-06-07 21:49   ` Tony Lindgren
  -1 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-07 21:49 UTC (permalink / raw)
  To: cjb
  Cc: Balaji T K, Andreas Fenkart, Andreas Fenkart, linux-mmc,
	Grant Likely, linux-omap, linux-arm-kernel

From: Andreas Fenkart <andreas.fenkart@streamunlimited.com>

Without functional clock the omap_hsmmc module can't forward SDIO IRQs to
the system. This patch reconfigures dat1 line as a gpio while the fclk is
off. When the fclk is present it uses the standard SDIO IRQ detection of
the module.

The gpio irq is managed via the 'disable_depth' ref counter of the irq
subsystem, this driver simply calls enable_irq/disable_irq when needed.

                      sdio irq    sdio irq
                      unmasked     masked
   -----------------------------------------
    runtime default  |    1     |   2
    runtime suspend  |    0     |   1

                  irq disable depth


only when sdio irq is enabled AND the module is idle, the reference
count drops to zero and the gpio irq is effectively armed.

Patch was tested on AM335x/Stream800. Test setup was two modules
with sdio wifi cards. Modules where connected to a dual-band AP, each
module using a different band. One of module was running iperf as server
the other as client connecting to the server in a while true loop. Test
was running for 4+ weeks. There were about 60 Mio. suspend/resume
transitions. Test was shut down regularly.

Cc: Andreas Fenkart <afenkart@gmail.com>
Cc: Balaji T K <balajitk@ti.com>
Signed-off-by: Andreas Fenkart <andreas.fenkart@streamunlimited.com>
Reviewed-by: Grant Likely <grant.likely@secretlab.ca>
[tony@atomide.com: updated and separated out pin muxing]
Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 .../devicetree/bindings/mmc/ti-omap-hsmmc.txt      |   42 ++++
 drivers/mmc/host/omap_hsmmc.c                      |  206 +++++++++++++++++++-
 include/linux/platform_data/mmc-omap.h             |    4 
 3 files changed, 239 insertions(+), 13 deletions(-)

diff --git a/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt b/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt
index ed271fc..5a3df37 100644
--- a/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt
+++ b/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt
@@ -20,6 +20,29 @@ ti,dual-volt: boolean, supports dual voltage cards
 ti,non-removable: non-removable slot (like eMMC)
 ti,needs-special-reset: Requires a special softreset sequence
 ti,needs-special-hs-handling: HSMMC IP needs special setting for handling High Speed
+ti,cirq-gpio: When omap_hsmmc module is suspended, its functional
+clock is turned off. Without fclk it can't forward SDIO IRQs to the
+system. For that to happen, it needs to tell the PRCM to restore
+its fclk, which is done through the swakeup line.
+
+                   ------
+                  | PRCM |
+                   ------
+                    | ^
+               fclk | | swakeup
+                    v |
+                  -------               ------
+      <-- IRQ -- | hsmmc | <-- CIRQ -- | card |
+                  -------               ------
+
+The problem is, that on the AM335x family the swakeup line is
+missing, it has not been routed from the module to the PRCM.
+The way to work around this, is to reconfigure the dat1 line as a
+GPIO upon suspend. Beyond this option you also need to set named
+states "default" and "idle "in the .dts file for the pins, using
+pinctrl-single.c. The MMC driver will then then toggle between
+default and idle during the runtime.
+
 
 Example:
 	mmc1: mmc@0x4809c000 {
@@ -31,3 +54,22 @@ Example:
 		vmmc-supply = <&vmmc>; /* phandle to regulator node */
 		ti,non-removable;
 	};
+
+[am335x with with gpio for sdio irq]
+
+	mmc1_cirq_pin: pinmux_cirq_pin {
+		pinctrl-single,pins = <
+			0x0f8 0x3f	/* MMC0_DAT1 as GPIO2_28 */
+		>;
+	};
+
+	mmc1: mmc@48060000 {
+		pinctrl-names = "default", "idle";
+		pinctrl-0 = <&mmc1_pins>;
+		pinctrl-1 = <&mmc1_cirq_pin>;
+		ti,cirq-gpio = <&gpio3 28 0>;
+		ti,non-removable;
+		bus-width = <4>;
+		vmmc-supply = <&ldo2_reg>;
+		vmmc_aux-supply = <&vmmc>;
+	};
diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
index 478849b..7e28501 100644
--- a/drivers/mmc/host/omap_hsmmc.c
+++ b/drivers/mmc/host/omap_hsmmc.c
@@ -22,6 +22,7 @@
 #include <linux/dmaengine.h>
 #include <linux/seq_file.h>
 #include <linux/interrupt.h>
+#include <linux/irq.h>
 #include <linux/delay.h>
 #include <linux/dma-mapping.h>
 #include <linux/platform_device.h>
@@ -102,6 +103,7 @@
 #define TC_EN			(1 << 1)
 #define BWR_EN			(1 << 4)
 #define BRR_EN			(1 << 5)
+#define CIRQ_EN			(1 << 8)
 #define ERR_EN			(1 << 15)
 #define CTO_EN			(1 << 16)
 #define CCRC_EN			(1 << 17)
@@ -182,9 +184,19 @@ struct omap_hsmmc_host {
 	int			use_reg;
 	int			req_in_progress;
 	struct omap_hsmmc_next	next_data;
+	bool			sdio_irq_en;
+	bool			active_pinmux;
 	struct	omap_mmc_platform_data	*pdata;
 };
 
+static irqreturn_t omap_hsmmc_cirq(int irq, void *dev_id)
+{
+	struct omap_hsmmc_host *host = dev_id;
+
+	mmc_signal_sdio_irq(host->mmc);
+	return IRQ_HANDLED;
+}
+
 static int omap_hsmmc_card_detect(struct device *dev, int slot)
 {
 	struct omap_hsmmc_host *host = dev_get_drvdata(dev);
@@ -419,10 +431,31 @@ static int omap_hsmmc_gpio_init(struct omap_mmc_platform_data *pdata)
 	} else
 		pdata->slots[0].gpio_wp = -EINVAL;
 
+	if (pdata->slots[0].gpio_cirq > 0 &&
+	    gpio_is_valid(pdata->slots[0].gpio_cirq)) {
+		pdata->slots[0].sdio_irq =
+				gpio_to_irq(pdata->slots[0].gpio_cirq);
+
+		ret = gpio_request(pdata->slots[0].gpio_cirq, "sdio_cirq");
+		if (ret)
+			goto err_free_ro;
+		ret = gpio_direction_input(pdata->slots[0].gpio_cirq);
+		if (ret)
+			goto err_free_cirq;
+
+	} else {
+		pdata->slots[0].gpio_cirq = -EINVAL;
+	}
+
+
 	return 0;
 
+err_free_cirq:
+	gpio_free(pdata->slots[0].gpio_cirq);
+err_free_ro:
+	if (gpio_is_valid(pdata->slots[0].gpio_wp))
 err_free_wp:
-	gpio_free(pdata->slots[0].gpio_wp);
+		gpio_free(pdata->slots[0].gpio_wp);
 err_free_cd:
 	if (gpio_is_valid(pdata->slots[0].switch_pin))
 err_free_sp:
@@ -436,6 +469,8 @@ static void omap_hsmmc_gpio_free(struct omap_mmc_platform_data *pdata)
 		gpio_free(pdata->slots[0].gpio_wp);
 	if (gpio_is_valid(pdata->slots[0].switch_pin))
 		gpio_free(pdata->slots[0].switch_pin);
+	if (gpio_is_valid(pdata->slots[0].gpio_cirq))
+		gpio_free(pdata->slots[0].gpio_cirq);
 }
 
 /*
@@ -461,27 +496,46 @@ static void omap_hsmmc_stop_clock(struct omap_hsmmc_host *host)
 static void omap_hsmmc_enable_irq(struct omap_hsmmc_host *host,
 				  struct mmc_command *cmd)
 {
-	unsigned int irq_mask;
+	u32 irq_mask = INT_EN_MASK;
+	unsigned long flags;
 
 	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 */
 	if (cmd->opcode == MMC_ERASE)
 		irq_mask &= ~DTO_EN;
 
+	spin_lock_irqsave(&host->irq_lock, flags);
+
 	OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
 	OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
+
+	/* latch pending CIRQ, but don't signal */
+	if (host->sdio_irq_en)
+		irq_mask |= CIRQ_EN;
+
 	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)
 {
-	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, need to signal cirq if */
+	if (host->sdio_irq_en)
+		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);
+
+	spin_unlock_irqrestore(&host->irq_lock, flags);
 }
 
 /* Calculate divisor for the given clock frequency */
@@ -1037,8 +1091,12 @@ static irqreturn_t omap_hsmmc_irq(int irq, void *dev_id)
 	int status;
 
 	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 */
 		OMAP_HSMMC_WRITE(host->base, STAT, status);
@@ -1554,6 +1612,51 @@ static void omap_hsmmc_init_card(struct mmc_host *mmc, struct mmc_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;
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->irq_lock, flags);
+
+	if (host->sdio_irq_en == enable) {
+		dev_dbg(host->dev, "en/disable:%d already set", enable);
+		spin_unlock_irqrestore(&host->irq_lock, flags);
+		return;
+	}
+
+	host->sdio_irq_en = (enable != 0) ? true : false;
+
+	if (host->active_pinmux) { /* register access fails without fclk */
+		irq_mask = OMAP_HSMMC_READ(host->base, ISE);
+		if (enable)
+			irq_mask |= CIRQ_EN;
+		else
+			irq_mask &= ~CIRQ_EN;
+		OMAP_HSMMC_WRITE(host->base, IE, irq_mask);
+
+		if (!host->req_in_progress)
+			OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
+
+		/*
+		 * evtl. need to flush posted write
+		 * OMAP_HSMMC_READ(host->base, IE);
+		 */
+	}
+
+	if ((mmc_slot(host).sdio_irq)) {
+		if (enable) {
+			enable_irq(mmc_slot(host).sdio_irq);
+		} else {
+			/* _nosync, see mmc_signal_sdio_irq */
+			disable_irq_nosync(mmc_slot(host).sdio_irq);
+		}
+	}
+
+	spin_unlock_irqrestore(&host->irq_lock, flags);
+}
+
 static void omap_hsmmc_conf_bus_power(struct omap_hsmmc_host *host)
 {
 	u32 hctl, capa, value;
@@ -1606,7 +1709,7 @@ static const struct mmc_host_ops omap_hsmmc_ops = {
 	.get_cd = omap_hsmmc_get_cd,
 	.get_ro = omap_hsmmc_get_ro,
 	.init_card = omap_hsmmc_init_card,
-	/* NYET -- enable_sdio_irq */
+	.enable_sdio_irq = omap_hsmmc_enable_sdio_irq,
 };
 
 #ifdef CONFIG_DEBUG_FS
@@ -1710,6 +1813,7 @@ static struct omap_mmc_platform_data *of_get_hsmmc_pdata(struct device *dev)
 	pdata->nr_slots = 1;
 	pdata->slots[0].switch_pin = cd_gpio;
 	pdata->slots[0].gpio_wp = wp_gpio;
+	pdata->slots[0].gpio_cirq = of_get_named_gpio(np, "ti,cirq-gpio", 0);
 
 	if (of_find_property(np, "ti,non-removable", NULL)) {
 		pdata->slots[0].nonremovable = true;
@@ -1802,6 +1906,8 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 	host->dma_ch	= -1;
 	host->irq	= irq;
 	host->slot_id	= 0;
+	host->sdio_irq_en = false;
+	host->active_pinmux = true;
 	host->mapbase	= res->start + pdata->reg_offset;
 	host->base	= ioremap(host->mapbase, SZ_4K);
 	host->power_mode = MMC_POWER_OFF;
@@ -1955,6 +2061,29 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 		pdata->resume = omap_hsmmc_resume_cdirq;
 	}
 
+	if ((mmc_slot(host).sdio_irq)) {
+		/* prevent auto-enabling of IRQ */
+		irq_set_status_flags(mmc_slot(host).sdio_irq, IRQ_NOAUTOEN);
+		ret = request_irq(mmc_slot(host).sdio_irq, omap_hsmmc_cirq,
+				  IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+				  mmc_hostname(mmc), host);
+		if (ret) {
+			dev_dbg(mmc_dev(host->mmc),
+				"Unable to grab MMC SDIO IRQ\n");
+			goto err_irq_sdio;
+		}
+
+		/*
+		 * sdio_irq is managed with ref count
+		 * - omap_hsmmc_enable_sdio_irq will +1/-1
+		 * - pm_suspend/pm_resume will +1/-1
+		 * only when sdio irq is enabled AND module will go to runtime
+		 * suspend the ref count will drop to zero and the irq is
+		 * effectively enabled. starting with ref count equal 2
+		 */
+		disable_irq(mmc_slot(host).sdio_irq);
+	}
+
 	omap_hsmmc_disable_irq(host);
 
 	pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
@@ -1962,6 +2091,19 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 		dev_warn(&pdev->dev,
 			"pins are not configured from the driver\n");
 
+	/*
+	 * For now, only support SDIO interrupt if we are doing
+	 * muxing of dat1 when booted with DT. This is because the
+	 * supposedly the wake-up events for CTPL don't work from deeper
+	 * idle states. 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.
+	 */
+	if (match) {
+		if (!IS_ERR(pinctrl) && mmc_slot(host).sdio_irq)
+			mmc->caps |= MMC_CAP_SDIO_IRQ;
+	}
+
 	omap_hsmmc_protect_card(host);
 
 	mmc_add_host(mmc);
@@ -1986,6 +2128,9 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 
 err_slot_name:
 	mmc_remove_host(mmc);
+	if ((mmc_slot(host).sdio_irq))
+		free_irq(mmc_slot(host).sdio_irq, host);
+err_irq_sdio:
 	free_irq(mmc_slot(host).card_detect_irq, host);
 err_irq_cd:
 	if (host->use_reg)
@@ -2032,9 +2177,10 @@ static int omap_hsmmc_remove(struct platform_device *pdev)
 	if (host->pdata->cleanup)
 		host->pdata->cleanup(&pdev->dev);
 	free_irq(host->irq, host);
+	if ((mmc_slot(host).sdio_irq))
+		free_irq(mmc_slot(host).sdio_irq, host);
 	if (mmc_slot(host).card_detect_irq)
 		free_irq(mmc_slot(host).card_detect_irq, host);
-
 	if (host->tx_chan)
 		dma_release_channel(host->tx_chan);
 	if (host->rx_chan)
@@ -2157,23 +2303,57 @@ static int omap_hsmmc_resume(struct device *dev)
 static int omap_hsmmc_runtime_suspend(struct device *dev)
 {
 	struct omap_hsmmc_host *host;
+	struct mmc_host *mmc;
+	unsigned long flags;
+	int ret = 0;
 
 	host = platform_get_drvdata(to_platform_device(dev));
+	mmc = host->mmc;
 	omap_hsmmc_context_save(host);
 	dev_dbg(dev, "disabled\n");
 
-	return 0;
+	if (mmc->caps & MMC_CAP_SDIO_IRQ) {
+		spin_lock_irqsave(&host->irq_lock, flags);
+		host->active_pinmux = false;
+		OMAP_HSMMC_WRITE(host->base, ISE, 0);
+		OMAP_HSMMC_WRITE(host->base, IE, 0);
+		OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
+		spin_unlock_irqrestore(&host->irq_lock, flags);
+
+		if (mmc_slot(host).sdio_irq)
+			enable_irq(mmc_slot(host).sdio_irq);
+	}
+
+	return ret;
 }
 
 static int omap_hsmmc_runtime_resume(struct device *dev)
 {
 	struct omap_hsmmc_host *host;
+	struct mmc_host *mmc;
+	unsigned long flags;
+	int ret = 0;
 
 	host = platform_get_drvdata(to_platform_device(dev));
+	mmc = host->mmc;
 	omap_hsmmc_context_restore(host);
 	dev_dbg(dev, "enabled\n");
 
-	return 0;
+	if (mmc->caps & MMC_CAP_SDIO_IRQ) {
+		if (mmc_slot(host).sdio_irq)
+			disable_irq(mmc_slot(host).sdio_irq);
+
+		spin_lock_irqsave(&host->irq_lock, flags);
+		host->active_pinmux = true;
+
+		if (host->sdio_irq_en) {
+			OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
+			OMAP_HSMMC_WRITE(host->base, ISE, CIRQ_EN);
+			OMAP_HSMMC_WRITE(host->base, IE, CIRQ_EN);
+		}
+		spin_unlock_irqrestore(&host->irq_lock, flags);
+	}
+	return ret;
 }
 
 static struct dev_pm_ops omap_hsmmc_dev_pm_ops = {
diff --git a/include/linux/platform_data/mmc-omap.h b/include/linux/platform_data/mmc-omap.h
index 2bf1b30..fd5fff5 100644
--- a/include/linux/platform_data/mmc-omap.h
+++ b/include/linux/platform_data/mmc-omap.h
@@ -115,6 +115,7 @@ struct omap_mmc_platform_data {
 
 		int switch_pin;			/* gpio (card detect) */
 		int gpio_wp;			/* gpio (write protect) */
+		int gpio_cirq;			/* gpio (card irq) */
 
 		int (*set_bus_mode)(struct device *dev, int slot, int bus_mode);
 		int (*set_power)(struct device *dev, int slot,
@@ -145,6 +146,9 @@ struct omap_mmc_platform_data {
 		int card_detect_irq;
 		int (*card_detect)(struct device *dev, int slot);
 
+		/* SDIO IRQs */
+		int sdio_irq;
+
 		unsigned int ban_openended:1;
 
 	} slots[OMAP_MMC_MAX_SLOTS];


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 2/4] mmc: omap_hsmmc: Enable SDIO IRQ using a GPIO in idle mode
@ 2013-06-07 21:49   ` Tony Lindgren
  0 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-07 21:49 UTC (permalink / raw)
  To: linux-arm-kernel

From: Andreas Fenkart <andreas.fenkart@streamunlimited.com>

Without functional clock the omap_hsmmc module can't forward SDIO IRQs to
the system. This patch reconfigures dat1 line as a gpio while the fclk is
off. When the fclk is present it uses the standard SDIO IRQ detection of
the module.

The gpio irq is managed via the 'disable_depth' ref counter of the irq
subsystem, this driver simply calls enable_irq/disable_irq when needed.

                      sdio irq    sdio irq
                      unmasked     masked
   -----------------------------------------
    runtime default  |    1     |   2
    runtime suspend  |    0     |   1

                  irq disable depth


only when sdio irq is enabled AND the module is idle, the reference
count drops to zero and the gpio irq is effectively armed.

Patch was tested on AM335x/Stream800. Test setup was two modules
with sdio wifi cards. Modules where connected to a dual-band AP, each
module using a different band. One of module was running iperf as server
the other as client connecting to the server in a while true loop. Test
was running for 4+ weeks. There were about 60 Mio. suspend/resume
transitions. Test was shut down regularly.

Cc: Andreas Fenkart <afenkart@gmail.com>
Cc: Balaji T K <balajitk@ti.com>
Signed-off-by: Andreas Fenkart <andreas.fenkart@streamunlimited.com>
Reviewed-by: Grant Likely <grant.likely@secretlab.ca>
[tony at atomide.com: updated and separated out pin muxing]
Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 .../devicetree/bindings/mmc/ti-omap-hsmmc.txt      |   42 ++++
 drivers/mmc/host/omap_hsmmc.c                      |  206 +++++++++++++++++++-
 include/linux/platform_data/mmc-omap.h             |    4 
 3 files changed, 239 insertions(+), 13 deletions(-)

diff --git a/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt b/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt
index ed271fc..5a3df37 100644
--- a/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt
+++ b/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt
@@ -20,6 +20,29 @@ ti,dual-volt: boolean, supports dual voltage cards
 ti,non-removable: non-removable slot (like eMMC)
 ti,needs-special-reset: Requires a special softreset sequence
 ti,needs-special-hs-handling: HSMMC IP needs special setting for handling High Speed
+ti,cirq-gpio: When omap_hsmmc module is suspended, its functional
+clock is turned off. Without fclk it can't forward SDIO IRQs to the
+system. For that to happen, it needs to tell the PRCM to restore
+its fclk, which is done through the swakeup line.
+
+                   ------
+                  | PRCM |
+                   ------
+                    | ^
+               fclk | | swakeup
+                    v |
+                  -------               ------
+      <-- IRQ -- | hsmmc | <-- CIRQ -- | card |
+                  -------               ------
+
+The problem is, that on the AM335x family the swakeup line is
+missing, it has not been routed from the module to the PRCM.
+The way to work around this, is to reconfigure the dat1 line as a
+GPIO upon suspend. Beyond this option you also need to set named
+states "default" and "idle "in the .dts file for the pins, using
+pinctrl-single.c. The MMC driver will then then toggle between
+default and idle during the runtime.
+
 
 Example:
 	mmc1: mmc@0x4809c000 {
@@ -31,3 +54,22 @@ Example:
 		vmmc-supply = <&vmmc>; /* phandle to regulator node */
 		ti,non-removable;
 	};
+
+[am335x with with gpio for sdio irq]
+
+	mmc1_cirq_pin: pinmux_cirq_pin {
+		pinctrl-single,pins = <
+			0x0f8 0x3f	/* MMC0_DAT1 as GPIO2_28 */
+		>;
+	};
+
+	mmc1: mmc at 48060000 {
+		pinctrl-names = "default", "idle";
+		pinctrl-0 = <&mmc1_pins>;
+		pinctrl-1 = <&mmc1_cirq_pin>;
+		ti,cirq-gpio = <&gpio3 28 0>;
+		ti,non-removable;
+		bus-width = <4>;
+		vmmc-supply = <&ldo2_reg>;
+		vmmc_aux-supply = <&vmmc>;
+	};
diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
index 478849b..7e28501 100644
--- a/drivers/mmc/host/omap_hsmmc.c
+++ b/drivers/mmc/host/omap_hsmmc.c
@@ -22,6 +22,7 @@
 #include <linux/dmaengine.h>
 #include <linux/seq_file.h>
 #include <linux/interrupt.h>
+#include <linux/irq.h>
 #include <linux/delay.h>
 #include <linux/dma-mapping.h>
 #include <linux/platform_device.h>
@@ -102,6 +103,7 @@
 #define TC_EN			(1 << 1)
 #define BWR_EN			(1 << 4)
 #define BRR_EN			(1 << 5)
+#define CIRQ_EN			(1 << 8)
 #define ERR_EN			(1 << 15)
 #define CTO_EN			(1 << 16)
 #define CCRC_EN			(1 << 17)
@@ -182,9 +184,19 @@ struct omap_hsmmc_host {
 	int			use_reg;
 	int			req_in_progress;
 	struct omap_hsmmc_next	next_data;
+	bool			sdio_irq_en;
+	bool			active_pinmux;
 	struct	omap_mmc_platform_data	*pdata;
 };
 
+static irqreturn_t omap_hsmmc_cirq(int irq, void *dev_id)
+{
+	struct omap_hsmmc_host *host = dev_id;
+
+	mmc_signal_sdio_irq(host->mmc);
+	return IRQ_HANDLED;
+}
+
 static int omap_hsmmc_card_detect(struct device *dev, int slot)
 {
 	struct omap_hsmmc_host *host = dev_get_drvdata(dev);
@@ -419,10 +431,31 @@ static int omap_hsmmc_gpio_init(struct omap_mmc_platform_data *pdata)
 	} else
 		pdata->slots[0].gpio_wp = -EINVAL;
 
+	if (pdata->slots[0].gpio_cirq > 0 &&
+	    gpio_is_valid(pdata->slots[0].gpio_cirq)) {
+		pdata->slots[0].sdio_irq =
+				gpio_to_irq(pdata->slots[0].gpio_cirq);
+
+		ret = gpio_request(pdata->slots[0].gpio_cirq, "sdio_cirq");
+		if (ret)
+			goto err_free_ro;
+		ret = gpio_direction_input(pdata->slots[0].gpio_cirq);
+		if (ret)
+			goto err_free_cirq;
+
+	} else {
+		pdata->slots[0].gpio_cirq = -EINVAL;
+	}
+
+
 	return 0;
 
+err_free_cirq:
+	gpio_free(pdata->slots[0].gpio_cirq);
+err_free_ro:
+	if (gpio_is_valid(pdata->slots[0].gpio_wp))
 err_free_wp:
-	gpio_free(pdata->slots[0].gpio_wp);
+		gpio_free(pdata->slots[0].gpio_wp);
 err_free_cd:
 	if (gpio_is_valid(pdata->slots[0].switch_pin))
 err_free_sp:
@@ -436,6 +469,8 @@ static void omap_hsmmc_gpio_free(struct omap_mmc_platform_data *pdata)
 		gpio_free(pdata->slots[0].gpio_wp);
 	if (gpio_is_valid(pdata->slots[0].switch_pin))
 		gpio_free(pdata->slots[0].switch_pin);
+	if (gpio_is_valid(pdata->slots[0].gpio_cirq))
+		gpio_free(pdata->slots[0].gpio_cirq);
 }
 
 /*
@@ -461,27 +496,46 @@ static void omap_hsmmc_stop_clock(struct omap_hsmmc_host *host)
 static void omap_hsmmc_enable_irq(struct omap_hsmmc_host *host,
 				  struct mmc_command *cmd)
 {
-	unsigned int irq_mask;
+	u32 irq_mask = INT_EN_MASK;
+	unsigned long flags;
 
 	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 */
 	if (cmd->opcode == MMC_ERASE)
 		irq_mask &= ~DTO_EN;
 
+	spin_lock_irqsave(&host->irq_lock, flags);
+
 	OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
 	OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
+
+	/* latch pending CIRQ, but don't signal */
+	if (host->sdio_irq_en)
+		irq_mask |= CIRQ_EN;
+
 	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)
 {
-	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, need to signal cirq if */
+	if (host->sdio_irq_en)
+		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);
+
+	spin_unlock_irqrestore(&host->irq_lock, flags);
 }
 
 /* Calculate divisor for the given clock frequency */
@@ -1037,8 +1091,12 @@ static irqreturn_t omap_hsmmc_irq(int irq, void *dev_id)
 	int status;
 
 	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 */
 		OMAP_HSMMC_WRITE(host->base, STAT, status);
@@ -1554,6 +1612,51 @@ static void omap_hsmmc_init_card(struct mmc_host *mmc, struct mmc_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;
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->irq_lock, flags);
+
+	if (host->sdio_irq_en == enable) {
+		dev_dbg(host->dev, "en/disable:%d already set", enable);
+		spin_unlock_irqrestore(&host->irq_lock, flags);
+		return;
+	}
+
+	host->sdio_irq_en = (enable != 0) ? true : false;
+
+	if (host->active_pinmux) { /* register access fails without fclk */
+		irq_mask = OMAP_HSMMC_READ(host->base, ISE);
+		if (enable)
+			irq_mask |= CIRQ_EN;
+		else
+			irq_mask &= ~CIRQ_EN;
+		OMAP_HSMMC_WRITE(host->base, IE, irq_mask);
+
+		if (!host->req_in_progress)
+			OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
+
+		/*
+		 * evtl. need to flush posted write
+		 * OMAP_HSMMC_READ(host->base, IE);
+		 */
+	}
+
+	if ((mmc_slot(host).sdio_irq)) {
+		if (enable) {
+			enable_irq(mmc_slot(host).sdio_irq);
+		} else {
+			/* _nosync, see mmc_signal_sdio_irq */
+			disable_irq_nosync(mmc_slot(host).sdio_irq);
+		}
+	}
+
+	spin_unlock_irqrestore(&host->irq_lock, flags);
+}
+
 static void omap_hsmmc_conf_bus_power(struct omap_hsmmc_host *host)
 {
 	u32 hctl, capa, value;
@@ -1606,7 +1709,7 @@ static const struct mmc_host_ops omap_hsmmc_ops = {
 	.get_cd = omap_hsmmc_get_cd,
 	.get_ro = omap_hsmmc_get_ro,
 	.init_card = omap_hsmmc_init_card,
-	/* NYET -- enable_sdio_irq */
+	.enable_sdio_irq = omap_hsmmc_enable_sdio_irq,
 };
 
 #ifdef CONFIG_DEBUG_FS
@@ -1710,6 +1813,7 @@ static struct omap_mmc_platform_data *of_get_hsmmc_pdata(struct device *dev)
 	pdata->nr_slots = 1;
 	pdata->slots[0].switch_pin = cd_gpio;
 	pdata->slots[0].gpio_wp = wp_gpio;
+	pdata->slots[0].gpio_cirq = of_get_named_gpio(np, "ti,cirq-gpio", 0);
 
 	if (of_find_property(np, "ti,non-removable", NULL)) {
 		pdata->slots[0].nonremovable = true;
@@ -1802,6 +1906,8 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 	host->dma_ch	= -1;
 	host->irq	= irq;
 	host->slot_id	= 0;
+	host->sdio_irq_en = false;
+	host->active_pinmux = true;
 	host->mapbase	= res->start + pdata->reg_offset;
 	host->base	= ioremap(host->mapbase, SZ_4K);
 	host->power_mode = MMC_POWER_OFF;
@@ -1955,6 +2061,29 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 		pdata->resume = omap_hsmmc_resume_cdirq;
 	}
 
+	if ((mmc_slot(host).sdio_irq)) {
+		/* prevent auto-enabling of IRQ */
+		irq_set_status_flags(mmc_slot(host).sdio_irq, IRQ_NOAUTOEN);
+		ret = request_irq(mmc_slot(host).sdio_irq, omap_hsmmc_cirq,
+				  IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+				  mmc_hostname(mmc), host);
+		if (ret) {
+			dev_dbg(mmc_dev(host->mmc),
+				"Unable to grab MMC SDIO IRQ\n");
+			goto err_irq_sdio;
+		}
+
+		/*
+		 * sdio_irq is managed with ref count
+		 * - omap_hsmmc_enable_sdio_irq will +1/-1
+		 * - pm_suspend/pm_resume will +1/-1
+		 * only when sdio irq is enabled AND module will go to runtime
+		 * suspend the ref count will drop to zero and the irq is
+		 * effectively enabled. starting with ref count equal 2
+		 */
+		disable_irq(mmc_slot(host).sdio_irq);
+	}
+
 	omap_hsmmc_disable_irq(host);
 
 	pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
@@ -1962,6 +2091,19 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 		dev_warn(&pdev->dev,
 			"pins are not configured from the driver\n");
 
+	/*
+	 * For now, only support SDIO interrupt if we are doing
+	 * muxing of dat1 when booted with DT. This is because the
+	 * supposedly the wake-up events for CTPL don't work from deeper
+	 * idle states. 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.
+	 */
+	if (match) {
+		if (!IS_ERR(pinctrl) && mmc_slot(host).sdio_irq)
+			mmc->caps |= MMC_CAP_SDIO_IRQ;
+	}
+
 	omap_hsmmc_protect_card(host);
 
 	mmc_add_host(mmc);
@@ -1986,6 +2128,9 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 
 err_slot_name:
 	mmc_remove_host(mmc);
+	if ((mmc_slot(host).sdio_irq))
+		free_irq(mmc_slot(host).sdio_irq, host);
+err_irq_sdio:
 	free_irq(mmc_slot(host).card_detect_irq, host);
 err_irq_cd:
 	if (host->use_reg)
@@ -2032,9 +2177,10 @@ static int omap_hsmmc_remove(struct platform_device *pdev)
 	if (host->pdata->cleanup)
 		host->pdata->cleanup(&pdev->dev);
 	free_irq(host->irq, host);
+	if ((mmc_slot(host).sdio_irq))
+		free_irq(mmc_slot(host).sdio_irq, host);
 	if (mmc_slot(host).card_detect_irq)
 		free_irq(mmc_slot(host).card_detect_irq, host);
-
 	if (host->tx_chan)
 		dma_release_channel(host->tx_chan);
 	if (host->rx_chan)
@@ -2157,23 +2303,57 @@ static int omap_hsmmc_resume(struct device *dev)
 static int omap_hsmmc_runtime_suspend(struct device *dev)
 {
 	struct omap_hsmmc_host *host;
+	struct mmc_host *mmc;
+	unsigned long flags;
+	int ret = 0;
 
 	host = platform_get_drvdata(to_platform_device(dev));
+	mmc = host->mmc;
 	omap_hsmmc_context_save(host);
 	dev_dbg(dev, "disabled\n");
 
-	return 0;
+	if (mmc->caps & MMC_CAP_SDIO_IRQ) {
+		spin_lock_irqsave(&host->irq_lock, flags);
+		host->active_pinmux = false;
+		OMAP_HSMMC_WRITE(host->base, ISE, 0);
+		OMAP_HSMMC_WRITE(host->base, IE, 0);
+		OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
+		spin_unlock_irqrestore(&host->irq_lock, flags);
+
+		if (mmc_slot(host).sdio_irq)
+			enable_irq(mmc_slot(host).sdio_irq);
+	}
+
+	return ret;
 }
 
 static int omap_hsmmc_runtime_resume(struct device *dev)
 {
 	struct omap_hsmmc_host *host;
+	struct mmc_host *mmc;
+	unsigned long flags;
+	int ret = 0;
 
 	host = platform_get_drvdata(to_platform_device(dev));
+	mmc = host->mmc;
 	omap_hsmmc_context_restore(host);
 	dev_dbg(dev, "enabled\n");
 
-	return 0;
+	if (mmc->caps & MMC_CAP_SDIO_IRQ) {
+		if (mmc_slot(host).sdio_irq)
+			disable_irq(mmc_slot(host).sdio_irq);
+
+		spin_lock_irqsave(&host->irq_lock, flags);
+		host->active_pinmux = true;
+
+		if (host->sdio_irq_en) {
+			OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
+			OMAP_HSMMC_WRITE(host->base, ISE, CIRQ_EN);
+			OMAP_HSMMC_WRITE(host->base, IE, CIRQ_EN);
+		}
+		spin_unlock_irqrestore(&host->irq_lock, flags);
+	}
+	return ret;
 }
 
 static struct dev_pm_ops omap_hsmmc_dev_pm_ops = {
diff --git a/include/linux/platform_data/mmc-omap.h b/include/linux/platform_data/mmc-omap.h
index 2bf1b30..fd5fff5 100644
--- a/include/linux/platform_data/mmc-omap.h
+++ b/include/linux/platform_data/mmc-omap.h
@@ -115,6 +115,7 @@ struct omap_mmc_platform_data {
 
 		int switch_pin;			/* gpio (card detect) */
 		int gpio_wp;			/* gpio (write protect) */
+		int gpio_cirq;			/* gpio (card irq) */
 
 		int (*set_bus_mode)(struct device *dev, int slot, int bus_mode);
 		int (*set_power)(struct device *dev, int slot,
@@ -145,6 +146,9 @@ struct omap_mmc_platform_data {
 		int card_detect_irq;
 		int (*card_detect)(struct device *dev, int slot);
 
+		/* SDIO IRQs */
+		int sdio_irq;
+
 		unsigned int ban_openended:1;
 
 	} slots[OMAP_MMC_MAX_SLOTS];

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 3/4] mmc: omap_hsmmc: Remux pins to support SDIO interrupt and PM runtime
  2013-06-07 21:49 ` Tony Lindgren
@ 2013-06-07 21:49   ` Tony Lindgren
  -1 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-07 21:49 UTC (permalink / raw)
  To: cjb
  Cc: Balaji T K, Linus Walleij, Andreas Fenkart, linux-mmc,
	linux-omap, linux-arm-kernel

On some omaps we need to remux MMC pins for PM, and for some omaps
we need to remux the SDIO IRQ pin.

Based on an earlier patch by Andreas Fenkart <afenkart@gmail.com>.

Cc: Andreas Fenkart <afenkart@gmail.com>
Cc: Balaji T K <balajitk@ti.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 drivers/mmc/host/omap_hsmmc.c |   93 +++++++++++++++++++++++++++++++++++++----
 1 file changed, 84 insertions(+), 9 deletions(-)

diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
index 7e28501..8ca08fb 100644
--- a/drivers/mmc/host/omap_hsmmc.c
+++ b/drivers/mmc/host/omap_hsmmc.c
@@ -185,6 +185,8 @@ struct omap_hsmmc_host {
 	int			req_in_progress;
 	struct omap_hsmmc_next	next_data;
 	bool			sdio_irq_en;
+	struct pinctrl		*pinctrl;
+	struct pinctrl_state	*fixed, *active, *idle;
 	bool			active_pinmux;
 	struct	omap_mmc_platform_data	*pdata;
 };
@@ -473,6 +475,67 @@ static void omap_hsmmc_gpio_free(struct omap_mmc_platform_data *pdata)
 		gpio_free(pdata->slots[0].gpio_cirq);
 }
 
+static int omap_hsmmc_pin_init(struct omap_hsmmc_host *host)
+{
+	int ret;
+
+	host->pinctrl = devm_pinctrl_get(host->dev);
+	if (IS_ERR(host->pinctrl)) {
+		dev_dbg(host->dev, "no pinctrl handle\n");
+		ret = 0;
+		goto out;
+	}
+
+	host->fixed = pinctrl_lookup_state(host->pinctrl,
+					   PINCTRL_STATE_DEFAULT);
+	if (IS_ERR(host->fixed)) {
+		dev_dbg(host->dev,
+			 "pins are not configured from the driver\n");
+		host->fixed = NULL;
+		ret = 0;
+		goto out;
+	}
+
+	ret = pinctrl_select_state(host->pinctrl, host->fixed);
+	if (ret < 0)
+		goto err;
+
+	/* For most cases we don't have wake-ups, and exit after this */
+	host->active = pinctrl_lookup_state(host->pinctrl, "active");
+	if (IS_ERR(host->active)) {
+		ret = PTR_ERR(host->active);
+		host->active = NULL;
+		return 0;
+	}
+
+	host->idle = pinctrl_lookup_state(host->pinctrl,
+					  PINCTRL_STATE_IDLE);
+	if (IS_ERR(host->idle)) {
+		ret = PTR_ERR(host->idle);
+		host->idle = NULL;
+		goto err;
+	}
+
+	/* Let's make sure the active and idle states work */
+	ret = pinctrl_select_state(host->pinctrl, host->idle);
+	if (ret < 0)
+		goto err;
+
+	ret = pinctrl_select_state(host->pinctrl, host->active);
+	if (ret < 0)
+		goto err;
+
+	dev_info(mmc_dev(host->mmc), "pins configured for wake-up events\n");
+
+	return 0;
+
+err:
+	dev_err(mmc_dev(host->mmc), "pins configuration error: %i\n", ret);
+
+out:
+	return ret;
+}
+
 /*
  * Start clock to the card
  */
@@ -1854,7 +1917,6 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 	const struct of_device_id *match;
 	dma_cap_mask_t mask;
 	unsigned tx_req, rx_req;
-	struct pinctrl *pinctrl;
 
 	match = of_match_device(of_match_ptr(omap_mmc_of_match), &pdev->dev);
 	if (match) {
@@ -2086,21 +2148,19 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 
 	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 are doing
-	 * muxing of dat1 when booted with DT. This is because the
+	 * For now, only support SDIO interrupt if we are doing dynamic
+	 * remuxing of dat1 when booted with DT. This is because the
 	 * supposedly the wake-up events for CTPL don't work from deeper
 	 * idle states. 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.
 	 */
 	if (match) {
-		if (!IS_ERR(pinctrl) && mmc_slot(host).sdio_irq)
+		ret = omap_hsmmc_pin_init(host);
+		if (ret)
+			goto err_pinctrl_state;
+		else if (host->idle && mmc_slot(host).sdio_irq)
 			mmc->caps |= MMC_CAP_SDIO_IRQ;
 	}
 
@@ -2128,6 +2188,8 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 
 err_slot_name:
 	mmc_remove_host(mmc);
+err_pinctrl_state:
+	devm_pinctrl_put(host->pinctrl);
 	if ((mmc_slot(host).sdio_irq))
 		free_irq(mmc_slot(host).sdio_irq, host);
 err_irq_sdio:
@@ -2185,6 +2247,7 @@ static int omap_hsmmc_remove(struct platform_device *pdev)
 		dma_release_channel(host->tx_chan);
 	if (host->rx_chan)
 		dma_release_channel(host->rx_chan);
+	devm_pinctrl_put(host->pinctrl);
 
 	pm_runtime_put_sync(host->dev);
 	pm_runtime_disable(host->dev);
@@ -2320,6 +2383,12 @@ static int omap_hsmmc_runtime_suspend(struct device *dev)
 		OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
 		spin_unlock_irqrestore(&host->irq_lock, flags);
 
+		if (host->pinctrl && host->idle) {
+			ret = pinctrl_select_state(host->pinctrl, host->idle);
+			if (ret < 0)
+				dev_warn(mmc_dev(host->mmc), "Unable to select idle pinmux\n");
+		}
+
 		if (mmc_slot(host).sdio_irq)
 			enable_irq(mmc_slot(host).sdio_irq);
 	}
@@ -2343,6 +2412,12 @@ static int omap_hsmmc_runtime_resume(struct device *dev)
 		if (mmc_slot(host).sdio_irq)
 			disable_irq(mmc_slot(host).sdio_irq);
 
+		if (host->pinctrl && host->active) {
+			ret = pinctrl_select_state(host->pinctrl, host->active);
+			if (ret < 0)
+				dev_warn(mmc_dev(host->mmc), "Unable to select active pinmux\n");
+		}
+
 		spin_lock_irqsave(&host->irq_lock, flags);
 		host->active_pinmux = true;
 


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 3/4] mmc: omap_hsmmc: Remux pins to support SDIO interrupt and PM runtime
@ 2013-06-07 21:49   ` Tony Lindgren
  0 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-07 21:49 UTC (permalink / raw)
  To: linux-arm-kernel

On some omaps we need to remux MMC pins for PM, and for some omaps
we need to remux the SDIO IRQ pin.

Based on an earlier patch by Andreas Fenkart <afenkart@gmail.com>.

Cc: Andreas Fenkart <afenkart@gmail.com>
Cc: Balaji T K <balajitk@ti.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 drivers/mmc/host/omap_hsmmc.c |   93 +++++++++++++++++++++++++++++++++++++----
 1 file changed, 84 insertions(+), 9 deletions(-)

diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
index 7e28501..8ca08fb 100644
--- a/drivers/mmc/host/omap_hsmmc.c
+++ b/drivers/mmc/host/omap_hsmmc.c
@@ -185,6 +185,8 @@ struct omap_hsmmc_host {
 	int			req_in_progress;
 	struct omap_hsmmc_next	next_data;
 	bool			sdio_irq_en;
+	struct pinctrl		*pinctrl;
+	struct pinctrl_state	*fixed, *active, *idle;
 	bool			active_pinmux;
 	struct	omap_mmc_platform_data	*pdata;
 };
@@ -473,6 +475,67 @@ static void omap_hsmmc_gpio_free(struct omap_mmc_platform_data *pdata)
 		gpio_free(pdata->slots[0].gpio_cirq);
 }
 
+static int omap_hsmmc_pin_init(struct omap_hsmmc_host *host)
+{
+	int ret;
+
+	host->pinctrl = devm_pinctrl_get(host->dev);
+	if (IS_ERR(host->pinctrl)) {
+		dev_dbg(host->dev, "no pinctrl handle\n");
+		ret = 0;
+		goto out;
+	}
+
+	host->fixed = pinctrl_lookup_state(host->pinctrl,
+					   PINCTRL_STATE_DEFAULT);
+	if (IS_ERR(host->fixed)) {
+		dev_dbg(host->dev,
+			 "pins are not configured from the driver\n");
+		host->fixed = NULL;
+		ret = 0;
+		goto out;
+	}
+
+	ret = pinctrl_select_state(host->pinctrl, host->fixed);
+	if (ret < 0)
+		goto err;
+
+	/* For most cases we don't have wake-ups, and exit after this */
+	host->active = pinctrl_lookup_state(host->pinctrl, "active");
+	if (IS_ERR(host->active)) {
+		ret = PTR_ERR(host->active);
+		host->active = NULL;
+		return 0;
+	}
+
+	host->idle = pinctrl_lookup_state(host->pinctrl,
+					  PINCTRL_STATE_IDLE);
+	if (IS_ERR(host->idle)) {
+		ret = PTR_ERR(host->idle);
+		host->idle = NULL;
+		goto err;
+	}
+
+	/* Let's make sure the active and idle states work */
+	ret = pinctrl_select_state(host->pinctrl, host->idle);
+	if (ret < 0)
+		goto err;
+
+	ret = pinctrl_select_state(host->pinctrl, host->active);
+	if (ret < 0)
+		goto err;
+
+	dev_info(mmc_dev(host->mmc), "pins configured for wake-up events\n");
+
+	return 0;
+
+err:
+	dev_err(mmc_dev(host->mmc), "pins configuration error: %i\n", ret);
+
+out:
+	return ret;
+}
+
 /*
  * Start clock to the card
  */
@@ -1854,7 +1917,6 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 	const struct of_device_id *match;
 	dma_cap_mask_t mask;
 	unsigned tx_req, rx_req;
-	struct pinctrl *pinctrl;
 
 	match = of_match_device(of_match_ptr(omap_mmc_of_match), &pdev->dev);
 	if (match) {
@@ -2086,21 +2148,19 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 
 	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 are doing
-	 * muxing of dat1 when booted with DT. This is because the
+	 * For now, only support SDIO interrupt if we are doing dynamic
+	 * remuxing of dat1 when booted with DT. This is because the
 	 * supposedly the wake-up events for CTPL don't work from deeper
 	 * idle states. 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.
 	 */
 	if (match) {
-		if (!IS_ERR(pinctrl) && mmc_slot(host).sdio_irq)
+		ret = omap_hsmmc_pin_init(host);
+		if (ret)
+			goto err_pinctrl_state;
+		else if (host->idle && mmc_slot(host).sdio_irq)
 			mmc->caps |= MMC_CAP_SDIO_IRQ;
 	}
 
@@ -2128,6 +2188,8 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 
 err_slot_name:
 	mmc_remove_host(mmc);
+err_pinctrl_state:
+	devm_pinctrl_put(host->pinctrl);
 	if ((mmc_slot(host).sdio_irq))
 		free_irq(mmc_slot(host).sdio_irq, host);
 err_irq_sdio:
@@ -2185,6 +2247,7 @@ static int omap_hsmmc_remove(struct platform_device *pdev)
 		dma_release_channel(host->tx_chan);
 	if (host->rx_chan)
 		dma_release_channel(host->rx_chan);
+	devm_pinctrl_put(host->pinctrl);
 
 	pm_runtime_put_sync(host->dev);
 	pm_runtime_disable(host->dev);
@@ -2320,6 +2383,12 @@ static int omap_hsmmc_runtime_suspend(struct device *dev)
 		OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
 		spin_unlock_irqrestore(&host->irq_lock, flags);
 
+		if (host->pinctrl && host->idle) {
+			ret = pinctrl_select_state(host->pinctrl, host->idle);
+			if (ret < 0)
+				dev_warn(mmc_dev(host->mmc), "Unable to select idle pinmux\n");
+		}
+
 		if (mmc_slot(host).sdio_irq)
 			enable_irq(mmc_slot(host).sdio_irq);
 	}
@@ -2343,6 +2412,12 @@ static int omap_hsmmc_runtime_resume(struct device *dev)
 		if (mmc_slot(host).sdio_irq)
 			disable_irq(mmc_slot(host).sdio_irq);
 
+		if (host->pinctrl && host->active) {
+			ret = pinctrl_select_state(host->pinctrl, host->active);
+			if (ret < 0)
+				dev_warn(mmc_dev(host->mmc), "Unable to select active pinmux\n");
+		}
+
 		spin_lock_irqsave(&host->irq_lock, flags);
 		host->active_pinmux = true;
 

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 4/4] mmc: omap_hsmmc: debugfs entries for GPIO and SDIO mode
  2013-06-07 21:49 ` Tony Lindgren
@ 2013-06-07 21:50   ` Tony Lindgren
  -1 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-07 21:50 UTC (permalink / raw)
  To: cjb
  Cc: Balaji T K, Andreas Fenkart, Andreas Fenkart, linux-mmc,
	linux-omap, linux-arm-kernel

From: Andreas Fenkart <andreas.fenkart@streamunlimited.com>

Update the debugfs related code for the SDIO support.
Note that PSTATE shows current state of data lines.

Cc: Andreas Fenkart <afenkart@gmail.com>
Cc: Balaji T K <balajitk@ti.com>
Signed-off-by: Andreas Fenkart <andreas.fenkart@streamunlimited.com>
[tony@atomide.com: combined both sysfs patches into one]
---
 drivers/mmc/host/omap_hsmmc.c |   26 +++++++++++++++++++++++++-
 1 file changed, 25 insertions(+), 1 deletion(-)

diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
index 8ca08fb..d0d3df0 100644
--- a/drivers/mmc/host/omap_hsmmc.c
+++ b/drivers/mmc/host/omap_hsmmc.c
@@ -53,6 +53,7 @@
 #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
@@ -188,6 +189,7 @@ struct omap_hsmmc_host {
 	struct pinctrl		*pinctrl;
 	struct pinctrl_state	*fixed, *active, *idle;
 	bool			active_pinmux;
+	int			pm_suspend_ct;
 	struct	omap_mmc_platform_data	*pdata;
 };
 
@@ -1781,6 +1783,23 @@ static int omap_hsmmc_regs_show(struct seq_file *s, void *data)
 {
 	struct mmc_host *mmc = s->private;
 	struct omap_hsmmc_host *host = mmc_priv(mmc);
+	unsigned long flags;
+
+	if (mmc_slot(host).sdio_irq) {
+		spin_lock_irqsave(&host->irq_lock, flags);
+		seq_printf(s, "\n");
+		seq_printf(s, "pinmux config\t%s\n", host->active_pinmux ?
+			   "sdio" : "gpio");
+		seq_printf(s, "sdio irq\t%s\n", host->sdio_irq_en ? "enabled" :
+			   "disabled");
+		if (!host->active_pinmux) {
+			seq_printf(s, "sdio irq pin\t%s\n",
+				   gpio_get_value(mmc_slot(host).gpio_cirq) ?
+				   "high" : "low");
+		}
+		seq_printf(s, "pm suspends\t%d\n", host->pm_suspend_ct);
+		spin_unlock_irqrestore(&host->irq_lock, flags);
+	}
 
 	if (host->suspended) {
 		seq_printf(s, "host suspended, can't read registers\n");
@@ -1788,9 +1807,11 @@ static int omap_hsmmc_regs_show(struct seq_file *s, void *data)
 	}
 
 	pm_runtime_get_sync(host->dev);
-
+	seq_printf(s, "\nregs:\n");
 	seq_printf(s, "CON:\t\t0x%08x\n",
 			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",
 			OMAP_HSMMC_READ(host->base, HCTL));
 	seq_printf(s, "SYSCTL:\t\t0x%08x\n",
@@ -1970,6 +1991,7 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 	host->slot_id	= 0;
 	host->sdio_irq_en = false;
 	host->active_pinmux = true;
+	host->pm_suspend_ct = 0;
 	host->mapbase	= res->start + pdata->reg_offset;
 	host->base	= ioremap(host->mapbase, SZ_4K);
 	host->power_mode = MMC_POWER_OFF;
@@ -2378,6 +2400,8 @@ static int omap_hsmmc_runtime_suspend(struct device *dev)
 	if (mmc->caps & MMC_CAP_SDIO_IRQ) {
 		spin_lock_irqsave(&host->irq_lock, flags);
 		host->active_pinmux = false;
+		host->pm_suspend_ct++;
+
 		OMAP_HSMMC_WRITE(host->base, ISE, 0);
 		OMAP_HSMMC_WRITE(host->base, IE, 0);
 		OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 4/4] mmc: omap_hsmmc: debugfs entries for GPIO and SDIO mode
@ 2013-06-07 21:50   ` Tony Lindgren
  0 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-07 21:50 UTC (permalink / raw)
  To: linux-arm-kernel

From: Andreas Fenkart <andreas.fenkart@streamunlimited.com>

Update the debugfs related code for the SDIO support.
Note that PSTATE shows current state of data lines.

Cc: Andreas Fenkart <afenkart@gmail.com>
Cc: Balaji T K <balajitk@ti.com>
Signed-off-by: Andreas Fenkart <andreas.fenkart@streamunlimited.com>
[tony at atomide.com: combined both sysfs patches into one]
---
 drivers/mmc/host/omap_hsmmc.c |   26 +++++++++++++++++++++++++-
 1 file changed, 25 insertions(+), 1 deletion(-)

diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
index 8ca08fb..d0d3df0 100644
--- a/drivers/mmc/host/omap_hsmmc.c
+++ b/drivers/mmc/host/omap_hsmmc.c
@@ -53,6 +53,7 @@
 #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
@@ -188,6 +189,7 @@ struct omap_hsmmc_host {
 	struct pinctrl		*pinctrl;
 	struct pinctrl_state	*fixed, *active, *idle;
 	bool			active_pinmux;
+	int			pm_suspend_ct;
 	struct	omap_mmc_platform_data	*pdata;
 };
 
@@ -1781,6 +1783,23 @@ static int omap_hsmmc_regs_show(struct seq_file *s, void *data)
 {
 	struct mmc_host *mmc = s->private;
 	struct omap_hsmmc_host *host = mmc_priv(mmc);
+	unsigned long flags;
+
+	if (mmc_slot(host).sdio_irq) {
+		spin_lock_irqsave(&host->irq_lock, flags);
+		seq_printf(s, "\n");
+		seq_printf(s, "pinmux config\t%s\n", host->active_pinmux ?
+			   "sdio" : "gpio");
+		seq_printf(s, "sdio irq\t%s\n", host->sdio_irq_en ? "enabled" :
+			   "disabled");
+		if (!host->active_pinmux) {
+			seq_printf(s, "sdio irq pin\t%s\n",
+				   gpio_get_value(mmc_slot(host).gpio_cirq) ?
+				   "high" : "low");
+		}
+		seq_printf(s, "pm suspends\t%d\n", host->pm_suspend_ct);
+		spin_unlock_irqrestore(&host->irq_lock, flags);
+	}
 
 	if (host->suspended) {
 		seq_printf(s, "host suspended, can't read registers\n");
@@ -1788,9 +1807,11 @@ static int omap_hsmmc_regs_show(struct seq_file *s, void *data)
 	}
 
 	pm_runtime_get_sync(host->dev);
-
+	seq_printf(s, "\nregs:\n");
 	seq_printf(s, "CON:\t\t0x%08x\n",
 			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",
 			OMAP_HSMMC_READ(host->base, HCTL));
 	seq_printf(s, "SYSCTL:\t\t0x%08x\n",
@@ -1970,6 +1991,7 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 	host->slot_id	= 0;
 	host->sdio_irq_en = false;
 	host->active_pinmux = true;
+	host->pm_suspend_ct = 0;
 	host->mapbase	= res->start + pdata->reg_offset;
 	host->base	= ioremap(host->mapbase, SZ_4K);
 	host->power_mode = MMC_POWER_OFF;
@@ -2378,6 +2400,8 @@ static int omap_hsmmc_runtime_suspend(struct device *dev)
 	if (mmc->caps & MMC_CAP_SDIO_IRQ) {
 		spin_lock_irqsave(&host->irq_lock, flags);
 		host->active_pinmux = false;
+		host->pm_suspend_ct++;
+
 		OMAP_HSMMC_WRITE(host->base, ISE, 0);
 		OMAP_HSMMC_WRITE(host->base, IE, 0);
 		OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* Re: [PATCH 1/4] mmc: omap_hsmmc: Fix context save and restore for DT
  2013-06-07 21:49   ` Tony Lindgren
@ 2013-06-08  4:25     ` Felipe Balbi
  -1 siblings, 0 replies; 32+ messages in thread
From: Felipe Balbi @ 2013-06-08  4:25 UTC (permalink / raw)
  To: Tony Lindgren
  Cc: cjb, linux-omap, linux-mmc, Balaji T K, linux-arm-kernel,
	Andreas Fenkart

[-- Attachment #1: Type: text/plain, Size: 1046 bytes --]

Hi,

On Fri, Jun 07, 2013 at 02:49:50PM -0700, Tony Lindgren wrote:
> We want to get rid of the omap specific platform init code
> callbacks as they don't play nice with device tree.

right, any plans to have similar functionality generically ? Maybe
something like:

probe()
{
	...

	/*
	 * tell PM runtime layer that we can handle runtime
	 * power gating of this device in certain conditions.
	 *
	 * Actual power gating will be triggered depending on
	 * PM QoS wakeup requirements.
	 */
	pm_runtime_enable_power_gating(dev);

	...
}

runtime_suspend()
{
	if (pm_runtime_will_power_gate(dev))
		save_context();
}

runtime_resume()
{
	if (pm_runtime_has_lost_power(dev))
		restore_context();
}

???

> Let's convert the context loss check to be based on a
> register state detection instead.

that might not work always. Specially when you consider that default
register values can change on every silicon release. I guess the above
might be a bit nicer, although a lot more work :-p

-- 
balbi

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 1/4] mmc: omap_hsmmc: Fix context save and restore for DT
@ 2013-06-08  4:25     ` Felipe Balbi
  0 siblings, 0 replies; 32+ messages in thread
From: Felipe Balbi @ 2013-06-08  4:25 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

On Fri, Jun 07, 2013 at 02:49:50PM -0700, Tony Lindgren wrote:
> We want to get rid of the omap specific platform init code
> callbacks as they don't play nice with device tree.

right, any plans to have similar functionality generically ? Maybe
something like:

probe()
{
	...

	/*
	 * tell PM runtime layer that we can handle runtime
	 * power gating of this device in certain conditions.
	 *
	 * Actual power gating will be triggered depending on
	 * PM QoS wakeup requirements.
	 */
	pm_runtime_enable_power_gating(dev);

	...
}

runtime_suspend()
{
	if (pm_runtime_will_power_gate(dev))
		save_context();
}

runtime_resume()
{
	if (pm_runtime_has_lost_power(dev))
		restore_context();
}

???

> Let's convert the context loss check to be based on a
> register state detection instead.

that might not work always. Specially when you consider that default
register values can change on every silicon release. I guess the above
might be a bit nicer, although a lot more work :-p

-- 
balbi
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20130608/8ae16bad/attachment.sig>

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 1/4] mmc: omap_hsmmc: Fix context save and restore for DT
  2013-06-08  4:25     ` Felipe Balbi
@ 2013-06-08 15:02       ` Tony Lindgren
  -1 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-08 15:02 UTC (permalink / raw)
  To: Felipe Balbi
  Cc: cjb, linux-omap, linux-mmc, Balaji T K, linux-arm-kernel,
	Andreas Fenkart

* Felipe Balbi <balbi@ti.com> [130607 21:32]:
> Hi,
> 
> On Fri, Jun 07, 2013 at 02:49:50PM -0700, Tony Lindgren wrote:
> > We want to get rid of the omap specific platform init code
> > callbacks as they don't play nice with device tree.
> 
> right, any plans to have similar functionality generically ? Maybe
> something like:
> 
> probe()
> {
> 	...
> 
> 	/*
> 	 * tell PM runtime layer that we can handle runtime
> 	 * power gating of this device in certain conditions.
> 	 *
> 	 * Actual power gating will be triggered depending on
> 	 * PM QoS wakeup requirements.
> 	 */
> 	pm_runtime_enable_power_gating(dev);
> 
> 	...
> }
> 
> runtime_suspend()
> {
> 	if (pm_runtime_will_power_gate(dev))
> 		save_context();
> }
> 
> runtime_resume()
> {
> 	if (pm_runtime_has_lost_power(dev))
> 		restore_context();
> }
> 
> ???

Yes some kind of generic approach is needed too, at least for some
drivers. The above API makes sense to me, sounds like you might
have a PM runtime patch coming along those lines?
 
> > Let's convert the context loss check to be based on a
> > register state detection instead.
> 
> that might not work always. Specially when you consider that default
> register values can change on every silicon release. I guess the above
> might be a bit nicer, although a lot more work :-p

These are not revision registers, here the driver configured values
are saved in PM runtime suspend, and then restored in resume.
We are already doing the same for GPIO for example. In these cases the
context loss count is not needed AFAIK.

With some hardware it is of course possible that the power cycle is so
short that the hardare is stuck in a limbo state and for those cases
keeping track of the context loss via PM runtime.

Regards,

Tony

^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 1/4] mmc: omap_hsmmc: Fix context save and restore for DT
@ 2013-06-08 15:02       ` Tony Lindgren
  0 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-08 15:02 UTC (permalink / raw)
  To: linux-arm-kernel

* Felipe Balbi <balbi@ti.com> [130607 21:32]:
> Hi,
> 
> On Fri, Jun 07, 2013 at 02:49:50PM -0700, Tony Lindgren wrote:
> > We want to get rid of the omap specific platform init code
> > callbacks as they don't play nice with device tree.
> 
> right, any plans to have similar functionality generically ? Maybe
> something like:
> 
> probe()
> {
> 	...
> 
> 	/*
> 	 * tell PM runtime layer that we can handle runtime
> 	 * power gating of this device in certain conditions.
> 	 *
> 	 * Actual power gating will be triggered depending on
> 	 * PM QoS wakeup requirements.
> 	 */
> 	pm_runtime_enable_power_gating(dev);
> 
> 	...
> }
> 
> runtime_suspend()
> {
> 	if (pm_runtime_will_power_gate(dev))
> 		save_context();
> }
> 
> runtime_resume()
> {
> 	if (pm_runtime_has_lost_power(dev))
> 		restore_context();
> }
> 
> ???

Yes some kind of generic approach is needed too, at least for some
drivers. The above API makes sense to me, sounds like you might
have a PM runtime patch coming along those lines?
 
> > Let's convert the context loss check to be based on a
> > register state detection instead.
> 
> that might not work always. Specially when you consider that default
> register values can change on every silicon release. I guess the above
> might be a bit nicer, although a lot more work :-p

These are not revision registers, here the driver configured values
are saved in PM runtime suspend, and then restored in resume.
We are already doing the same for GPIO for example. In these cases the
context loss count is not needed AFAIK.

With some hardware it is of course possible that the power cycle is so
short that the hardare is stuck in a limbo state and for those cases
keeping track of the context loss via PM runtime.

Regards,

Tony

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 3/4] mmc: omap_hsmmc: Remux pins to support SDIO interrupt and PM runtime
  2013-06-07 21:49   ` Tony Lindgren
@ 2013-06-10 16:03     ` Linus Walleij
  -1 siblings, 0 replies; 32+ messages in thread
From: Linus Walleij @ 2013-06-10 16:03 UTC (permalink / raw)
  To: Tony Lindgren
  Cc: Chris Ball, Balaji T K, Andreas Fenkart, linux-mmc, Linux-OMAP,
	linux-arm-kernel

On Fri, Jun 7, 2013 at 11:49 PM, Tony Lindgren <tony@atomide.com> wrote:

> On some omaps we need to remux MMC pins for PM, and for some omaps
> we need to remux the SDIO IRQ pin.
>
> Based on an earlier patch by Andreas Fenkart <afenkart@gmail.com>.
(...)
> +       host->pinctrl = devm_pinctrl_get(host->dev);
> +       if (IS_ERR(host->pinctrl)) {
> +               dev_dbg(host->dev, "no pinctrl handle\n");
> +               ret = 0;
> +               goto out;
> +       }
> +
> +       host->fixed = pinctrl_lookup_state(host->pinctrl,
> +                                          PINCTRL_STATE_DEFAULT);
> +       if (IS_ERR(host->fixed)) {
> +               dev_dbg(host->dev,
> +                        "pins are not configured from the driver\n");
> +               host->fixed = NULL;
> +               ret = 0;
> +               goto out;
> +       }
> +
> +       ret = pinctrl_select_state(host->pinctrl, host->fixed);
> +       if (ret < 0)
> +               goto err;
> +
> +       /* For most cases we don't have wake-ups, and exit after this */
> +       host->active = pinctrl_lookup_state(host->pinctrl, "active");
> +       if (IS_ERR(host->active)) {
> +               ret = PTR_ERR(host->active);
> +               host->active = NULL;
> +               return 0;
> +       }
> +
> +       host->idle = pinctrl_lookup_state(host->pinctrl,
> +                                         PINCTRL_STATE_IDLE);
> +       if (IS_ERR(host->idle)) {
> +               ret = PTR_ERR(host->idle);
> +               host->idle = NULL;
> +               goto err;
> +       }

You can use the new infrastructure to make the core select:

pinctrl_pm_select_default_state(host->dev);
pinctrl_pm_select_idle_state(host->dev);

What is the semantic difference between "default" and "active"?

If this is something very generic that a lot of platforms will want
to have, why not add it to include/linux/pinctrl/pinctrl-state.h
and augment the core to cache and handle this too?

However in this case I *suspect* that what you really want
to do it to rename the state called "default" to "sleep"
(it appears the default state is sleepy) and then rename
the "active" state to "default" (as this is the defined semantic
meaning of "default" from <linux/pinctrl/pinctrl-state.h>.

But maybe I'm not quite getting the subtle difference between
"default" and "active" here so enlighten me.

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 3/4] mmc: omap_hsmmc: Remux pins to support SDIO interrupt and PM runtime
@ 2013-06-10 16:03     ` Linus Walleij
  0 siblings, 0 replies; 32+ messages in thread
From: Linus Walleij @ 2013-06-10 16:03 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, Jun 7, 2013 at 11:49 PM, Tony Lindgren <tony@atomide.com> wrote:

> On some omaps we need to remux MMC pins for PM, and for some omaps
> we need to remux the SDIO IRQ pin.
>
> Based on an earlier patch by Andreas Fenkart <afenkart@gmail.com>.
(...)
> +       host->pinctrl = devm_pinctrl_get(host->dev);
> +       if (IS_ERR(host->pinctrl)) {
> +               dev_dbg(host->dev, "no pinctrl handle\n");
> +               ret = 0;
> +               goto out;
> +       }
> +
> +       host->fixed = pinctrl_lookup_state(host->pinctrl,
> +                                          PINCTRL_STATE_DEFAULT);
> +       if (IS_ERR(host->fixed)) {
> +               dev_dbg(host->dev,
> +                        "pins are not configured from the driver\n");
> +               host->fixed = NULL;
> +               ret = 0;
> +               goto out;
> +       }
> +
> +       ret = pinctrl_select_state(host->pinctrl, host->fixed);
> +       if (ret < 0)
> +               goto err;
> +
> +       /* For most cases we don't have wake-ups, and exit after this */
> +       host->active = pinctrl_lookup_state(host->pinctrl, "active");
> +       if (IS_ERR(host->active)) {
> +               ret = PTR_ERR(host->active);
> +               host->active = NULL;
> +               return 0;
> +       }
> +
> +       host->idle = pinctrl_lookup_state(host->pinctrl,
> +                                         PINCTRL_STATE_IDLE);
> +       if (IS_ERR(host->idle)) {
> +               ret = PTR_ERR(host->idle);
> +               host->idle = NULL;
> +               goto err;
> +       }

You can use the new infrastructure to make the core select:

pinctrl_pm_select_default_state(host->dev);
pinctrl_pm_select_idle_state(host->dev);

What is the semantic difference between "default" and "active"?

If this is something very generic that a lot of platforms will want
to have, why not add it to include/linux/pinctrl/pinctrl-state.h
and augment the core to cache and handle this too?

However in this case I *suspect* that what you really want
to do it to rename the state called "default" to "sleep"
(it appears the default state is sleepy) and then rename
the "active" state to "default" (as this is the defined semantic
meaning of "default" from <linux/pinctrl/pinctrl-state.h>.

But maybe I'm not quite getting the subtle difference between
"default" and "active" here so enlighten me.

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 3/4] mmc: omap_hsmmc: Remux pins to support SDIO interrupt and PM runtime
  2013-06-10 16:03     ` Linus Walleij
@ 2013-06-10 16:23       ` Tony Lindgren
  -1 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-10 16:23 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Chris Ball, Balaji T K, Andreas Fenkart, linux-mmc, Linux-OMAP,
	linux-arm-kernel

* Linus Walleij <linus.walleij@linaro.org> [130610 09:09]:
> 
> You can use the new infrastructure to make the core select:
> 
> pinctrl_pm_select_default_state(host->dev);
> pinctrl_pm_select_idle_state(host->dev);

OK great.
 
> What is the semantic difference between "default" and "active"?

We only should remux the pins that need remuxing as that's done
every time hitting idle. So I think we should have the following
default groups:

default		Static pins that don't change, no need to remux
		configured in consumer driver probe like we already
		do

active		Optional dynamic pins remuxed for runtime, can be
		configured and selected in consumer driver probe.
		The consumer driver may also want to select this
		state in PM runtime resume.

idle		Optional dynamic pins remuxed for idle. The consumer
		driver may also want to select this state in PM
		runtime suspend depending on device_can_wakeup()
		and driver specific needs.
 
> If this is something very generic that a lot of platforms will want
> to have, why not add it to include/linux/pinctrl/pinctrl-state.h
> and augment the core to cache and handle this too?

Yes we should do that assuming the above grouping makes sense
to you.
 
> However in this case I *suspect* that what you really want
> to do it to rename the state called "default" to "sleep"
> (it appears the default state is sleepy) and then rename
> the "active" state to "default" (as this is the defined semantic
> meaning of "default" from <linux/pinctrl/pinctrl-state.h>.

The idle state above could also be called sleep instead of idle
if you prefer that.

> But maybe I'm not quite getting the subtle difference between
> "default" and "active" here so enlighten me.

I think the confusion is caused by the fact that we need three
mux groups, not just two :) The toggling between active and idle
is the hotpath as that can potentially happen for multiple drivers
every time we enter and exit idle.

Regards,

Tony

^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 3/4] mmc: omap_hsmmc: Remux pins to support SDIO interrupt and PM runtime
@ 2013-06-10 16:23       ` Tony Lindgren
  0 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-10 16:23 UTC (permalink / raw)
  To: linux-arm-kernel

* Linus Walleij <linus.walleij@linaro.org> [130610 09:09]:
> 
> You can use the new infrastructure to make the core select:
> 
> pinctrl_pm_select_default_state(host->dev);
> pinctrl_pm_select_idle_state(host->dev);

OK great.
 
> What is the semantic difference between "default" and "active"?

We only should remux the pins that need remuxing as that's done
every time hitting idle. So I think we should have the following
default groups:

default		Static pins that don't change, no need to remux
		configured in consumer driver probe like we already
		do

active		Optional dynamic pins remuxed for runtime, can be
		configured and selected in consumer driver probe.
		The consumer driver may also want to select this
		state in PM runtime resume.

idle		Optional dynamic pins remuxed for idle. The consumer
		driver may also want to select this state in PM
		runtime suspend depending on device_can_wakeup()
		and driver specific needs.
 
> If this is something very generic that a lot of platforms will want
> to have, why not add it to include/linux/pinctrl/pinctrl-state.h
> and augment the core to cache and handle this too?

Yes we should do that assuming the above grouping makes sense
to you.
 
> However in this case I *suspect* that what you really want
> to do it to rename the state called "default" to "sleep"
> (it appears the default state is sleepy) and then rename
> the "active" state to "default" (as this is the defined semantic
> meaning of "default" from <linux/pinctrl/pinctrl-state.h>.

The idle state above could also be called sleep instead of idle
if you prefer that.

> But maybe I'm not quite getting the subtle difference between
> "default" and "active" here so enlighten me.

I think the confusion is caused by the fact that we need three
mux groups, not just two :) The toggling between active and idle
is the hotpath as that can potentially happen for multiple drivers
every time we enter and exit idle.

Regards,

Tony

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 3/4] mmc: omap_hsmmc: Remux pins to support SDIO interrupt and PM runtime
  2013-06-10 16:23       ` Tony Lindgren
@ 2013-06-11  7:54         ` Linus Walleij
  -1 siblings, 0 replies; 32+ messages in thread
From: Linus Walleij @ 2013-06-11  7:54 UTC (permalink / raw)
  To: Tony Lindgren, Kevin Hilman
  Cc: Chris Ball, Balaji T K, Andreas Fenkart, linux-mmc, Linux-OMAP,
	linux-arm-kernel, Mark Brown, Dmitry Torokhov

On Mon, Jun 10, 2013 at 6:23 PM, Tony Lindgren <tony@atomide.com> wrote:

> We only should remux the pins that need remuxing as that's done
> every time hitting idle. So I think we should have the following
> default groups:
>
> default         Static pins that don't change, no need to remux
>                 configured in consumer driver probe like we already
>                 do
>
> active          Optional dynamic pins remuxed for runtime, can be
>                 configured and selected in consumer driver probe.
>                 The consumer driver may also want to select this
>                 state in PM runtime resume.
>
> idle            Optional dynamic pins remuxed for idle. The consumer
>                 driver may also want to select this state in PM
>                 runtime suspend depending on device_can_wakeup()
>                 and driver specific needs.

The one thing I don't understand is why a driver would select the
active state in probe(), unless it's a driver that does not support
runtime PM. (But maybe that's what you mean.)

Compare this to <linus/pinctrl/pinctrl-state.h>:

/**
 * @PINCTRL_STATE_DEFAULT: the state the pinctrl handle shall be put
 *      into as default, usually this means the pins are up and ready to
 *      be used by the device driver. This state is commonly used by
 *      hogs to configure muxing and pins at boot, and also as a state
 *      to go into when returning from sleep and idle in
 *      .pm_runtime_resume() or ordinary .resume() for example.
 * @PINCTRL_STATE_IDLE: the state the pinctrl handle shall be put into
 *      when the pins are idle. This is a state where the system is relaxed
 *      but not fully sleeping - some power may be on but clocks gated for
 *      example. Could typically be set from a pm_runtime_suspend() or
 *      pm_runtime_idle() operation.
 * @PINCTRL_STATE_SLEEP: the state the pinctrl handle shall be put into
 *      when the pins are sleeping. This is a state where the system is in
 *      its lowest sleep state. Could typically be set from an
 *      ordinary .suspend() function.
 */
#define PINCTRL_STATE_DEFAULT "default"
#define PINCTRL_STATE_IDLE "idle"
#define PINCTRL_STATE_SLEEP "sleep"

The way I currently use these in e.g.
drivers/spi/spi-pl022 is:

probe:
 -> default

runtime_suspend:
 -> idle

runtime_resume:
 -> default

suspend:
 -> sleep

resume:
  -> default
  -> idle

Notice that we go to default then idle on probe and
runtime resume. This is because the idle state is
optional (as is the sleep state).

So I guess if we should extend this terminology to match
what you are using for the OMAP it would rather be like
this:

probe:
 -> default

runtime_suspend:
 -> idle

runtime_resume:
 -> default
 -> active

suspend:
 -> sleep

resume:
  -> default
  -> idle

Just one more optional "active" state in runtime resume.
Correct?

If we can agree on this I will add the active state to the
state table and add a container in the core for this as well
as pinctrl_pm_select_active_state() so we can skip all the
pointless boilerplate also in the OMAP drivers, plus increase
the readability and portability quite a bit.

>> However in this case I *suspect* that what you really want
>> to do it to rename the state called "default" to "sleep"
>> (it appears the default state is sleepy) and then rename
>> the "active" state to "default" (as this is the defined semantic
>> meaning of "default" from <linux/pinctrl/pinctrl-state.h>.
>
> The idle state above could also be called sleep instead of idle
> if you prefer that.

No I think we should reserve that name for the pin state
associated with suspend(). Let's leave it like this.

> I think the confusion is caused by the fact that we need three
> mux groups, not just two :) The toggling between active and idle
> is the hotpath as that can potentially happen for multiple drivers
> every time we enter and exit idle.

Actually we have the same thing, it's just that our "default"
and "active" are the same thing. But it seems we need to
add your granularity to this.

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 3/4] mmc: omap_hsmmc: Remux pins to support SDIO interrupt and PM runtime
@ 2013-06-11  7:54         ` Linus Walleij
  0 siblings, 0 replies; 32+ messages in thread
From: Linus Walleij @ 2013-06-11  7:54 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Jun 10, 2013 at 6:23 PM, Tony Lindgren <tony@atomide.com> wrote:

> We only should remux the pins that need remuxing as that's done
> every time hitting idle. So I think we should have the following
> default groups:
>
> default         Static pins that don't change, no need to remux
>                 configured in consumer driver probe like we already
>                 do
>
> active          Optional dynamic pins remuxed for runtime, can be
>                 configured and selected in consumer driver probe.
>                 The consumer driver may also want to select this
>                 state in PM runtime resume.
>
> idle            Optional dynamic pins remuxed for idle. The consumer
>                 driver may also want to select this state in PM
>                 runtime suspend depending on device_can_wakeup()
>                 and driver specific needs.

The one thing I don't understand is why a driver would select the
active state in probe(), unless it's a driver that does not support
runtime PM. (But maybe that's what you mean.)

Compare this to <linus/pinctrl/pinctrl-state.h>:

/**
 * @PINCTRL_STATE_DEFAULT: the state the pinctrl handle shall be put
 *      into as default, usually this means the pins are up and ready to
 *      be used by the device driver. This state is commonly used by
 *      hogs to configure muxing and pins at boot, and also as a state
 *      to go into when returning from sleep and idle in
 *      .pm_runtime_resume() or ordinary .resume() for example.
 * @PINCTRL_STATE_IDLE: the state the pinctrl handle shall be put into
 *      when the pins are idle. This is a state where the system is relaxed
 *      but not fully sleeping - some power may be on but clocks gated for
 *      example. Could typically be set from a pm_runtime_suspend() or
 *      pm_runtime_idle() operation.
 * @PINCTRL_STATE_SLEEP: the state the pinctrl handle shall be put into
 *      when the pins are sleeping. This is a state where the system is in
 *      its lowest sleep state. Could typically be set from an
 *      ordinary .suspend() function.
 */
#define PINCTRL_STATE_DEFAULT "default"
#define PINCTRL_STATE_IDLE "idle"
#define PINCTRL_STATE_SLEEP "sleep"

The way I currently use these in e.g.
drivers/spi/spi-pl022 is:

probe:
 -> default

runtime_suspend:
 -> idle

runtime_resume:
 -> default

suspend:
 -> sleep

resume:
  -> default
  -> idle

Notice that we go to default then idle on probe and
runtime resume. This is because the idle state is
optional (as is the sleep state).

So I guess if we should extend this terminology to match
what you are using for the OMAP it would rather be like
this:

probe:
 -> default

runtime_suspend:
 -> idle

runtime_resume:
 -> default
 -> active

suspend:
 -> sleep

resume:
  -> default
  -> idle

Just one more optional "active" state in runtime resume.
Correct?

If we can agree on this I will add the active state to the
state table and add a container in the core for this as well
as pinctrl_pm_select_active_state() so we can skip all the
pointless boilerplate also in the OMAP drivers, plus increase
the readability and portability quite a bit.

>> However in this case I *suspect* that what you really want
>> to do it to rename the state called "default" to "sleep"
>> (it appears the default state is sleepy) and then rename
>> the "active" state to "default" (as this is the defined semantic
>> meaning of "default" from <linux/pinctrl/pinctrl-state.h>.
>
> The idle state above could also be called sleep instead of idle
> if you prefer that.

No I think we should reserve that name for the pin state
associated with suspend(). Let's leave it like this.

> I think the confusion is caused by the fact that we need three
> mux groups, not just two :) The toggling between active and idle
> is the hotpath as that can potentially happen for multiple drivers
> every time we enter and exit idle.

Actually we have the same thing, it's just that our "default"
and "active" are the same thing. But it seems we need to
add your granularity to this.

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 3/4] mmc: omap_hsmmc: Remux pins to support SDIO interrupt and PM runtime
  2013-06-11  7:54         ` Linus Walleij
@ 2013-06-12 13:21           ` Tony Lindgren
  -1 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-12 13:21 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Kevin Hilman, Chris Ball, Balaji T K, Andreas Fenkart, linux-mmc,
	Linux-OMAP, linux-arm-kernel, Mark Brown, Dmitry Torokhov

* Linus Walleij <linus.walleij@linaro.org> [130611 01:00]:
> On Mon, Jun 10, 2013 at 6:23 PM, Tony Lindgren <tony@atomide.com> wrote:
> 
> > We only should remux the pins that need remuxing as that's done
> > every time hitting idle. So I think we should have the following
> > default groups:
> >
> > default         Static pins that don't change, no need to remux
> >                 configured in consumer driver probe like we already
> >                 do
> >
> > active          Optional dynamic pins remuxed for runtime, can be
> >                 configured and selected in consumer driver probe.
> >                 The consumer driver may also want to select this
> >                 state in PM runtime resume.
> >
> > idle            Optional dynamic pins remuxed for idle. The consumer
> >                 driver may also want to select this state in PM
> >                 runtime suspend depending on device_can_wakeup()
> >                 and driver specific needs.
> 
> The one thing I don't understand is why a driver would select the
> active state in probe(), unless it's a driver that does not support
> runtime PM. (But maybe that's what you mean.)

Yes you're right, there should not be any need to select active state
in probe, that should be selected by PM runtime.
 
> Compare this to <linus/pinctrl/pinctrl-state.h>:
> 
> /**
>  * @PINCTRL_STATE_DEFAULT: the state the pinctrl handle shall be put
>  *      into as default, usually this means the pins are up and ready to
>  *      be used by the device driver. This state is commonly used by
>  *      hogs to configure muxing and pins at boot, and also as a state
>  *      to go into when returning from sleep and idle in
>  *      .pm_runtime_resume() or ordinary .resume() for example.
>  * @PINCTRL_STATE_IDLE: the state the pinctrl handle shall be put into
>  *      when the pins are idle. This is a state where the system is relaxed
>  *      but not fully sleeping - some power may be on but clocks gated for
>  *      example. Could typically be set from a pm_runtime_suspend() or
>  *      pm_runtime_idle() operation.
>  * @PINCTRL_STATE_SLEEP: the state the pinctrl handle shall be put into
>  *      when the pins are sleeping. This is a state where the system is in
>  *      its lowest sleep state. Could typically be set from an
>  *      ordinary .suspend() function.
>  */
> #define PINCTRL_STATE_DEFAULT "default"
> #define PINCTRL_STATE_IDLE "idle"
> #define PINCTRL_STATE_SLEEP "sleep"
> 
> The way I currently use these in e.g.
> drivers/spi/spi-pl022 is:
> 
> probe:
>  -> default
> 
> runtime_suspend:
>  -> idle
> 
> runtime_resume:
>  -> default
> 
> suspend:
>  -> sleep
> 
> resume:
>   -> default
>   -> idle
> 
> Notice that we go to default then idle on probe and
> runtime resume. This is because the idle state is
> optional (as is the sleep state).
> 
> So I guess if we should extend this terminology to match
> what you are using for the OMAP it would rather be like
> this:
> 
> probe:
>  -> default
> 
> runtime_suspend:
>  -> idle
> 
> runtime_resume:
>  -> default
>  -> active

At least for omaps, there's no need to select default in
runtime_resume as the default pins stay that way.
 
> suspend:
>  -> sleep

For omaps, we would just select idle pins again in the
suspend case.
 
> resume:
>   -> default
>   -> idle

And for omaps, there's no need to select default in resume
either. Just selecting active would do the trick for resume.

So for omaps, the sequence would be:

probe:
 -> default (typically all device pins except rx pin)

runtime_suspend:
suspend:
 -> idle (remux rx pin from device to gpio input for wake)
 
runtime_resume:
resume:
 -> active (remux rx pin from gpio input to device)
 
> Just one more optional "active" state in runtime resume.
> Correct?

Yes the "active" is needed, but "sleep" would be unused for
omaps.
 
> If we can agree on this I will add the active state to the
> state table and add a container in the core for this as well
> as pinctrl_pm_select_active_state() so we can skip all the
> pointless boilerplate also in the OMAP drivers, plus increase
> the readability and portability quite a bit.

Sounds good to me as long as we don't always need to select
the default pins over and over in PM runtime_resume.
 
> >> However in this case I *suspect* that what you really want
> >> to do it to rename the state called "default" to "sleep"
> >> (it appears the default state is sleepy) and then rename
> >> the "active" state to "default" (as this is the defined semantic
> >> meaning of "default" from <linux/pinctrl/pinctrl-state.h>.
> >
> > The idle state above could also be called sleep instead of idle
> > if you prefer that.
> 
> No I think we should reserve that name for the pin state
> associated with suspend(). Let's leave it like this.

OK
 
> > I think the confusion is caused by the fact that we need three
> > mux groups, not just two :) The toggling between active and idle
> > is the hotpath as that can potentially happen for multiple drivers
> > every time we enter and exit idle.
> 
> Actually we have the same thing, it's just that our "default"
> and "active" are the same thing. But it seems we need to
> add your granularity to this.

Well the difference seems to be that you need to remux all the
device pins for runtime_suspend and resume while in most of the
cases I know of only one device pins needs to be toggled and the
rest can be selected in driver probe.

Regards,

Tony

^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 3/4] mmc: omap_hsmmc: Remux pins to support SDIO interrupt and PM runtime
@ 2013-06-12 13:21           ` Tony Lindgren
  0 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-12 13:21 UTC (permalink / raw)
  To: linux-arm-kernel

* Linus Walleij <linus.walleij@linaro.org> [130611 01:00]:
> On Mon, Jun 10, 2013 at 6:23 PM, Tony Lindgren <tony@atomide.com> wrote:
> 
> > We only should remux the pins that need remuxing as that's done
> > every time hitting idle. So I think we should have the following
> > default groups:
> >
> > default         Static pins that don't change, no need to remux
> >                 configured in consumer driver probe like we already
> >                 do
> >
> > active          Optional dynamic pins remuxed for runtime, can be
> >                 configured and selected in consumer driver probe.
> >                 The consumer driver may also want to select this
> >                 state in PM runtime resume.
> >
> > idle            Optional dynamic pins remuxed for idle. The consumer
> >                 driver may also want to select this state in PM
> >                 runtime suspend depending on device_can_wakeup()
> >                 and driver specific needs.
> 
> The one thing I don't understand is why a driver would select the
> active state in probe(), unless it's a driver that does not support
> runtime PM. (But maybe that's what you mean.)

Yes you're right, there should not be any need to select active state
in probe, that should be selected by PM runtime.
 
> Compare this to <linus/pinctrl/pinctrl-state.h>:
> 
> /**
>  * @PINCTRL_STATE_DEFAULT: the state the pinctrl handle shall be put
>  *      into as default, usually this means the pins are up and ready to
>  *      be used by the device driver. This state is commonly used by
>  *      hogs to configure muxing and pins at boot, and also as a state
>  *      to go into when returning from sleep and idle in
>  *      .pm_runtime_resume() or ordinary .resume() for example.
>  * @PINCTRL_STATE_IDLE: the state the pinctrl handle shall be put into
>  *      when the pins are idle. This is a state where the system is relaxed
>  *      but not fully sleeping - some power may be on but clocks gated for
>  *      example. Could typically be set from a pm_runtime_suspend() or
>  *      pm_runtime_idle() operation.
>  * @PINCTRL_STATE_SLEEP: the state the pinctrl handle shall be put into
>  *      when the pins are sleeping. This is a state where the system is in
>  *      its lowest sleep state. Could typically be set from an
>  *      ordinary .suspend() function.
>  */
> #define PINCTRL_STATE_DEFAULT "default"
> #define PINCTRL_STATE_IDLE "idle"
> #define PINCTRL_STATE_SLEEP "sleep"
> 
> The way I currently use these in e.g.
> drivers/spi/spi-pl022 is:
> 
> probe:
>  -> default
> 
> runtime_suspend:
>  -> idle
> 
> runtime_resume:
>  -> default
> 
> suspend:
>  -> sleep
> 
> resume:
>   -> default
>   -> idle
> 
> Notice that we go to default then idle on probe and
> runtime resume. This is because the idle state is
> optional (as is the sleep state).
> 
> So I guess if we should extend this terminology to match
> what you are using for the OMAP it would rather be like
> this:
> 
> probe:
>  -> default
> 
> runtime_suspend:
>  -> idle
> 
> runtime_resume:
>  -> default
>  -> active

At least for omaps, there's no need to select default in
runtime_resume as the default pins stay that way.
 
> suspend:
>  -> sleep

For omaps, we would just select idle pins again in the
suspend case.
 
> resume:
>   -> default
>   -> idle

And for omaps, there's no need to select default in resume
either. Just selecting active would do the trick for resume.

So for omaps, the sequence would be:

probe:
 -> default (typically all device pins except rx pin)

runtime_suspend:
suspend:
 -> idle (remux rx pin from device to gpio input for wake)
 
runtime_resume:
resume:
 -> active (remux rx pin from gpio input to device)
 
> Just one more optional "active" state in runtime resume.
> Correct?

Yes the "active" is needed, but "sleep" would be unused for
omaps.
 
> If we can agree on this I will add the active state to the
> state table and add a container in the core for this as well
> as pinctrl_pm_select_active_state() so we can skip all the
> pointless boilerplate also in the OMAP drivers, plus increase
> the readability and portability quite a bit.

Sounds good to me as long as we don't always need to select
the default pins over and over in PM runtime_resume.
 
> >> However in this case I *suspect* that what you really want
> >> to do it to rename the state called "default" to "sleep"
> >> (it appears the default state is sleepy) and then rename
> >> the "active" state to "default" (as this is the defined semantic
> >> meaning of "default" from <linux/pinctrl/pinctrl-state.h>.
> >
> > The idle state above could also be called sleep instead of idle
> > if you prefer that.
> 
> No I think we should reserve that name for the pin state
> associated with suspend(). Let's leave it like this.

OK
 
> > I think the confusion is caused by the fact that we need three
> > mux groups, not just two :) The toggling between active and idle
> > is the hotpath as that can potentially happen for multiple drivers
> > every time we enter and exit idle.
> 
> Actually we have the same thing, it's just that our "default"
> and "active" are the same thing. But it seems we need to
> add your granularity to this.

Well the difference seems to be that you need to remux all the
device pins for runtime_suspend and resume while in most of the
cases I know of only one device pins needs to be toggled and the
rest can be selected in driver probe.

Regards,

Tony

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 2/4] mmc: omap_hsmmc: Enable SDIO IRQ using a GPIO in idle mode
  2013-06-07 21:49   ` Tony Lindgren
@ 2013-06-14  7:37     ` Tony Lindgren
  -1 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-14  7:37 UTC (permalink / raw)
  To: cjb
  Cc: Balaji T K, Andreas Fenkart, Andreas Fenkart, linux-mmc,
	Grant Likely, linux-omap, linux-arm-kernel

* Tony Lindgren <tony@atomide.com> [130607 14:56]:
> From: Andreas Fenkart <andreas.fenkart@streamunlimited.com>
> --- a/drivers/mmc/host/omap_hsmmc.c
> +++ b/drivers/mmc/host/omap_hsmmc.c
>  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, need to signal cirq if */
> +	if (host->sdio_irq_en)
> +		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);
> +
> +	spin_unlock_irqrestore(&host->irq_lock, flags);
>  }

Found one bug in Andreas' patch. Keeping the CIRQ_EN enabled here
is wrong. I've noticed that it causes eternal sdio interrupt loops
for example when running iw dev wlan0 scan when omap is hitting
idle states.

As we already have a separate GPIO interrupt muxed for the
idle states, there's no reason to keep CIRQ_EN set.

Updated patch below without the CIRQ_EN set.

Regards,

Tony


From: Andreas Fenkart <andreas.fenkart@streamunlimited.com>
Date: Thu, 13 Jun 2013 23:19:09 -0700
Subject: [PATCH] mmc: omap_hsmmc: Enable SDIO IRQ using a GPIO in idle mode

Without functional clock the omap_hsmmc module can't forward SDIO IRQs to
the system. This patch reconfigures dat1 line as a gpio while the fclk is
off. When the fclk is present it uses the standard SDIO IRQ detection of
the module.

The gpio irq is managed via the 'disable_depth' ref counter of the irq
subsystem, this driver simply calls enable_irq/disable_irq when needed.

                      sdio irq    sdio irq
                      unmasked     masked
   -----------------------------------------
    runtime default  |    1     |   2
    runtime suspend  |    0     |   1

                  irq disable depth


only when sdio irq is enabled AND the module is idle, the reference
count drops to zero and the gpio irq is effectively armed.

Patch was tested on AM335x/Stream800. Test setup was two modules
with sdio wifi cards. Modules where connected to a dual-band AP, each
module using a different band. One of module was running iperf as server
the other as client connecting to the server in a while true loop. Test
was running for 4+ weeks. There were about 60 Mio. suspend/resume
transitions. Test was shut down regularly.

Cc: Andreas Fenkart <afenkart@gmail.com>
Cc: Balaji T K <balajitk@ti.com>
Signed-off-by: Andreas Fenkart <andreas.fenkart@streamunlimited.com>
Reviewed-by: Grant Likely <grant.likely@secretlab.ca>
[tony@atomide.com: updated and separated out pin muxing]
Signed-off-by: Tony Lindgren <tony@atomide.com>

diff --git a/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt b/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt
index ed271fc..5a3df37 100644
--- a/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt
+++ b/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt
@@ -20,6 +20,29 @@ ti,dual-volt: boolean, supports dual voltage cards
 ti,non-removable: non-removable slot (like eMMC)
 ti,needs-special-reset: Requires a special softreset sequence
 ti,needs-special-hs-handling: HSMMC IP needs special setting for handling High Speed
+ti,cirq-gpio: When omap_hsmmc module is suspended, its functional
+clock is turned off. Without fclk it can't forward SDIO IRQs to the
+system. For that to happen, it needs to tell the PRCM to restore
+its fclk, which is done through the swakeup line.
+
+                   ------
+                  | PRCM |
+                   ------
+                    | ^
+               fclk | | swakeup
+                    v |
+                  -------               ------
+      <-- IRQ -- | hsmmc | <-- CIRQ -- | card |
+                  -------               ------
+
+The problem is, that on the AM335x family the swakeup line is
+missing, it has not been routed from the module to the PRCM.
+The way to work around this, is to reconfigure the dat1 line as a
+GPIO upon suspend. Beyond this option you also need to set named
+states "default" and "idle "in the .dts file for the pins, using
+pinctrl-single.c. The MMC driver will then then toggle between
+default and idle during the runtime.
+
 
 Example:
 	mmc1: mmc@0x4809c000 {
@@ -31,3 +54,22 @@ Example:
 		vmmc-supply = <&vmmc>; /* phandle to regulator node */
 		ti,non-removable;
 	};
+
+[am335x with with gpio for sdio irq]
+
+	mmc1_cirq_pin: pinmux_cirq_pin {
+		pinctrl-single,pins = <
+			0x0f8 0x3f	/* MMC0_DAT1 as GPIO2_28 */
+		>;
+	};
+
+	mmc1: mmc@48060000 {
+		pinctrl-names = "default", "idle";
+		pinctrl-0 = <&mmc1_pins>;
+		pinctrl-1 = <&mmc1_cirq_pin>;
+		ti,cirq-gpio = <&gpio3 28 0>;
+		ti,non-removable;
+		bus-width = <4>;
+		vmmc-supply = <&ldo2_reg>;
+		vmmc_aux-supply = <&vmmc>;
+	};
diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
index 478849b..35a6288 100644
--- a/drivers/mmc/host/omap_hsmmc.c
+++ b/drivers/mmc/host/omap_hsmmc.c
@@ -22,6 +22,7 @@
 #include <linux/dmaengine.h>
 #include <linux/seq_file.h>
 #include <linux/interrupt.h>
+#include <linux/irq.h>
 #include <linux/delay.h>
 #include <linux/dma-mapping.h>
 #include <linux/platform_device.h>
@@ -102,6 +103,7 @@
 #define TC_EN			(1 << 1)
 #define BWR_EN			(1 << 4)
 #define BRR_EN			(1 << 5)
+#define CIRQ_EN			(1 << 8)
 #define ERR_EN			(1 << 15)
 #define CTO_EN			(1 << 16)
 #define CCRC_EN			(1 << 17)
@@ -182,9 +184,19 @@ struct omap_hsmmc_host {
 	int			use_reg;
 	int			req_in_progress;
 	struct omap_hsmmc_next	next_data;
+	bool			sdio_irq_en;
+	bool			active_pinmux;
 	struct	omap_mmc_platform_data	*pdata;
 };
 
+static irqreturn_t omap_hsmmc_cirq(int irq, void *dev_id)
+{
+	struct omap_hsmmc_host *host = dev_id;
+
+	mmc_signal_sdio_irq(host->mmc);
+	return IRQ_HANDLED;
+}
+
 static int omap_hsmmc_card_detect(struct device *dev, int slot)
 {
 	struct omap_hsmmc_host *host = dev_get_drvdata(dev);
@@ -419,10 +431,31 @@ static int omap_hsmmc_gpio_init(struct omap_mmc_platform_data *pdata)
 	} else
 		pdata->slots[0].gpio_wp = -EINVAL;
 
+	if (pdata->slots[0].gpio_cirq > 0 &&
+	    gpio_is_valid(pdata->slots[0].gpio_cirq)) {
+		pdata->slots[0].sdio_irq =
+				gpio_to_irq(pdata->slots[0].gpio_cirq);
+
+		ret = gpio_request(pdata->slots[0].gpio_cirq, "sdio_cirq");
+		if (ret)
+			goto err_free_ro;
+		ret = gpio_direction_input(pdata->slots[0].gpio_cirq);
+		if (ret)
+			goto err_free_cirq;
+
+	} else {
+		pdata->slots[0].gpio_cirq = -EINVAL;
+	}
+
+
 	return 0;
 
+err_free_cirq:
+	gpio_free(pdata->slots[0].gpio_cirq);
+err_free_ro:
+	if (gpio_is_valid(pdata->slots[0].gpio_wp))
 err_free_wp:
-	gpio_free(pdata->slots[0].gpio_wp);
+		gpio_free(pdata->slots[0].gpio_wp);
 err_free_cd:
 	if (gpio_is_valid(pdata->slots[0].switch_pin))
 err_free_sp:
@@ -436,6 +469,8 @@ static void omap_hsmmc_gpio_free(struct omap_mmc_platform_data *pdata)
 		gpio_free(pdata->slots[0].gpio_wp);
 	if (gpio_is_valid(pdata->slots[0].switch_pin))
 		gpio_free(pdata->slots[0].switch_pin);
+	if (gpio_is_valid(pdata->slots[0].gpio_cirq))
+		gpio_free(pdata->slots[0].gpio_cirq);
 }
 
 /*
@@ -461,27 +496,39 @@ static void omap_hsmmc_stop_clock(struct omap_hsmmc_host *host)
 static void omap_hsmmc_enable_irq(struct omap_hsmmc_host *host,
 				  struct mmc_command *cmd)
 {
-	unsigned int irq_mask;
+	u32 irq_mask = INT_EN_MASK;
+	unsigned long flags;
 
 	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 */
 	if (cmd->opcode == MMC_ERASE)
 		irq_mask &= ~DTO_EN;
 
+	spin_lock_irqsave(&host->irq_lock, flags);
+
 	OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
 	OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
+
+	/* latch pending CIRQ, but don't signal */
+	if (host->sdio_irq_en)
+		irq_mask |= CIRQ_EN;
+
 	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)
 {
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->irq_lock, flags);
 	OMAP_HSMMC_WRITE(host->base, ISE, 0);
 	OMAP_HSMMC_WRITE(host->base, IE, 0);
 	OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
+	spin_unlock_irqrestore(&host->irq_lock, flags);
 }
 
 /* Calculate divisor for the given clock frequency */
@@ -1037,8 +1084,12 @@ static irqreturn_t omap_hsmmc_irq(int irq, void *dev_id)
 	int status;
 
 	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 */
 		OMAP_HSMMC_WRITE(host->base, STAT, status);
@@ -1554,6 +1605,51 @@ static void omap_hsmmc_init_card(struct mmc_host *mmc, struct mmc_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;
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->irq_lock, flags);
+
+	if (host->sdio_irq_en == enable) {
+		dev_dbg(host->dev, "en/disable:%d already set", enable);
+		spin_unlock_irqrestore(&host->irq_lock, flags);
+		return;
+	}
+
+	host->sdio_irq_en = (enable != 0) ? true : false;
+
+	if (host->active_pinmux) { /* register access fails without fclk */
+		irq_mask = OMAP_HSMMC_READ(host->base, ISE);
+		if (enable)
+			irq_mask |= CIRQ_EN;
+		else
+			irq_mask &= ~CIRQ_EN;
+		OMAP_HSMMC_WRITE(host->base, IE, irq_mask);
+
+		if (!host->req_in_progress)
+			OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
+
+		/*
+		 * evtl. need to flush posted write
+		 * OMAP_HSMMC_READ(host->base, IE);
+		 */
+	}
+
+	if ((mmc_slot(host).sdio_irq)) {
+		if (enable) {
+			enable_irq(mmc_slot(host).sdio_irq);
+		} else {
+			/* _nosync, see mmc_signal_sdio_irq */
+			disable_irq_nosync(mmc_slot(host).sdio_irq);
+		}
+	}
+
+	spin_unlock_irqrestore(&host->irq_lock, flags);
+}
+
 static void omap_hsmmc_conf_bus_power(struct omap_hsmmc_host *host)
 {
 	u32 hctl, capa, value;
@@ -1606,7 +1702,7 @@ static const struct mmc_host_ops omap_hsmmc_ops = {
 	.get_cd = omap_hsmmc_get_cd,
 	.get_ro = omap_hsmmc_get_ro,
 	.init_card = omap_hsmmc_init_card,
-	/* NYET -- enable_sdio_irq */
+	.enable_sdio_irq = omap_hsmmc_enable_sdio_irq,
 };
 
 #ifdef CONFIG_DEBUG_FS
@@ -1710,6 +1806,7 @@ static struct omap_mmc_platform_data *of_get_hsmmc_pdata(struct device *dev)
 	pdata->nr_slots = 1;
 	pdata->slots[0].switch_pin = cd_gpio;
 	pdata->slots[0].gpio_wp = wp_gpio;
+	pdata->slots[0].gpio_cirq = of_get_named_gpio(np, "ti,cirq-gpio", 0);
 
 	if (of_find_property(np, "ti,non-removable", NULL)) {
 		pdata->slots[0].nonremovable = true;
@@ -1802,6 +1899,8 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 	host->dma_ch	= -1;
 	host->irq	= irq;
 	host->slot_id	= 0;
+	host->sdio_irq_en = false;
+	host->active_pinmux = true;
 	host->mapbase	= res->start + pdata->reg_offset;
 	host->base	= ioremap(host->mapbase, SZ_4K);
 	host->power_mode = MMC_POWER_OFF;
@@ -1955,6 +2054,29 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 		pdata->resume = omap_hsmmc_resume_cdirq;
 	}
 
+	if ((mmc_slot(host).sdio_irq)) {
+		/* prevent auto-enabling of IRQ */
+		irq_set_status_flags(mmc_slot(host).sdio_irq, IRQ_NOAUTOEN);
+		ret = request_irq(mmc_slot(host).sdio_irq, omap_hsmmc_cirq,
+				  IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+				  mmc_hostname(mmc), host);
+		if (ret) {
+			dev_dbg(mmc_dev(host->mmc),
+				"Unable to grab MMC SDIO IRQ\n");
+			goto err_irq_sdio;
+		}
+
+		/*
+		 * sdio_irq is managed with ref count
+		 * - omap_hsmmc_enable_sdio_irq will +1/-1
+		 * - pm_suspend/pm_resume will +1/-1
+		 * only when sdio irq is enabled AND module will go to runtime
+		 * suspend the ref count will drop to zero and the irq is
+		 * effectively enabled. starting with ref count equal 2
+		 */
+		disable_irq(mmc_slot(host).sdio_irq);
+	}
+
 	omap_hsmmc_disable_irq(host);
 
 	pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
@@ -1962,6 +2084,19 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 		dev_warn(&pdev->dev,
 			"pins are not configured from the driver\n");
 
+	/*
+	 * For now, only support SDIO interrupt if we are doing
+	 * muxing of dat1 when booted with DT. This is because the
+	 * supposedly the wake-up events for CTPL don't work from deeper
+	 * idle states. 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.
+	 */
+	if (match) {
+		if (!IS_ERR(pinctrl) && mmc_slot(host).sdio_irq)
+			mmc->caps |= MMC_CAP_SDIO_IRQ;
+	}
+
 	omap_hsmmc_protect_card(host);
 
 	mmc_add_host(mmc);
@@ -1986,6 +2121,9 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 
 err_slot_name:
 	mmc_remove_host(mmc);
+	if ((mmc_slot(host).sdio_irq))
+		free_irq(mmc_slot(host).sdio_irq, host);
+err_irq_sdio:
 	free_irq(mmc_slot(host).card_detect_irq, host);
 err_irq_cd:
 	if (host->use_reg)
@@ -2032,9 +2170,10 @@ static int omap_hsmmc_remove(struct platform_device *pdev)
 	if (host->pdata->cleanup)
 		host->pdata->cleanup(&pdev->dev);
 	free_irq(host->irq, host);
+	if ((mmc_slot(host).sdio_irq))
+		free_irq(mmc_slot(host).sdio_irq, host);
 	if (mmc_slot(host).card_detect_irq)
 		free_irq(mmc_slot(host).card_detect_irq, host);
-
 	if (host->tx_chan)
 		dma_release_channel(host->tx_chan);
 	if (host->rx_chan)
@@ -2157,23 +2296,57 @@ static int omap_hsmmc_resume(struct device *dev)
 static int omap_hsmmc_runtime_suspend(struct device *dev)
 {
 	struct omap_hsmmc_host *host;
+	struct mmc_host *mmc;
+	unsigned long flags;
+	int ret = 0;
 
 	host = platform_get_drvdata(to_platform_device(dev));
+	mmc = host->mmc;
 	omap_hsmmc_context_save(host);
 	dev_dbg(dev, "disabled\n");
 
-	return 0;
+	if (mmc->caps & MMC_CAP_SDIO_IRQ) {
+		spin_lock_irqsave(&host->irq_lock, flags);
+		host->active_pinmux = false;
+		OMAP_HSMMC_WRITE(host->base, ISE, 0);
+		OMAP_HSMMC_WRITE(host->base, IE, 0);
+		OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
+		spin_unlock_irqrestore(&host->irq_lock, flags);
+
+		if (mmc_slot(host).sdio_irq)
+			enable_irq(mmc_slot(host).sdio_irq);
+	}
+
+	return ret;
 }
 
 static int omap_hsmmc_runtime_resume(struct device *dev)
 {
 	struct omap_hsmmc_host *host;
+	struct mmc_host *mmc;
+	unsigned long flags;
+	int ret = 0;
 
 	host = platform_get_drvdata(to_platform_device(dev));
+	mmc = host->mmc;
 	omap_hsmmc_context_restore(host);
 	dev_dbg(dev, "enabled\n");
 
-	return 0;
+	if (mmc->caps & MMC_CAP_SDIO_IRQ) {
+		if (mmc_slot(host).sdio_irq)
+			disable_irq(mmc_slot(host).sdio_irq);
+
+		spin_lock_irqsave(&host->irq_lock, flags);
+		host->active_pinmux = true;
+
+		if (host->sdio_irq_en) {
+			OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
+			OMAP_HSMMC_WRITE(host->base, ISE, CIRQ_EN);
+			OMAP_HSMMC_WRITE(host->base, IE, CIRQ_EN);
+		}
+		spin_unlock_irqrestore(&host->irq_lock, flags);
+	}
+	return ret;
 }
 
 static struct dev_pm_ops omap_hsmmc_dev_pm_ops = {
diff --git a/include/linux/platform_data/mmc-omap.h b/include/linux/platform_data/mmc-omap.h
index 2bf1b30..fd5fff5 100644
--- a/include/linux/platform_data/mmc-omap.h
+++ b/include/linux/platform_data/mmc-omap.h
@@ -115,6 +115,7 @@ struct omap_mmc_platform_data {
 
 		int switch_pin;			/* gpio (card detect) */
 		int gpio_wp;			/* gpio (write protect) */
+		int gpio_cirq;			/* gpio (card irq) */
 
 		int (*set_bus_mode)(struct device *dev, int slot, int bus_mode);
 		int (*set_power)(struct device *dev, int slot,
@@ -145,6 +146,9 @@ struct omap_mmc_platform_data {
 		int card_detect_irq;
 		int (*card_detect)(struct device *dev, int slot);
 
+		/* SDIO IRQs */
+		int sdio_irq;
+
 		unsigned int ban_openended:1;
 
 	} slots[OMAP_MMC_MAX_SLOTS];

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 2/4] mmc: omap_hsmmc: Enable SDIO IRQ using a GPIO in idle mode
@ 2013-06-14  7:37     ` Tony Lindgren
  0 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-14  7:37 UTC (permalink / raw)
  To: linux-arm-kernel

* Tony Lindgren <tony@atomide.com> [130607 14:56]:
> From: Andreas Fenkart <andreas.fenkart@streamunlimited.com>
> --- a/drivers/mmc/host/omap_hsmmc.c
> +++ b/drivers/mmc/host/omap_hsmmc.c
>  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, need to signal cirq if */
> +	if (host->sdio_irq_en)
> +		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);
> +
> +	spin_unlock_irqrestore(&host->irq_lock, flags);
>  }

Found one bug in Andreas' patch. Keeping the CIRQ_EN enabled here
is wrong. I've noticed that it causes eternal sdio interrupt loops
for example when running iw dev wlan0 scan when omap is hitting
idle states.

As we already have a separate GPIO interrupt muxed for the
idle states, there's no reason to keep CIRQ_EN set.

Updated patch below without the CIRQ_EN set.

Regards,

Tony


From: Andreas Fenkart <andreas.fenkart@streamunlimited.com>
Date: Thu, 13 Jun 2013 23:19:09 -0700
Subject: [PATCH] mmc: omap_hsmmc: Enable SDIO IRQ using a GPIO in idle mode

Without functional clock the omap_hsmmc module can't forward SDIO IRQs to
the system. This patch reconfigures dat1 line as a gpio while the fclk is
off. When the fclk is present it uses the standard SDIO IRQ detection of
the module.

The gpio irq is managed via the 'disable_depth' ref counter of the irq
subsystem, this driver simply calls enable_irq/disable_irq when needed.

                      sdio irq    sdio irq
                      unmasked     masked
   -----------------------------------------
    runtime default  |    1     |   2
    runtime suspend  |    0     |   1

                  irq disable depth


only when sdio irq is enabled AND the module is idle, the reference
count drops to zero and the gpio irq is effectively armed.

Patch was tested on AM335x/Stream800. Test setup was two modules
with sdio wifi cards. Modules where connected to a dual-band AP, each
module using a different band. One of module was running iperf as server
the other as client connecting to the server in a while true loop. Test
was running for 4+ weeks. There were about 60 Mio. suspend/resume
transitions. Test was shut down regularly.

Cc: Andreas Fenkart <afenkart@gmail.com>
Cc: Balaji T K <balajitk@ti.com>
Signed-off-by: Andreas Fenkart <andreas.fenkart@streamunlimited.com>
Reviewed-by: Grant Likely <grant.likely@secretlab.ca>
[tony at atomide.com: updated and separated out pin muxing]
Signed-off-by: Tony Lindgren <tony@atomide.com>

diff --git a/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt b/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt
index ed271fc..5a3df37 100644
--- a/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt
+++ b/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt
@@ -20,6 +20,29 @@ ti,dual-volt: boolean, supports dual voltage cards
 ti,non-removable: non-removable slot (like eMMC)
 ti,needs-special-reset: Requires a special softreset sequence
 ti,needs-special-hs-handling: HSMMC IP needs special setting for handling High Speed
+ti,cirq-gpio: When omap_hsmmc module is suspended, its functional
+clock is turned off. Without fclk it can't forward SDIO IRQs to the
+system. For that to happen, it needs to tell the PRCM to restore
+its fclk, which is done through the swakeup line.
+
+                   ------
+                  | PRCM |
+                   ------
+                    | ^
+               fclk | | swakeup
+                    v |
+                  -------               ------
+      <-- IRQ -- | hsmmc | <-- CIRQ -- | card |
+                  -------               ------
+
+The problem is, that on the AM335x family the swakeup line is
+missing, it has not been routed from the module to the PRCM.
+The way to work around this, is to reconfigure the dat1 line as a
+GPIO upon suspend. Beyond this option you also need to set named
+states "default" and "idle "in the .dts file for the pins, using
+pinctrl-single.c. The MMC driver will then then toggle between
+default and idle during the runtime.
+
 
 Example:
 	mmc1: mmc@0x4809c000 {
@@ -31,3 +54,22 @@ Example:
 		vmmc-supply = <&vmmc>; /* phandle to regulator node */
 		ti,non-removable;
 	};
+
+[am335x with with gpio for sdio irq]
+
+	mmc1_cirq_pin: pinmux_cirq_pin {
+		pinctrl-single,pins = <
+			0x0f8 0x3f	/* MMC0_DAT1 as GPIO2_28 */
+		>;
+	};
+
+	mmc1: mmc at 48060000 {
+		pinctrl-names = "default", "idle";
+		pinctrl-0 = <&mmc1_pins>;
+		pinctrl-1 = <&mmc1_cirq_pin>;
+		ti,cirq-gpio = <&gpio3 28 0>;
+		ti,non-removable;
+		bus-width = <4>;
+		vmmc-supply = <&ldo2_reg>;
+		vmmc_aux-supply = <&vmmc>;
+	};
diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
index 478849b..35a6288 100644
--- a/drivers/mmc/host/omap_hsmmc.c
+++ b/drivers/mmc/host/omap_hsmmc.c
@@ -22,6 +22,7 @@
 #include <linux/dmaengine.h>
 #include <linux/seq_file.h>
 #include <linux/interrupt.h>
+#include <linux/irq.h>
 #include <linux/delay.h>
 #include <linux/dma-mapping.h>
 #include <linux/platform_device.h>
@@ -102,6 +103,7 @@
 #define TC_EN			(1 << 1)
 #define BWR_EN			(1 << 4)
 #define BRR_EN			(1 << 5)
+#define CIRQ_EN			(1 << 8)
 #define ERR_EN			(1 << 15)
 #define CTO_EN			(1 << 16)
 #define CCRC_EN			(1 << 17)
@@ -182,9 +184,19 @@ struct omap_hsmmc_host {
 	int			use_reg;
 	int			req_in_progress;
 	struct omap_hsmmc_next	next_data;
+	bool			sdio_irq_en;
+	bool			active_pinmux;
 	struct	omap_mmc_platform_data	*pdata;
 };
 
+static irqreturn_t omap_hsmmc_cirq(int irq, void *dev_id)
+{
+	struct omap_hsmmc_host *host = dev_id;
+
+	mmc_signal_sdio_irq(host->mmc);
+	return IRQ_HANDLED;
+}
+
 static int omap_hsmmc_card_detect(struct device *dev, int slot)
 {
 	struct omap_hsmmc_host *host = dev_get_drvdata(dev);
@@ -419,10 +431,31 @@ static int omap_hsmmc_gpio_init(struct omap_mmc_platform_data *pdata)
 	} else
 		pdata->slots[0].gpio_wp = -EINVAL;
 
+	if (pdata->slots[0].gpio_cirq > 0 &&
+	    gpio_is_valid(pdata->slots[0].gpio_cirq)) {
+		pdata->slots[0].sdio_irq =
+				gpio_to_irq(pdata->slots[0].gpio_cirq);
+
+		ret = gpio_request(pdata->slots[0].gpio_cirq, "sdio_cirq");
+		if (ret)
+			goto err_free_ro;
+		ret = gpio_direction_input(pdata->slots[0].gpio_cirq);
+		if (ret)
+			goto err_free_cirq;
+
+	} else {
+		pdata->slots[0].gpio_cirq = -EINVAL;
+	}
+
+
 	return 0;
 
+err_free_cirq:
+	gpio_free(pdata->slots[0].gpio_cirq);
+err_free_ro:
+	if (gpio_is_valid(pdata->slots[0].gpio_wp))
 err_free_wp:
-	gpio_free(pdata->slots[0].gpio_wp);
+		gpio_free(pdata->slots[0].gpio_wp);
 err_free_cd:
 	if (gpio_is_valid(pdata->slots[0].switch_pin))
 err_free_sp:
@@ -436,6 +469,8 @@ static void omap_hsmmc_gpio_free(struct omap_mmc_platform_data *pdata)
 		gpio_free(pdata->slots[0].gpio_wp);
 	if (gpio_is_valid(pdata->slots[0].switch_pin))
 		gpio_free(pdata->slots[0].switch_pin);
+	if (gpio_is_valid(pdata->slots[0].gpio_cirq))
+		gpio_free(pdata->slots[0].gpio_cirq);
 }
 
 /*
@@ -461,27 +496,39 @@ static void omap_hsmmc_stop_clock(struct omap_hsmmc_host *host)
 static void omap_hsmmc_enable_irq(struct omap_hsmmc_host *host,
 				  struct mmc_command *cmd)
 {
-	unsigned int irq_mask;
+	u32 irq_mask = INT_EN_MASK;
+	unsigned long flags;
 
 	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 */
 	if (cmd->opcode == MMC_ERASE)
 		irq_mask &= ~DTO_EN;
 
+	spin_lock_irqsave(&host->irq_lock, flags);
+
 	OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
 	OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
+
+	/* latch pending CIRQ, but don't signal */
+	if (host->sdio_irq_en)
+		irq_mask |= CIRQ_EN;
+
 	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)
 {
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->irq_lock, flags);
 	OMAP_HSMMC_WRITE(host->base, ISE, 0);
 	OMAP_HSMMC_WRITE(host->base, IE, 0);
 	OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
+	spin_unlock_irqrestore(&host->irq_lock, flags);
 }
 
 /* Calculate divisor for the given clock frequency */
@@ -1037,8 +1084,12 @@ static irqreturn_t omap_hsmmc_irq(int irq, void *dev_id)
 	int status;
 
 	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 */
 		OMAP_HSMMC_WRITE(host->base, STAT, status);
@@ -1554,6 +1605,51 @@ static void omap_hsmmc_init_card(struct mmc_host *mmc, struct mmc_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;
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->irq_lock, flags);
+
+	if (host->sdio_irq_en == enable) {
+		dev_dbg(host->dev, "en/disable:%d already set", enable);
+		spin_unlock_irqrestore(&host->irq_lock, flags);
+		return;
+	}
+
+	host->sdio_irq_en = (enable != 0) ? true : false;
+
+	if (host->active_pinmux) { /* register access fails without fclk */
+		irq_mask = OMAP_HSMMC_READ(host->base, ISE);
+		if (enable)
+			irq_mask |= CIRQ_EN;
+		else
+			irq_mask &= ~CIRQ_EN;
+		OMAP_HSMMC_WRITE(host->base, IE, irq_mask);
+
+		if (!host->req_in_progress)
+			OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
+
+		/*
+		 * evtl. need to flush posted write
+		 * OMAP_HSMMC_READ(host->base, IE);
+		 */
+	}
+
+	if ((mmc_slot(host).sdio_irq)) {
+		if (enable) {
+			enable_irq(mmc_slot(host).sdio_irq);
+		} else {
+			/* _nosync, see mmc_signal_sdio_irq */
+			disable_irq_nosync(mmc_slot(host).sdio_irq);
+		}
+	}
+
+	spin_unlock_irqrestore(&host->irq_lock, flags);
+}
+
 static void omap_hsmmc_conf_bus_power(struct omap_hsmmc_host *host)
 {
 	u32 hctl, capa, value;
@@ -1606,7 +1702,7 @@ static const struct mmc_host_ops omap_hsmmc_ops = {
 	.get_cd = omap_hsmmc_get_cd,
 	.get_ro = omap_hsmmc_get_ro,
 	.init_card = omap_hsmmc_init_card,
-	/* NYET -- enable_sdio_irq */
+	.enable_sdio_irq = omap_hsmmc_enable_sdio_irq,
 };
 
 #ifdef CONFIG_DEBUG_FS
@@ -1710,6 +1806,7 @@ static struct omap_mmc_platform_data *of_get_hsmmc_pdata(struct device *dev)
 	pdata->nr_slots = 1;
 	pdata->slots[0].switch_pin = cd_gpio;
 	pdata->slots[0].gpio_wp = wp_gpio;
+	pdata->slots[0].gpio_cirq = of_get_named_gpio(np, "ti,cirq-gpio", 0);
 
 	if (of_find_property(np, "ti,non-removable", NULL)) {
 		pdata->slots[0].nonremovable = true;
@@ -1802,6 +1899,8 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 	host->dma_ch	= -1;
 	host->irq	= irq;
 	host->slot_id	= 0;
+	host->sdio_irq_en = false;
+	host->active_pinmux = true;
 	host->mapbase	= res->start + pdata->reg_offset;
 	host->base	= ioremap(host->mapbase, SZ_4K);
 	host->power_mode = MMC_POWER_OFF;
@@ -1955,6 +2054,29 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 		pdata->resume = omap_hsmmc_resume_cdirq;
 	}
 
+	if ((mmc_slot(host).sdio_irq)) {
+		/* prevent auto-enabling of IRQ */
+		irq_set_status_flags(mmc_slot(host).sdio_irq, IRQ_NOAUTOEN);
+		ret = request_irq(mmc_slot(host).sdio_irq, omap_hsmmc_cirq,
+				  IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+				  mmc_hostname(mmc), host);
+		if (ret) {
+			dev_dbg(mmc_dev(host->mmc),
+				"Unable to grab MMC SDIO IRQ\n");
+			goto err_irq_sdio;
+		}
+
+		/*
+		 * sdio_irq is managed with ref count
+		 * - omap_hsmmc_enable_sdio_irq will +1/-1
+		 * - pm_suspend/pm_resume will +1/-1
+		 * only when sdio irq is enabled AND module will go to runtime
+		 * suspend the ref count will drop to zero and the irq is
+		 * effectively enabled. starting with ref count equal 2
+		 */
+		disable_irq(mmc_slot(host).sdio_irq);
+	}
+
 	omap_hsmmc_disable_irq(host);
 
 	pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
@@ -1962,6 +2084,19 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 		dev_warn(&pdev->dev,
 			"pins are not configured from the driver\n");
 
+	/*
+	 * For now, only support SDIO interrupt if we are doing
+	 * muxing of dat1 when booted with DT. This is because the
+	 * supposedly the wake-up events for CTPL don't work from deeper
+	 * idle states. 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.
+	 */
+	if (match) {
+		if (!IS_ERR(pinctrl) && mmc_slot(host).sdio_irq)
+			mmc->caps |= MMC_CAP_SDIO_IRQ;
+	}
+
 	omap_hsmmc_protect_card(host);
 
 	mmc_add_host(mmc);
@@ -1986,6 +2121,9 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 
 err_slot_name:
 	mmc_remove_host(mmc);
+	if ((mmc_slot(host).sdio_irq))
+		free_irq(mmc_slot(host).sdio_irq, host);
+err_irq_sdio:
 	free_irq(mmc_slot(host).card_detect_irq, host);
 err_irq_cd:
 	if (host->use_reg)
@@ -2032,9 +2170,10 @@ static int omap_hsmmc_remove(struct platform_device *pdev)
 	if (host->pdata->cleanup)
 		host->pdata->cleanup(&pdev->dev);
 	free_irq(host->irq, host);
+	if ((mmc_slot(host).sdio_irq))
+		free_irq(mmc_slot(host).sdio_irq, host);
 	if (mmc_slot(host).card_detect_irq)
 		free_irq(mmc_slot(host).card_detect_irq, host);
-
 	if (host->tx_chan)
 		dma_release_channel(host->tx_chan);
 	if (host->rx_chan)
@@ -2157,23 +2296,57 @@ static int omap_hsmmc_resume(struct device *dev)
 static int omap_hsmmc_runtime_suspend(struct device *dev)
 {
 	struct omap_hsmmc_host *host;
+	struct mmc_host *mmc;
+	unsigned long flags;
+	int ret = 0;
 
 	host = platform_get_drvdata(to_platform_device(dev));
+	mmc = host->mmc;
 	omap_hsmmc_context_save(host);
 	dev_dbg(dev, "disabled\n");
 
-	return 0;
+	if (mmc->caps & MMC_CAP_SDIO_IRQ) {
+		spin_lock_irqsave(&host->irq_lock, flags);
+		host->active_pinmux = false;
+		OMAP_HSMMC_WRITE(host->base, ISE, 0);
+		OMAP_HSMMC_WRITE(host->base, IE, 0);
+		OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
+		spin_unlock_irqrestore(&host->irq_lock, flags);
+
+		if (mmc_slot(host).sdio_irq)
+			enable_irq(mmc_slot(host).sdio_irq);
+	}
+
+	return ret;
 }
 
 static int omap_hsmmc_runtime_resume(struct device *dev)
 {
 	struct omap_hsmmc_host *host;
+	struct mmc_host *mmc;
+	unsigned long flags;
+	int ret = 0;
 
 	host = platform_get_drvdata(to_platform_device(dev));
+	mmc = host->mmc;
 	omap_hsmmc_context_restore(host);
 	dev_dbg(dev, "enabled\n");
 
-	return 0;
+	if (mmc->caps & MMC_CAP_SDIO_IRQ) {
+		if (mmc_slot(host).sdio_irq)
+			disable_irq(mmc_slot(host).sdio_irq);
+
+		spin_lock_irqsave(&host->irq_lock, flags);
+		host->active_pinmux = true;
+
+		if (host->sdio_irq_en) {
+			OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
+			OMAP_HSMMC_WRITE(host->base, ISE, CIRQ_EN);
+			OMAP_HSMMC_WRITE(host->base, IE, CIRQ_EN);
+		}
+		spin_unlock_irqrestore(&host->irq_lock, flags);
+	}
+	return ret;
 }
 
 static struct dev_pm_ops omap_hsmmc_dev_pm_ops = {
diff --git a/include/linux/platform_data/mmc-omap.h b/include/linux/platform_data/mmc-omap.h
index 2bf1b30..fd5fff5 100644
--- a/include/linux/platform_data/mmc-omap.h
+++ b/include/linux/platform_data/mmc-omap.h
@@ -115,6 +115,7 @@ struct omap_mmc_platform_data {
 
 		int switch_pin;			/* gpio (card detect) */
 		int gpio_wp;			/* gpio (write protect) */
+		int gpio_cirq;			/* gpio (card irq) */
 
 		int (*set_bus_mode)(struct device *dev, int slot, int bus_mode);
 		int (*set_power)(struct device *dev, int slot,
@@ -145,6 +146,9 @@ struct omap_mmc_platform_data {
 		int card_detect_irq;
 		int (*card_detect)(struct device *dev, int slot);
 
+		/* SDIO IRQs */
+		int sdio_irq;
+
 		unsigned int ban_openended:1;
 
 	} slots[OMAP_MMC_MAX_SLOTS];

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* Re: [PATCH 3/4] mmc: omap_hsmmc: Remux pins to support SDIO interrupt and PM runtime
  2013-06-12 13:21           ` Tony Lindgren
@ 2013-06-14  7:40             ` Tony Lindgren
  -1 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-14  7:40 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Kevin Hilman, Chris Ball, Balaji T K, Andreas Fenkart, linux-mmc,
	Linux-OMAP, linux-arm-kernel, Mark Brown, Dmitry Torokhov

* Tony Lindgren <tony@atomide.com> [130612 06:27]:
> * Linus Walleij <linus.walleij@linaro.org> [130611 01:00]:
> > If we can agree on this I will add the active state to the
> > state table and add a container in the core for this as well
> > as pinctrl_pm_select_active_state() so we can skip all the
> > pointless boilerplate also in the OMAP drivers, plus increase
> > the readability and portability quite a bit.
> 
> Sounds good to me as long as we don't always need to select
> the default pins over and over in PM runtime_resume.

Here's this patch updated for pinctrl next with the active state
added. This patch should be merged separately towards the end
of the merge window once pinctrl code has been merged. The other
three patches can be merged independently.

Regards,

Tony


From: Tony Lindgren <tony@atomide.com>
Date: Thu, 13 Jun 2013 23:19:09 -0700
Subject: [PATCH] mmc: omap_hsmmc: Remux pins to support SDIO interrupt and PM runtime

On some omaps we need to remux MMC pins for PM, and for some omaps
we need to remux the SDIO IRQ pin.

Based on an earlier patch by Andreas Fenkart <afenkart@gmail.com>.

Cc: Andreas Fenkart <afenkart@gmail.com>
Cc: Balaji T K <balajitk@ti.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Tony Lindgren <tony@atomide.com>

diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
index 6377836..7029d34 100644
--- a/drivers/mmc/host/omap_hsmmc.c
+++ b/drivers/mmc/host/omap_hsmmc.c
@@ -475,6 +475,39 @@ static void omap_hsmmc_gpio_free(struct omap_mmc_platform_data *pdata)
 		gpio_free(pdata->slots[0].gpio_cirq);
 }
 
+static int omap_hsmmc_pinctrl_init(struct omap_hsmmc_host *host)
+{
+	struct device *dev = mmc_dev(host->mmc);
+	int res, found = 0;
+
+	if (!dev->pins)
+		return 0;
+
+	/*
+	 * The active and idle pins are optional, and used for
+	 * SDIO interrupt, or eMMC pulls for off-idle.
+	 */
+	if (IS_ERR(dev->pins->active_state) ||
+	    IS_ERR(dev->pins->idle_state)) {
+		return 0;
+	}
+
+	/* Let's make sure the idle and active states work */
+	res = pinctrl_pm_select_idle_state(dev);
+	if (res < 0)
+		return -ENODEV;
+	found++;
+
+	res = pinctrl_pm_select_active_state(dev);
+	if (res < 0)
+		return -ENODEV;
+	found++;
+
+	dev_info(mmc_dev(dev), "pins configured for dynamic remuxing\n");
+
+	return found;
+}
+
 /*
  * Start clock to the card
  */
@@ -1868,7 +1901,6 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 	const struct of_device_id *match;
 	dma_cap_mask_t mask;
 	unsigned tx_req, rx_req;
-	struct pinctrl *pinctrl;
 
 	match = of_match_device(of_match_ptr(omap_mmc_of_match), &pdev->dev);
 	if (match) {
@@ -2101,21 +2133,19 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 
 	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 are doing
-	 * muxing of dat1 when booted with DT. This is because the
+	 * For now, only support SDIO interrupt if we are doing dynamic
+	 * remuxing of dat1 when booted with DT. This is because the
 	 * supposedly the wake-up events for CTPL don't work from deeper
 	 * idle states. 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.
 	 */
 	if (match) {
-		if (!IS_ERR(pinctrl) && mmc_slot(host).sdio_irq)
+		ret = omap_hsmmc_pinctrl_init(host);
+		if (ret < 0)
+			goto err_pinctrl_state;
+		else if (ret > 1 && mmc_slot(host).sdio_irq)
 			mmc->caps |= MMC_CAP_SDIO_IRQ;
 	}
 
@@ -2143,6 +2173,7 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 
 err_slot_name:
 	mmc_remove_host(mmc);
+err_pinctrl_state:
 	if ((mmc_slot(host).sdio_irq))
 		free_irq(mmc_slot(host).sdio_irq, host);
 err_irq_sdio:
@@ -2337,6 +2368,10 @@ static int omap_hsmmc_runtime_suspend(struct device *dev)
 		OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
 		spin_unlock_irqrestore(&host->irq_lock, flags);
 
+		ret = pinctrl_pm_select_idle_state(dev);
+		if (ret < 0)
+			dev_err(dev, "Unable to select idle pinmux\n");
+
 		if (mmc_slot(host).sdio_irq)
 			enable_irq(mmc_slot(host).sdio_irq);
 	}
@@ -2360,6 +2395,10 @@ static int omap_hsmmc_runtime_resume(struct device *dev)
 		if (mmc_slot(host).sdio_irq)
 			disable_irq(mmc_slot(host).sdio_irq);
 
+		ret = pinctrl_pm_select_active_state(dev);
+		if (ret < 0)
+			dev_err(dev, "Unable to select active pinmux\n");
+
 		spin_lock_irqsave(&host->irq_lock, flags);
 		host->active_pinmux = true;
 

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 3/4] mmc: omap_hsmmc: Remux pins to support SDIO interrupt and PM runtime
@ 2013-06-14  7:40             ` Tony Lindgren
  0 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-14  7:40 UTC (permalink / raw)
  To: linux-arm-kernel

* Tony Lindgren <tony@atomide.com> [130612 06:27]:
> * Linus Walleij <linus.walleij@linaro.org> [130611 01:00]:
> > If we can agree on this I will add the active state to the
> > state table and add a container in the core for this as well
> > as pinctrl_pm_select_active_state() so we can skip all the
> > pointless boilerplate also in the OMAP drivers, plus increase
> > the readability and portability quite a bit.
> 
> Sounds good to me as long as we don't always need to select
> the default pins over and over in PM runtime_resume.

Here's this patch updated for pinctrl next with the active state
added. This patch should be merged separately towards the end
of the merge window once pinctrl code has been merged. The other
three patches can be merged independently.

Regards,

Tony


From: Tony Lindgren <tony@atomide.com>
Date: Thu, 13 Jun 2013 23:19:09 -0700
Subject: [PATCH] mmc: omap_hsmmc: Remux pins to support SDIO interrupt and PM runtime

On some omaps we need to remux MMC pins for PM, and for some omaps
we need to remux the SDIO IRQ pin.

Based on an earlier patch by Andreas Fenkart <afenkart@gmail.com>.

Cc: Andreas Fenkart <afenkart@gmail.com>
Cc: Balaji T K <balajitk@ti.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Tony Lindgren <tony@atomide.com>

diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
index 6377836..7029d34 100644
--- a/drivers/mmc/host/omap_hsmmc.c
+++ b/drivers/mmc/host/omap_hsmmc.c
@@ -475,6 +475,39 @@ static void omap_hsmmc_gpio_free(struct omap_mmc_platform_data *pdata)
 		gpio_free(pdata->slots[0].gpio_cirq);
 }
 
+static int omap_hsmmc_pinctrl_init(struct omap_hsmmc_host *host)
+{
+	struct device *dev = mmc_dev(host->mmc);
+	int res, found = 0;
+
+	if (!dev->pins)
+		return 0;
+
+	/*
+	 * The active and idle pins are optional, and used for
+	 * SDIO interrupt, or eMMC pulls for off-idle.
+	 */
+	if (IS_ERR(dev->pins->active_state) ||
+	    IS_ERR(dev->pins->idle_state)) {
+		return 0;
+	}
+
+	/* Let's make sure the idle and active states work */
+	res = pinctrl_pm_select_idle_state(dev);
+	if (res < 0)
+		return -ENODEV;
+	found++;
+
+	res = pinctrl_pm_select_active_state(dev);
+	if (res < 0)
+		return -ENODEV;
+	found++;
+
+	dev_info(mmc_dev(dev), "pins configured for dynamic remuxing\n");
+
+	return found;
+}
+
 /*
  * Start clock to the card
  */
@@ -1868,7 +1901,6 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 	const struct of_device_id *match;
 	dma_cap_mask_t mask;
 	unsigned tx_req, rx_req;
-	struct pinctrl *pinctrl;
 
 	match = of_match_device(of_match_ptr(omap_mmc_of_match), &pdev->dev);
 	if (match) {
@@ -2101,21 +2133,19 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 
 	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 are doing
-	 * muxing of dat1 when booted with DT. This is because the
+	 * For now, only support SDIO interrupt if we are doing dynamic
+	 * remuxing of dat1 when booted with DT. This is because the
 	 * supposedly the wake-up events for CTPL don't work from deeper
 	 * idle states. 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.
 	 */
 	if (match) {
-		if (!IS_ERR(pinctrl) && mmc_slot(host).sdio_irq)
+		ret = omap_hsmmc_pinctrl_init(host);
+		if (ret < 0)
+			goto err_pinctrl_state;
+		else if (ret > 1 && mmc_slot(host).sdio_irq)
 			mmc->caps |= MMC_CAP_SDIO_IRQ;
 	}
 
@@ -2143,6 +2173,7 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
 
 err_slot_name:
 	mmc_remove_host(mmc);
+err_pinctrl_state:
 	if ((mmc_slot(host).sdio_irq))
 		free_irq(mmc_slot(host).sdio_irq, host);
 err_irq_sdio:
@@ -2337,6 +2368,10 @@ static int omap_hsmmc_runtime_suspend(struct device *dev)
 		OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
 		spin_unlock_irqrestore(&host->irq_lock, flags);
 
+		ret = pinctrl_pm_select_idle_state(dev);
+		if (ret < 0)
+			dev_err(dev, "Unable to select idle pinmux\n");
+
 		if (mmc_slot(host).sdio_irq)
 			enable_irq(mmc_slot(host).sdio_irq);
 	}
@@ -2360,6 +2395,10 @@ static int omap_hsmmc_runtime_resume(struct device *dev)
 		if (mmc_slot(host).sdio_irq)
 			disable_irq(mmc_slot(host).sdio_irq);
 
+		ret = pinctrl_pm_select_active_state(dev);
+		if (ret < 0)
+			dev_err(dev, "Unable to select active pinmux\n");
+
 		spin_lock_irqsave(&host->irq_lock, flags);
 		host->active_pinmux = true;
 

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* Re: [PATCH 2/4] mmc: omap_hsmmc: Enable SDIO IRQ using a GPIO in idle mode
  2013-06-07 21:49   ` Tony Lindgren
@ 2013-06-14 11:50     ` Ulf Hansson
  -1 siblings, 0 replies; 32+ messages in thread
From: Ulf Hansson @ 2013-06-14 11:50 UTC (permalink / raw)
  To: Tony Lindgren, Andreas Fenkart
  Cc: cjb, Balaji T K, Andreas Fenkart, linux-mmc, Grant Likely,
	linux-omap, linux-arm-kernel

On 7 June 2013 23:49, Tony Lindgren <tony@atomide.com> wrote:
> From: Andreas Fenkart <andreas.fenkart@streamunlimited.com>
>
> Without functional clock the omap_hsmmc module can't forward SDIO IRQs to
> the system. This patch reconfigures dat1 line as a gpio while the fclk is
> off. When the fclk is present it uses the standard SDIO IRQ detection of
> the module.
>
> The gpio irq is managed via the 'disable_depth' ref counter of the irq
> subsystem, this driver simply calls enable_irq/disable_irq when needed.
>
>                       sdio irq    sdio irq
>                       unmasked     masked
>    -----------------------------------------
>     runtime default  |    1     |   2
>     runtime suspend  |    0     |   1
>
>                   irq disable depth
>
>
> only when sdio irq is enabled AND the module is idle, the reference
> count drops to zero and the gpio irq is effectively armed.
>
> Patch was tested on AM335x/Stream800. Test setup was two modules
> with sdio wifi cards. Modules where connected to a dual-band AP, each
> module using a different band. One of module was running iperf as server
> the other as client connecting to the server in a while true loop. Test
> was running for 4+ weeks. There were about 60 Mio. suspend/resume
> transitions. Test was shut down regularly.
>
> Cc: Andreas Fenkart <afenkart@gmail.com>
> Cc: Balaji T K <balajitk@ti.com>
> Signed-off-by: Andreas Fenkart <andreas.fenkart@streamunlimited.com>
> Reviewed-by: Grant Likely <grant.likely@secretlab.ca>
> [tony@atomide.com: updated and separated out pin muxing]
> Signed-off-by: Tony Lindgren <tony@atomide.com>
> ---
>  .../devicetree/bindings/mmc/ti-omap-hsmmc.txt      |   42 ++++
>  drivers/mmc/host/omap_hsmmc.c                      |  206 +++++++++++++++++++-
>  include/linux/platform_data/mmc-omap.h             |    4
>  3 files changed, 239 insertions(+), 13 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt b/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt
> index ed271fc..5a3df37 100644
> --- a/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt
> +++ b/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt
> @@ -20,6 +20,29 @@ ti,dual-volt: boolean, supports dual voltage cards
>  ti,non-removable: non-removable slot (like eMMC)
>  ti,needs-special-reset: Requires a special softreset sequence
>  ti,needs-special-hs-handling: HSMMC IP needs special setting for handling High Speed
> +ti,cirq-gpio: When omap_hsmmc module is suspended, its functional
> +clock is turned off. Without fclk it can't forward SDIO IRQs to the
> +system. For that to happen, it needs to tell the PRCM to restore
> +its fclk, which is done through the swakeup line.
> +
> +                   ------
> +                  | PRCM |
> +                   ------
> +                    | ^
> +               fclk | | swakeup
> +                    v |
> +                  -------               ------
> +      <-- IRQ -- | hsmmc | <-- CIRQ -- | card |
> +                  -------               ------
> +
> +The problem is, that on the AM335x family the swakeup line is
> +missing, it has not been routed from the module to the PRCM.
> +The way to work around this, is to reconfigure the dat1 line as a
> +GPIO upon suspend. Beyond this option you also need to set named
> +states "default" and "idle "in the .dts file for the pins, using
> +pinctrl-single.c. The MMC driver will then then toggle between
> +default and idle during the runtime.
> +
>
>  Example:
>         mmc1: mmc@0x4809c000 {
> @@ -31,3 +54,22 @@ Example:
>                 vmmc-supply = <&vmmc>; /* phandle to regulator node */
>                 ti,non-removable;
>         };
> +
> +[am335x with with gpio for sdio irq]
> +
> +       mmc1_cirq_pin: pinmux_cirq_pin {
> +               pinctrl-single,pins = <
> +                       0x0f8 0x3f      /* MMC0_DAT1 as GPIO2_28 */
> +               >;
> +       };
> +
> +       mmc1: mmc@48060000 {
> +               pinctrl-names = "default", "idle";
> +               pinctrl-0 = <&mmc1_pins>;
> +               pinctrl-1 = <&mmc1_cirq_pin>;
> +               ti,cirq-gpio = <&gpio3 28 0>;
> +               ti,non-removable;
> +               bus-width = <4>;
> +               vmmc-supply = <&ldo2_reg>;
> +               vmmc_aux-supply = <&vmmc>;
> +       };
> diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
> index 478849b..7e28501 100644
> --- a/drivers/mmc/host/omap_hsmmc.c
> +++ b/drivers/mmc/host/omap_hsmmc.c
> @@ -22,6 +22,7 @@
>  #include <linux/dmaengine.h>
>  #include <linux/seq_file.h>
>  #include <linux/interrupt.h>
> +#include <linux/irq.h>
>  #include <linux/delay.h>
>  #include <linux/dma-mapping.h>
>  #include <linux/platform_device.h>
> @@ -102,6 +103,7 @@
>  #define TC_EN                  (1 << 1)
>  #define BWR_EN                 (1 << 4)
>  #define BRR_EN                 (1 << 5)
> +#define CIRQ_EN                        (1 << 8)
>  #define ERR_EN                 (1 << 15)
>  #define CTO_EN                 (1 << 16)
>  #define CCRC_EN                        (1 << 17)
> @@ -182,9 +184,19 @@ struct omap_hsmmc_host {
>         int                     use_reg;
>         int                     req_in_progress;
>         struct omap_hsmmc_next  next_data;
> +       bool                    sdio_irq_en;
> +       bool                    active_pinmux;
>         struct  omap_mmc_platform_data  *pdata;
>  };
>
> +static irqreturn_t omap_hsmmc_cirq(int irq, void *dev_id)
> +{
> +       struct omap_hsmmc_host *host = dev_id;
> +
> +       mmc_signal_sdio_irq(host->mmc);
> +       return IRQ_HANDLED;
> +}
> +
>  static int omap_hsmmc_card_detect(struct device *dev, int slot)
>  {
>         struct omap_hsmmc_host *host = dev_get_drvdata(dev);
> @@ -419,10 +431,31 @@ static int omap_hsmmc_gpio_init(struct omap_mmc_platform_data *pdata)
>         } else
>                 pdata->slots[0].gpio_wp = -EINVAL;
>
> +       if (pdata->slots[0].gpio_cirq > 0 &&
> +           gpio_is_valid(pdata->slots[0].gpio_cirq)) {
> +               pdata->slots[0].sdio_irq =
> +                               gpio_to_irq(pdata->slots[0].gpio_cirq);
> +
> +               ret = gpio_request(pdata->slots[0].gpio_cirq, "sdio_cirq");
> +               if (ret)
> +                       goto err_free_ro;
> +               ret = gpio_direction_input(pdata->slots[0].gpio_cirq);
> +               if (ret)
> +                       goto err_free_cirq;
> +
> +       } else {
> +               pdata->slots[0].gpio_cirq = -EINVAL;
> +       }
> +
> +
>         return 0;
>
> +err_free_cirq:
> +       gpio_free(pdata->slots[0].gpio_cirq);
> +err_free_ro:
> +       if (gpio_is_valid(pdata->slots[0].gpio_wp))
>  err_free_wp:
> -       gpio_free(pdata->slots[0].gpio_wp);
> +               gpio_free(pdata->slots[0].gpio_wp);
>  err_free_cd:
>         if (gpio_is_valid(pdata->slots[0].switch_pin))
>  err_free_sp:
> @@ -436,6 +469,8 @@ static void omap_hsmmc_gpio_free(struct omap_mmc_platform_data *pdata)
>                 gpio_free(pdata->slots[0].gpio_wp);
>         if (gpio_is_valid(pdata->slots[0].switch_pin))
>                 gpio_free(pdata->slots[0].switch_pin);
> +       if (gpio_is_valid(pdata->slots[0].gpio_cirq))
> +               gpio_free(pdata->slots[0].gpio_cirq);
>  }
>
>  /*
> @@ -461,27 +496,46 @@ static void omap_hsmmc_stop_clock(struct omap_hsmmc_host *host)
>  static void omap_hsmmc_enable_irq(struct omap_hsmmc_host *host,
>                                   struct mmc_command *cmd)
>  {
> -       unsigned int irq_mask;
> +       u32 irq_mask = INT_EN_MASK;
> +       unsigned long flags;
>
>         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 */
>         if (cmd->opcode == MMC_ERASE)
>                 irq_mask &= ~DTO_EN;
>
> +       spin_lock_irqsave(&host->irq_lock, flags);
> +
>         OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
>         OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
> +
> +       /* latch pending CIRQ, but don't signal */
> +       if (host->sdio_irq_en)
> +               irq_mask |= CIRQ_EN;
> +
>         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)
>  {
> -       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, need to signal cirq if */
> +       if (host->sdio_irq_en)
> +               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);
> +
> +       spin_unlock_irqrestore(&host->irq_lock, flags);
>  }
>
>  /* Calculate divisor for the given clock frequency */
> @@ -1037,8 +1091,12 @@ static irqreturn_t omap_hsmmc_irq(int irq, void *dev_id)
>         int status;
>
>         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 */
>                 OMAP_HSMMC_WRITE(host->base, STAT, status);
> @@ -1554,6 +1612,51 @@ static void omap_hsmmc_init_card(struct mmc_host *mmc, struct mmc_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;
> +       unsigned long flags;
> +
> +       spin_lock_irqsave(&host->irq_lock, flags);
> +
> +       if (host->sdio_irq_en == enable) {
> +               dev_dbg(host->dev, "en/disable:%d already set", enable);
> +               spin_unlock_irqrestore(&host->irq_lock, flags);
> +               return;
> +       }
> +

Hi Tony/Andreas,

I belive a "pm_runtime_get_sync" would be needed here, outside the
spinlock ofcourse. Before returning from this function, obviusly
return the references by a pm_runtime_put* in some form.

Then you will be able to remove the "active_pinmux" variable entirely,
since you know the runtime callbacks is the only place were you need
to handle the gpio irq enable|disable.

Kind regards
Ulf Hansson

> +       host->sdio_irq_en = (enable != 0) ? true : false;
> +
> +       if (host->active_pinmux) { /* register access fails without fclk */
> +               irq_mask = OMAP_HSMMC_READ(host->base, ISE);
> +               if (enable)
> +                       irq_mask |= CIRQ_EN;
> +               else
> +                       irq_mask &= ~CIRQ_EN;
> +               OMAP_HSMMC_WRITE(host->base, IE, irq_mask);
> +
> +               if (!host->req_in_progress)
> +                       OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
> +
> +               /*
> +                * evtl. need to flush posted write
> +                * OMAP_HSMMC_READ(host->base, IE);
> +                */
> +       }
> +
> +       if ((mmc_slot(host).sdio_irq)) {
> +               if (enable) {
> +                       enable_irq(mmc_slot(host).sdio_irq);
> +               } else {
> +                       /* _nosync, see mmc_signal_sdio_irq */
> +                       disable_irq_nosync(mmc_slot(host).sdio_irq);
> +               }
> +       }
> +
> +       spin_unlock_irqrestore(&host->irq_lock, flags);
> +}
> +
>  static void omap_hsmmc_conf_bus_power(struct omap_hsmmc_host *host)
>  {
>         u32 hctl, capa, value;
> @@ -1606,7 +1709,7 @@ static const struct mmc_host_ops omap_hsmmc_ops = {
>         .get_cd = omap_hsmmc_get_cd,
>         .get_ro = omap_hsmmc_get_ro,
>         .init_card = omap_hsmmc_init_card,
> -       /* NYET -- enable_sdio_irq */
> +       .enable_sdio_irq = omap_hsmmc_enable_sdio_irq,
>  };
>
>  #ifdef CONFIG_DEBUG_FS
> @@ -1710,6 +1813,7 @@ static struct omap_mmc_platform_data *of_get_hsmmc_pdata(struct device *dev)
>         pdata->nr_slots = 1;
>         pdata->slots[0].switch_pin = cd_gpio;
>         pdata->slots[0].gpio_wp = wp_gpio;
> +       pdata->slots[0].gpio_cirq = of_get_named_gpio(np, "ti,cirq-gpio", 0);
>
>         if (of_find_property(np, "ti,non-removable", NULL)) {
>                 pdata->slots[0].nonremovable = true;
> @@ -1802,6 +1906,8 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
>         host->dma_ch    = -1;
>         host->irq       = irq;
>         host->slot_id   = 0;
> +       host->sdio_irq_en = false;
> +       host->active_pinmux = true;
>         host->mapbase   = res->start + pdata->reg_offset;
>         host->base      = ioremap(host->mapbase, SZ_4K);
>         host->power_mode = MMC_POWER_OFF;
> @@ -1955,6 +2061,29 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
>                 pdata->resume = omap_hsmmc_resume_cdirq;
>         }
>
> +       if ((mmc_slot(host).sdio_irq)) {
> +               /* prevent auto-enabling of IRQ */
> +               irq_set_status_flags(mmc_slot(host).sdio_irq, IRQ_NOAUTOEN);
> +               ret = request_irq(mmc_slot(host).sdio_irq, omap_hsmmc_cirq,
> +                                 IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> +                                 mmc_hostname(mmc), host);
> +               if (ret) {
> +                       dev_dbg(mmc_dev(host->mmc),
> +                               "Unable to grab MMC SDIO IRQ\n");
> +                       goto err_irq_sdio;
> +               }
> +
> +               /*
> +                * sdio_irq is managed with ref count
> +                * - omap_hsmmc_enable_sdio_irq will +1/-1
> +                * - pm_suspend/pm_resume will +1/-1
> +                * only when sdio irq is enabled AND module will go to runtime
> +                * suspend the ref count will drop to zero and the irq is
> +                * effectively enabled. starting with ref count equal 2
> +                */
> +               disable_irq(mmc_slot(host).sdio_irq);
> +       }
> +
>         omap_hsmmc_disable_irq(host);
>
>         pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
> @@ -1962,6 +2091,19 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
>                 dev_warn(&pdev->dev,
>                         "pins are not configured from the driver\n");
>
> +       /*
> +        * For now, only support SDIO interrupt if we are doing
> +        * muxing of dat1 when booted with DT. This is because the
> +        * supposedly the wake-up events for CTPL don't work from deeper
> +        * idle states. 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.
> +        */
> +       if (match) {
> +               if (!IS_ERR(pinctrl) && mmc_slot(host).sdio_irq)
> +                       mmc->caps |= MMC_CAP_SDIO_IRQ;
> +       }
> +
>         omap_hsmmc_protect_card(host);
>
>         mmc_add_host(mmc);
> @@ -1986,6 +2128,9 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
>
>  err_slot_name:
>         mmc_remove_host(mmc);
> +       if ((mmc_slot(host).sdio_irq))
> +               free_irq(mmc_slot(host).sdio_irq, host);
> +err_irq_sdio:
>         free_irq(mmc_slot(host).card_detect_irq, host);
>  err_irq_cd:
>         if (host->use_reg)
> @@ -2032,9 +2177,10 @@ static int omap_hsmmc_remove(struct platform_device *pdev)
>         if (host->pdata->cleanup)
>                 host->pdata->cleanup(&pdev->dev);
>         free_irq(host->irq, host);
> +       if ((mmc_slot(host).sdio_irq))
> +               free_irq(mmc_slot(host).sdio_irq, host);
>         if (mmc_slot(host).card_detect_irq)
>                 free_irq(mmc_slot(host).card_detect_irq, host);
> -
>         if (host->tx_chan)
>                 dma_release_channel(host->tx_chan);
>         if (host->rx_chan)
> @@ -2157,23 +2303,57 @@ static int omap_hsmmc_resume(struct device *dev)
>  static int omap_hsmmc_runtime_suspend(struct device *dev)
>  {
>         struct omap_hsmmc_host *host;
> +       struct mmc_host *mmc;
> +       unsigned long flags;
> +       int ret = 0;
>
>         host = platform_get_drvdata(to_platform_device(dev));
> +       mmc = host->mmc;
>         omap_hsmmc_context_save(host);
>         dev_dbg(dev, "disabled\n");
>
> -       return 0;
> +       if (mmc->caps & MMC_CAP_SDIO_IRQ) {
> +               spin_lock_irqsave(&host->irq_lock, flags);
> +               host->active_pinmux = false;
> +               OMAP_HSMMC_WRITE(host->base, ISE, 0);
> +               OMAP_HSMMC_WRITE(host->base, IE, 0);
> +               OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
> +               spin_unlock_irqrestore(&host->irq_lock, flags);
> +
> +               if (mmc_slot(host).sdio_irq)
> +                       enable_irq(mmc_slot(host).sdio_irq);
> +       }
> +
> +       return ret;
>  }
>
>  static int omap_hsmmc_runtime_resume(struct device *dev)
>  {
>         struct omap_hsmmc_host *host;
> +       struct mmc_host *mmc;
> +       unsigned long flags;
> +       int ret = 0;
>
>         host = platform_get_drvdata(to_platform_device(dev));
> +       mmc = host->mmc;
>         omap_hsmmc_context_restore(host);
>         dev_dbg(dev, "enabled\n");
>
> -       return 0;
> +       if (mmc->caps & MMC_CAP_SDIO_IRQ) {
> +               if (mmc_slot(host).sdio_irq)
> +                       disable_irq(mmc_slot(host).sdio_irq);
> +
> +               spin_lock_irqsave(&host->irq_lock, flags);
> +               host->active_pinmux = true;
> +
> +               if (host->sdio_irq_en) {
> +                       OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
> +                       OMAP_HSMMC_WRITE(host->base, ISE, CIRQ_EN);
> +                       OMAP_HSMMC_WRITE(host->base, IE, CIRQ_EN);
> +               }
> +               spin_unlock_irqrestore(&host->irq_lock, flags);
> +       }
> +       return ret;
>  }
>
>  static struct dev_pm_ops omap_hsmmc_dev_pm_ops = {
> diff --git a/include/linux/platform_data/mmc-omap.h b/include/linux/platform_data/mmc-omap.h
> index 2bf1b30..fd5fff5 100644
> --- a/include/linux/platform_data/mmc-omap.h
> +++ b/include/linux/platform_data/mmc-omap.h
> @@ -115,6 +115,7 @@ struct omap_mmc_platform_data {
>
>                 int switch_pin;                 /* gpio (card detect) */
>                 int gpio_wp;                    /* gpio (write protect) */
> +               int gpio_cirq;                  /* gpio (card irq) */
>
>                 int (*set_bus_mode)(struct device *dev, int slot, int bus_mode);
>                 int (*set_power)(struct device *dev, int slot,
> @@ -145,6 +146,9 @@ struct omap_mmc_platform_data {
>                 int card_detect_irq;
>                 int (*card_detect)(struct device *dev, int slot);
>
> +               /* SDIO IRQs */
> +               int sdio_irq;
> +
>                 unsigned int ban_openended:1;
>
>         } slots[OMAP_MMC_MAX_SLOTS];
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 2/4] mmc: omap_hsmmc: Enable SDIO IRQ using a GPIO in idle mode
@ 2013-06-14 11:50     ` Ulf Hansson
  0 siblings, 0 replies; 32+ messages in thread
From: Ulf Hansson @ 2013-06-14 11:50 UTC (permalink / raw)
  To: linux-arm-kernel

On 7 June 2013 23:49, Tony Lindgren <tony@atomide.com> wrote:
> From: Andreas Fenkart <andreas.fenkart@streamunlimited.com>
>
> Without functional clock the omap_hsmmc module can't forward SDIO IRQs to
> the system. This patch reconfigures dat1 line as a gpio while the fclk is
> off. When the fclk is present it uses the standard SDIO IRQ detection of
> the module.
>
> The gpio irq is managed via the 'disable_depth' ref counter of the irq
> subsystem, this driver simply calls enable_irq/disable_irq when needed.
>
>                       sdio irq    sdio irq
>                       unmasked     masked
>    -----------------------------------------
>     runtime default  |    1     |   2
>     runtime suspend  |    0     |   1
>
>                   irq disable depth
>
>
> only when sdio irq is enabled AND the module is idle, the reference
> count drops to zero and the gpio irq is effectively armed.
>
> Patch was tested on AM335x/Stream800. Test setup was two modules
> with sdio wifi cards. Modules where connected to a dual-band AP, each
> module using a different band. One of module was running iperf as server
> the other as client connecting to the server in a while true loop. Test
> was running for 4+ weeks. There were about 60 Mio. suspend/resume
> transitions. Test was shut down regularly.
>
> Cc: Andreas Fenkart <afenkart@gmail.com>
> Cc: Balaji T K <balajitk@ti.com>
> Signed-off-by: Andreas Fenkart <andreas.fenkart@streamunlimited.com>
> Reviewed-by: Grant Likely <grant.likely@secretlab.ca>
> [tony at atomide.com: updated and separated out pin muxing]
> Signed-off-by: Tony Lindgren <tony@atomide.com>
> ---
>  .../devicetree/bindings/mmc/ti-omap-hsmmc.txt      |   42 ++++
>  drivers/mmc/host/omap_hsmmc.c                      |  206 +++++++++++++++++++-
>  include/linux/platform_data/mmc-omap.h             |    4
>  3 files changed, 239 insertions(+), 13 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt b/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt
> index ed271fc..5a3df37 100644
> --- a/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt
> +++ b/Documentation/devicetree/bindings/mmc/ti-omap-hsmmc.txt
> @@ -20,6 +20,29 @@ ti,dual-volt: boolean, supports dual voltage cards
>  ti,non-removable: non-removable slot (like eMMC)
>  ti,needs-special-reset: Requires a special softreset sequence
>  ti,needs-special-hs-handling: HSMMC IP needs special setting for handling High Speed
> +ti,cirq-gpio: When omap_hsmmc module is suspended, its functional
> +clock is turned off. Without fclk it can't forward SDIO IRQs to the
> +system. For that to happen, it needs to tell the PRCM to restore
> +its fclk, which is done through the swakeup line.
> +
> +                   ------
> +                  | PRCM |
> +                   ------
> +                    | ^
> +               fclk | | swakeup
> +                    v |
> +                  -------               ------
> +      <-- IRQ -- | hsmmc | <-- CIRQ -- | card |
> +                  -------               ------
> +
> +The problem is, that on the AM335x family the swakeup line is
> +missing, it has not been routed from the module to the PRCM.
> +The way to work around this, is to reconfigure the dat1 line as a
> +GPIO upon suspend. Beyond this option you also need to set named
> +states "default" and "idle "in the .dts file for the pins, using
> +pinctrl-single.c. The MMC driver will then then toggle between
> +default and idle during the runtime.
> +
>
>  Example:
>         mmc1: mmc at 0x4809c000 {
> @@ -31,3 +54,22 @@ Example:
>                 vmmc-supply = <&vmmc>; /* phandle to regulator node */
>                 ti,non-removable;
>         };
> +
> +[am335x with with gpio for sdio irq]
> +
> +       mmc1_cirq_pin: pinmux_cirq_pin {
> +               pinctrl-single,pins = <
> +                       0x0f8 0x3f      /* MMC0_DAT1 as GPIO2_28 */
> +               >;
> +       };
> +
> +       mmc1: mmc at 48060000 {
> +               pinctrl-names = "default", "idle";
> +               pinctrl-0 = <&mmc1_pins>;
> +               pinctrl-1 = <&mmc1_cirq_pin>;
> +               ti,cirq-gpio = <&gpio3 28 0>;
> +               ti,non-removable;
> +               bus-width = <4>;
> +               vmmc-supply = <&ldo2_reg>;
> +               vmmc_aux-supply = <&vmmc>;
> +       };
> diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
> index 478849b..7e28501 100644
> --- a/drivers/mmc/host/omap_hsmmc.c
> +++ b/drivers/mmc/host/omap_hsmmc.c
> @@ -22,6 +22,7 @@
>  #include <linux/dmaengine.h>
>  #include <linux/seq_file.h>
>  #include <linux/interrupt.h>
> +#include <linux/irq.h>
>  #include <linux/delay.h>
>  #include <linux/dma-mapping.h>
>  #include <linux/platform_device.h>
> @@ -102,6 +103,7 @@
>  #define TC_EN                  (1 << 1)
>  #define BWR_EN                 (1 << 4)
>  #define BRR_EN                 (1 << 5)
> +#define CIRQ_EN                        (1 << 8)
>  #define ERR_EN                 (1 << 15)
>  #define CTO_EN                 (1 << 16)
>  #define CCRC_EN                        (1 << 17)
> @@ -182,9 +184,19 @@ struct omap_hsmmc_host {
>         int                     use_reg;
>         int                     req_in_progress;
>         struct omap_hsmmc_next  next_data;
> +       bool                    sdio_irq_en;
> +       bool                    active_pinmux;
>         struct  omap_mmc_platform_data  *pdata;
>  };
>
> +static irqreturn_t omap_hsmmc_cirq(int irq, void *dev_id)
> +{
> +       struct omap_hsmmc_host *host = dev_id;
> +
> +       mmc_signal_sdio_irq(host->mmc);
> +       return IRQ_HANDLED;
> +}
> +
>  static int omap_hsmmc_card_detect(struct device *dev, int slot)
>  {
>         struct omap_hsmmc_host *host = dev_get_drvdata(dev);
> @@ -419,10 +431,31 @@ static int omap_hsmmc_gpio_init(struct omap_mmc_platform_data *pdata)
>         } else
>                 pdata->slots[0].gpio_wp = -EINVAL;
>
> +       if (pdata->slots[0].gpio_cirq > 0 &&
> +           gpio_is_valid(pdata->slots[0].gpio_cirq)) {
> +               pdata->slots[0].sdio_irq =
> +                               gpio_to_irq(pdata->slots[0].gpio_cirq);
> +
> +               ret = gpio_request(pdata->slots[0].gpio_cirq, "sdio_cirq");
> +               if (ret)
> +                       goto err_free_ro;
> +               ret = gpio_direction_input(pdata->slots[0].gpio_cirq);
> +               if (ret)
> +                       goto err_free_cirq;
> +
> +       } else {
> +               pdata->slots[0].gpio_cirq = -EINVAL;
> +       }
> +
> +
>         return 0;
>
> +err_free_cirq:
> +       gpio_free(pdata->slots[0].gpio_cirq);
> +err_free_ro:
> +       if (gpio_is_valid(pdata->slots[0].gpio_wp))
>  err_free_wp:
> -       gpio_free(pdata->slots[0].gpio_wp);
> +               gpio_free(pdata->slots[0].gpio_wp);
>  err_free_cd:
>         if (gpio_is_valid(pdata->slots[0].switch_pin))
>  err_free_sp:
> @@ -436,6 +469,8 @@ static void omap_hsmmc_gpio_free(struct omap_mmc_platform_data *pdata)
>                 gpio_free(pdata->slots[0].gpio_wp);
>         if (gpio_is_valid(pdata->slots[0].switch_pin))
>                 gpio_free(pdata->slots[0].switch_pin);
> +       if (gpio_is_valid(pdata->slots[0].gpio_cirq))
> +               gpio_free(pdata->slots[0].gpio_cirq);
>  }
>
>  /*
> @@ -461,27 +496,46 @@ static void omap_hsmmc_stop_clock(struct omap_hsmmc_host *host)
>  static void omap_hsmmc_enable_irq(struct omap_hsmmc_host *host,
>                                   struct mmc_command *cmd)
>  {
> -       unsigned int irq_mask;
> +       u32 irq_mask = INT_EN_MASK;
> +       unsigned long flags;
>
>         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 */
>         if (cmd->opcode == MMC_ERASE)
>                 irq_mask &= ~DTO_EN;
>
> +       spin_lock_irqsave(&host->irq_lock, flags);
> +
>         OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
>         OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
> +
> +       /* latch pending CIRQ, but don't signal */
> +       if (host->sdio_irq_en)
> +               irq_mask |= CIRQ_EN;
> +
>         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)
>  {
> -       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, need to signal cirq if */
> +       if (host->sdio_irq_en)
> +               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);
> +
> +       spin_unlock_irqrestore(&host->irq_lock, flags);
>  }
>
>  /* Calculate divisor for the given clock frequency */
> @@ -1037,8 +1091,12 @@ static irqreturn_t omap_hsmmc_irq(int irq, void *dev_id)
>         int status;
>
>         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 */
>                 OMAP_HSMMC_WRITE(host->base, STAT, status);
> @@ -1554,6 +1612,51 @@ static void omap_hsmmc_init_card(struct mmc_host *mmc, struct mmc_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;
> +       unsigned long flags;
> +
> +       spin_lock_irqsave(&host->irq_lock, flags);
> +
> +       if (host->sdio_irq_en == enable) {
> +               dev_dbg(host->dev, "en/disable:%d already set", enable);
> +               spin_unlock_irqrestore(&host->irq_lock, flags);
> +               return;
> +       }
> +

Hi Tony/Andreas,

I belive a "pm_runtime_get_sync" would be needed here, outside the
spinlock ofcourse. Before returning from this function, obviusly
return the references by a pm_runtime_put* in some form.

Then you will be able to remove the "active_pinmux" variable entirely,
since you know the runtime callbacks is the only place were you need
to handle the gpio irq enable|disable.

Kind regards
Ulf Hansson

> +       host->sdio_irq_en = (enable != 0) ? true : false;
> +
> +       if (host->active_pinmux) { /* register access fails without fclk */
> +               irq_mask = OMAP_HSMMC_READ(host->base, ISE);
> +               if (enable)
> +                       irq_mask |= CIRQ_EN;
> +               else
> +                       irq_mask &= ~CIRQ_EN;
> +               OMAP_HSMMC_WRITE(host->base, IE, irq_mask);
> +
> +               if (!host->req_in_progress)
> +                       OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
> +
> +               /*
> +                * evtl. need to flush posted write
> +                * OMAP_HSMMC_READ(host->base, IE);
> +                */
> +       }
> +
> +       if ((mmc_slot(host).sdio_irq)) {
> +               if (enable) {
> +                       enable_irq(mmc_slot(host).sdio_irq);
> +               } else {
> +                       /* _nosync, see mmc_signal_sdio_irq */
> +                       disable_irq_nosync(mmc_slot(host).sdio_irq);
> +               }
> +       }
> +
> +       spin_unlock_irqrestore(&host->irq_lock, flags);
> +}
> +
>  static void omap_hsmmc_conf_bus_power(struct omap_hsmmc_host *host)
>  {
>         u32 hctl, capa, value;
> @@ -1606,7 +1709,7 @@ static const struct mmc_host_ops omap_hsmmc_ops = {
>         .get_cd = omap_hsmmc_get_cd,
>         .get_ro = omap_hsmmc_get_ro,
>         .init_card = omap_hsmmc_init_card,
> -       /* NYET -- enable_sdio_irq */
> +       .enable_sdio_irq = omap_hsmmc_enable_sdio_irq,
>  };
>
>  #ifdef CONFIG_DEBUG_FS
> @@ -1710,6 +1813,7 @@ static struct omap_mmc_platform_data *of_get_hsmmc_pdata(struct device *dev)
>         pdata->nr_slots = 1;
>         pdata->slots[0].switch_pin = cd_gpio;
>         pdata->slots[0].gpio_wp = wp_gpio;
> +       pdata->slots[0].gpio_cirq = of_get_named_gpio(np, "ti,cirq-gpio", 0);
>
>         if (of_find_property(np, "ti,non-removable", NULL)) {
>                 pdata->slots[0].nonremovable = true;
> @@ -1802,6 +1906,8 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
>         host->dma_ch    = -1;
>         host->irq       = irq;
>         host->slot_id   = 0;
> +       host->sdio_irq_en = false;
> +       host->active_pinmux = true;
>         host->mapbase   = res->start + pdata->reg_offset;
>         host->base      = ioremap(host->mapbase, SZ_4K);
>         host->power_mode = MMC_POWER_OFF;
> @@ -1955,6 +2061,29 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
>                 pdata->resume = omap_hsmmc_resume_cdirq;
>         }
>
> +       if ((mmc_slot(host).sdio_irq)) {
> +               /* prevent auto-enabling of IRQ */
> +               irq_set_status_flags(mmc_slot(host).sdio_irq, IRQ_NOAUTOEN);
> +               ret = request_irq(mmc_slot(host).sdio_irq, omap_hsmmc_cirq,
> +                                 IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> +                                 mmc_hostname(mmc), host);
> +               if (ret) {
> +                       dev_dbg(mmc_dev(host->mmc),
> +                               "Unable to grab MMC SDIO IRQ\n");
> +                       goto err_irq_sdio;
> +               }
> +
> +               /*
> +                * sdio_irq is managed with ref count
> +                * - omap_hsmmc_enable_sdio_irq will +1/-1
> +                * - pm_suspend/pm_resume will +1/-1
> +                * only when sdio irq is enabled AND module will go to runtime
> +                * suspend the ref count will drop to zero and the irq is
> +                * effectively enabled. starting with ref count equal 2
> +                */
> +               disable_irq(mmc_slot(host).sdio_irq);
> +       }
> +
>         omap_hsmmc_disable_irq(host);
>
>         pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
> @@ -1962,6 +2091,19 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
>                 dev_warn(&pdev->dev,
>                         "pins are not configured from the driver\n");
>
> +       /*
> +        * For now, only support SDIO interrupt if we are doing
> +        * muxing of dat1 when booted with DT. This is because the
> +        * supposedly the wake-up events for CTPL don't work from deeper
> +        * idle states. 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.
> +        */
> +       if (match) {
> +               if (!IS_ERR(pinctrl) && mmc_slot(host).sdio_irq)
> +                       mmc->caps |= MMC_CAP_SDIO_IRQ;
> +       }
> +
>         omap_hsmmc_protect_card(host);
>
>         mmc_add_host(mmc);
> @@ -1986,6 +2128,9 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
>
>  err_slot_name:
>         mmc_remove_host(mmc);
> +       if ((mmc_slot(host).sdio_irq))
> +               free_irq(mmc_slot(host).sdio_irq, host);
> +err_irq_sdio:
>         free_irq(mmc_slot(host).card_detect_irq, host);
>  err_irq_cd:
>         if (host->use_reg)
> @@ -2032,9 +2177,10 @@ static int omap_hsmmc_remove(struct platform_device *pdev)
>         if (host->pdata->cleanup)
>                 host->pdata->cleanup(&pdev->dev);
>         free_irq(host->irq, host);
> +       if ((mmc_slot(host).sdio_irq))
> +               free_irq(mmc_slot(host).sdio_irq, host);
>         if (mmc_slot(host).card_detect_irq)
>                 free_irq(mmc_slot(host).card_detect_irq, host);
> -
>         if (host->tx_chan)
>                 dma_release_channel(host->tx_chan);
>         if (host->rx_chan)
> @@ -2157,23 +2303,57 @@ static int omap_hsmmc_resume(struct device *dev)
>  static int omap_hsmmc_runtime_suspend(struct device *dev)
>  {
>         struct omap_hsmmc_host *host;
> +       struct mmc_host *mmc;
> +       unsigned long flags;
> +       int ret = 0;
>
>         host = platform_get_drvdata(to_platform_device(dev));
> +       mmc = host->mmc;
>         omap_hsmmc_context_save(host);
>         dev_dbg(dev, "disabled\n");
>
> -       return 0;
> +       if (mmc->caps & MMC_CAP_SDIO_IRQ) {
> +               spin_lock_irqsave(&host->irq_lock, flags);
> +               host->active_pinmux = false;
> +               OMAP_HSMMC_WRITE(host->base, ISE, 0);
> +               OMAP_HSMMC_WRITE(host->base, IE, 0);
> +               OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
> +               spin_unlock_irqrestore(&host->irq_lock, flags);
> +
> +               if (mmc_slot(host).sdio_irq)
> +                       enable_irq(mmc_slot(host).sdio_irq);
> +       }
> +
> +       return ret;
>  }
>
>  static int omap_hsmmc_runtime_resume(struct device *dev)
>  {
>         struct omap_hsmmc_host *host;
> +       struct mmc_host *mmc;
> +       unsigned long flags;
> +       int ret = 0;
>
>         host = platform_get_drvdata(to_platform_device(dev));
> +       mmc = host->mmc;
>         omap_hsmmc_context_restore(host);
>         dev_dbg(dev, "enabled\n");
>
> -       return 0;
> +       if (mmc->caps & MMC_CAP_SDIO_IRQ) {
> +               if (mmc_slot(host).sdio_irq)
> +                       disable_irq(mmc_slot(host).sdio_irq);
> +
> +               spin_lock_irqsave(&host->irq_lock, flags);
> +               host->active_pinmux = true;
> +
> +               if (host->sdio_irq_en) {
> +                       OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
> +                       OMAP_HSMMC_WRITE(host->base, ISE, CIRQ_EN);
> +                       OMAP_HSMMC_WRITE(host->base, IE, CIRQ_EN);
> +               }
> +               spin_unlock_irqrestore(&host->irq_lock, flags);
> +       }
> +       return ret;
>  }
>
>  static struct dev_pm_ops omap_hsmmc_dev_pm_ops = {
> diff --git a/include/linux/platform_data/mmc-omap.h b/include/linux/platform_data/mmc-omap.h
> index 2bf1b30..fd5fff5 100644
> --- a/include/linux/platform_data/mmc-omap.h
> +++ b/include/linux/platform_data/mmc-omap.h
> @@ -115,6 +115,7 @@ struct omap_mmc_platform_data {
>
>                 int switch_pin;                 /* gpio (card detect) */
>                 int gpio_wp;                    /* gpio (write protect) */
> +               int gpio_cirq;                  /* gpio (card irq) */
>
>                 int (*set_bus_mode)(struct device *dev, int slot, int bus_mode);
>                 int (*set_power)(struct device *dev, int slot,
> @@ -145,6 +146,9 @@ struct omap_mmc_platform_data {
>                 int card_detect_irq;
>                 int (*card_detect)(struct device *dev, int slot);
>
> +               /* SDIO IRQs */
> +               int sdio_irq;
> +
>                 unsigned int ban_openended:1;
>
>         } slots[OMAP_MMC_MAX_SLOTS];
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
> the body of a message to majordomo at vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 2/4] mmc: omap_hsmmc: Enable SDIO IRQ using a GPIO in idle mode
  2013-06-14 11:50     ` Ulf Hansson
@ 2013-06-20  7:24       ` Tony Lindgren
  -1 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-20  7:24 UTC (permalink / raw)
  To: Ulf Hansson
  Cc: Andreas Fenkart, cjb, Balaji T K, Andreas Fenkart, linux-mmc,
	Grant Likely, linux-omap, linux-arm-kernel

* Ulf Hansson <ulf.hansson@linaro.org> [130614 04:55]:
> On 7 June 2013 23:49, Tony Lindgren <tony@atomide.com> wrote:
> > From: Andreas Fenkart <andreas.fenkart@streamunlimited.com>
> > --- a/drivers/mmc/host/omap_hsmmc.c
> > +++ b/drivers/mmc/host/omap_hsmmc.c
> > +static void omap_hsmmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
> > +{
> > +       struct omap_hsmmc_host *host = mmc_priv(mmc);
> > +       u32 irq_mask;
> > +       unsigned long flags;
> > +
> > +       spin_lock_irqsave(&host->irq_lock, flags);
> > +
> > +       if (host->sdio_irq_en == enable) {
> > +               dev_dbg(host->dev, "en/disable:%d already set", enable);
> > +               spin_unlock_irqrestore(&host->irq_lock, flags);
> > +               return;
> > +       }
> > +
> 
> Hi Tony/Andreas,
> 
> I belive a "pm_runtime_get_sync" would be needed here, outside the
> spinlock ofcourse. Before returning from this function, obviusly
> return the references by a pm_runtime_put* in some form.
> 
> Then you will be able to remove the "active_pinmux" variable entirely,
> since you know the runtime callbacks is the only place were you need
> to handle the gpio irq enable|disable.

Thanks for the review, that's a good point. I'll check this as
soon as I have a chance.

Regards,

Tony

^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 2/4] mmc: omap_hsmmc: Enable SDIO IRQ using a GPIO in idle mode
@ 2013-06-20  7:24       ` Tony Lindgren
  0 siblings, 0 replies; 32+ messages in thread
From: Tony Lindgren @ 2013-06-20  7:24 UTC (permalink / raw)
  To: linux-arm-kernel

* Ulf Hansson <ulf.hansson@linaro.org> [130614 04:55]:
> On 7 June 2013 23:49, Tony Lindgren <tony@atomide.com> wrote:
> > From: Andreas Fenkart <andreas.fenkart@streamunlimited.com>
> > --- a/drivers/mmc/host/omap_hsmmc.c
> > +++ b/drivers/mmc/host/omap_hsmmc.c
> > +static void omap_hsmmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
> > +{
> > +       struct omap_hsmmc_host *host = mmc_priv(mmc);
> > +       u32 irq_mask;
> > +       unsigned long flags;
> > +
> > +       spin_lock_irqsave(&host->irq_lock, flags);
> > +
> > +       if (host->sdio_irq_en == enable) {
> > +               dev_dbg(host->dev, "en/disable:%d already set", enable);
> > +               spin_unlock_irqrestore(&host->irq_lock, flags);
> > +               return;
> > +       }
> > +
> 
> Hi Tony/Andreas,
> 
> I belive a "pm_runtime_get_sync" would be needed here, outside the
> spinlock ofcourse. Before returning from this function, obviusly
> return the references by a pm_runtime_put* in some form.
> 
> Then you will be able to remove the "active_pinmux" variable entirely,
> since you know the runtime callbacks is the only place were you need
> to handle the gpio irq enable|disable.

Thanks for the review, that's a good point. I'll check this as
soon as I have a chance.

Regards,

Tony

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 2/4] mmc: omap_hsmmc: Enable SDIO IRQ using a GPIO in idle mode
  2013-06-07 21:49   ` Tony Lindgren
@ 2013-07-08  9:02     ` Felipe Balbi
  -1 siblings, 0 replies; 32+ messages in thread
From: Felipe Balbi @ 2013-07-08  9:02 UTC (permalink / raw)
  To: Tony Lindgren
  Cc: cjb, Balaji T K, Andreas Fenkart, Andreas Fenkart, linux-mmc,
	Grant Likely, linux-omap, linux-arm-kernel

[-- Attachment #1: Type: text/plain, Size: 1818 bytes --]

On Fri, Jun 07, 2013 at 02:49:52PM -0700, Tony Lindgren wrote:
> From: Andreas Fenkart <andreas.fenkart@streamunlimited.com>
> 
> Without functional clock the omap_hsmmc module can't forward SDIO IRQs to
> the system. This patch reconfigures dat1 line as a gpio while the fclk is
> off. When the fclk is present it uses the standard SDIO IRQ detection of
> the module.
> 
> The gpio irq is managed via the 'disable_depth' ref counter of the irq
> subsystem, this driver simply calls enable_irq/disable_irq when needed.
> 
>                       sdio irq    sdio irq
>                       unmasked     masked
>    -----------------------------------------
>     runtime default  |    1     |   2
>     runtime suspend  |    0     |   1
> 
>                   irq disable depth
> 
> 
> only when sdio irq is enabled AND the module is idle, the reference
> count drops to zero and the gpio irq is effectively armed.
> 
> Patch was tested on AM335x/Stream800. Test setup was two modules
> with sdio wifi cards. Modules where connected to a dual-band AP, each
> module using a different band. One of module was running iperf as server
> the other as client connecting to the server in a while true loop. Test
> was running for 4+ weeks. There were about 60 Mio. suspend/resume
> transitions. Test was shut down regularly.
> 
> Cc: Andreas Fenkart <afenkart@gmail.com>
> Cc: Balaji T K <balajitk@ti.com>
> Signed-off-by: Andreas Fenkart <andreas.fenkart@streamunlimited.com>
> Reviewed-by: Grant Likely <grant.likely@secretlab.ca>
> [tony@atomide.com: updated and separated out pin muxing]
> Signed-off-by: Tony Lindgren <tony@atomide.com>

right, as I discussed before the gpio part should also be split to a
separate patch. That's a workaround for broken HW anyway.

-- 
balbi

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 2/4] mmc: omap_hsmmc: Enable SDIO IRQ using a GPIO in idle mode
@ 2013-07-08  9:02     ` Felipe Balbi
  0 siblings, 0 replies; 32+ messages in thread
From: Felipe Balbi @ 2013-07-08  9:02 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, Jun 07, 2013 at 02:49:52PM -0700, Tony Lindgren wrote:
> From: Andreas Fenkart <andreas.fenkart@streamunlimited.com>
> 
> Without functional clock the omap_hsmmc module can't forward SDIO IRQs to
> the system. This patch reconfigures dat1 line as a gpio while the fclk is
> off. When the fclk is present it uses the standard SDIO IRQ detection of
> the module.
> 
> The gpio irq is managed via the 'disable_depth' ref counter of the irq
> subsystem, this driver simply calls enable_irq/disable_irq when needed.
> 
>                       sdio irq    sdio irq
>                       unmasked     masked
>    -----------------------------------------
>     runtime default  |    1     |   2
>     runtime suspend  |    0     |   1
> 
>                   irq disable depth
> 
> 
> only when sdio irq is enabled AND the module is idle, the reference
> count drops to zero and the gpio irq is effectively armed.
> 
> Patch was tested on AM335x/Stream800. Test setup was two modules
> with sdio wifi cards. Modules where connected to a dual-band AP, each
> module using a different band. One of module was running iperf as server
> the other as client connecting to the server in a while true loop. Test
> was running for 4+ weeks. There were about 60 Mio. suspend/resume
> transitions. Test was shut down regularly.
> 
> Cc: Andreas Fenkart <afenkart@gmail.com>
> Cc: Balaji T K <balajitk@ti.com>
> Signed-off-by: Andreas Fenkart <andreas.fenkart@streamunlimited.com>
> Reviewed-by: Grant Likely <grant.likely@secretlab.ca>
> [tony at atomide.com: updated and separated out pin muxing]
> Signed-off-by: Tony Lindgren <tony@atomide.com>

right, as I discussed before the gpio part should also be split to a
separate patch. That's a workaround for broken HW anyway.

-- 
balbi
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20130708/faf18bc1/attachment.sig>

^ permalink raw reply	[flat|nested] 32+ messages in thread

end of thread, other threads:[~2013-07-08  9:02 UTC | newest]

Thread overview: 32+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-06-07 21:49 [PATCH 0/4] Updated omap_hsmmc SDIO and remuxing patches Tony Lindgren
2013-06-07 21:49 ` Tony Lindgren
2013-06-07 21:49 ` [PATCH 1/4] mmc: omap_hsmmc: Fix context save and restore for DT Tony Lindgren
2013-06-07 21:49   ` Tony Lindgren
2013-06-08  4:25   ` Felipe Balbi
2013-06-08  4:25     ` Felipe Balbi
2013-06-08 15:02     ` Tony Lindgren
2013-06-08 15:02       ` Tony Lindgren
2013-06-07 21:49 ` [PATCH 2/4] mmc: omap_hsmmc: Enable SDIO IRQ using a GPIO in idle mode Tony Lindgren
2013-06-07 21:49   ` Tony Lindgren
2013-06-14  7:37   ` Tony Lindgren
2013-06-14  7:37     ` Tony Lindgren
2013-06-14 11:50   ` Ulf Hansson
2013-06-14 11:50     ` Ulf Hansson
2013-06-20  7:24     ` Tony Lindgren
2013-06-20  7:24       ` Tony Lindgren
2013-07-08  9:02   ` Felipe Balbi
2013-07-08  9:02     ` Felipe Balbi
2013-06-07 21:49 ` [PATCH 3/4] mmc: omap_hsmmc: Remux pins to support SDIO interrupt and PM runtime Tony Lindgren
2013-06-07 21:49   ` Tony Lindgren
2013-06-10 16:03   ` Linus Walleij
2013-06-10 16:03     ` Linus Walleij
2013-06-10 16:23     ` Tony Lindgren
2013-06-10 16:23       ` Tony Lindgren
2013-06-11  7:54       ` Linus Walleij
2013-06-11  7:54         ` Linus Walleij
2013-06-12 13:21         ` Tony Lindgren
2013-06-12 13:21           ` Tony Lindgren
2013-06-14  7:40           ` Tony Lindgren
2013-06-14  7:40             ` Tony Lindgren
2013-06-07 21:50 ` [PATCH 4/4] mmc: omap_hsmmc: debugfs entries for GPIO and SDIO mode Tony Lindgren
2013-06-07 21:50   ` Tony Lindgren

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.