Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit af4d3665 authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "mmc: sdhci-msm: add sdio wakeup support"

parents 1d3871d3 d569f144
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -55,6 +55,9 @@ Optional Properties:
				   no default value that the driver assumes if this property
				   is not specified. So if this property is not specified,
				   then SDHC driver will not vote for PM QOS.
        - qcom,dat1-mpm-int:    specifies MPM interrupt number (e.g. sdhc_2 node below)
				corresponding to DAT1 line of SDHC (used only if slot has dedicated
				DAT1 MSM pin (not GPIO))

In the following, <supply> can be vdd (flash core voltage) or vdd-io (I/O voltage).
	- qcom,<supply>-always-on - specifies whether supply should be kept "on" always.
@@ -165,4 +168,5 @@ Example:
				<81 512 106496 212992>, /* 208 MB/s */
				<81 512 2147483647 4294967295>; /* Max. bandwidth */
		qcom,bus-bw-vectors-bps = <0 13631488 27262976 54525952 109051904 218103808 4294967295>;
		qcom,dat1-mpm-int = <44>;
	};
+219 −3
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@
#include <linux/delay.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/irq.h>
#include <linux/mmc/mmc.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
@@ -38,10 +39,18 @@
#include <linux/dma-mapping.h>
#include <mach/gpio.h>
#include <mach/msm_bus.h>
#include <mach/mpm.h>
#include <linux/iopoll.h>

#include "sdhci-pltfm.h"

enum sdc_mpm_pin_state {
	SDC_DAT1_DISABLE,
	SDC_DAT1_ENABLE,
	SDC_DAT1_ENWAKE,
	SDC_DAT1_DISWAKE,
};

#define SDHCI_VER_100		0x2B
#define CORE_HC_MODE		0x78
#define HC_MODE_EN		0x1
@@ -165,6 +174,9 @@

#define INVALID_TUNING_PHASE	-1

#define sdhci_is_valid_mpm_wakeup_int(_h) ((_h)->pdata->mpm_sdiowakeup_int >= 0)
#define sdhci_is_valid_gpio_wakeup_int(_h) ((_h)->pdata->sdiowakeup_irq >= 0)

