diff mbox

[v9,resend,1/3] mmc: omap_hsmmc: Enable SDIO interrupt

Message ID 1395404448-30030-2-git-send-email-afenkart@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Andreas Fenkart March 21, 2014, 12:20 p.m. UTC
There have been various patches floating around for enabling
the SDIO IRQ for hsmmc, but none of them ever got merged.

Probably the reason for not merging the SDIO interrupt patches
has been the lack of wake-up path for SDIO on some omaps that
has also needed remuxing the SDIO DAT1 line to a GPIO making
the patches complex.

This patch adds the minimal SDIO IRQ support to hsmmc for
omaps that do have the wake-up path. For those omaps, the
DAT1 line need to have the wake-up enable bit set, and the
wake-up interrupt is the same as for the MMC controller.

This patch has been tested on am3730 es1.2 with mwifiex
connected to MMC3 with mwifiex waking to Ethernet traffic
from off-idle mode. Note that for omaps that do not have
the SDIO wake-up path, this patch will not work for idle
modes and further patches for remuxing DAT1 to GPIO are
needed.

Based on earlier patches [1][2] by David Vrabel
<david.vrabel@csr.com>, Steve Sakoman <steve@sakoman.com>
and Andreas Fenkart <afenkart@gmail.com> with the SDIO IRQ
handing improved following how sdhci.c is doing it.

For now, only support SDIO interrupt if we are booted with
a separate wake-irq configued via device tree. This is
because omaps need the wake-irq for idle states, and some
omaps need special quirks. And we don't want to add new
legacy mux platform init code callbacks any longer as we
are moving to DT based booting anyways.

To use it, you need to specify the wake-irq using the
interrupts-extended property.

[1] http://www.sakoman.com/cgi-bin/gitweb.cgi?p=linux.git;a=commitdiff_plain;h=010810d22f6f49ac03da4ba384969432e0320453
[2] http://comments.gmane.org/gmane.linux.kernel.mmc/20446

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

Comments

Balaji T K March 21, 2014, 4:10 p.m. UTC | #1
On Friday 21 March 2014 05:50 PM, Andreas Fenkart wrote:

Thanks Andreas for the patch series

I rebased against latest mmc-next, made few changes to your patch.
I have hosted your series along with devm cleanups on a branch[1] for testing
[1]
git://git.ti.com/~balajitk/ti-linux-kernel/omap-hsmmc.git omap_hsmmc_sdio_irq_devm_cleanup

Can you please test on your platform and provide feedback.

Details about the changes below.

> diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
> @@ -1088,6 +1113,45 @@ static irqreturn_t omap_hsmmc_irq(int irq, void *dev_id)
>   	return IRQ_HANDLED;
>   }
>
> +static inline void hsmmc_enable_wake_irq(struct omap_hsmmc_host *host)
> +{
> +	unsigned long flags;
> +
> +	if (!host->wake_irq)
> +		return;
> +
> +	spin_lock_irqsave(&host->irq_lock, flags);
> +	enable_irq(host->wake_irq);
> +	host->wake_irq_en = true;

Using wake_irq_en flag leads to wake_irq enabled always after
suspend/resume due to unbalanced disable/enable_irq
so adding back HSMMC_WAKE_IRQ_ENABLED to host->flags

> +	spin_unlock_irqrestore(&host->irq_lock, flags);
> +}
> +
> +static inline void hsmmc_disable_wake_irq(struct omap_hsmmc_host *host)
> +{
> +	unsigned long flags;
> +
> +	if (!host->wake_irq)
> +		return;
> +
> +	spin_lock_irqsave(&host->irq_lock, flags);
> +	if (host->wake_irq_en)
> +		disable_irq_nosync(host->wake_irq);
> +	host->wake_irq_en = false;
> +	spin_unlock_irqrestore(&host->irq_lock, flags);
> +}
> +
> +static irqreturn_t omap_hsmmc_wake_irq(int irq, void *dev_id)
> +{
> +	struct omap_hsmmc_host *host = dev_id;
> +
> +	/* cirq is level triggered, disable to avoid infinite loop */
> +	hsmmc_disable_wake_irq(host);
> +
> +	pm_request_resume(host->dev); /* no use counter */
> +
> +	return IRQ_HANDLED;
> +}
> +
>   static void set_sd_bus_power(struct omap_hsmmc_host *host)
>   {
>   	unsigned long i;
> @@ -1591,6 +1655,72 @@ 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);
> +

Introduced check for runtime suspend to be sure and explicitly
enable clocks using runtime_get_sync for enable sdio irq path.

