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>
This commit is contained in:
Sujit Reddy Thumma 2012-11-07 21:23:14 +05:30 committed by Stephen Boyd
parent 4080abfa57
commit eb6ad18edd
2 changed files with 203 additions and 2 deletions

View file

@ -39,7 +39,7 @@ SD and MMC Device Attributes
All attributes are read-only.
cid Card Identifaction Register
cid Card Identification Register
csd Card Specific Data Register
scr SD Card Configuration Register (SD only)
date Manufacturing Date (from CID Register)
@ -108,3 +108,41 @@ This attribute appears only if CONFIG_MMC_CLKGATE is enabled.
clkgate_delay Tune the clock gating delay with desired value in milliseconds.
echo <desired delay> > /sys/class/mmc_host/mmcX/clkgate_delay
SD/MMC/SDIO Clock Scaling Attributes
====================================
Read and write accesses are provided to following attributes.
polling_interval Measured in milliseconds, this attribute
defines how often we need to check the card
usage and make decisions on frequency scaling.
up_threshold This attribute defines what should be the
average card usage between the polling
interval for the mmc core to make a decision
on whether it should increase the frequency.
For example when it is set to '35' it means
that between the checking intervals the card
needs to be on average more than 35% in use to
scale up the frequency. The value should be
between 0 - 100 so that it can be compared
against load percentage.
down_threshold Similar to up_threshold, but on lowering the
frequency. For example, when it is set to '2'
it means that between the checking intervals
the card needs to be on average less than 2%
in use to scale down the clocks to minimum
frequency. The value should be between 0 - 100
so that it can be compared against load
percentage.
enable Enable clock scaling for hosts (and cards)
that support ultrahigh speed modes
(SDR104, DDR50, HS200).
echo <desired value> > /sys/class/mmc_host/mmcX/clk_scaling/polling_interval
echo <desired value> > /sys/class/mmc_host/mmcX/clk_scaling/up_threshold
echo <desired value> > /sys/class/mmc_host/mmcX/clk_scaling/down_threshold
echo <desired value> > /sys/class/mmc_host/mmcX/clk_scaling/enable

View file

@ -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
@ -491,6 +492,163 @@ free:
}
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)
@ -582,6 +740,11 @@ int mmc_add_host(struct mmc_host *host)
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",
@ -615,7 +778,7 @@ void mmc_remove_host(struct mmc_host *host)
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);