static const u32 tuning_block_64[] = {
	0x00FF0FFF, 0xCCC3CCFF, 0xFFCC3CC3, 0xEFFEFFFE,
	0xDDFFDFFF, 0xFBFFFBFF, 0xFF7FFFBF, 0xEFBDF777,
@@ -292,6 +304,8 @@ struct sdhci_msm_pltfm_data {
	struct sdhci_msm_bus_voting_data *voting_data;
	u32 *sup_clk_table;
	unsigned char sup_clk_cnt;
	int mpm_sdiowakeup_int;
	int sdiowakeup_irq;
};

struct sdhci_msm_bus_vote {
@@ -328,6 +342,7 @@ struct sdhci_msm_host {
	u8 saved_tuning_phase;
	bool en_auto_cmd21;
	struct device_attribute auto_cmd21_attr;
	bool is_sdiowakeup_enabled;
};

enum vdd_io_level {
@@ -1435,7 +1450,7 @@ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev)
	struct device_node *np = dev->of_node;
	u32 bus_width = 0;
	u32 cpu_dma_latency;
	int len, i;
	int len, i, mpm_int;
	int clk_table_len;
	u32 *clk_table = NULL;
	enum of_gpio_flags flags = OF_GPIO_ACTIVE_LOW;
@@ -1530,6 +1545,12 @@ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev)
	if (of_get_property(np, "qcom,nonremovable", NULL))
		pdata->nonremovable = true;

	if (!of_property_read_u32(np, "qcom,dat1-mpm-int",
				  &mpm_int))
		pdata->mpm_sdiowakeup_int = mpm_int;
	else
		pdata->mpm_sdiowakeup_int = -1;

	return pdata;
out:
	return NULL;
@@ -2032,6 +2053,39 @@ static int sdhci_msm_set_vdd_io_vol(struct sdhci_msm_pltfm_data *pdata,
	return ret;
}

/*
 * Acquire spin-lock host->lock before calling this function
 */
static void sdhci_msm_cfg_sdiowakeup_gpio_irq(struct sdhci_host *host,
					      bool enable)
{
	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
	struct sdhci_msm_host *msm_host = pltfm_host->priv;

	if (enable && !msm_host->is_sdiowakeup_enabled)
		enable_irq(msm_host->pdata->sdiowakeup_irq);
	else if (!enable && msm_host->is_sdiowakeup_enabled)
		disable_irq_nosync(msm_host->pdata->sdiowakeup_irq);
	else
		dev_warn(&msm_host->pdev->dev, "%s: wakeup to config: %d curr: %d\n",
			__func__, enable, msm_host->is_sdiowakeup_enabled);
	msm_host->is_sdiowakeup_enabled = enable;
}

static irqreturn_t sdhci_msm_sdiowakeup_irq(int irq, void *data)
{
	struct sdhci_host *host = (struct sdhci_host *)data;
	unsigned long flags;

	pr_debug("%s: irq (%d) received\n", __func__, irq);

	spin_lock_irqsave(&host->lock, flags);
	sdhci_msm_cfg_sdiowakeup_gpio_irq(host, false);
	spin_unlock_irqrestore(&host->lock, flags);

	return IRQ_HANDLED;
}

static irqreturn_t sdhci_msm_pwr_irq(int irq, void *data)
{
	struct sdhci_host *host = (struct sdhci_host *)data;
@@ -2729,6 +2783,38 @@ static struct sdhci_ops sdhci_msm_ops = {
	.config_auto_tuning_cmd = sdhci_msm_config_auto_tuning_cmd,
};

static int sdhci_msm_cfg_mpm_pin_wakeup(struct sdhci_host *host, unsigned mode)
{
	int ret = 0;
	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
	struct sdhci_msm_host *msm_host = pltfm_host->priv;
	unsigned int pin = msm_host->pdata->mpm_sdiowakeup_int;

	if (!pin)
		return 0;

	switch (mode) {
	case SDC_DAT1_DISABLE:
		ret = msm_mpm_enable_pin(pin, 0);
		break;
	case SDC_DAT1_ENABLE:
		ret = msm_mpm_set_pin_type(pin, IRQ_TYPE_LEVEL_LOW);
		if (!ret)
			ret = msm_mpm_enable_pin(pin, 1);
		break;
	case SDC_DAT1_ENWAKE:
		ret = msm_mpm_set_pin_wake(pin, 1);
		break;
	case SDC_DAT1_DISWAKE:
		ret = msm_mpm_set_pin_wake(pin, 0);
		break;
	default:
		ret = -EINVAL;
		break;
	}
	return ret;
}

static int sdhci_msm_probe(struct platform_device *pdev)
{
	struct sdhci_host *host;
@@ -2738,6 +2824,7 @@ static int sdhci_msm_probe(struct platform_device *pdev)
	int ret = 0, dead = 0;
	u16 host_version;
	u32 pwr, irq_status, irq_ctl;
	unsigned long flags;

	pr_debug("%s: Enter %s\n", dev_name(&pdev->dev), __func__);
	msm_host = devm_kzalloc(&pdev->dev, sizeof(struct sdhci_msm_host),
@@ -2990,7 +3077,7 @@ static int sdhci_msm_probe(struct platform_device *pdev)
	msm_host->mmc->caps2 |= MMC_CAP2_CLK_SCALE;
	msm_host->mmc->caps2 |= MMC_CAP2_STOP_REQUEST;
	msm_host->mmc->caps2 |= MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE;
	msm_host->mmc->pm_caps |= MMC_PM_KEEP_POWER;
	msm_host->mmc->pm_caps |= MMC_PM_KEEP_POWER | MMC_PM_WAKE_SDIO_IRQ;

	if (msm_host->pdata->nonremovable)
		msm_host->mmc->caps |= MMC_CAP_NONREMOVABLE;
@@ -3016,6 +3103,27 @@ static int sdhci_msm_probe(struct platform_device *pdev)
		dev_err(&pdev->dev, "%s: Failed to set dma mask\n", __func__);
	}

	msm_host->pdata->sdiowakeup_irq = platform_get_irq_byname(pdev,
							  "sdiowakeup_irq");
	if (msm_host->pdata->sdiowakeup_irq >= 0) {
		msm_host->is_sdiowakeup_enabled = true;
		ret = request_irq(msm_host->pdata->sdiowakeup_irq,
				  sdhci_msm_sdiowakeup_irq,
				  IRQF_SHARED | IRQF_TRIGGER_LOW,
				  "sdhci-msm sdiowakeup", host);
		if (ret) {
			dev_err(&pdev->dev, "%s: request sdiowakeup IRQ %d: failed: %d\n",
				__func__, msm_host->pdata->sdiowakeup_irq, ret);
			msm_host->pdata->sdiowakeup_irq = -1;
			msm_host->is_sdiowakeup_enabled = false;
			goto free_cd_gpio;
		} else {
			spin_lock_irqsave(&host->lock, flags);
			sdhci_msm_cfg_sdiowakeup_gpio_irq(host, false);
			spin_unlock_irqrestore(&host->lock, flags);
		}
	}

	ret = sdhci_add_host(host);
	if (ret) {
		dev_err(&pdev->dev, "Add host failed (%d)\n", ret);
@@ -3061,6 +3169,16 @@ static int sdhci_msm_probe(struct platform_device *pdev)
		device_remove_file(&pdev->dev, &msm_host->auto_cmd21_attr);
	}

	if (msm_host->pdata->mpm_sdiowakeup_int != -1) {
		ret = sdhci_msm_cfg_mpm_pin_wakeup(host, SDC_DAT1_ENABLE);
		if (ret) {
			pr_err("%s: enabling wakeup: failed: ret: %d\n",
			       mmc_hostname(host->mmc), ret);
			ret = 0;
			msm_host->pdata->mpm_sdiowakeup_int = -1;
		}
	}

	/* Successful initialization */
	goto out;

@@ -3072,6 +3190,8 @@ remove_host:
free_cd_gpio:
	if (gpio_is_valid(msm_host->pdata->status_gpio))
		mmc_gpio_free_cd(msm_host->mmc);
	if (sdhci_is_valid_gpio_wakeup_int(msm_host))
		free_irq(msm_host->pdata->sdiowakeup_irq, host);
vreg_deinit:
	sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false);
bus_unregister:
@@ -3117,6 +3237,12 @@ static int sdhci_msm_remove(struct platform_device *pdev)
	pm_runtime_disable(&pdev->dev);
	sdhci_pltfm_free(pdev);

	if (sdhci_is_valid_mpm_wakeup_int(msm_host))
		sdhci_msm_cfg_mpm_pin_wakeup(host, SDC_DAT1_DISABLE);

	if (sdhci_is_valid_gpio_wakeup_int(msm_host))
		free_irq(msm_host->pdata->sdiowakeup_irq, host);

	if (gpio_is_valid(msm_host->pdata->status_gpio))
		mmc_gpio_free_cd(msm_host->mmc);

@@ -3132,13 +3258,75 @@ static int sdhci_msm_remove(struct platform_device *pdev)
	return 0;
}

static int sdhci_msm_cfg_sdio_wakeup(struct sdhci_host *host, bool enable)
{
	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
	struct sdhci_msm_host *msm_host = pltfm_host->priv;
	unsigned long flags;
	int ret = 0;

	if (!(host->mmc->card && mmc_card_sdio(host->mmc->card) &&
	      (sdhci_is_valid_mpm_wakeup_int(msm_host) ||
	      sdhci_is_valid_gpio_wakeup_int(msm_host)) &&
	      mmc_card_wake_sdio_irq(host->mmc))) {
		return 1;
	}

	spin_lock_irqsave(&host->lock, flags);
	if (enable) {
		/* configure DAT1 gpio if applicable */
		if (sdhci_is_valid_gpio_wakeup_int(msm_host)) {
			ret = enable_irq_wake(msm_host->pdata->sdiowakeup_irq);
			if (!ret)
				sdhci_msm_cfg_sdiowakeup_gpio_irq(host, true);
			goto out;
		} else {
			ret = sdhci_msm_cfg_mpm_pin_wakeup(host,
							   SDC_DAT1_ENWAKE);
			if (ret)
				goto out;
			ret = enable_irq_wake(host->irq);
			if (ret)
				sdhci_msm_cfg_mpm_pin_wakeup(host,
							     SDC_DAT1_DISWAKE);
		}
	} else {
		if (sdhci_is_valid_gpio_wakeup_int(msm_host)) {
			ret = disable_irq_wake(msm_host->pdata->sdiowakeup_irq);
			sdhci_msm_cfg_sdiowakeup_gpio_irq(host, false);
		} else {
			ret = sdhci_msm_cfg_mpm_pin_wakeup(host,
							   SDC_DAT1_DISWAKE);
			if (ret)
				goto out;
			ret = disable_irq_wake(host->irq);
		}
	}
out:
	if (ret)
		pr_err("%s: %s: %sable wakeup: failed: %d gpio: %d mpm: %d\n",
		       mmc_hostname(host->mmc), __func__, enable ? "en" : "dis",
		       ret, msm_host->pdata->sdiowakeup_irq,
		       msm_host->pdata->mpm_sdiowakeup_int);
	spin_unlock_irqrestore(&host->lock, flags);
	return ret;
}

static int sdhci_msm_runtime_suspend(struct device *dev)
{
	struct sdhci_host *host = dev_get_drvdata(dev);
	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
	struct sdhci_msm_host *msm_host = pltfm_host->priv;
	int ret;

	ret = sdhci_msm_cfg_sdio_wakeup(host, true);
	/* pwr_irq is not monitored by mpm on suspend, hence disable it */
	if (!ret)
		goto skip_disable_host_irq;

	disable_irq(host->irq);

skip_disable_host_irq:
	disable_irq(msm_host->pwr_irq);

	/*
@@ -3159,10 +3347,17 @@ static int sdhci_msm_runtime_resume(struct device *dev)
	struct sdhci_host *host = dev_get_drvdata(dev);
	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
	struct sdhci_msm_host *msm_host = pltfm_host->priv;
	int ret;

	ret = sdhci_msm_cfg_sdio_wakeup(host, false);
	if (!ret)
		goto skip_enable_host_irq;

	enable_irq(msm_host->pwr_irq);
	enable_irq(host->irq);

skip_enable_host_irq:
	enable_irq(msm_host->pwr_irq);

	return 0;
}

@@ -3214,6 +3409,26 @@ static int sdhci_msm_resume(struct device *dev)
out:
	return ret;
}

static int sdhci_msm_suspend_noirq(struct device *dev)
{
	struct sdhci_host *host = dev_get_drvdata(dev);
	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
	struct sdhci_msm_host *msm_host = pltfm_host->priv;
	int ret = 0;

	/*
	 * ksdioirqd may get scheduled after sdhc suspend, hence retry
	 * suspend in case the clocks are ON
	 */
	if (atomic_read(&msm_host->clks_on)) {
		pr_warn("%s: %s: clock ON after suspend, aborting suspend\n",
			mmc_hostname(host->mmc), __func__);
		ret = -EAGAIN;
	}

	return ret;
}
#endif

#ifdef CONFIG_PM
@@ -3221,6 +3436,7 @@ static const struct dev_pm_ops sdhci_msm_pmops = {
	SET_SYSTEM_SLEEP_PM_OPS(sdhci_msm_suspend, sdhci_msm_resume)
	SET_RUNTIME_PM_OPS(sdhci_msm_runtime_suspend, sdhci_msm_runtime_resume,
			   NULL)
	.suspend_noirq = sdhci_msm_suspend_noirq,
};

#define SDHCI_MSM_PMOPS (&sdhci_msm_pmops)