> +	irq_mask = OMAP_HSMMC_READ(host->base, ISE);
> +	if (enable) {
> +		host->flags |= HSMMC_SDIO_IRQ_ENABLED;
> +		irq_mask |= CIRQ_EN;
> +	} else {
> +		host->flags &= ~HSMMC_SDIO_IRQ_ENABLED;
> +		irq_mask &= ~CIRQ_EN;
> +	}
> +	OMAP_HSMMC_WRITE(host->base, IE, irq_mask);
> +
> +	/*
> +	 * if enable, piggy back detection on current request
> +	 * but always disable immediately
> +	 */
> +	if (!host->req_in_progress || !enable)
> +		OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
> +
> +	/* flush posted write */
> +	OMAP_HSMMC_READ(host->base, IE);
> +
> +	spin_unlock_irqrestore(&host->irq_lock, flags);
> +}
> +
> +static int omap_hscmm_configure_wake_irq(struct omap_hsmmc_host *host)
> +{
> +	struct mmc_host *mmc = host->mmc;
> +	int ret;
> +
> +	/*
> +	 * The wake-irq is needed for omaps with wake-up path and also
> +	 * when doing GPIO remuxing, because omap_hsmmc is doing runtime PM.
> +	 * So there's nothing stopping from shutting it down. And there's
> +	 * really no need to block runtime PM for it as it's working.
> +	 */
> +	if (!host->dev->of_node || !host->wake_irq)
> +		return -ENODEV;
> +
> +	/* Prevent auto-enabling of IRQ */
> +	irq_set_status_flags(host->wake_irq, IRQ_NOAUTOEN);
> +	ret = request_irq(host->wake_irq, omap_hsmmc_wake_irq,
> +			  IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> +			  mmc_hostname(mmc), host);

Replaced request_irq with devm_request_irq

> +	if (ret) {
> +		dev_err(mmc_dev(host->mmc),
> +			"Unable to request wake IRQ\n");
> +		return ret;
> +	}
> +
> +	/*
> +	 * Some omaps don't have wake-up path from deeper idle states
> +	 * and need to remux SDIO DAT1 to GPIO for wake-up from idle.
> +	 */
> +	if (host->pdata->controller_flags & OMAP_HSMMC_SWAKEUP_MISSING)
> +		host->flags |= HSMMC_SWAKEUP_QUIRK;
> +
> +	return 0;
> +}
> +
>   static void omap_hsmmc_conf_bus_power(struct omap_hsmmc_host *host)
>   {
>   	u32 hctl, capa, value;
> @@ -1643,7 +1773,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
> @@ -1704,8 +1834,19 @@ static void omap_hsmmc_debugfs(struct mmc_host *mmc)
>
>   #endif
>
> +struct of_data {
> +	u16 offset;
> +	int flags;
> +};
> +
>   #ifdef CONFIG_OF
> -static u16 omap4_reg_offset = 0x100;
> +static struct of_data omap4_data = {
> +	.offset	= 0x100,
> +};
> +static struct of_data am33xx_data = {
> +	.offset	= 0x100,
> +	.flags	= OMAP_HSMMC_SWAKEUP_MISSING,
> +};
>
>   static const struct of_device_id omap_mmc_of_match[] = {
>   	{
> @@ -1716,7 +1857,11 @@ static const struct of_device_id omap_mmc_of_match[] = {
>   	},
>   	{
>   		.compatible = "ti,omap4-hsmmc",
> -		.data = &omap4_reg_offset,
> +		.data = &omap4_data,
> +	},
> +	{
> +		.compatible = "ti,am33xx-hsmmc",
> +		.data = &am33xx_data,
>   	},
>   	{},
>   };
> @@ -1779,6 +1924,7 @@ static inline struct omap_mmc_platform_data
>   {
>   	return NULL;
>   }
> +#define omap_mmc_of_match	NULL
>   #endif
>
>   static int omap_hsmmc_probe(struct platform_device *pdev)
> @@ -1787,7 +1933,7 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
>   	struct mmc_host *mmc;
>   	struct omap_hsmmc_host *host = NULL;
>   	struct resource *res;
> -	int ret, irq;
> +	int ret, irq, _wake_irq = 0;
>   	const struct of_device_id *match;
>   	dma_cap_mask_t mask;
>   	unsigned tx_req, rx_req;
> @@ -1801,8 +1947,9 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
>   			return PTR_ERR(pdata);
>
>   		if (match->data) {
> -			const u16 *offsetp = match->data;
> -			pdata->reg_offset = *offsetp;
> +			const struct of_data *d = match->data;
> +			pdata->reg_offset = d->offset;
> +			pdata->controller_flags |= d->flags;
>   		}
>   	}
>
> @@ -1821,6 +1968,9 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
>   	if (res == NULL || irq < 0)
>   		return -ENXIO;
>
> +	if (pdev->dev.of_node)
> +		_wake_irq = irq_of_parse_and_map(pdev->dev.of_node, 1);
> +

Moved this code further down to remove _wake_irq

>   	res = request_mem_region(res->start, resource_size(res), pdev->name);
>   	if (res == NULL)
>   		return -EBUSY;
> @@ -1842,6 +1992,7 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
>   	host->use_dma	= 1;
>   	host->dma_ch	= -1;
>   	host->irq	= irq;
> +	host->wake_irq = _wake_irq;
>   	host->slot_id	= 0;
>   	host->mapbase	= res->start + pdata->reg_offset;
>   	host->base	= ioremap(host->mapbase, SZ_4K);
> @@ -2018,6 +2169,18 @@ 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 have a separate
> +	 * wake-up interrupt configured from device tree. This is because
> +	 * the wake-up interrupt is needed for idle state and some
> +	 * platforms need special quirks. And we don't want to add new
> +	 * legacy mux platform init code callbacks any longer as we
> +	 * are moving to DT based booting anyways.
> +	 */
> +	ret = omap_hscmm_configure_wake_irq(host);

fixed the typo in hsmmc :-)

