mmc: core: Add sysfs entries for dynamic control of clock scaling
Add sysfs attributes to allow dynamic control of clock scaling
parameters. These attributes are used to enable/disable clock
scaling at runtime and change the up_threshold, down_threshold,
and polling_interval values. Complete documentation for these
sysfs entries are provided at "Documentation/mmc/mmc-dev-attrs.txt".
Change-Id: I4753c75c3b3eeea91e93bceba7af2535b793612e
Signed-off-by: Sujit Reddy Thumma <sthumma@codeaurora.org>
diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c
index d30f10f..f279d8d 100644
--- a/drivers/mmc/core/host.c
+++ b/drivers/mmc/core/host.c
@@ -4,6 +4,7 @@
* Copyright (C) 2003 Russell King, All Rights Reserved.
* Copyright (C) 2007-2008 Pierre Ossman
* Copyright (C) 2010 Linus Walleij
+ * Copyright (c) 2012, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@@ -362,6 +363,163 @@
}
EXPORT_SYMBOL(mmc_alloc_host);
+
+static ssize_t show_enable(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mmc_host *host = cls_dev_to_mmc_host(dev);
+
+ if (!host)
+ return -EINVAL;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", mmc_can_scale_clk(host));
+}
+
+static ssize_t store_enable(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct mmc_host *host = cls_dev_to_mmc_host(dev);
+ unsigned long value, freq;
+ int retval = -EINVAL;
+
+ if (!host || !host->card || kstrtoul(buf, 0, &value))
+ goto err;
+
+ if (value && !mmc_can_scale_clk(host)) {
+ if (mmc_card_ddr_mode(host->card) ||
+ mmc_card_hs200(host->card) ||
+ mmc_card_uhs(host->card)) {
+ host->caps2 |= MMC_CAP2_CLK_SCALE;
+ mmc_init_clk_scaling(host);
+ }
+
+ if (!mmc_can_scale_clk(host)) {
+ host->caps2 &= ~MMC_CAP2_CLK_SCALE;
+ goto err;
+ }
+ } else if (!value && mmc_can_scale_clk(host)) {
+ host->caps2 &= ~MMC_CAP2_CLK_SCALE;
+ mmc_disable_clk_scaling(host);
+
+ /* Set to max. frequency, since we are disabling */
+ if (host->bus_ops && host->bus_ops->change_bus_speed) {
+ freq = mmc_get_max_frequency(host);
+ if (host->bus_ops->change_bus_speed(host, &freq))
+ goto err;
+ }
+ host->clk_scaling.initialized = false;
+ }
+ return count;
+err:
+ return retval;
+}
+
+static ssize_t show_up_threshold(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mmc_host *host = cls_dev_to_mmc_host(dev);
+
+ if (!host)
+ return -EINVAL;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", host->clk_scaling.up_threshold);
+}
+
+#define MAX_PERCENTAGE 100
+static ssize_t store_up_threshold(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct mmc_host *host = cls_dev_to_mmc_host(dev);
+ unsigned long value;
+
+ if (!host || kstrtoul(buf, 0, &value) || (value > MAX_PERCENTAGE))
+ return -EINVAL;
+
+ host->clk_scaling.up_threshold = value;
+
+ pr_debug("%s: clkscale_up_thresh set to %lu\n",
+ mmc_hostname(host), value);
+ return count;
+}
+
+static ssize_t show_down_threshold(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mmc_host *host = cls_dev_to_mmc_host(dev);
+
+ if (!host)
+ return -EINVAL;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n",
+ host->clk_scaling.down_threshold);
+}
+
+static ssize_t store_down_threshold(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct mmc_host *host = cls_dev_to_mmc_host(dev);
+ unsigned long value;
+
+ if (!host || kstrtoul(buf, 0, &value) || (value > MAX_PERCENTAGE))
+ return -EINVAL;
+
+ host->clk_scaling.down_threshold = value;
+
+ pr_debug("%s: clkscale_down_thresh set to %lu\n",
+ mmc_hostname(host), value);
+ return count;
+}
+
+static ssize_t show_polling(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mmc_host *host = cls_dev_to_mmc_host(dev);
+
+ if (!host)
+ return -EINVAL;
+
+ return snprintf(buf, PAGE_SIZE, "%lu milliseconds\n",
+ host->clk_scaling.polling_delay_ms);
+}
+
+static ssize_t store_polling(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct mmc_host *host = cls_dev_to_mmc_host(dev);
+ unsigned long value;
+
+ if (!host || kstrtoul(buf, 0, &value))
+ return -EINVAL;
+
+ host->clk_scaling.polling_delay_ms = value;
+
+ pr_debug("%s: clkscale_polling_delay_ms set to %lu\n",
+ mmc_hostname(host), value);
+ return count;
+}
+
+DEVICE_ATTR(enable, S_IRUGO | S_IWUSR,
+ show_enable, store_enable);
+DEVICE_ATTR(polling_interval, S_IRUGO | S_IWUSR,
+ show_polling, store_polling);
+DEVICE_ATTR(up_threshold, S_IRUGO | S_IWUSR,
+ show_up_threshold, store_up_threshold);
+DEVICE_ATTR(down_threshold, S_IRUGO | S_IWUSR,
+ show_down_threshold, store_down_threshold);
+
+static struct attribute *clk_scaling_attrs[] = {
+ &dev_attr_enable.attr,
+ &dev_attr_up_threshold.attr,
+ &dev_attr_down_threshold.attr,
+ &dev_attr_polling_interval.attr,
+ NULL,
+};
+
+static struct attribute_group clk_scaling_attr_grp = {
+ .name = "clk_scaling",
+ .attrs = clk_scaling_attrs,
+};
+
#ifdef CONFIG_MMC_PERF_PROFILING
static ssize_t
show_perf(struct device *dev, struct device_attribute *attr, char *buf)
@@ -453,6 +611,11 @@
host->clk_scaling.down_threshold = 5;
host->clk_scaling.polling_delay_ms = 100;
+ err = sysfs_create_group(&host->class_dev.kobj, &clk_scaling_attr_grp);
+ if (err)
+ pr_err("%s: failed to create clk scale sysfs group with err %d\n",
+ __func__, err);
+
err = sysfs_create_group(&host->parent->kobj, &dev_attr_grp);
if (err)
pr_err("%s: failed to create sysfs group with err %d\n",
@@ -486,7 +649,7 @@
mmc_remove_host_debugfs(host);
#endif
sysfs_remove_group(&host->parent->kobj, &dev_attr_grp);
-
+ sysfs_remove_group(&host->class_dev.kobj, &clk_scaling_attr_grp);
device_del(&host->class_dev);