diff --git a/Documentation/mmc/mmc-dev-attrs.txt b/Documentation/mmc/mmc-dev-attrs.txt index d143f16afe69..e9d2debb29d3 100644 --- a/Documentation/mmc/mmc-dev-attrs.txt +++ b/Documentation/mmc/mmc-dev-attrs.txt @@ -30,7 +30,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) @@ -99,3 +99,41 @@ This attribute appears only if CONFIG_MMC_CLKGATE is enabled. clkgate_delay Tune the clock gating delay with desired value in milliseconds. echo > /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 > /sys/class/mmc_host/mmcX/clk_scaling/polling_interval +echo > /sys/class/mmc_host/mmcX/clk_scaling/up_threshold +echo > /sys/class/mmc_host/mmcX/clk_scaling/down_threshold +echo > /sys/class/mmc_host/mmcX/clk_scaling/enable diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 90c863688806..72eda77a6f3b 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 @@ -613,6 +614,162 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev) } 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_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) @@ -704,6 +861,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", @@ -737,7 +899,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);