> +	if (!ret)
> +		mmc->caps |= MMC_CAP_SDIO_IRQ;
> +
>   	omap_hsmmc_protect_card(host);
>
>   	mmc_add_host(mmc);
> @@ -2042,7 +2205,10 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
>
>   err_slot_name:
>   	mmc_remove_host(mmc);
> -	free_irq(mmc_slot(host).card_detect_irq, host);
> +	if (host->wake_irq)
> +		free_irq(host->wake_irq, host);
> +	if (mmc_slot(host).card_detect_irq)
> +		free_irq(mmc_slot(host).card_detect_irq, host);
>   err_irq_cd:
>   	if (host->use_reg)
>   		omap_hsmmc_reg_put(host);
> @@ -2087,6 +2253,8 @@ static int omap_hsmmc_remove(struct platform_device *pdev)
>   	if (host->pdata->cleanup)
>   		host->pdata->cleanup(&pdev->dev);
>   	free_irq(host->irq, host);
> +	if (host->wake_irq)
> +		free_irq(host->wake_irq, host);
>   	if (mmc_slot(host).card_detect_irq)
>   		free_irq(mmc_slot(host).card_detect_irq, host);
>
> @@ -2149,6 +2317,8 @@ static int omap_hsmmc_suspend(struct device *dev)
>   				OMAP_HSMMC_READ(host->base, HCTL) & ~SDBP);
>   	}
>
> +	hsmmc_disable_wake_irq(host);
> +

Made disable/enable_wake_irq conditional on MMC_PM_WAKE_SDIO_IRQ cap

