Message ID | 1395404448-30030-2-git-send-email-afenkart@gmail.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
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
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(-)
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
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 --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;