1504 lines
42 KiB
C
1504 lines
42 KiB
C
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
||
|
|
/*
|
||
|
|
* Qualcomm Technologies, Inc. SDHCI Platform driver.
|
||
|
|
*
|
||
|
|
* Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <linux/of_device.h>
|
||
|
|
#include "sdhci-msm.h"
|
||
|
|
#include "sdhci-msm-scaling.h"
|
||
|
|
|
||
|
|
#define cls_dev_to_mmc_host(d) container_of(d, struct mmc_host, class_dev)
|
||
|
|
|
||
|
|
static int mmc_dt_get_array(struct device *dev, const char *prop_name,
|
||
|
|
u32 **out, int *len, u32 size)
|
||
|
|
{
|
||
|
|
int ret = 0;
|
||
|
|
struct device_node *np = dev->of_node;
|
||
|
|
size_t sz;
|
||
|
|
u32 *arr = NULL;
|
||
|
|
|
||
|
|
if (!of_get_property(np, prop_name, len)) {
|
||
|
|
ret = -EINVAL;
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
sz = *len = *len / sizeof(*arr);
|
||
|
|
if (sz <= 0 || (size > 0 && (sz > size))) {
|
||
|
|
dev_err(dev, "%s invalid size\n", prop_name);
|
||
|
|
ret = -EINVAL;
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
|
||
|
|
arr = devm_kcalloc(dev, sz, sizeof(*arr), GFP_KERNEL);
|
||
|
|
if (!arr) {
|
||
|
|
ret = -ENOMEM;
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = of_property_read_u32_array(np, prop_name, arr, sz);
|
||
|
|
if (ret < 0) {
|
||
|
|
dev_err(dev, "%s failed reading array %d\n", prop_name, ret);
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
*out = arr;
|
||
|
|
out:
|
||
|
|
if (ret)
|
||
|
|
*len = 0;
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
void sdhci_msm_scale_parse_dt(struct device *dev, struct sdhci_msm_host *msm_host)
|
||
|
|
{
|
||
|
|
struct device_node *np = dev->of_node;
|
||
|
|
const char *lower_bus_speed = NULL;
|
||
|
|
|
||
|
|
if (mmc_dt_get_array(dev, "qcom,devfreq,freq-table",
|
||
|
|
&msm_host->clk_scaling.pltfm_freq_table,
|
||
|
|
&msm_host->clk_scaling.pltfm_freq_table_sz, 0))
|
||
|
|
pr_debug("%s: no clock scaling frequencies were supplied\n",
|
||
|
|
dev_name(dev));
|
||
|
|
else if (!msm_host->clk_scaling.pltfm_freq_table ||
|
||
|
|
!msm_host->clk_scaling.pltfm_freq_table_sz)
|
||
|
|
dev_info(dev, "bad dts clock scaling frequencies\n");
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Few hosts can support DDR52 mode at the same lower
|
||
|
|
* system voltage corner as high-speed mode. In such
|
||
|
|
* cases, it is always better to put it in DDR
|
||
|
|
* mode which will improve the performance
|
||
|
|
* without any power impact.
|
||
|
|
*/
|
||
|
|
if (!of_property_read_string(np, "qcom,scaling-lower-bus-speed-mode",
|
||
|
|
&lower_bus_speed)) {
|
||
|
|
if (!strcmp(lower_bus_speed, "DDR52"))
|
||
|
|
msm_host->clk_scaling.lower_bus_speed_mode |=
|
||
|
|
MMC_SCALING_LOWER_DDR52_MODE;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(sdhci_msm_scale_parse_dt);
|
||
|
|
|
||
|
|
void sdhci_msm_dec_active_req(struct mmc_host *mhost)
|
||
|
|
{
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
|
||
|
|
atomic_dec(&host->active_reqs);
|
||
|
|
}
|
||
|
|
|
||
|
|
void sdhci_msm_inc_active_req(struct mmc_host *mhost)
|
||
|
|
{
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
|
||
|
|
atomic_inc(&host->active_reqs);
|
||
|
|
}
|
||
|
|
|
||
|
|
void sdhci_msm_is_dcmd(int data, int *err)
|
||
|
|
{
|
||
|
|
if (data)
|
||
|
|
*err = 1;
|
||
|
|
else
|
||
|
|
*err = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
void sdhci_msm_mmc_cqe_clk_scaling_stop_busy(struct mmc_host *mhost, struct mmc_request *mrq)
|
||
|
|
{
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
|
||
|
|
struct mmc_queue_req *mqrq = container_of(mrq, struct mmc_queue_req,
|
||
|
|
brq.mrq);
|
||
|
|
struct request *req = mmc_queue_req_to_req(mqrq);
|
||
|
|
struct request_queue *q = req->q;
|
||
|
|
struct mmc_queue *mq = q->queuedata;
|
||
|
|
int is_dcmd;
|
||
|
|
|
||
|
|
sdhci_msm_dec_active_req(mhost);
|
||
|
|
is_dcmd = (mmc_issue_type(mq, req) == MMC_ISSUE_DCMD);
|
||
|
|
_sdhci_msm_mmc_cqe_clk_scaling_stop_busy(host, true, is_dcmd);
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(sdhci_msm_mmc_cqe_clk_scaling_stop_busy);
|
||
|
|
|
||
|
|
void sdhci_msm_mmc_cqe_clk_scaling_start_busy(struct mmc_host *mhost, struct mmc_request *mrq)
|
||
|
|
{
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
struct mmc_queue_req *mqrq = container_of(mrq, struct mmc_queue_req,
|
||
|
|
brq.mrq);
|
||
|
|
struct request *req = mmc_queue_req_to_req(mqrq);
|
||
|
|
struct request_queue *q = req->q;
|
||
|
|
struct mmc_queue *mq = q->queuedata;
|
||
|
|
|
||
|
|
sdhci_msm_mmc_init_clk_scaling(mhost);
|
||
|
|
|
||
|
|
if (host->defer_clk_scaling_resume == 1) {
|
||
|
|
sdhci_msm_mmc_resume_clk_scaling(mhost);
|
||
|
|
host->defer_clk_scaling_resume = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
sdhci_msm_inc_active_req(mhost);
|
||
|
|
|
||
|
|
sdhci_msm_mmc_deferred_scaling(host);
|
||
|
|
_sdhci_msm_mmc_cqe_clk_scaling_start_busy(mq, host, true);
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(sdhci_msm_mmc_cqe_clk_scaling_start_busy);
|
||
|
|
|
||
|
|
void sdhci_msm_cqe_scaling_resume(struct mmc_host *mhost)
|
||
|
|
{
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
|
||
|
|
if (host->scaling_suspended == 1) {
|
||
|
|
sdhci_msm_mmc_resume_clk_scaling(mhost);
|
||
|
|
host->scaling_suspended = 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(sdhci_msm_cqe_scaling_resume);
|
||
|
|
|
||
|
|
void sdhci_msm_mmc_clk_scaling_stop_busy(struct mmc_host *mhost, bool data)
|
||
|
|
{
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
|
||
|
|
if (host->clk_scaling.is_busy_started)
|
||
|
|
_sdhci_msm_mmc_clk_scaling_stop_busy(host, data);
|
||
|
|
}
|
||
|
|
|
||
|
|
void sdhci_msm_mmc_clk_scaling_start_busy(struct mmc_host *mhost, bool data, u32 opcode)
|
||
|
|
{
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
|
||
|
|
if (sdhci_msm_mmc_is_data_request(opcode)) {
|
||
|
|
sdhci_msm_mmc_deferred_scaling(host);
|
||
|
|
_sdhci_msm_mmc_clk_scaling_start_busy(host, data);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (host->clk_scaling.state == MMC_LOAD_LOW)
|
||
|
|
mhost->cqe_ops->cqe_wait_for_idle(mhost);
|
||
|
|
}
|
||
|
|
|
||
|
|
void sdhci_msm_set_active_reqs(struct mmc_host *mhost)
|
||
|
|
{
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
|
||
|
|
atomic_set(&host->active_reqs, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
void sdhci_msm_set_factors(struct mmc_host *mhost)
|
||
|
|
{
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
|
||
|
|
host->clk_scaling.upthreshold = MMC_DEVFRQ_DEFAULT_UP_THRESHOLD;
|
||
|
|
host->clk_scaling.downthreshold = MMC_DEVFRQ_DEFAULT_DOWN_THRESHOLD;
|
||
|
|
host->clk_scaling.polling_delay_ms = MMC_DEVFRQ_DEFAULT_POLLING_MSEC;
|
||
|
|
host->clk_scaling.skip_clk_scale_freq_update = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
void sdhci_msm_mmc_init_setup_scaling(struct mmc_card *card, struct mmc_host *mhost)
|
||
|
|
{
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
|
||
|
|
host->clk_scaling_lowest = mhost->f_min;
|
||
|
|
if ((card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS400) ||
|
||
|
|
(card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS200))
|
||
|
|
host->clk_scaling_highest = card->ext_csd.hs200_max_dtr;
|
||
|
|
else if ((card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS) ||
|
||
|
|
(card->mmc_avail_type & EXT_CSD_CARD_TYPE_DDR_52))
|
||
|
|
host->clk_scaling_highest = card->ext_csd.hs_max_dtr;
|
||
|
|
else
|
||
|
|
host->clk_scaling_highest = card->csd.max_dtr;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void sdhci_msm_mmc_exit_clk_scaling(struct mmc_host *mhost)
|
||
|
|
{
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
|
||
|
|
if (host->scale_caps & MMC_CAP2_CLK_SCALE)
|
||
|
|
_sdhci_msm_mmc_exit_clk_scaling(host);
|
||
|
|
}
|
||
|
|
|
||
|
|
void sdhci_msm_mmc_suspend_clk_scaling(struct mmc_host *mhost)
|
||
|
|
{
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
|
||
|
|
_sdhci_msm_mmc_suspend_clk_scaling(host);
|
||
|
|
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(sdhci_msm_mmc_suspend_clk_scaling);
|
||
|
|
|
||
|
|
void sdhci_msm_mmc_resume_clk_scaling(struct mmc_host *mhost)
|
||
|
|
{
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
|
||
|
|
_sdhci_msm_mmc_resume_clk_scaling(host);
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(sdhci_msm_mmc_resume_clk_scaling);
|
||
|
|
|
||
|
|
void sdhci_msm_mmc_init_clk_scaling(struct mmc_host *mhost)
|
||
|
|
{
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
|
||
|
|
if (host->clk_scale_init_done)
|
||
|
|
return;
|
||
|
|
sdhci_msm_set_factors(mhost);
|
||
|
|
sdhci_msm_set_active_reqs(mhost);
|
||
|
|
sdhci_msm_mmc_init_setup_scaling(mhost->card, mhost);
|
||
|
|
|
||
|
|
_sdhci_msm_mmc_init_clk_scaling(host);
|
||
|
|
host->clk_scale_init_done = 1;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(sdhci_msm_mmc_init_clk_scaling);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* mmc_sd_change_bus_speed() - Change SD card bus frequency at runtime
|
||
|
|
* @host: pointer to mmc host structure
|
||
|
|
* @freq: pointer to desired frequency to be set
|
||
|
|
*
|
||
|
|
* Change the SD card bus frequency at runtime after the card is
|
||
|
|
* initialized. Callers are expected to make sure of the card's
|
||
|
|
* state (DATA/RCV/TRANSFER) beforing changing the frequency at runtime.
|
||
|
|
*
|
||
|
|
* If the frequency to change is greater than max. supported by card,
|
||
|
|
* *freq is changed to max. supported by card and if it is less than min.
|
||
|
|
* supported by host, *freq is changed to min. supported by host.
|
||
|
|
*/
|
||
|
|
static int sdhci_msm_mmc_sd_change_bus_speed(struct sdhci_msm_host *host, unsigned long *freq)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = host->mmc;
|
||
|
|
int err = 0;
|
||
|
|
struct mmc_card *card;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Assign card pointer after claiming host to avoid race
|
||
|
|
* conditions that may arise during removal of the card.
|
||
|
|
*/
|
||
|
|
card = mhost->card;
|
||
|
|
|
||
|
|
/* sanity checks */
|
||
|
|
if (!card || !freq) {
|
||
|
|
err = -EINVAL;
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
|
||
|
|
mmc_set_clock(mhost, (unsigned int) (*freq));
|
||
|
|
|
||
|
|
if (!mmc_host_is_spi(card->host) && mmc_card_uhs(card)
|
||
|
|
&& card->host->ops->execute_tuning) {
|
||
|
|
/*
|
||
|
|
* We try to probe host driver for tuning for any
|
||
|
|
* frequency, it is host driver responsibility to
|
||
|
|
* perform actual tuning only when required.
|
||
|
|
*/
|
||
|
|
err = card->host->ops->execute_tuning(card->host,
|
||
|
|
MMC_SEND_TUNING_BLOCK);
|
||
|
|
if (err) {
|
||
|
|
pr_warn("%s: %s: tuning execution failed %d. Restoring to previous clock %lu\n",
|
||
|
|
mmc_hostname(card->host), __func__, err,
|
||
|
|
host->clk_scaling.curr_freq);
|
||
|
|
mmc_set_clock(mhost, host->clk_scaling.curr_freq);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
out:
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sdhci_msm_mmc_select_hs_ddr52(struct sdhci_msm_host *host, unsigned long freq)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = host->mmc;
|
||
|
|
int err;
|
||
|
|
|
||
|
|
mmc_select_hs(mhost->card);
|
||
|
|
err = mmc_select_bus_width(mhost->card);
|
||
|
|
if (err < 0) {
|
||
|
|
pr_err("%s: %s: select_bus_width failed(%d)\n",
|
||
|
|
mmc_hostname(mhost), __func__, err);
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
|
||
|
|
err = mmc_select_hs_ddr(mhost->card);
|
||
|
|
mmc_set_clock(mhost, freq);
|
||
|
|
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Scale down from HS400 to HS in order to allow frequency change.
|
||
|
|
* This is needed for cards that doesn't support changing frequency in HS400
|
||
|
|
*/
|
||
|
|
static int sdhci_msm_mmc_scale_low(struct sdhci_msm_host *host, unsigned long freq)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = host->mmc;
|
||
|
|
int err = 0;
|
||
|
|
|
||
|
|
mmc_set_timing(mhost, MMC_TIMING_LEGACY);
|
||
|
|
mmc_set_clock(mhost, MMC_HIGH_26_MAX_DTR);
|
||
|
|
|
||
|
|
if (host->clk_scaling.lower_bus_speed_mode &
|
||
|
|
MMC_SCALING_LOWER_DDR52_MODE) {
|
||
|
|
err = sdhci_msm_mmc_select_hs_ddr52(host, freq);
|
||
|
|
if (err)
|
||
|
|
pr_err("%s: %s: failed to switch to DDR52: err: %d\n",
|
||
|
|
mmc_hostname(mhost), __func__, err);
|
||
|
|
else
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
|
||
|
|
err = mmc_select_hs(mhost->card);
|
||
|
|
if (err) {
|
||
|
|
pr_err("%s: %s: scaling low: failed (%d)\n",
|
||
|
|
mmc_hostname(mhost), __func__, err);
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
|
||
|
|
err = mmc_select_bus_width(mhost->card);
|
||
|
|
if (err < 0) {
|
||
|
|
pr_err("%s: %s: select_bus_width failed(%d)\n",
|
||
|
|
mmc_hostname(mhost), __func__, err);
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
|
||
|
|
mmc_set_clock(mhost, freq);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Scale UP from HS to HS200/H400
|
||
|
|
*/
|
||
|
|
static int sdhci_msm_mmc_scale_high(struct sdhci_msm_host *host)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = host->mmc;
|
||
|
|
int err = 0;
|
||
|
|
|
||
|
|
if (mmc_card_ddr52(mhost->card)) {
|
||
|
|
mmc_set_timing(mhost, MMC_TIMING_LEGACY);
|
||
|
|
mmc_set_clock(mhost, MMC_HIGH_26_MAX_DTR);
|
||
|
|
}
|
||
|
|
|
||
|
|
mmc_set_initial_state(mhost);
|
||
|
|
err = mmc_select_timing(mhost->card);
|
||
|
|
if (err) {
|
||
|
|
pr_err("%s: %s: select hs400 failed (%d)\n",
|
||
|
|
mmc_hostname(mhost), __func__, err);
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (mmc_card_hs200(mhost->card)) {
|
||
|
|
err = mmc_hs200_tuning(mhost->card);
|
||
|
|
if (err) {
|
||
|
|
pr_err("%s: %s: hs200 tuning failed (%d)\n",
|
||
|
|
mmc_hostname(mhost), __func__, err);
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
|
||
|
|
err = mmc_select_hs400(mhost->card);
|
||
|
|
if (err) {
|
||
|
|
pr_err("%s: %s: select hs400 failed (%d)\n",
|
||
|
|
mmc_hostname(mhost), __func__, err);
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sdhci_msm_mmc_set_clock_bus_speed(struct sdhci_msm_host *host, unsigned long freq)
|
||
|
|
{
|
||
|
|
int err = 0;
|
||
|
|
|
||
|
|
if (freq == MMC_HS200_MAX_DTR)
|
||
|
|
err = sdhci_msm_mmc_scale_high(host);
|
||
|
|
else
|
||
|
|
err = sdhci_msm_mmc_scale_low(host, freq);
|
||
|
|
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
|
||
|
|
static inline unsigned long sdhci_msm_mmc_ddr_freq_accommodation(unsigned long freq)
|
||
|
|
{
|
||
|
|
if (freq == MMC_HIGH_DDR_MAX_DTR)
|
||
|
|
return freq;
|
||
|
|
|
||
|
|
return freq/2;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* mmc_change_bus_speed() - Change MMC card bus frequency at runtime
|
||
|
|
* @host: pointer to mmc host structure
|
||
|
|
* @freq: pointer to desired frequency to be set
|
||
|
|
*
|
||
|
|
* Change the MMC card bus frequency at runtime after the card is
|
||
|
|
* initialized. Callers are expected to make sure of the card's
|
||
|
|
* state (DATA/RCV/TRANSFER) before changing the frequency at runtime.
|
||
|
|
*
|
||
|
|
* If the frequency to change is greater than max. supported by card,
|
||
|
|
* *freq is changed to max. supported by card. If it is less than min.
|
||
|
|
* supported by host, *freq is changed to min. supported by host.
|
||
|
|
* Host is assumed to be calimed while calling this funciton.
|
||
|
|
*/
|
||
|
|
static int sdhci_msm_mmc_change_bus_speed(struct sdhci_msm_host *host, unsigned long *freq)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = host->mmc;
|
||
|
|
int err = 0;
|
||
|
|
struct mmc_card *card;
|
||
|
|
unsigned long actual_freq;
|
||
|
|
|
||
|
|
card = mhost->card;
|
||
|
|
|
||
|
|
if (!card || !freq) {
|
||
|
|
err = -EINVAL;
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
actual_freq = *freq;
|
||
|
|
|
||
|
|
WARN_ON(!mhost->claimed);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* For scaling up/down HS400 we'll need special handling,
|
||
|
|
* for other timings we can simply do clock frequency change
|
||
|
|
*/
|
||
|
|
if (mmc_card_hs400(card) ||
|
||
|
|
(!mmc_card_hs200(mhost->card) && *freq == MMC_HS200_MAX_DTR)) {
|
||
|
|
err = sdhci_msm_mmc_set_clock_bus_speed(host, *freq);
|
||
|
|
if (err) {
|
||
|
|
pr_err("%s: %s: failed (%d)to set bus and clock speed (freq=%lu)\n",
|
||
|
|
mmc_hostname(mhost), __func__, err, *freq);
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
} else if (mmc_card_hs200(mhost->card)) {
|
||
|
|
mmc_set_clock(mhost, *freq);
|
||
|
|
err = mmc_hs200_tuning(mhost->card);
|
||
|
|
if (err) {
|
||
|
|
pr_warn("%s: %s: tuning execution failed %d\n",
|
||
|
|
mmc_hostname(card->host),
|
||
|
|
__func__, err);
|
||
|
|
mmc_set_clock(mhost, host->clk_scaling.curr_freq);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if (mmc_card_ddr52(mhost->card))
|
||
|
|
actual_freq = sdhci_msm_mmc_ddr_freq_accommodation(*freq);
|
||
|
|
mmc_set_clock(mhost, actual_freq);
|
||
|
|
}
|
||
|
|
|
||
|
|
out:
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool sdhci_msm_mmc_is_data_request(u32 opcode)
|
||
|
|
{
|
||
|
|
switch (opcode) {
|
||
|
|
case MMC_READ_SINGLE_BLOCK:
|
||
|
|
case MMC_READ_MULTIPLE_BLOCK:
|
||
|
|
case MMC_WRITE_BLOCK:
|
||
|
|
case MMC_WRITE_MULTIPLE_BLOCK:
|
||
|
|
return true;
|
||
|
|
default:
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void _sdhci_msm_mmc_clk_scaling_start_busy(struct sdhci_msm_host *host, bool lock_needed)
|
||
|
|
{
|
||
|
|
struct sdhci_msm_mmc_devfeq_clk_scaling *clk_scaling = &host->clk_scaling;
|
||
|
|
unsigned long flags;
|
||
|
|
|
||
|
|
if (!clk_scaling->enable)
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (lock_needed)
|
||
|
|
spin_lock_irqsave(&clk_scaling->lock, flags);
|
||
|
|
|
||
|
|
clk_scaling->start_busy = ktime_get();
|
||
|
|
clk_scaling->is_busy_started = true;
|
||
|
|
|
||
|
|
if (lock_needed)
|
||
|
|
spin_unlock_irqrestore(&clk_scaling->lock, flags);
|
||
|
|
}
|
||
|
|
|
||
|
|
void _sdhci_msm_mmc_clk_scaling_stop_busy(struct sdhci_msm_host *host, bool lock_needed)
|
||
|
|
{
|
||
|
|
struct sdhci_msm_mmc_devfeq_clk_scaling *clk_scaling = &host->clk_scaling;
|
||
|
|
unsigned long flags;
|
||
|
|
|
||
|
|
if (!clk_scaling->enable)
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (lock_needed)
|
||
|
|
spin_lock_irqsave(&clk_scaling->lock, flags);
|
||
|
|
|
||
|
|
if (!clk_scaling->is_busy_started) {
|
||
|
|
WARN_ON(1);
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
|
||
|
|
clk_scaling->total_busy_time_us +=
|
||
|
|
ktime_to_us(ktime_sub(ktime_get(),
|
||
|
|
clk_scaling->start_busy));
|
||
|
|
pr_debug("%s: accumulated busy time is %lu usec\n",
|
||
|
|
mmc_hostname(host->mmc), clk_scaling->total_busy_time_us);
|
||
|
|
clk_scaling->is_busy_started = false;
|
||
|
|
|
||
|
|
out:
|
||
|
|
if (lock_needed)
|
||
|
|
spin_unlock_irqrestore(&clk_scaling->lock, flags);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* mmc_cqe_clk_scaling_start_busy() - start busy timer for data requests
|
||
|
|
* @host: pointer to mmc host structure
|
||
|
|
* @lock_needed: flag indication if locking is needed
|
||
|
|
*
|
||
|
|
* This function starts the busy timer in case it was not already started.
|
||
|
|
*/
|
||
|
|
void _sdhci_msm_mmc_cqe_clk_scaling_start_busy(struct mmc_queue *mq,
|
||
|
|
struct sdhci_msm_host *host, bool lock_needed)
|
||
|
|
{
|
||
|
|
unsigned long flags;
|
||
|
|
|
||
|
|
if (!host->clk_scaling.enable)
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (lock_needed)
|
||
|
|
spin_lock_irqsave(&host->clk_scaling.lock, flags);
|
||
|
|
|
||
|
|
if (!host->clk_scaling.is_busy_started &&
|
||
|
|
!(mq->cqe_busy & MMC_CQE_DCMD_BUSY)) {
|
||
|
|
host->clk_scaling.start_busy = ktime_get();
|
||
|
|
host->clk_scaling.is_busy_started = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (lock_needed)
|
||
|
|
spin_unlock_irqrestore(&host->clk_scaling.lock, flags);
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(_sdhci_msm_mmc_cqe_clk_scaling_start_busy);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* mmc_cqe_clk_scaling_stop_busy() - stop busy timer for last data requests
|
||
|
|
* @host: pointer to mmc host structure
|
||
|
|
* @lock_needed: flag indication if locking is needed
|
||
|
|
*
|
||
|
|
* This function stops the busy timer in case it is the last data request.
|
||
|
|
* In case the current request is not the last one, the busy time till
|
||
|
|
* now will be accumulated and the counter will be restarted.
|
||
|
|
*/
|
||
|
|
void _sdhci_msm_mmc_cqe_clk_scaling_stop_busy(struct sdhci_msm_host *host,
|
||
|
|
bool lock_needed, int is_cqe_dcmd)
|
||
|
|
{
|
||
|
|
unsigned int cqe_active_reqs = 0;
|
||
|
|
unsigned long flags;
|
||
|
|
|
||
|
|
if (!host->clk_scaling.enable)
|
||
|
|
return;
|
||
|
|
|
||
|
|
cqe_active_reqs = atomic_read(&host->active_reqs);
|
||
|
|
|
||
|
|
if (lock_needed)
|
||
|
|
spin_lock_irqsave(&host->clk_scaling.lock, flags);
|
||
|
|
|
||
|
|
host->clk_scaling.total_busy_time_us +=
|
||
|
|
ktime_to_us(ktime_sub(ktime_get(),
|
||
|
|
host->clk_scaling.start_busy));
|
||
|
|
|
||
|
|
if (cqe_active_reqs) {
|
||
|
|
host->clk_scaling.is_busy_started = true;
|
||
|
|
host->clk_scaling.start_busy = ktime_get();
|
||
|
|
} else {
|
||
|
|
host->clk_scaling.is_busy_started = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (lock_needed)
|
||
|
|
spin_unlock_irqrestore(&host->clk_scaling.lock, flags);
|
||
|
|
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(_sdhci_msm_mmc_cqe_clk_scaling_stop_busy);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* mmc_can_scale_clk() - Check clock scaling capability
|
||
|
|
* @host: pointer to mmc host structure
|
||
|
|
*/
|
||
|
|
bool sdhci_msm_mmc_can_scale_clk(struct sdhci_msm_host *msm_host)
|
||
|
|
{
|
||
|
|
struct mmc_host *host = msm_host->mmc;
|
||
|
|
|
||
|
|
if (!host) {
|
||
|
|
pr_err("bad host parameter\n");
|
||
|
|
WARN_ON(1);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return msm_host->scale_caps & MMC_CAP2_CLK_SCALE;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(sdhci_msm_mmc_can_scale_clk);
|
||
|
|
|
||
|
|
static int sdhci_msm_mmc_devfreq_get_dev_status(struct device *dev,
|
||
|
|
struct devfreq_dev_status *status)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = container_of(dev, struct mmc_host, class_dev);
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
struct sdhci_msm_mmc_devfeq_clk_scaling *clk_scaling;
|
||
|
|
unsigned long flags;
|
||
|
|
|
||
|
|
if (!host) {
|
||
|
|
pr_err("bad host parameter\n");
|
||
|
|
WARN_ON(1);
|
||
|
|
return -EINVAL;
|
||
|
|
}
|
||
|
|
|
||
|
|
clk_scaling = &host->clk_scaling;
|
||
|
|
|
||
|
|
if (!clk_scaling->enable)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
spin_lock_irqsave(&host->clk_scaling.lock, flags);
|
||
|
|
|
||
|
|
/* accumulate the busy time of ongoing work */
|
||
|
|
memset(status, 0, sizeof(*status));
|
||
|
|
if (clk_scaling->is_busy_started) {
|
||
|
|
if (mhost->cqe_on) {
|
||
|
|
/* the "busy-timer" will be restarted in case there
|
||
|
|
* are pending data requests
|
||
|
|
*/
|
||
|
|
_sdhci_msm_mmc_cqe_clk_scaling_stop_busy(host, false, false);
|
||
|
|
} else {
|
||
|
|
_sdhci_msm_mmc_clk_scaling_stop_busy(host, false);
|
||
|
|
_sdhci_msm_mmc_clk_scaling_start_busy(host, false);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
status->busy_time = clk_scaling->total_busy_time_us;
|
||
|
|
status->total_time = ktime_to_us(ktime_sub(ktime_get(),
|
||
|
|
clk_scaling->measure_interval_start));
|
||
|
|
clk_scaling->total_busy_time_us = 0;
|
||
|
|
status->current_frequency = clk_scaling->curr_freq;
|
||
|
|
clk_scaling->measure_interval_start = ktime_get();
|
||
|
|
|
||
|
|
pr_debug("%s: status: load = %lu%% - total_time=%lu busy_time = %lu, clk=%lu\n",
|
||
|
|
mmc_hostname(mhost),
|
||
|
|
(status->busy_time*100)/status->total_time,
|
||
|
|
status->total_time, status->busy_time,
|
||
|
|
status->current_frequency);
|
||
|
|
|
||
|
|
spin_unlock_irqrestore(&host->clk_scaling.lock, flags);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static bool sdhci_msm_mmc_is_valid_state_for_clk_scaling(struct sdhci_msm_host *host)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = host->mmc;
|
||
|
|
struct mmc_card *card = mhost->card;
|
||
|
|
u32 status;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* If the current partition type is RPMB, clock switching may not
|
||
|
|
* work properly as sending tuning command (CMD21) is illegal in
|
||
|
|
* this mode.
|
||
|
|
* For RPMB transaction cmdq would be disabled.
|
||
|
|
*/
|
||
|
|
if (!card || (mmc_card_mmc(card) && !card->ext_csd.cmdq_en))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if (mmc_send_status(card, &status)) {
|
||
|
|
pr_err("%s: Get card status fail\n", mmc_hostname(card->host));
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return R1_CURRENT_STATE(status) == R1_STATE_TRAN;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sdhci_msm_notify_load(struct sdhci_msm_host *msm_host, enum sdhci_msm_mmc_load state)
|
||
|
|
{
|
||
|
|
int ret = 0;
|
||
|
|
u32 clk_rate = 0;
|
||
|
|
|
||
|
|
if (!IS_ERR(msm_host->bulk_clks[2].clk)) {
|
||
|
|
clk_rate = (state == MMC_LOAD_LOW) ?
|
||
|
|
msm_host->ice_clk_min :
|
||
|
|
msm_host->ice_clk_max;
|
||
|
|
if (msm_host->ice_clk_rate == clk_rate)
|
||
|
|
return 0;
|
||
|
|
pr_debug("%s: changing ICE clk rate to %u\n",
|
||
|
|
mmc_hostname(msm_host->mmc), clk_rate);
|
||
|
|
ret = clk_set_rate(msm_host->bulk_clks[2].clk, clk_rate);
|
||
|
|
if (ret) {
|
||
|
|
pr_err("%s: ICE_CLK rate set failed (%d) for %u\n",
|
||
|
|
mmc_hostname(msm_host->mmc), ret, clk_rate);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
msm_host->ice_clk_rate = clk_rate;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int sdhci_msm_mmc_clk_update_freq(struct sdhci_msm_host *host,
|
||
|
|
unsigned long freq, enum sdhci_msm_mmc_load state)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = host->mmc;
|
||
|
|
int err = 0;
|
||
|
|
|
||
|
|
if (!host) {
|
||
|
|
pr_err("bad host parameter\n");
|
||
|
|
WARN_ON(1);
|
||
|
|
return -EINVAL;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* make sure the card supports the frequency we want */
|
||
|
|
if (unlikely(freq > host->clk_scaling_highest)) {
|
||
|
|
freq = host->clk_scaling_highest;
|
||
|
|
pr_warn("%s: %s: High freq was overridden to %lu\n",
|
||
|
|
mmc_hostname(mhost), __func__,
|
||
|
|
host->clk_scaling_highest);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (unlikely(freq < host->clk_scaling_lowest)) {
|
||
|
|
freq = host->clk_scaling_lowest;
|
||
|
|
pr_warn("%s: %s: Low freq was overridden to %lu\n",
|
||
|
|
mmc_hostname(mhost), __func__,
|
||
|
|
host->clk_scaling_lowest);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (freq == host->clk_scaling.curr_freq)
|
||
|
|
goto out;
|
||
|
|
|
||
|
|
if (mhost->cqe_on) {
|
||
|
|
err = mhost->cqe_ops->cqe_wait_for_idle(mhost);
|
||
|
|
if (err) {
|
||
|
|
pr_err("%s: %s: CQE went in recovery path\n",
|
||
|
|
mmc_hostname(mhost), __func__);
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
mhost->cqe_ops->cqe_off(mhost);
|
||
|
|
}
|
||
|
|
|
||
|
|
err = sdhci_msm_notify_load(host, state);
|
||
|
|
if (err) {
|
||
|
|
pr_err("%s: %s: fail on notify_load\n",
|
||
|
|
mmc_hostname(mhost), __func__);
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!sdhci_msm_mmc_is_valid_state_for_clk_scaling(host)) {
|
||
|
|
pr_debug("%s: invalid state for clock scaling - skipping\n",
|
||
|
|
mmc_hostname(mhost));
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (mmc_card_sd(mhost->card))
|
||
|
|
err = sdhci_msm_mmc_sd_change_bus_speed(host, &freq);
|
||
|
|
else
|
||
|
|
err = sdhci_msm_mmc_change_bus_speed(host, &freq);
|
||
|
|
if (!err)
|
||
|
|
host->clk_scaling.curr_freq = freq;
|
||
|
|
else
|
||
|
|
pr_err("%s: %s: failed (%d) at freq=%lu\n",
|
||
|
|
mmc_hostname(mhost), __func__, err, freq);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* CQE would be enabled as part of CQE issueing path
|
||
|
|
* So no need to unhalt it explicitly
|
||
|
|
*/
|
||
|
|
|
||
|
|
out:
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(sdhci_msm_mmc_clk_update_freq);
|
||
|
|
|
||
|
|
static int sdhci_msm_mmc_devfreq_set_target(struct device *dev,
|
||
|
|
unsigned long *freq, u32 devfreq_flags)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = container_of(dev, struct mmc_host, class_dev);
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
|
||
|
|
struct sdhci_msm_mmc_devfeq_clk_scaling *clk_scaling;
|
||
|
|
int err = 0;
|
||
|
|
int abort;
|
||
|
|
unsigned long pflags = current->flags;
|
||
|
|
unsigned long flags;
|
||
|
|
|
||
|
|
/* Ensure scaling would happen even in memory pressure conditions */
|
||
|
|
current->flags |= PF_MEMALLOC;
|
||
|
|
|
||
|
|
if (!(host && freq)) {
|
||
|
|
pr_err("%s: unexpected host/freq parameter\n", __func__);
|
||
|
|
err = -EINVAL;
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
|
||
|
|
clk_scaling = &host->clk_scaling;
|
||
|
|
|
||
|
|
if (!clk_scaling->enable)
|
||
|
|
goto out;
|
||
|
|
|
||
|
|
pr_debug("%s: target freq = %lu (%s)\n", mmc_hostname(mhost),
|
||
|
|
*freq, current->comm);
|
||
|
|
|
||
|
|
spin_lock_irqsave(&clk_scaling->lock, flags);
|
||
|
|
if (clk_scaling->target_freq == *freq) {
|
||
|
|
spin_unlock_irqrestore(&clk_scaling->lock, flags);
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
|
||
|
|
clk_scaling->need_freq_change = true;
|
||
|
|
clk_scaling->target_freq = *freq;
|
||
|
|
clk_scaling->state = *freq < clk_scaling->curr_freq ?
|
||
|
|
MMC_LOAD_LOW : MMC_LOAD_HIGH;
|
||
|
|
spin_unlock_irqrestore(&clk_scaling->lock, flags);
|
||
|
|
|
||
|
|
if (!clk_scaling->is_suspended && mhost->ios.clock)
|
||
|
|
abort = __mmc_claim_host(mhost, NULL,
|
||
|
|
&clk_scaling->devfreq_abort);
|
||
|
|
else
|
||
|
|
goto out;
|
||
|
|
|
||
|
|
if (abort)
|
||
|
|
goto out;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* In case we were able to claim host there is no need to
|
||
|
|
* defer the frequency change. It will be done now
|
||
|
|
*/
|
||
|
|
clk_scaling->need_freq_change = false;
|
||
|
|
|
||
|
|
err = sdhci_msm_mmc_clk_update_freq(host, *freq, clk_scaling->state);
|
||
|
|
if (err && err != -EAGAIN)
|
||
|
|
pr_err("%s: clock scale to %lu failed with error %d\n",
|
||
|
|
mmc_hostname(mhost), *freq, err);
|
||
|
|
else
|
||
|
|
pr_debug("%s: clock change to %lu finished successfully (%s)\n",
|
||
|
|
mmc_hostname(mhost), *freq, current->comm);
|
||
|
|
|
||
|
|
mmc_release_host(mhost);
|
||
|
|
out:
|
||
|
|
current_restore_flags(pflags, PF_MEMALLOC);
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* mmc_deferred_scaling() - scale clocks from data path (mmc thread context)
|
||
|
|
* @host: pointer to mmc host structure
|
||
|
|
*
|
||
|
|
* This function does clock scaling in case "need_freq_change" flag was set
|
||
|
|
* by the clock scaling logic.
|
||
|
|
*/
|
||
|
|
void sdhci_msm_mmc_deferred_scaling(struct sdhci_msm_host *host)
|
||
|
|
{
|
||
|
|
unsigned long target_freq;
|
||
|
|
int err;
|
||
|
|
struct sdhci_msm_mmc_devfeq_clk_scaling clk_scaling;
|
||
|
|
unsigned long flags;
|
||
|
|
struct mmc_host *mhost = host->mmc;
|
||
|
|
|
||
|
|
if (!host->clk_scaling.enable)
|
||
|
|
return;
|
||
|
|
|
||
|
|
spin_lock_irqsave(&host->clk_scaling.lock, flags);
|
||
|
|
|
||
|
|
if (!host->clk_scaling.need_freq_change) {
|
||
|
|
spin_unlock_irqrestore(&host->clk_scaling.lock, flags);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
atomic_inc(&host->clk_scaling.devfreq_abort);
|
||
|
|
target_freq = host->clk_scaling.target_freq;
|
||
|
|
/*
|
||
|
|
* Store the clock scaling state while the lock is acquired so that
|
||
|
|
* if devfreq context modifies clk_scaling, it will get reflected only
|
||
|
|
* in the next deferred scaling check.
|
||
|
|
*/
|
||
|
|
clk_scaling = host->clk_scaling;
|
||
|
|
host->clk_scaling.need_freq_change = false;
|
||
|
|
spin_unlock_irqrestore(&host->clk_scaling.lock, flags);
|
||
|
|
|
||
|
|
pr_debug("%s: doing deferred frequency change (%lu) (%s)\n",
|
||
|
|
mmc_hostname(mhost),
|
||
|
|
target_freq, current->comm);
|
||
|
|
|
||
|
|
err = sdhci_msm_mmc_clk_update_freq(host, target_freq,
|
||
|
|
clk_scaling.state);
|
||
|
|
if (err && err != -EAGAIN)
|
||
|
|
pr_err("%s: failed on deferred scale clocks (%d)\n",
|
||
|
|
mmc_hostname(mhost), err);
|
||
|
|
else
|
||
|
|
pr_debug("%s: clocks were successfully scaled to %lu (%s)\n",
|
||
|
|
mmc_hostname(mhost),
|
||
|
|
target_freq, current->comm);
|
||
|
|
atomic_dec(&host->clk_scaling.devfreq_abort);
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(sdhci_msm_mmc_deferred_scaling);
|
||
|
|
|
||
|
|
static int sdhci_msm_mmc_devfreq_create_freq_table(struct sdhci_msm_host *host)
|
||
|
|
{
|
||
|
|
int i;
|
||
|
|
struct sdhci_msm_mmc_devfeq_clk_scaling *clk_scaling = &host->clk_scaling;
|
||
|
|
struct mmc_host *mhost = host->mmc;
|
||
|
|
|
||
|
|
pr_debug("%s: supported: lowest=%lu, highest=%lu\n",
|
||
|
|
mmc_hostname(mhost),
|
||
|
|
host->clk_scaling_lowest,
|
||
|
|
host->clk_scaling_highest);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Create the frequency table and initialize it with default values.
|
||
|
|
* Initialize it with platform specific frequencies if the frequency
|
||
|
|
* table supplied by platform driver is present, otherwise initialize
|
||
|
|
* it with min and max frequencies supported by the card.
|
||
|
|
*/
|
||
|
|
if (!clk_scaling->freq_table) {
|
||
|
|
if (clk_scaling->pltfm_freq_table_sz)
|
||
|
|
clk_scaling->freq_table_sz =
|
||
|
|
clk_scaling->pltfm_freq_table_sz;
|
||
|
|
else
|
||
|
|
clk_scaling->freq_table_sz = 2;
|
||
|
|
|
||
|
|
clk_scaling->freq_table = kcalloc(
|
||
|
|
clk_scaling->freq_table_sz,
|
||
|
|
sizeof(*(clk_scaling->freq_table)), GFP_KERNEL);
|
||
|
|
if (!clk_scaling->freq_table)
|
||
|
|
return -ENOMEM;
|
||
|
|
|
||
|
|
if (clk_scaling->pltfm_freq_table) {
|
||
|
|
memcpy(clk_scaling->freq_table,
|
||
|
|
clk_scaling->pltfm_freq_table,
|
||
|
|
(clk_scaling->pltfm_freq_table_sz *
|
||
|
|
sizeof(*(clk_scaling->pltfm_freq_table))));
|
||
|
|
} else {
|
||
|
|
pr_debug("%s: no frequency table defined - setting default\n",
|
||
|
|
mmc_hostname(mhost));
|
||
|
|
clk_scaling->freq_table[0] =
|
||
|
|
host->clk_scaling_lowest;
|
||
|
|
clk_scaling->freq_table[1] =
|
||
|
|
host->clk_scaling_highest;
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (host->clk_scaling_lowest >
|
||
|
|
clk_scaling->freq_table[0])
|
||
|
|
pr_debug("%s: frequency table undershot possible freq\n",
|
||
|
|
mmc_hostname(mhost));
|
||
|
|
|
||
|
|
for (i = 0; i < clk_scaling->freq_table_sz; i++) {
|
||
|
|
if (clk_scaling->freq_table[i] <=
|
||
|
|
host->clk_scaling_highest)
|
||
|
|
continue;
|
||
|
|
clk_scaling->freq_table[i] =
|
||
|
|
host->clk_scaling_highest;
|
||
|
|
clk_scaling->freq_table_sz = i + 1;
|
||
|
|
pr_debug("%s: frequency table overshot possible freq (%d)\n",
|
||
|
|
mmc_hostname(mhost), clk_scaling->freq_table[i]);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (mmc_card_sd(mhost->card) && (clk_scaling->freq_table_sz < 2)) {
|
||
|
|
clk_scaling->freq_table[clk_scaling->freq_table_sz] =
|
||
|
|
host->clk_scaling_highest;
|
||
|
|
clk_scaling->freq_table_sz++;
|
||
|
|
}
|
||
|
|
out:
|
||
|
|
/**
|
||
|
|
* devfreq requires unsigned long type freq_table while the
|
||
|
|
* freq_table in clk_scaling is un32. Here allocates an individual
|
||
|
|
* memory space for it and release it when exit clock scaling.
|
||
|
|
*/
|
||
|
|
clk_scaling->devfreq_profile.freq_table = kcalloc(
|
||
|
|
clk_scaling->freq_table_sz,
|
||
|
|
sizeof(*(clk_scaling->devfreq_profile.freq_table)),
|
||
|
|
GFP_KERNEL);
|
||
|
|
if (!clk_scaling->devfreq_profile.freq_table) {
|
||
|
|
kfree(clk_scaling->freq_table);
|
||
|
|
return -ENOMEM;
|
||
|
|
}
|
||
|
|
clk_scaling->devfreq_profile.max_state = clk_scaling->freq_table_sz;
|
||
|
|
|
||
|
|
for (i = 0; i < clk_scaling->freq_table_sz; i++) {
|
||
|
|
clk_scaling->devfreq_profile.freq_table[i] =
|
||
|
|
clk_scaling->freq_table[i];
|
||
|
|
pr_debug("%s: freq[%d] = %u\n",
|
||
|
|
mmc_hostname(mhost), i, clk_scaling->freq_table[i]);
|
||
|
|
}
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static ssize_t enable_show(struct device *dev,
|
||
|
|
struct device_attribute *attr, char *buf)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = cls_dev_to_mmc_host(dev);
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
|
||
|
|
if (!host)
|
||
|
|
return -EINVAL;
|
||
|
|
|
||
|
|
return scnprintf(buf, PAGE_SIZE, "%d\n", sdhci_msm_mmc_can_scale_clk(host));
|
||
|
|
}
|
||
|
|
|
||
|
|
static ssize_t enable_store(struct device *dev,
|
||
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = cls_dev_to_mmc_host(dev);
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
unsigned long value;
|
||
|
|
|
||
|
|
if (!host || !mhost->card || kstrtoul(buf, 0, &value))
|
||
|
|
return -EINVAL;
|
||
|
|
|
||
|
|
mmc_get_card(mhost->card, NULL);
|
||
|
|
|
||
|
|
if (!value) {
|
||
|
|
/* Suspend the clock scaling and mask host capability */
|
||
|
|
if (host->clk_scaling.enable)
|
||
|
|
sdhci_msm_mmc_suspend_clk_scaling(mhost);
|
||
|
|
host->clk_scaling.enable = false;
|
||
|
|
mhost->caps2 &= ~MMC_CAP2_CLK_SCALE;
|
||
|
|
host->scale_caps = mhost->caps2;
|
||
|
|
host->clk_scaling.state = MMC_LOAD_HIGH;
|
||
|
|
/* Set to max. frequency when disabling */
|
||
|
|
sdhci_msm_mmc_clk_update_freq(host, host->clk_scaling_highest,
|
||
|
|
host->clk_scaling.state);
|
||
|
|
} else if (value) {
|
||
|
|
/* Unmask host capability and resume scaling */
|
||
|
|
mhost->caps2 |= MMC_CAP2_CLK_SCALE;
|
||
|
|
host->scale_caps = mhost->caps2;
|
||
|
|
if (!host->clk_scaling.enable) {
|
||
|
|
host->clk_scaling.enable = true;
|
||
|
|
sdhci_msm_mmc_resume_clk_scaling(mhost);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
mmc_put_card(mhost->card, NULL);
|
||
|
|
|
||
|
|
return count;
|
||
|
|
}
|
||
|
|
|
||
|
|
static ssize_t up_threshold_show(struct device *dev,
|
||
|
|
struct device_attribute *attr, char *buf)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = cls_dev_to_mmc_host(dev);
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
|
||
|
|
if (!host)
|
||
|
|
return -EINVAL;
|
||
|
|
|
||
|
|
return scnprintf(buf, PAGE_SIZE, "%d\n", host->clk_scaling.upthreshold);
|
||
|
|
}
|
||
|
|
|
||
|
|
#define MAX_PERCENTAGE 100
|
||
|
|
static ssize_t up_threshold_store(struct device *dev,
|
||
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = cls_dev_to_mmc_host(dev);
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
unsigned long value;
|
||
|
|
|
||
|
|
if (!host || kstrtoul(buf, 0, &value) || (value > MAX_PERCENTAGE))
|
||
|
|
return -EINVAL;
|
||
|
|
|
||
|
|
host->clk_scaling.upthreshold = value;
|
||
|
|
|
||
|
|
pr_debug("%s: clkscale_up_thresh set to %lu\n",
|
||
|
|
mmc_hostname(mhost), value);
|
||
|
|
return count;
|
||
|
|
}
|
||
|
|
|
||
|
|
static ssize_t down_threshold_show(struct device *dev,
|
||
|
|
struct device_attribute *attr, char *buf)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = cls_dev_to_mmc_host(dev);
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
|
||
|
|
if (!host)
|
||
|
|
return -EINVAL;
|
||
|
|
|
||
|
|
return scnprintf(buf, PAGE_SIZE, "%d\n",
|
||
|
|
host->clk_scaling.downthreshold);
|
||
|
|
}
|
||
|
|
|
||
|
|
static ssize_t down_threshold_store(struct device *dev,
|
||
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = cls_dev_to_mmc_host(dev);
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
unsigned long value;
|
||
|
|
|
||
|
|
if (!host || kstrtoul(buf, 0, &value) || (value > MAX_PERCENTAGE))
|
||
|
|
return -EINVAL;
|
||
|
|
|
||
|
|
host->clk_scaling.downthreshold = value;
|
||
|
|
|
||
|
|
pr_debug("%s: clkscale_down_thresh set to %lu\n",
|
||
|
|
mmc_hostname(mhost), value);
|
||
|
|
return count;
|
||
|
|
}
|
||
|
|
|
||
|
|
static ssize_t polling_interval_show(struct device *dev,
|
||
|
|
struct device_attribute *attr, char *buf)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = cls_dev_to_mmc_host(dev);
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
|
||
|
|
if (!host)
|
||
|
|
return -EINVAL;
|
||
|
|
|
||
|
|
return scnprintf(buf, PAGE_SIZE, "%lu milliseconds\n",
|
||
|
|
host->clk_scaling.polling_delay_ms);
|
||
|
|
}
|
||
|
|
|
||
|
|
static ssize_t polling_interval_store(struct device *dev,
|
||
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = cls_dev_to_mmc_host(dev);
|
||
|
|
struct sdhci_host *shost = mmc_priv(mhost);
|
||
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(shost);
|
||
|
|
struct sdhci_msm_host *host = sdhci_pltfm_priv(pltfm_host);
|
||
|
|
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(mhost), value);
|
||
|
|
return count;
|
||
|
|
}
|
||
|
|
|
||
|
|
DEVICE_ATTR_RW(enable);
|
||
|
|
DEVICE_ATTR_RW(polling_interval);
|
||
|
|
DEVICE_ATTR_RW(up_threshold);
|
||
|
|
DEVICE_ATTR_RW(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,
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* mmc_init_devfreq_clk_scaling() - Initialize clock scaling
|
||
|
|
* @host: pointer to mmc host structure
|
||
|
|
*
|
||
|
|
* Initialize clock scaling for supported hosts. It is assumed that the caller
|
||
|
|
* ensure clock is running at maximum possible frequency before calling this
|
||
|
|
* function. Shall use struct devfreq_simple_ondemand_data to configure
|
||
|
|
* governor.
|
||
|
|
*/
|
||
|
|
int _sdhci_msm_mmc_init_clk_scaling(struct sdhci_msm_host *host)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = host->mmc;
|
||
|
|
int err;
|
||
|
|
struct devfreq *devfreq;
|
||
|
|
|
||
|
|
if (!mhost || !mhost->card) {
|
||
|
|
pr_err("%s: unexpected host/card parameters\n",
|
||
|
|
__func__);
|
||
|
|
return -EINVAL;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!sdhci_msm_mmc_can_scale_clk(host)) {
|
||
|
|
pr_debug("%s: clock scaling is not supported\n",
|
||
|
|
mmc_hostname(mhost));
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
pr_debug("registering %s dev (%pK) to devfreq\n",
|
||
|
|
mmc_hostname(mhost),
|
||
|
|
mmc_classdev(mhost));
|
||
|
|
|
||
|
|
if (host->clk_scaling.devfreq) {
|
||
|
|
pr_err("%s: dev is already registered for dev %pK\n",
|
||
|
|
mmc_hostname(mhost),
|
||
|
|
mmc_dev(mhost));
|
||
|
|
return -EPERM;
|
||
|
|
}
|
||
|
|
spin_lock_init(&host->clk_scaling.lock);
|
||
|
|
atomic_set(&host->clk_scaling.devfreq_abort, 0);
|
||
|
|
host->clk_scaling.curr_freq = mhost->ios.clock;
|
||
|
|
host->clk_scaling.need_freq_change = false;
|
||
|
|
host->clk_scaling.is_busy_started = false;
|
||
|
|
|
||
|
|
host->clk_scaling.devfreq_profile.polling_ms =
|
||
|
|
host->clk_scaling.polling_delay_ms;
|
||
|
|
host->clk_scaling.devfreq_profile.get_dev_status =
|
||
|
|
sdhci_msm_mmc_devfreq_get_dev_status;
|
||
|
|
host->clk_scaling.devfreq_profile.target = sdhci_msm_mmc_devfreq_set_target;
|
||
|
|
host->clk_scaling.devfreq_profile.initial_freq = mhost->ios.clock;
|
||
|
|
host->clk_scaling.devfreq_profile.timer = DEVFREQ_TIMER_DELAYED;
|
||
|
|
|
||
|
|
host->clk_scaling.ondemand_gov_data.upthreshold =
|
||
|
|
host->clk_scaling.upthreshold;
|
||
|
|
host->clk_scaling.ondemand_gov_data.downdifferential =
|
||
|
|
host->clk_scaling.upthreshold - host->clk_scaling.downthreshold;
|
||
|
|
|
||
|
|
err = sdhci_msm_mmc_devfreq_create_freq_table(host);
|
||
|
|
if (err) {
|
||
|
|
pr_err("%s: fail to create devfreq frequency table\n",
|
||
|
|
mmc_hostname(mhost));
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
|
||
|
|
dev_pm_opp_add(mmc_classdev(mhost),
|
||
|
|
host->clk_scaling.devfreq_profile.freq_table[0], 0);
|
||
|
|
dev_pm_opp_add(mmc_classdev(mhost),
|
||
|
|
host->clk_scaling.devfreq_profile.freq_table[1], 0);
|
||
|
|
|
||
|
|
pr_debug("%s: adding devfreq with: upthreshold=%u downthreshold=%u polling=%u\n",
|
||
|
|
mmc_hostname(mhost),
|
||
|
|
host->clk_scaling.ondemand_gov_data.upthreshold,
|
||
|
|
host->clk_scaling.ondemand_gov_data.downdifferential,
|
||
|
|
host->clk_scaling.devfreq_profile.polling_ms);
|
||
|
|
|
||
|
|
devfreq = devfreq_add_device(
|
||
|
|
mmc_classdev(mhost),
|
||
|
|
&host->clk_scaling.devfreq_profile,
|
||
|
|
"simple_ondemand",
|
||
|
|
&host->clk_scaling.ondemand_gov_data);
|
||
|
|
|
||
|
|
if (IS_ERR(devfreq)) {
|
||
|
|
pr_err("%s: unable to register with devfreq\n",
|
||
|
|
mmc_hostname(mhost));
|
||
|
|
dev_pm_opp_remove(mmc_classdev(mhost),
|
||
|
|
host->clk_scaling.devfreq_profile.freq_table[0]);
|
||
|
|
dev_pm_opp_remove(mmc_classdev(mhost),
|
||
|
|
host->clk_scaling.devfreq_profile.freq_table[1]);
|
||
|
|
return PTR_ERR(devfreq);
|
||
|
|
}
|
||
|
|
|
||
|
|
host->clk_scaling.devfreq = devfreq;
|
||
|
|
pr_debug("%s: clk scaling is enabled for device %s (%pK) with devfreq %pK (clock = %uHz)\n",
|
||
|
|
mmc_hostname(mhost),
|
||
|
|
dev_name(mmc_classdev(mhost)),
|
||
|
|
mmc_classdev(mhost),
|
||
|
|
host->clk_scaling.devfreq,
|
||
|
|
mhost->ios.clock);
|
||
|
|
|
||
|
|
host->clk_scaling.enable = true;
|
||
|
|
|
||
|
|
err = sysfs_create_group(&mhost->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);
|
||
|
|
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(_sdhci_msm_mmc_init_clk_scaling);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* mmc_suspend_clk_scaling() - suspend clock scaling
|
||
|
|
* @host: pointer to mmc host structure
|
||
|
|
*
|
||
|
|
* This API will suspend devfreq feature for the specific host.
|
||
|
|
* The statistics collected by mmc will be cleared.
|
||
|
|
* This function is intended to be called by the pm callbacks
|
||
|
|
* (e.g. runtime_suspend, suspend) of the mmc device
|
||
|
|
*/
|
||
|
|
int _sdhci_msm_mmc_suspend_clk_scaling(struct sdhci_msm_host *host)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = host->mmc;
|
||
|
|
int err;
|
||
|
|
|
||
|
|
if (!host) {
|
||
|
|
WARN(1, "bad host parameter\n");
|
||
|
|
return -EINVAL;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!sdhci_msm_mmc_can_scale_clk(host) || !host->clk_scaling.enable ||
|
||
|
|
host->clk_scaling.is_suspended)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
if (!host->clk_scaling.devfreq) {
|
||
|
|
pr_err("%s: %s: no devfreq is assosiated with this device\n",
|
||
|
|
mmc_hostname(mhost), __func__);
|
||
|
|
return -EPERM;
|
||
|
|
}
|
||
|
|
|
||
|
|
atomic_inc(&host->clk_scaling.devfreq_abort);
|
||
|
|
wake_up(&mhost->wq);
|
||
|
|
err = devfreq_suspend_device(host->clk_scaling.devfreq);
|
||
|
|
if (err) {
|
||
|
|
pr_err("%s: %s: failed to suspend devfreq\n",
|
||
|
|
mmc_hostname(mhost), __func__);
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
host->clk_scaling.is_suspended = true;
|
||
|
|
|
||
|
|
host->clk_scaling.total_busy_time_us = 0;
|
||
|
|
|
||
|
|
pr_debug("%s: devfreq was removed\n", mmc_hostname(mhost));
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(_sdhci_msm_mmc_suspend_clk_scaling);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* mmc_resume_clk_scaling() - resume clock scaling
|
||
|
|
* @host: pointer to mmc host structure
|
||
|
|
*
|
||
|
|
* This API will resume devfreq feature for the specific host.
|
||
|
|
* This API is intended to be called by the pm callbacks
|
||
|
|
* (e.g. runtime_suspend, suspend) of the mmc device
|
||
|
|
*/
|
||
|
|
int _sdhci_msm_mmc_resume_clk_scaling(struct sdhci_msm_host *host)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = host->mmc;
|
||
|
|
int err = 0;
|
||
|
|
u32 max_clk_idx = 0;
|
||
|
|
u32 devfreq_max_clk = 0;
|
||
|
|
u32 devfreq_min_clk = 0;
|
||
|
|
|
||
|
|
if (!host) {
|
||
|
|
WARN(1, "bad host parameter\n");
|
||
|
|
return -EINVAL;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!sdhci_msm_mmc_can_scale_clk(host))
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* If clock scaling is already exited when resume is called, like
|
||
|
|
* during mmc shutdown, it is not an error and should not fail the
|
||
|
|
* API calling this.
|
||
|
|
*/
|
||
|
|
if (!host->clk_scaling.devfreq) {
|
||
|
|
pr_warn("%s: %s: no devfreq is assosiated with this device\n",
|
||
|
|
mmc_hostname(mhost), __func__);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
atomic_set(&host->clk_scaling.devfreq_abort, 0);
|
||
|
|
|
||
|
|
max_clk_idx = host->clk_scaling.freq_table_sz - 1;
|
||
|
|
devfreq_max_clk = host->clk_scaling.freq_table[max_clk_idx];
|
||
|
|
devfreq_min_clk = host->clk_scaling.freq_table[0];
|
||
|
|
|
||
|
|
host->clk_scaling.curr_freq = devfreq_max_clk;
|
||
|
|
if (mhost->ios.clock < host->clk_scaling.freq_table[max_clk_idx])
|
||
|
|
host->clk_scaling.curr_freq = devfreq_min_clk;
|
||
|
|
host->clk_scaling.target_freq = host->clk_scaling.curr_freq;
|
||
|
|
|
||
|
|
err = devfreq_resume_device(host->clk_scaling.devfreq);
|
||
|
|
if (err) {
|
||
|
|
pr_err("%s: %s: failed to resume devfreq (%d)\n",
|
||
|
|
mmc_hostname(mhost), __func__, err);
|
||
|
|
} else {
|
||
|
|
host->clk_scaling.is_suspended = false;
|
||
|
|
pr_debug("%s: devfreq resumed\n", mmc_hostname(mhost));
|
||
|
|
}
|
||
|
|
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(_sdhci_msm_mmc_resume_clk_scaling);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* mmc_exit_devfreq_clk_scaling() - Disable clock scaling
|
||
|
|
* @host: pointer to mmc host structure
|
||
|
|
*
|
||
|
|
* Disable clock scaling permanently.
|
||
|
|
*/
|
||
|
|
int _sdhci_msm_mmc_exit_clk_scaling(struct sdhci_msm_host *host)
|
||
|
|
{
|
||
|
|
struct mmc_host *mhost = host->mmc;
|
||
|
|
int err;
|
||
|
|
|
||
|
|
if (!host) {
|
||
|
|
pr_err("%s: bad host parameter\n", __func__);
|
||
|
|
WARN_ON(1);
|
||
|
|
return -EINVAL;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!sdhci_msm_mmc_can_scale_clk(host))
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
if (!host->clk_scaling.devfreq) {
|
||
|
|
pr_err("%s: %s: no devfreq is assosiated with this device\n",
|
||
|
|
mmc_hostname(mhost), __func__);
|
||
|
|
return -EPERM;
|
||
|
|
}
|
||
|
|
|
||
|
|
err = _sdhci_msm_mmc_suspend_clk_scaling(host);
|
||
|
|
if (err) {
|
||
|
|
pr_err("%s: %s: fail to suspend clock scaling (%d)\n",
|
||
|
|
mmc_hostname(mhost), __func__, err);
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
|
||
|
|
err = devfreq_remove_device(host->clk_scaling.devfreq);
|
||
|
|
if (err) {
|
||
|
|
pr_err("%s: remove devfreq failed (%d)\n",
|
||
|
|
mmc_hostname(mhost), err);
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
|
||
|
|
dev_pm_opp_remove(mmc_classdev(mhost),
|
||
|
|
host->clk_scaling.devfreq_profile.freq_table[0]);
|
||
|
|
dev_pm_opp_remove(mmc_classdev(mhost),
|
||
|
|
host->clk_scaling.devfreq_profile.freq_table[1]);
|
||
|
|
|
||
|
|
kfree(host->clk_scaling.devfreq_profile.freq_table);
|
||
|
|
|
||
|
|
host->clk_scaling.devfreq = NULL;
|
||
|
|
atomic_set(&host->clk_scaling.devfreq_abort, 1);
|
||
|
|
|
||
|
|
kfree(host->clk_scaling.freq_table);
|
||
|
|
host->clk_scaling.freq_table = NULL;
|
||
|
|
|
||
|
|
pr_debug("%s: devfreq was removed\n", mmc_hostname(mhost));
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(_sdhci_msm_mmc_exit_clk_scaling);
|
||
|
|
|
||
|
|
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. SDHCI Controller Support");
|
||
|
|
MODULE_LICENSE("GPL v2");
|