>   	if (host->dbclk)
>   		clk_disable_unprepare(host->dbclk);
>
> @@ -2176,6 +2346,8 @@ static int omap_hsmmc_resume(struct device *dev)
>
>   	omap_hsmmc_protect_card(host);
>
> +	hsmmc_enable_wake_irq(host);
> +
>   	pm_runtime_mark_last_busy(host->dev);
>   	pm_runtime_put_autosuspend(host->dev);
>   	return 0;
> @@ -2191,23 +2363,38 @@ static int omap_hsmmc_resume(struct device *dev)
>   static int omap_hsmmc_runtime_suspend(struct device *dev)
>   {
>   	struct omap_hsmmc_host *host;
> +	int ret = 0;
>
>   	host = platform_get_drvdata(to_platform_device(dev));
>   	omap_hsmmc_context_save(host);
>   	dev_dbg(dev, "disabled\n");
>
> -	return 0;
> +	if (host->mmc->caps & MMC_CAP_SDIO_IRQ) {
> +		OMAP_HSMMC_WRITE(host->base, ISE, 0);
> +		OMAP_HSMMC_WRITE(host->base, IE, 0);
> +		OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
> +		hsmmc_enable_wake_irq(host);

here too.

> +	}
> +
> +	return ret;
>   }
>
>   static int omap_hsmmc_runtime_resume(struct device *dev)
>   {
>   	struct omap_hsmmc_host *host;
> +	int ret = 0;
>
>   	host = platform_get_drvdata(to_platform_device(dev));
>   	omap_hsmmc_context_restore(host);
>   	dev_dbg(dev, "enabled\n");
>
> -	return 0;
> +	if (host->mmc->caps & MMC_CAP_SDIO_IRQ) {

This leads to unconditional re-enabling sdio_irq/wake_irq
on next runtime resume, so replaced the check with HSMMC_SDIO_IRQ_ENABLED

> +		hsmmc_disable_wake_irq(host);
> +		OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
> +		OMAP_HSMMC_WRITE(host->base, ISE, CIRQ_EN);
> +		OMAP_HSMMC_WRITE(host->base, IE, CIRQ_EN);
> +	}
> +	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..51e70cf 100644
> --- a/include/linux/platform_data/mmc-omap.h
> +++ b/include/linux/platform_data/mmc-omap.h
> @@ -28,6 +28,7 @@
>    */
>   #define OMAP_HSMMC_SUPPORTS_DUAL_VOLT		BIT(0)
>   #define OMAP_HSMMC_BROKEN_MULTIBLOCK_READ	BIT(1)
> +#define OMAP_HSMMC_SWAKEUP_MISSING		BIT(2)
>
>   struct mmc_card;
>
>

--
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
Balaji T K March 21, 2014, 4:17 p.m. UTC | #2
Andreas Fenkart (3):
  mmc: omap_hsmmc: Enable SDIO interrupt
  mmc: omap_hsmmc: Pin remux workaround to support SDIO interrupt on
    AM335x
  mmc: omap_hsmmc: Extend debugfs for SDIO IRQ, GPIO and pinmux

Balaji T K (6):
  mmc: omap_hsmmc: use devm_clk_get
  mmc: omap_hsmmc: use devm_request_irq
  mmc: omap_hsmmc: use devm_request_threaded_irq
  mmc: omap_hsmmc: use devm_request_mem_region
  mmc: omap_hsmmc: use devm_ioremap
  mmc: omap_hsmmc: enable wakeup event for sdio

 .../devicetree/bindings/mmc/ti-omap-hsmmc.txt      |   50 +++
 drivers/mmc/host/omap_hsmmc.c                      |  349 +++++++++++++++++---
 include/linux/platform_data/mmc-omap.h             |    1 +
 3 files changed, 346 insertions(+), 54 deletions(-)
Dmitry Lifshitz March 24, 2014, 9:30 a.m. UTC | #3
Hi,

I've tested the branch omap_hsmmc_sdio_irq_devm_cleanup on custom OMAP5 
based board.
We have mwifiex connected to MMC3.

I used two approaches:

* Using dat1 line as GPIO with dynamic remuxing
* Using separate GPIO connected to dat1 with static mux settings

Both works just fine. Thanks Andreas for a great solution.

I have just one issue - how to define GPIO IRQ using DT in OMAP5 case.
As a temporary hack for testing I used a fixed GPIO number and 
gpio_to_irq() call in omap_hsmmc_configure_wake_irq().

OMAP5 MMC3 has the following DT entry (omap5.dtsi):

                 mmc3: mmc@480ad000 {
                         compatible = "ti,omap4-hsmmc";
                         reg = <0x480ad000 0x400>;
                         interrupts = <GIC_SPI 94 IRQ_TYPE_LEVEL_HIGH>;
                         ti,hwmods = "mmc3";
                         ti,needs-special-reset;
                         dmas = <&sdma 77>, <&sdma 78>;
                         dma-names = "tx", "rx";
                 };

What should be by board MMC3 setup?

Something like this?

&mmc3 {
         pinctrl-names = "default", "active", "idle";
         pinctrl-0 = <&mmc3_pins>;
         pinctrl-1 = <&mmc3_pins>;
         pinctrl-2 = <&mmc3_cirq_pin>;

         interrupts = <GIC_SPI 94 IRQ_TYPE_LEVEL_HIGH>,
                             <????????>;

         vmmc-supply = <&ldo2_reg>;
         bus-width = <4>;
         ti,non-removable;
};

I tried to specify the second interrupt as (wlsdio_data1 / gpio5_131):

<&gpio5 3 IRQ_TYPE_NONE>

but it fails:

[    2.275777] ------------[ cut here ]------------
[    2.280604] WARNING: CPU: 1 PID: 6 at 
/home/lifshitz/workroot/OMAP5-eewiki/omap5-kernel/kernel/irq/manage.c:1418 
request_threaded_irq+0x11c/0x12c()
[    2.294414] Modules linked in:
[    2.297611] CPU: 1 PID: 6 Comm: kworker/u4:0 Tainted: G W    
3.14.0-rc4-cm-t54-test-suit+ #30
[    2.307165] Workqueue: deferwq deferred_probe_work_func
[    2.312629] [<c001597c>] (unwind_backtrace) from [<c001283c>] 
(show_stack+0x10/0x14)
[    2.320739] [<c001283c>] (show_stack) from [<c06494dc>] 
(dump_stack+0x70/0x88)
[    2.328294] [<c06494dc>] (dump_stack) from [<c003a520>] 
(warn_slowpath_common+0x70/0x88)
[    2.336761] [<c003a520>] (warn_slowpath_common) from [<c003a554>] 
(warn_slowpath_null+0x1c/0x24)
[    2.345940] [<c003a554>] (warn_slowpath_null) from [<c0083ce4>] 
(request_threaded_irq+0x11c/0x12c)
[    2.355312] [<c0083ce4>] (request_threaded_irq) from [<c0085964>] 
(devm_request_threaded_irq+0x5c/0x90)
[    2.361973] mmc1: host does not support reading read-only switch. 
assuming write-enable.
[    2.362020] mmc1: new SDHC card at address e624
[    2.362331] mmcblk1: mmc1:e624 SU08G 7.40 GiB
[    2.368152]  mmcblk1: p1 p2
[    2.385850] [<c0085964>] (devm_request_threaded_irq) from 
[<c047d958>] (omap_hsmmc_configure_wake_irq+0x7c/0xfc)
[    2.393150] usb 1-2: New USB device found, idVendor=0424, idProduct=3503
[    2.393156] usb 1-2: New USB device strings: Mfr=0, Product=0, 
SerialNumber=0
[    2.393674] hub 1-2:1.0: USB hub found
[    2.393769] hub 1-2:1.0: 3 ports detected
[    2.419038] [<c047d958>] (omap_hsmmc_configure_wake_irq) from 
[<c047ea28>] (omap_hsmmc_probe+0x5b4/0x854)
[    2.429052] [<c047ea28>] (omap_hsmmc_probe) from [<c0359ec0>] 
(platform_drv_probe+0x18/0x48)
[    2.437879] [<c0359ec0>] (platform_drv_probe) from [<c03580c8>] 
(really_probe+0x80/0x208)
[    2.446427] [<c03580c8>] (really_probe) from [<c0358360>] 
(driver_probe_device+0x30/0x48)
[    2.454976] [<c0358360>] (driver_probe_device) from [<c0356a58>] 
(bus_for_each_drv+0x5c/0x88)
[    2.463891] [<c0356a58>] (bus_for_each_drv) from [<c03582f4>] 
(device_attach+0x80/0xa0)
[    2.472247] [<c03582f4>] (device_attach) from [<c03577b4>] 
(bus_probe_device+0x84/0xa8)
[    2.480617] [<c03577b4>] (bus_probe_device) from [<c0357bb8>] 
(deferred_probe_work_func+0x68/0x98)
[    2.489987] [<c0357bb8>] (deferred_probe_work_func) from [<c00533c0>] 
(process_one_work+0x150/0x41c)
[    2.499542] [<c00533c0>] (process_one_work) from [<c0053d10>] 
(worker_thread+0xf4/0x31c)
[    2.508011] [<c0053d10>] (worker_thread) from [<c0059994>] 
(kthread+0xd4/0xe8)
[    2.512785] usb 1-3: new high-speed USB device number 3 using ehci-omap
[    2.522474] [<c0059994>] (kthread) from [<c000e878>] 
(ret_from_fork+0x14/0x3c)
[    2.530023] ---[ end trace 8d60f1d3adba88d7 ]---


Regards,

Dmitry

On 03/21/2014 06:10 PM, Balaji T K wrote:
> On Friday 21 March 2014 05:50 PM, Andreas Fenkart wrote:
>
> Thanks Andreas for the patch series
>
> I rebased against latest mmc-next, made few changes to your patch.
> I have hosted your series along with devm cleanups on a branch[1] for 
> testing
> [1]
> git://git.ti.com/~balajitk/ti-linux-kernel/omap-hsmmc.git 
> omap_hsmmc_sdio_irq_devm_cleanup
>
> Can you please test on your platform and provide feedback.
>
> Details about the changes below.

--
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
Tony Lindgren April 18, 2014, 4:46 p.m. UTC | #4
Hi,

Sorry for the delay, just noticed this one.

* Dmitry Lifshitz <lifshitz@compulab.co.il> [140324 02:34]:
> Hi,
> 
> I've tested the branch omap_hsmmc_sdio_irq_devm_cleanup on custom OMAP5
> based board.
> We have mwifiex connected to MMC3.
> 
> I used two approaches:
> 
> * Using dat1 line as GPIO with dynamic remuxing
> * Using separate GPIO connected to dat1 with static mux settings
> 
> Both works just fine. Thanks Andreas for a great solution.
> 
> I have just one issue - how to define GPIO IRQ using DT in OMAP5 case.
> As a temporary hack for testing I used a fixed GPIO number and gpio_to_irq()
> call in omap_hsmmc_configure_wake_irq().
> 
> OMAP5 MMC3 has the following DT entry (omap5.dtsi):
> 
>                 mmc3: mmc@480ad000 {
>                         compatible = "ti,omap4-hsmmc";
>                         reg = <0x480ad000 0x400>;
>                         interrupts = <GIC_SPI 94 IRQ_TYPE_LEVEL_HIGH>;
>                         ti,hwmods = "mmc3";
>                         ti,needs-special-reset;
>                         dmas = <&sdma 77>, <&sdma 78>;
>                         dma-names = "tx", "rx";
>                 };
> 
> What should be by board MMC3 setup?

You need to use the interrupts-extended property in omap5.dtsi
for the MMC interrupt. And if using the wake-up interrupts
for your board, you need to sepcify interrupts-extended in your
board file again to add the second wake-up interrupt.

If using the async wake-up path, the wake-up interrupt is the
OMAP5_CORE_IOPAD() or OMAP5_WKUP_IOPAD() pinctrl register offset
for that pin. If remuxing to GPIO for the wake-up, the wake-up
interrupt is the standard gpio interrupt property.

Then you also need this patch to avoid warnings during boot:

http://lkml.org/lkml/2014/4/10/620

After the above gets merged, we can apply the serial wake-up
patch too:

http://lists.infradead.org/pipermail/linux-arm-kernel/2013-November/213873.html

That shows you some examples on how to specify the wake-up
interrupt.

Regards,

Tony
--
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
diff mbox

Patch

diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
index 317a9d5..06bf669 100644
--- a/drivers/mmc/host/omap_hsmmc.c
+++ b/drivers/mmc/host/omap_hsmmc.c
@@ -29,6 +29,7 @@ 
 #include <linux/timer.h>
 #include <linux/clk.h>
 #include <linux/of.h>
+#include <linux/of_irq.h>
 #include <linux/of_gpio.h>
 #include <linux/of_device.h>
 #include <linux/omap-dma.h>
@@ -36,6 +37,7 @@ 
 #include <linux/mmc/core.h>
 #include <linux/mmc/mmc.h>
 #include <linux/io.h>
+#include <linux/irq.h>
 #include <linux/gpio.h>
 #include <linux/regulator/consumer.h>
 #include <linux/pinctrl/consumer.h>
@@ -131,6 +133,7 @@  static void apply_clk_hack(struct device *dev)
 #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)
@@ -205,6 +208,8 @@  struct omap_hsmmc_host {
 	u32			sysctl;
 	u32			capa;
 	int			irq;
+	int			wake_irq;
+	int			wake_irq_en;
 	int			use_dma, dma_ch;
 	struct dma_chan		*tx_chan;
 	struct dma_chan		*rx_chan;
@@ -215,6 +220,9 @@  struct omap_hsmmc_host {
 	int			reqs_blocked;
 	int			use_reg;
 	int			req_in_progress;
+	int                     flags;
+#define HSMMC_SDIO_IRQ_ENABLED	(1 << 0)        /* SDIO irq enabled */
+#define HSMMC_SWAKEUP_QUIRK	(1 << 1)
 	struct omap_hsmmc_next	next_data;
 	struct	omap_mmc_platform_data	*pdata;
 };
@@ -495,27 +503,40 @@  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 MMC core */
+	if (host->flags & HSMMC_SDIO_IRQ_ENABLED)
+		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 but need to keep cirq if enabled */
+	if (host->flags & HSMMC_SDIO_IRQ_ENABLED)
+		irq_mask |= CIRQ_EN;
+	OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
+	OMAP_HSMMC_WRITE(host->base, IE, irq_mask);
 	OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
+	spin_unlock_irqrestore(&host->irq_lock, flags);
 }
 
 /* Calculate divisor for the given clock frequency */
@@ -1078,8 +1099,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 */
 		status = OMAP_HSMMC_READ(host->base, STAT);
@@ -1088,6 +1113,45 @@  static irqreturn_t omap_hsmmc_irq(int irq, void *dev_id)
 	return IRQ_HANDLED;
 }
 
