[PATCH 5.4 097/214] mmc: sdhci: Add LTR support for some Intel BYT based controllers

From: Greg Kroah-Hartman
Date: Tue Nov 03 2020 - 16:40:40 EST


From: Adrian Hunter <adrian.hunter@xxxxxxxxx>

commit 46f4a69ec8ed6ab9f6a6172afe50df792c8bc1b6 upstream.

Some Intel BYT based host controllers support the setting of latency
tolerance. Accordingly, implement the PM QoS ->set_latency_tolerance()
callback. The raw register values are also exposed via debugfs.

Intel EHL controllers require this support.

Signed-off-by: Adrian Hunter <adrian.hunter@xxxxxxxxx>
Fixes: cb3a7d4a0aec4e ("mmc: sdhci-pci: Add support for Intel EHL")
Cc: stable@xxxxxxxxxxxxxxx
Link: https://lore.kernel.org/r/20200818104508.7149-1-adrian.hunter@xxxxxxxxx
Signed-off-by: Ulf Hansson <ulf.hansson@xxxxxxxxxx>
Signed-off-by: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>

---
drivers/mmc/host/sdhci-pci-core.c | 154 ++++++++++++++++++++++++++++++++++++++
1 file changed, 154 insertions(+)

--- a/drivers/mmc/host/sdhci-pci-core.c
+++ b/drivers/mmc/host/sdhci-pci-core.c
@@ -24,6 +24,8 @@
#include <linux/iopoll.h>
#include <linux/gpio.h>
#include <linux/pm_runtime.h>
+#include <linux/pm_qos.h>
+#include <linux/debugfs.h>
#include <linux/mmc/slot-gpio.h>
#include <linux/mmc/sdhci-pci-data.h>
#include <linux/acpi.h>
@@ -520,6 +522,8 @@ struct intel_host {
bool rpm_retune_ok;
u32 glk_rx_ctrl1;
u32 glk_tun_val;
+ u32 active_ltr;
+ u32 idle_ltr;
};

static const guid_t intel_dsm_guid =
@@ -764,6 +768,108 @@ static int intel_execute_tuning(struct m
return 0;
}

