diff mbox

[2/4] mmc: omap_hsmmc: Enable SDIO IRQ using a GPIO in idle mode

Message ID 20130607214952.18581.90049.stgit@localhost (mailing list archive)
State New, archived
Headers show

Commit Message

Tony Lindgren June 7, 2013, 9:49 p.m. UTC
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(-)

Comments

Ulf Hansson June 14, 2013, 11:50 a.m. UTC | #1
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
Tony Lindgren June 20, 2013, 7:24 a.m. UTC | #2
* 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
Felipe Balbi July 8, 2013, 9:02 a.m. UTC | #3
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.
diff mbox

Patch

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];