+static inline void hsmmc_enable_wake_irq(struct omap_hsmmc_host *host)
+{
+	unsigned long flags;
+
+	if (!host->wake_irq)
+		return;
+
+	spin_lock_irqsave(&host->irq_lock, flags);
+	enable_irq(host->wake_irq);
+	host->wake_irq_en = true;
+	spin_unlock_irqrestore(&host->irq_lock, flags);
+}
+
+static inline void hsmmc_disable_wake_irq(struct omap_hsmmc_host *host)
+{
+	unsigned long flags;
+
+	if (!host->wake_irq)
+		return;
+
+	spin_lock_irqsave(&host->irq_lock, flags);
+	if (host->wake_irq_en)
+		disable_irq_nosync(host->wake_irq);
+	host->wake_irq_en = false;
+	spin_unlock_irqrestore(&host->irq_lock, flags);
+}
+
+static irqreturn_t omap_hsmmc_wake_irq(int irq, void *dev_id)
+{
+	struct omap_hsmmc_host *host = dev_id;
+
+	/* cirq is level triggered, disable to avoid infinite loop */
+	hsmmc_disable_wake_irq(host);
+
+	pm_request_resume(host->dev); /* no use counter */
+
+	return IRQ_HANDLED;
+}
+
 static void set_sd_bus_power(struct omap_hsmmc_host *host)
 {
 	unsigned long i;
@@ -1591,6 +1655,72 @@  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);
+
+	irq_mask = OMAP_HSMMC_READ(host->base, ISE);
+	if (enable) {
+		host->flags |= HSMMC_SDIO_IRQ_ENABLED;
+		irq_mask |= CIRQ_EN;
+	} else {
+		host->flags &= ~HSMMC_SDIO_IRQ_ENABLED;
+		irq_mask &= ~CIRQ_EN;
+	}
+	OMAP_HSMMC_WRITE(host->base, IE, irq_mask);
+
+	/*
+	 * if enable, piggy back detection on current request
+	 * but always disable immediately
+	 */
+	if (!host->req_in_progress || !enable)
+		OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
+
+	/* flush posted write */
+	OMAP_HSMMC_READ(host->base, IE);
+
+	spin_unlock_irqrestore(&host->irq_lock, flags);
+}
+
+static int omap_hscmm_configure_wake_irq(struct omap_hsmmc_host *host)
+{
+	struct mmc_host *mmc = host->mmc;
+	int ret;
+
+	/*
+	 * The wake-irq is needed for omaps with wake-up path and also
+	 * when doing GPIO remuxing, because omap_hsmmc is doing runtime PM.
+	 * So there's nothing stopping from shutting it down. And there's
+	 * really no need to block runtime PM for it as it's working.
+	 */
+	if (!host->dev->of_node || !host->wake_irq)
+		return -ENODEV;
+
+	/* Prevent auto-enabling of IRQ */
+	irq_set_status_flags(host->wake_irq, IRQ_NOAUTOEN);
+	ret = request_irq(host->wake_irq, omap_hsmmc_wake_irq,
+			  IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+			  mmc_hostname(mmc), host);
+	if (ret) {
+		dev_err(mmc_dev(host->mmc),
+			"Unable to request wake IRQ\n");
+		return ret;
+	}
+
+	/*
+	 * Some omaps don't have wake-up path from deeper idle states
+	 * and need to remux SDIO DAT1 to GPIO for wake-up from idle.
+	 */
+	if (host->pdata->controller_flags & OMAP_HSMMC_SWAKEUP_MISSING)
+		host->flags |= HSMMC_SWAKEUP_QUIRK;
+
+	return 0;
+}
+
 static void omap_hsmmc_conf_bus_power(struct omap_hsmmc_host *host)
 {
 	u32 hctl, capa, value;
@@ -1643,7 +1773,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
@@ -1704,8 +1834,19 @@  static void omap_hsmmc_debugfs(struct mmc_host *mmc)
 
 #endif
 
+struct of_data {
+	u16 offset;
+	int flags;
+};
+
 #ifdef CONFIG_OF
-static u16 omap4_reg_offset = 0x100;
+static struct of_data omap4_data = {
+	.offset	= 0x100,
+};
+static struct of_data am33xx_data = {
+	.offset	= 0x100,
+	.flags	= OMAP_HSMMC_SWAKEUP_MISSING,
+};
 
 static const struct of_device_id omap_mmc_of_match[] = {
 	{
@@ -1716,7 +1857,11 @@  static const struct of_device_id omap_mmc_of_match[] = {
 	},
 	{
 		.compatible = "ti,omap4-hsmmc",
-		.data = &omap4_reg_offset,
+		.data = &omap4_data,
+	},
+	{
+		.compatible = "ti,am33xx-hsmmc",
+		.data = &am33xx_data,
 	},
 	{},
 };
@@ -1779,6 +1924,7 @@  static inline struct omap_mmc_platform_data
 {
 	return NULL;
 }
+#define omap_mmc_of_match	NULL
 #endif
 
 static int omap_hsmmc_probe(struct platform_device *pdev)
@@ -1787,7 +1933,7 @@  static int omap_hsmmc_probe(struct platform_device *pdev)
 	struct mmc_host *mmc;
 	struct omap_hsmmc_host *host = NULL;
 	struct resource *res;
-	int ret, irq;
+	int ret, irq, _wake_irq = 0;
 	const struct of_device_id *match;
 	dma_cap_mask_t mask;
 	unsigned tx_req, rx_req;
@@ -1801,8 +1947,9 @@  static int omap_hsmmc_probe(struct platform_device *pdev)
 			return PTR_ERR(pdata);
 
 		if (match->data) {
-			const u16 *offsetp = match->data;
-			pdata->reg_offset = *offsetp;
+			const struct of_data *d = match->data;
+			pdata->reg_offset = d->offset;
+			pdata->controller_flags |= d->flags;
 		}
 	}
 
@@ -1821,6 +1968,9 @@  static int omap_hsmmc_probe(struct platform_device *pdev)
 	if (res == NULL || irq < 0)
 		return -ENXIO;
 
+	if (pdev->dev.of_node)
+		_wake_irq = irq_of_parse_and_map(pdev->dev.of_node, 1);
+
 	res = request_mem_region(res->start, resource_size(res), pdev->name);
 	if (res == NULL)
 		return -EBUSY;
@@ -1842,6 +1992,7 @@  static int omap_hsmmc_probe(struct platform_device *pdev)
 	host->use_dma	= 1;
 	host->dma_ch	= -1;
 	host->irq	= irq;
+	host->wake_irq = _wake_irq;
 	host->slot_id	= 0;
 	host->mapbase	= res->start + pdata->reg_offset;
 	host->base	= ioremap(host->mapbase, SZ_4K);
@@ -2018,6 +2169,18 @@  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 have a separate
+	 * wake-up interrupt configured from device tree. This is because
+	 * the wake-up interrupt is needed for idle state and some
+	 * platforms need special quirks. And we don't want to add new
+	 * legacy mux platform init code callbacks any longer as we
+	 * are moving to DT based booting anyways.
+	 */
+	ret = omap_hscmm_configure_wake_irq(host);
+	if (!ret)
+		mmc->caps |= MMC_CAP_SDIO_IRQ;
+
 	omap_hsmmc_protect_card(host);
 
 	mmc_add_host(mmc);
@@ -2042,7 +2205,10 @@  static int omap_hsmmc_probe(struct platform_device *pdev)
 
 err_slot_name:
 	mmc_remove_host(mmc);
-	free_irq(mmc_slot(host).card_detect_irq, host);
+	if (host->wake_irq)
+		free_irq(host->wake_irq, host);
+	if (mmc_slot(host).card_detect_irq)
+		free_irq(mmc_slot(host).card_detect_irq, host);
 err_irq_cd:
 	if (host->use_reg)
 		omap_hsmmc_reg_put(host);
@@ -2087,6 +2253,8 @@  static int omap_hsmmc_remove(struct platform_device *pdev)
 	if (host->pdata->cleanup)
 		host->pdata->cleanup(&pdev->dev);
 	free_irq(host->irq, host);
+	if (host->wake_irq)
+		free_irq(host->wake_irq, host);
 	if (mmc_slot(host).card_detect_irq)
 		free_irq(mmc_slot(host).card_detect_irq, host);
 
@@ -2149,6 +2317,8 @@  static int omap_hsmmc_suspend(struct device *dev)
 				OMAP_HSMMC_READ(host->base, HCTL) & ~SDBP);
 	}
 