+#define INTEL_ACTIVELTR 0x804
+#define INTEL_IDLELTR 0x808
+
+#define INTEL_LTR_REQ BIT(15)
+#define INTEL_LTR_SCALE_MASK GENMASK(11, 10)
+#define INTEL_LTR_SCALE_1US (2 << 10)
+#define INTEL_LTR_SCALE_32US (3 << 10)
+#define INTEL_LTR_VALUE_MASK GENMASK(9, 0)
+
+static void intel_cache_ltr(struct sdhci_pci_slot *slot)
+{
+ struct intel_host *intel_host = sdhci_pci_priv(slot);
+ struct sdhci_host *host = slot->host;
+
+ intel_host->active_ltr = readl(host->ioaddr + INTEL_ACTIVELTR);
+ intel_host->idle_ltr = readl(host->ioaddr + INTEL_IDLELTR);
+}
+
+static void intel_ltr_set(struct device *dev, s32 val)
+{
+ struct sdhci_pci_chip *chip = dev_get_drvdata(dev);
+ struct sdhci_pci_slot *slot = chip->slots[0];
+ struct intel_host *intel_host = sdhci_pci_priv(slot);
+ struct sdhci_host *host = slot->host;
+ u32 ltr;
+
+ pm_runtime_get_sync(dev);
+
+ /*
+ * Program latency tolerance (LTR) accordingly what has been asked
+ * by the PM QoS layer or disable it in case we were passed
+ * negative value or PM_QOS_LATENCY_ANY.
+ */
+ ltr = readl(host->ioaddr + INTEL_ACTIVELTR);
+
+ if (val == PM_QOS_LATENCY_ANY || val < 0) {
+ ltr &= ~INTEL_LTR_REQ;
+ } else {
+ ltr |= INTEL_LTR_REQ;
+ ltr &= ~INTEL_LTR_SCALE_MASK;
+ ltr &= ~INTEL_LTR_VALUE_MASK;
+
+ if (val > INTEL_LTR_VALUE_MASK) {
+ val >>= 5;
+ if (val > INTEL_LTR_VALUE_MASK)
+ val = INTEL_LTR_VALUE_MASK;
+ ltr |= INTEL_LTR_SCALE_32US | val;
+ } else {
+ ltr |= INTEL_LTR_SCALE_1US | val;
+ }
+ }
+
+ if (ltr == intel_host->active_ltr)
+ goto out;
+
+ writel(ltr, host->ioaddr + INTEL_ACTIVELTR);
+ writel(ltr, host->ioaddr + INTEL_IDLELTR);
+
+ /* Cache the values into lpss structure */
+ intel_cache_ltr(slot);
+out:
+ pm_runtime_put_autosuspend(dev);
+}
+
+static bool intel_use_ltr(struct sdhci_pci_chip *chip)
+{
+ switch (chip->pdev->device) {
+ case PCI_DEVICE_ID_INTEL_BYT_EMMC:
+ case PCI_DEVICE_ID_INTEL_BYT_EMMC2:
+ case PCI_DEVICE_ID_INTEL_BYT_SDIO:
+ case PCI_DEVICE_ID_INTEL_BYT_SD:
+ case PCI_DEVICE_ID_INTEL_BSW_EMMC:
+ case PCI_DEVICE_ID_INTEL_BSW_SDIO:
+ case PCI_DEVICE_ID_INTEL_BSW_SD:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static void intel_ltr_expose(struct sdhci_pci_chip *chip)
+{
+ struct device *dev = &chip->pdev->dev;
+
+ if (!intel_use_ltr(chip))
+ return;
+
+ dev->power.set_latency_tolerance = intel_ltr_set;
+ dev_pm_qos_expose_latency_tolerance(dev);
+}
+
+static void intel_ltr_hide(struct sdhci_pci_chip *chip)
+{
+ struct device *dev = &chip->pdev->dev;
+
+ if (!intel_use_ltr(chip))
+ return;
+
+ dev_pm_qos_hide_latency_tolerance(dev);
+ dev->power.set_latency_tolerance = NULL;
+}
+
static void byt_probe_slot(struct sdhci_pci_slot *slot)
{
struct mmc_host_ops *ops = &slot->host->mmc_host_ops;
@@ -778,6 +884,43 @@ static void byt_probe_slot(struct sdhci_
ops->start_signal_voltage_switch = intel_start_signal_voltage_switch;

device_property_read_u32(dev, "max-frequency", &mmc->f_max);
+
+ if (!mmc->slotno) {
+ slot->chip->slots[mmc->slotno] = slot;
+ intel_ltr_expose(slot->chip);
+ }
+}
+
+static void byt_add_debugfs(struct sdhci_pci_slot *slot)
+{
+ struct intel_host *intel_host = sdhci_pci_priv(slot);
+ struct mmc_host *mmc = slot->host->mmc;
+ struct dentry *dir = mmc->debugfs_root;
+
+ if (!intel_use_ltr(slot->chip))
+ return;
+
+ debugfs_create_x32("active_ltr", 0444, dir, &intel_host->active_ltr);
+ debugfs_create_x32("idle_ltr", 0444, dir, &intel_host->idle_ltr);
+
+ intel_cache_ltr(slot);
+}
+
+static int byt_add_host(struct sdhci_pci_slot *slot)
+{
+ int ret = sdhci_add_host(slot->host);
+
+ if (!ret)
+ byt_add_debugfs(slot);
+ return ret;
+}
+
+static void byt_remove_slot(struct sdhci_pci_slot *slot, int dead)
+{
+ struct mmc_host *mmc = slot->host->mmc;
+
+ if (!mmc->slotno)
+ intel_ltr_hide(slot->chip);
}

static int byt_emmc_probe_slot(struct sdhci_pci_slot *slot)
@@ -859,6 +1002,8 @@ static int glk_emmc_add_host(struct sdhc
if (ret)
goto cleanup;

+ byt_add_debugfs(slot);
+
return 0;

cleanup:
@@ -1036,6 +1181,8 @@ static const struct sdhci_pci_fixes sdhc
#endif
.allow_runtime_pm = true,
.probe_slot = byt_emmc_probe_slot,
+ .add_host = byt_add_host,
+ .remove_slot = byt_remove_slot,
.quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC |
SDHCI_QUIRK_NO_LED,
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
@@ -1049,6 +1196,7 @@ static const struct sdhci_pci_fixes sdhc
.allow_runtime_pm = true,
.probe_slot = glk_emmc_probe_slot,
.add_host = glk_emmc_add_host,
+ .remove_slot = byt_remove_slot,
#ifdef CONFIG_PM_SLEEP
.suspend = sdhci_cqhci_suspend,
.resume = sdhci_cqhci_resume,
@@ -1079,6 +1227,8 @@ static const struct sdhci_pci_fixes sdhc
SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
.allow_runtime_pm = true,
.probe_slot = ni_byt_sdio_probe_slot,
+ .add_host = byt_add_host,
+ .remove_slot = byt_remove_slot,
.ops = &sdhci_intel_byt_ops,
.priv_size = sizeof(struct intel_host),
};
@@ -1096,6 +1246,8 @@ static const struct sdhci_pci_fixes sdhc
SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
.allow_runtime_pm = true,
.probe_slot = byt_sdio_probe_slot,
+ .add_host = byt_add_host,
+ .remove_slot = byt_remove_slot,
.ops = &sdhci_intel_byt_ops,
.priv_size = sizeof(struct intel_host),
};
@@ -1115,6 +1267,8 @@ static const struct sdhci_pci_fixes sdhc
.allow_runtime_pm = true,
.own_cd_for_runtime_pm = true,
.probe_slot = byt_sd_probe_slot,
+ .add_host = byt_add_host,
+ .remove_slot = byt_remove_slot,
.ops = &sdhci_intel_byt_ops,
.priv_size = sizeof(struct intel_host),
};