+	hsmmc_disable_wake_irq(host);
+
 	if (host->dbclk)
 		clk_disable_unprepare(host->dbclk);
 
@@ -2176,6 +2346,8 @@  static int omap_hsmmc_resume(struct device *dev)
 
 	omap_hsmmc_protect_card(host);
 
+	hsmmc_enable_wake_irq(host);
+
 	pm_runtime_mark_last_busy(host->dev);
 	pm_runtime_put_autosuspend(host->dev);
 	return 0;
@@ -2191,23 +2363,38 @@  static int omap_hsmmc_resume(struct device *dev)
 static int omap_hsmmc_runtime_suspend(struct device *dev)
 {
 	struct omap_hsmmc_host *host;
+	int ret = 0;
 
 	host = platform_get_drvdata(to_platform_device(dev));
 	omap_hsmmc_context_save(host);
 	dev_dbg(dev, "disabled\n");
 
-	return 0;
+	if (host->mmc->caps & MMC_CAP_SDIO_IRQ) {
+		OMAP_HSMMC_WRITE(host->base, ISE, 0);
+		OMAP_HSMMC_WRITE(host->base, IE, 0);
+		OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
+		hsmmc_enable_wake_irq(host);
+	}
+
+	return ret;
 }
 
 static int omap_hsmmc_runtime_resume(struct device *dev)
 {
 	struct omap_hsmmc_host *host;
+	int ret = 0;
 
 	host = platform_get_drvdata(to_platform_device(dev));
 	omap_hsmmc_context_restore(host);
 	dev_dbg(dev, "enabled\n");
 
-	return 0;
+	if (host->mmc->caps & MMC_CAP_SDIO_IRQ) {
+		hsmmc_disable_wake_irq(host);
+		OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
+		OMAP_HSMMC_WRITE(host->base, ISE, CIRQ_EN);
+		OMAP_HSMMC_WRITE(host->base, IE, CIRQ_EN);
+	}
+	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..51e70cf 100644
--- a/include/linux/platform_data/mmc-omap.h
+++ b/include/linux/platform_data/mmc-omap.h
@@ -28,6 +28,7 @@ 
  */
 #define OMAP_HSMMC_SUPPORTS_DUAL_VOLT		BIT(0)
 #define OMAP_HSMMC_BROKEN_MULTIBLOCK_READ	BIT(1)
+#define OMAP_HSMMC_SWAKEUP_MISSING		BIT(2)
 
 struct mmc_card;