Merge "mmc: sdhci-msm: Update DDR_CONFIG reg with HSR value if supplied"

This commit is contained in:
qctecmdr 2020-08-16 02:32:35 -07:00 • committed by Gerrit - the friendly Code Review server
commit 349f41a0dc

View file

@ -14,6 +14,11 @@
#include <linux/interconnect.h>
#include <linux/iopoll.h>
#include <linux/regulator/consumer.h>
#include <linux/pm_qos.h>
#include <linux/cpu.h>
#include <linux/cpumask.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include "sdhci-pltfm.h"
#include "cqhci.h"
@ -126,6 +131,8 @@
#define CMUX_SHIFT_PHASE_MASK (7 << CMUX_SHIFT_PHASE_SHIFT)
#define MSM_MMC_AUTOSUSPEND_DELAY_MS 50
#define MSM_PMQOS_UNVOTING_DELAY_MS 10 /* msec */
#define MSM_CLK_GATING_DELAY_MS 200 /* msec */
/* Timeout value to avoid infinite waiting for pwr_irq */
#define MSM_PWR_IRQ_TIMEOUT_MS 5000
@ -359,6 +366,30 @@ struct sdhci_msm_vreg_data {
struct sdhci_msm_reg_data *vdd_io_data;
};
/* Per cpu cluster qos group */
struct qos_cpu_group {
cpumask_t mask; /* CPU mask of cluster */
unsigned int *votes; /* Different votes for cluster */
struct dev_pm_qos_request *qos_req; /* Pointer to host qos request*/
bool voted;
struct sdhci_msm_host *host;
bool initialized;
bool curr_vote;
};
/* Per host qos request structure */
struct sdhci_msm_qos_req {
struct qos_cpu_group *qcg; /* CPU group per host */
unsigned int num_groups; /* Number of groups */
unsigned int active_mask; /* Active affine irq mask */
};
enum constraint {
QOS_PERF,
QOS_POWER,
QOS_MAX,
};
struct sdhci_msm_host {
struct platform_device *pdev;
void __iomem *core_mem; /* MSM SDCC mapped address */
@ -389,15 +420,39 @@ struct sdhci_msm_host {
bool skip_bus_bw_voting;
struct sdhci_msm_bus_vote_data *bus_vote_data;
struct delayed_work bus_vote_work;
struct delayed_work pmqos_unvote_work;
struct delayed_work clk_gating_work;
bool pltfm_init_done;
bool core_3_0v_support;
bool use_7nm_dll;
struct sdhci_msm_dll_hsr *dll_hsr;
struct sdhci_msm_regs_restore regs_restore;
struct workqueue_struct *workq; /* QoS work queue */
struct sdhci_msm_qos_req *sdhci_qos;
struct irq_affinity_notify affinity_notify;
struct device_attribute clk_gating;
struct device_attribute pm_qos;
u32 clk_gating_delay;
u32 pm_qos_delay;
};
#define ANDROID_BOOT_DEV_MAX 30
static char android_boot_dev[ANDROID_BOOT_DEV_MAX];
#ifndef MODULE
static int __init get_android_boot_dev(char *str)
{
strlcpy(android_boot_dev, str, ANDROID_BOOT_DEV_MAX);
return 1;
}
__setup("androidboot.bootdevice=", get_android_boot_dev);
#endif
static struct sdhci_msm_host *sdhci_slot[2];
static int sdhci_msm_update_qos_constraints(struct qos_cpu_group *qcg,
enum constraint type);
static void sdhci_msm_bus_voting(struct sdhci_host *host, bool enable);
static int sdhci_msm_dt_get_array(struct device *dev, const char *prop_name,
@ -1143,7 +1198,13 @@ static int sdhci_msm_cm_dll_sdc4_calibration(struct sdhci_host *host)
ddr_cfg_offset = msm_offset->core_ddr_config;
else
ddr_cfg_offset = msm_offset->core_ddr_config_old;
writel_relaxed(DDR_CONFIG_POR_VAL, host->ioaddr + ddr_cfg_offset);
if (msm_host->dll_hsr->ddr_config)
config = msm_host->dll_hsr->ddr_config;
else
config = DDR_CONFIG_POR_VAL;
writel_relaxed(config, host->ioaddr + ddr_cfg_offset);
if (mmc->ios.enhanced_strobe) {
config = readl_relaxed(host->ioaddr +
@ -1169,9 +1230,21 @@ static int sdhci_msm_cm_dll_sdc4_calibration(struct sdhci_host *host)
goto out;
}
config = readl_relaxed(host->ioaddr + msm_offset->core_vendor_spec3);
config |= CORE_PWRSAVE_DLL;
writel_relaxed(config, host->ioaddr + msm_offset->core_vendor_spec3);
/*
* Set CORE_PWRSAVE_DLL bit in CORE_VENDOR_SPEC3.
* When MCLK is gated OFF, it is not gated for less than 0.5us
* and MCLK must be switched on for at-least 1us before DATA
* starts coming. Controllers with 14lpp and later tech DLL cannot
* guarantee above requirement. So PWRSAVE_DLL should not be
* turned on for host controllers using this DLL.
*/
if (!msm_host->use_14lpp_dll_reset) {
config = readl_relaxed(host->ioaddr +
msm_offset->core_vendor_spec3);
config |= CORE_PWRSAVE_DLL;
writel_relaxed(config, host->ioaddr +
msm_offset->core_vendor_spec3);
}
/*
* Drain writebuffer to ensure above DLL calibration
@ -2228,8 +2301,14 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock)
msm_set_clock_rate_for_bus_mode(host, clock);
out:
if (!msm_host->skip_bus_bw_voting)
sdhci_msm_bus_voting(host, !!clock);
/* Vote on bus only with clock frequency or when changing clock
* frequency. No need to vote when setting clock frequency as 0
* because after setting clock at 0, we release host, which will
* eventually call host runtime suspend and unvoting would be
* taken care in runtime suspend call.
*/
if (!msm_host->skip_bus_bw_voting && clock)
sdhci_msm_bus_voting(host, true);
__sdhci_msm_set_clock(host, clock);
}
@ -2598,33 +2677,11 @@ out:
return cpu_rc;
}
/*
* Internal work. Work to set 0 bandwidth for msm bus.
*/
static void sdhci_msm_bus_work(struct work_struct *work)
{
struct sdhci_msm_host *msm_host;
struct sdhci_host *host;
msm_host = container_of(work, struct sdhci_msm_host,
bus_vote_work.work);
host = platform_get_drvdata(msm_host->pdev);
if (!msm_host->bus_vote_data->sdhc_ddr ||
!msm_host->bus_vote_data->cpu_sdhc)
return;
/* don't vote for 0 bandwidth if any request is in progress */
if (!host->mmc->ongoing_mrq)
sdhci_msm_bus_set_vote(msm_host, 0);
else
pr_debug("Transfer in progress. Skipping bus voting to 0\n");
}
/*
* This function cancels any scheduled delayed work and sets the bus
* vote based on bw (bandwidth) argument.
*/
static void sdhci_msm_bus_cancel_work_and_set_vote(struct sdhci_host *host,
static void sdhci_msm_bus_get_and_set_vote(struct sdhci_host *host,
unsigned int bw)
{
int vote;
@ -2635,30 +2692,10 @@ static void sdhci_msm_bus_cancel_work_and_set_vote(struct sdhci_host *host,
!msm_host->bus_vote_data->sdhc_ddr ||
!msm_host->bus_vote_data->cpu_sdhc)
return;
cancel_delayed_work_sync(&msm_host->bus_vote_work);
vote = sdhci_msm_bus_get_vote_for_bw(msm_host, bw);
sdhci_msm_bus_set_vote(msm_host, vote);
}
#define MSM_MMC_BUS_VOTING_DELAY 200 /* msecs */
#define VOTE_ZERO 0
/*
* This function queues a work which will set the bandwidth
* requirement to 0.
*/
static void sdhci_msm_bus_queue_work(struct sdhci_host *host)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host);
if (msm_host->bus_vote_data &&
msm_host->bus_vote_data->curr_vote != VOTE_ZERO)
queue_delayed_work(system_wq,
&msm_host->bus_vote_work,
msecs_to_jiffies(MSM_MMC_BUS_VOTING_DELAY));
}
static struct sdhci_msm_bus_vote_data *sdhci_msm_get_bus_vote_data(struct device
*dev, struct sdhci_msm_host *host)
@ -2778,8 +2815,6 @@ static int sdhci_msm_bus_register(struct sdhci_msm_host *host,
return ret;
}
INIT_DELAYED_WORK(&host->bus_vote_work, sdhci_msm_bus_work);
return ret;
}
@ -2799,9 +2834,9 @@ static void sdhci_msm_bus_voting(struct sdhci_host *host, bool enable)
if (enable) {
bw = sdhci_get_bw_required(host, ios);
sdhci_msm_bus_cancel_work_and_set_vote(host, bw);
sdhci_msm_bus_get_and_set_vote(host, bw);
} else
sdhci_msm_bus_queue_work(host);
sdhci_msm_bus_get_and_set_vote(host, 0);
}
/*****************************************************************************\
@ -3067,6 +3102,450 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host,
msm_host->use_7nm_dll = true;
}
static void sdhci_msm_clkgate_bus_delayed_work(struct work_struct *work)
{
struct sdhci_msm_host *msm_host = container_of(work,
struct sdhci_msm_host, clk_gating_work.work);
struct sdhci_host *host = mmc_priv(msm_host->mmc);
sdhci_msm_registers_save(host);
clk_bulk_disable_unprepare(ARRAY_SIZE(msm_host->bulk_clks),
msm_host->bulk_clks);
sdhci_msm_bus_voting(host, false);
}
/* Find cpu group qos from a given cpu */
static struct qos_cpu_group *cpu_to_group(struct sdhci_msm_qos_req *r, int cpu)
{
int i;
struct qos_cpu_group *g = r->qcg;
if (cpu < 0 || cpu > num_possible_cpus())
return NULL;
for (i = 0; i < r->num_groups; i++, g++) {
if (cpumask_test_cpu(cpu, &g->mask))
return &r->qcg[i];
}
return NULL;
}
/*
* Function to put qos vote. This takes qos cpu group of
* host and type of vote as input
*/
static int sdhci_msm_update_qos_constraints(struct qos_cpu_group *qcg,
enum constraint type)
{
unsigned int vote;
int cpu, err;
struct dev_pm_qos_request *qos_req = qcg->qos_req;
if (type == QOS_MAX)
vote = S32_MAX;
else
vote = qcg->votes[type];
if (qcg->curr_vote == vote)
return 0;
for_each_cpu(cpu, &qcg->mask) {
err = dev_pm_qos_update_request(qos_req, vote);
if (err < 0)
return err;
++qos_req;
}
if (type == QOS_MAX)
qcg->voted = false;
else
qcg->voted = true;
qcg->curr_vote = vote;
return 0;
}
/* Unregister pm qos requests */
static int remove_group_qos(struct qos_cpu_group *qcg)
{
int err, cpu;
struct dev_pm_qos_request *qos_req = qcg->qos_req;
for_each_cpu(cpu, &qcg->mask) {
if (!dev_pm_qos_request_active(qos_req)) {
++qos_req;
continue;
}
err = dev_pm_qos_remove_request(qos_req);
if (err < 0)
return err;
qos_req++;
}
return 0;
}
/* Register pm qos request */
static int add_group_qos(struct qos_cpu_group *qcg, enum constraint type)
{
int cpu, err;
struct dev_pm_qos_request *qos_req = qcg->qos_req;
for_each_cpu(cpu, &qcg->mask) {
memset(qos_req, 0,
sizeof(struct dev_pm_qos_request));
err = dev_pm_qos_add_request(get_cpu_device(cpu),
qos_req,
DEV_PM_QOS_RESUME_LATENCY,
type);
if (err < 0)
return err;
qos_req++;
}
return 0;
}
/* Function to remove pm qos vote */
static void sdhci_msm_unvote_qos_all(struct work_struct *work)
{
struct sdhci_msm_host *msm_host = container_of(work,
struct sdhci_msm_host, pmqos_unvote_work.work);
struct sdhci_msm_qos_req *qos_req = msm_host->sdhci_qos;
struct qos_cpu_group *qcg;
int i, err;
if (!qos_req)
return;
qcg = qos_req->qcg;
for (i = 0; ((i < qos_req->num_groups) && qcg->initialized); i++,
qcg++) {
err = sdhci_msm_update_qos_constraints(qcg, QOS_MAX);
if (err)
dev_err(&msm_host->pdev->dev,
"Failed (%d) removing qos vote(%d)\n", err, i);
}
}
/* Function to vote pmqos from sdcc. */
static void sdhci_msm_vote_pmqos(struct mmc_host *mmc, int cpu)
{
struct sdhci_host *host = mmc_priv(mmc);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host);
struct qos_cpu_group *qcg;
qcg = cpu_to_group(msm_host->sdhci_qos, cpu);
if (!qcg) {
dev_dbg(&msm_host->pdev->dev, "QoS group is undefined\n");
return;
}
if (qcg->voted)
return;
if (sdhci_msm_update_qos_constraints(qcg, QOS_PERF))
dev_err(&qcg->host->pdev->dev, "%s: update qos - failed\n",
__func__);
dev_dbg(&msm_host->pdev->dev, "Voted pmqos - cpu: %d\n", cpu);
}
/**
* sdhci_msm_irq_affinity_notify - Callback for affinity changes
* @notify: context as to what irq was changed
* @mask: the new affinity mask
*
* This is a callback function used by the irq_set_affinity_notifier function
* so that we may register to receive changes to the irq affinity masks.
*/
static void
sdhci_msm_irq_affinity_notify(struct irq_affinity_notify *notify,
const cpumask_t *mask)
{
struct sdhci_msm_host *msm_host =
container_of(notify, struct sdhci_msm_host, affinity_notify);
struct platform_device *pdev = msm_host->pdev;
struct sdhci_msm_qos_req *qos_req = msm_host->sdhci_qos;
struct qos_cpu_group *qcg;
int i, err;
if (!qos_req)
return;
/*
* If device is in suspend mode, just update the active mask,
* vote would be taken care when device resumes.
*/
msm_host->sdhci_qos->active_mask = cpumask_first(mask);
if (pm_runtime_status_suspended(&pdev->dev))
return;
/* Cancel previous scheduled work and unvote votes */
qcg = qos_req->qcg;
for (i = 0; i < qos_req->num_groups; i++, qcg++) {
err = sdhci_msm_update_qos_constraints(qcg, QOS_MAX);
if (err)
pr_err("%s: Failed (%d) removing qos vote of grp(%d)\n",
mmc_hostname(msm_host->mmc), err, i);
}
cancel_delayed_work_sync(&msm_host->pmqos_unvote_work);
sdhci_msm_vote_pmqos(msm_host->mmc,
msm_host->sdhci_qos->active_mask);
}
/**
* sdhci_msm_irq_affinity_release - Callback for affinity notifier release
* @ref: internal core kernel usage
*
* This is a callback function used by the irq_set_affinity_notifier function
* to inform the current notification subscriber that they will no longer
* receive notifications.
*/
static void
inline sdhci_msm_irq_affinity_release(struct kref __always_unused *ref)
{ }
/* Function for settig up qos based on parsed dt entries */
static int sdhci_msm_setup_qos(struct sdhci_msm_host *msm_host)
{
struct platform_device *pdev = msm_host->pdev;
struct sdhci_msm_qos_req *qr = msm_host->sdhci_qos;
struct qos_cpu_group *qcg = qr->qcg;
struct mmc_host *mmc = msm_host->mmc;
struct sdhci_host *host = mmc_priv(mmc);
int i, err;
if (!msm_host->sdhci_qos)
return 0;
/* Affine irq to first set of mask */
WARN_ON(irq_set_affinity_hint(host->irq, &qcg->mask));
/* Setup notifier for case of affinity change/migration */
msm_host->affinity_notify.notify = sdhci_msm_irq_affinity_notify;
msm_host->affinity_notify.release = sdhci_msm_irq_affinity_release;
irq_set_affinity_notifier(host->irq, &msm_host->affinity_notify);
for (i = 0; i < qr->num_groups; i++, qcg++) {
qcg->qos_req = kcalloc(cpumask_weight(&qcg->mask),
sizeof(struct dev_pm_qos_request),
GFP_KERNEL);
if (!qcg->qos_req) {
dev_err(&pdev->dev, "Memory allocation failed\n");
if (!i)
return -ENOMEM;
goto free_mem;
}
err = add_group_qos(qcg, S32_MAX);
if (err < 0) {
dev_err(&pdev->dev, "Fail (%d) add qos-req: grp-%d\n",
err, i);
if (!i) {
kfree(qcg->qos_req);
return err;
}
goto free_mem;
}
qcg->initialized = true;
dev_dbg(&pdev->dev, "%s: qcg: 0x%08x | mask: 0x%08x\n",
__func__, qcg, qcg->mask);
}
INIT_DELAYED_WORK(&msm_host->pmqos_unvote_work,
sdhci_msm_unvote_qos_all);
/* Vote pmqos during setup for first set of mask*/
sdhci_msm_update_qos_constraints(qr->qcg, QOS_PERF);
qr->active_mask = cpumask_first(&qr->qcg->mask);
return 0;
free_mem:
while (i--) {
kfree(qcg->qos_req);
qcg--;
}
return err;
}
/*
* QoS init function. It parses dt entries and intializes data
* structures.
*/
static void sdhci_msm_qos_init(struct sdhci_msm_host *msm_host)
{
struct platform_device *pdev = msm_host->pdev;
struct device_node *np = pdev->dev.of_node;
struct device_node *group_node;
struct sdhci_msm_qos_req *qr;
struct qos_cpu_group *qcg;
int i, err, mask = 0;
qr = kzalloc(sizeof(*qr), GFP_KERNEL);
if (!qr)
return;
msm_host->sdhci_qos = qr;
/* find numbers of qos child node present */
qr->num_groups = of_get_available_child_count(np);
dev_dbg(&pdev->dev, "num-groups: %d\n", qr->num_groups);
if (!qr->num_groups) {
dev_err(&pdev->dev, "QoS groups undefined\n");
kfree(qr);
msm_host->sdhci_qos = NULL;
return;
}
qcg = kzalloc(sizeof(*qcg) * qr->num_groups, GFP_KERNEL);
if (!qcg) {
msm_host->sdhci_qos = NULL;
kfree(qr);
return;
}
/*
* Assign qos cpu group/cluster to host qos request and
* read child entries of qos node
*/
qr->qcg = qcg;
for_each_available_child_of_node(np, group_node) {
err = of_property_read_u32(group_node, "mask", &mask);
if (err) {
dev_dbg(&pdev->dev, "Error reading group mask: %d\n",
err);
continue;
}
qcg->mask.bits[0] = mask;
if (!cpumask_subset(&qcg->mask, cpu_possible_mask)) {
dev_err(&pdev->dev, "Invalid group mask\n");
goto out_vote_err;
}
err = of_property_count_u32_elems(group_node, "vote");
if (err <= 0) {
dev_err(&pdev->dev, "1 vote is needed, bailing out\n");
goto out_vote_err;
}
qcg->votes = kmalloc(sizeof(*qcg->votes) * err, GFP_KERNEL);
if (!qcg->votes)
goto out_vote_err;
for (i = 0; i < err; i++) {
if (of_property_read_u32_index(group_node, "vote", i,
&qcg->votes[i]))
goto out_vote_err;
}
qcg->host = msm_host;
++qcg;
}
err = sdhci_msm_setup_qos(msm_host);
if (!err)
return;
dev_err(&pdev->dev, "Failed to setup PM QoS.\n");
out_vote_err:
for (i = 0, qcg = qr->qcg; i < qr->num_groups; i++, qcg++)
kfree(qcg->votes);
kfree(qr->qcg);
kfree(qr);
msm_host->sdhci_qos = NULL;
}
static ssize_t show_sdhci_msm_clk_gating(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sdhci_host *host = dev_get_drvdata(dev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host);
return scnprintf(buf, PAGE_SIZE, "%u\n", msm_host->clk_gating_delay);
}
static ssize_t store_sdhci_msm_clk_gating(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct sdhci_host *host = dev_get_drvdata(dev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host);
uint32_t value;
if (!kstrtou32(buf, 0, &value)) {
msm_host->clk_gating_delay = value;
dev_info(dev, "set clk scaling work delay (%u)\n", value);
}
return count;
}
static ssize_t show_sdhci_msm_pm_qos(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sdhci_host *host = dev_get_drvdata(dev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host);
return scnprintf(buf, PAGE_SIZE, "%u\n", msm_host->pm_qos_delay);
}
static ssize_t store_sdhci_msm_pm_qos(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct sdhci_host *host = dev_get_drvdata(dev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host);
uint32_t value;
if (!kstrtou32(buf, 0, &value)) {
msm_host->pm_qos_delay = value;
dev_info(dev, "set pm qos work delay (%u)\n", value);
}
return count;
}
static void sdhci_msm_init_sysfs_gating_qos(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host);
int ret;
msm_host->clk_gating.show = show_sdhci_msm_clk_gating;
msm_host->clk_gating.store = store_sdhci_msm_clk_gating;
sysfs_attr_init(&msm_host->clk_gating.attr);
msm_host->clk_gating.attr.name = "clk_gating";
msm_host->clk_gating.attr.mode = 0644;
ret = device_create_file(dev, &msm_host->clk_gating);
if (ret) {
pr_err("%s: %s: failed creating clk gating attr: %d\n",
mmc_hostname(host->mmc), __func__, ret);
}
msm_host->pm_qos.show = show_sdhci_msm_pm_qos;
msm_host->pm_qos.store = store_sdhci_msm_pm_qos;
sysfs_attr_init(&msm_host->pm_qos.attr);
msm_host->pm_qos.attr.name = "pm_qos";
msm_host->pm_qos.attr.mode = 0644;
ret = device_create_file(dev, &msm_host->pm_qos);
if (ret) {
pr_err("%s: %s: failed creating pm qos attr: %d\n",
mmc_hostname(host->mmc), __func__, ret);
}
}
static void sdhci_msm_setup_pm(struct platform_device *pdev,
struct sdhci_msm_host *msm_host)
{
pm_runtime_get_noresume(&pdev->dev);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
if (!(msm_host->mmc->caps & MMC_CAP_SYNC_RUNTIME_PM)) {
pm_runtime_set_autosuspend_delay(&pdev->dev,
MSM_MMC_AUTOSUSPEND_DELAY_MS);
pm_runtime_use_autosuspend(&pdev->dev);
}
}
static int sdhci_msm_probe(struct platform_device *pdev)
{
struct sdhci_host *host;
@ -3103,6 +3582,13 @@ static int sdhci_msm_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "get slot index failed %d\n", ret);
else if (ret <= 2)
sdhci_slot[ret-1] = msm_host;
if (of_property_read_bool(dev->of_node, "non-removable") &&
strlen(android_boot_dev) &&
strcmp(android_boot_dev, dev_name(dev))) {
ret = -ENODEV;
goto pltfm_free;
}
}
/*
@ -3192,6 +3678,9 @@ static int sdhci_msm_probe(struct platform_device *pdev)
dev_warn(&pdev->dev, "TCXO clk not present (%d)\n", ret);
}
INIT_DELAYED_WORK(&msm_host->clk_gating_work,
sdhci_msm_clkgate_bus_delayed_work);
ret = sdhci_msm_bus_register(msm_host, pdev);
if (ret && !msm_host->skip_bus_bw_voting) {
dev_err(&pdev->dev, "Bus registration failed (%d)\n", ret);
@ -3298,20 +3787,27 @@ static int sdhci_msm_probe(struct platform_device *pdev)
goto vreg_deinit;
}
msm_host->mmc->caps |= MMC_CAP_AGGRESSIVE_PM;
msm_host->mmc->caps |= MMC_CAP_AGGRESSIVE_PM | MMC_CAP_SYNC_RUNTIME_PM;
msm_host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY | MMC_CAP_NEED_RSP_BUSY;
#if defined(CONFIG_SDC_QTI)
msm_host->mmc->caps2 |= MMC_CAP2_CLK_SCALE;
#endif
pm_runtime_get_noresume(&pdev->dev);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
pm_runtime_set_autosuspend_delay(&pdev->dev,
MSM_MMC_AUTOSUSPEND_DELAY_MS);
pm_runtime_use_autosuspend(&pdev->dev);
sdhci_msm_setup_pm(pdev, msm_host);
host->mmc_host_ops.execute_tuning = sdhci_msm_execute_tuning;
msm_host->workq = create_workqueue("sdhci_msm_generic_swq");
if (!msm_host->workq)
dev_err(&pdev->dev, "Generic swq creation failed\n");
msm_host->clk_gating_delay = MSM_CLK_GATING_DELAY_MS;
msm_host->pm_qos_delay = MSM_PMQOS_UNVOTING_DELAY_MS;
/* Initialize pmqos */
sdhci_msm_qos_init(msm_host);
/* Initialize sysfs entries */
sdhci_msm_init_sysfs_gating_qos(dev);
if (of_property_read_bool(node, "supports-cqe"))
ret = sdhci_msm_cqe_add_host(host, pdev);
else
@ -3347,7 +3843,7 @@ vreg_deinit:
sdhci_msm_vreg_init(&pdev->dev, msm_host, false);
bus_unregister:
if (!msm_host->skip_bus_bw_voting) {
sdhci_msm_bus_cancel_work_and_set_vote(host, 0);
sdhci_msm_bus_get_and_set_vote(host, 0);
sdhci_msm_bus_unregister(&pdev->dev, msm_host);
}
clk_disable:
@ -3366,6 +3862,9 @@ static int sdhci_msm_remove(struct platform_device *pdev)
struct sdhci_host *host = platform_get_drvdata(pdev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host);
struct sdhci_msm_qos_req *r = msm_host->sdhci_qos;
struct qos_cpu_group *qcg;
int i;
int dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) ==
0xffffffff);
@ -3374,6 +3873,19 @@ static int sdhci_msm_remove(struct platform_device *pdev)
sdhci_msm_vreg_init(&pdev->dev, msm_host, false);
pm_runtime_get_sync(&pdev->dev);
/* Add delay to complete resume where qos vote is scheduled */
if (!r)
goto skip_removing_qos;
qcg = r->qcg;
msleep(50);
for (i = 0; i < r->num_groups; i++, qcg++) {
sdhci_msm_update_qos_constraints(qcg, QOS_MAX);
remove_group_qos(qcg);
}
destroy_workqueue(msm_host->workq);
skip_removing_qos:
pm_runtime_disable(&pdev->dev);
pm_runtime_put_noidle(&pdev->dev);
@ -3382,7 +3894,7 @@ static int sdhci_msm_remove(struct platform_device *pdev)
if (!IS_ERR(msm_host->bus_clk))
clk_disable_unprepare(msm_host->bus_clk);
if (!msm_host->skip_bus_bw_voting) {
sdhci_msm_bus_cancel_work_and_set_vote(host, 0);
sdhci_msm_bus_get_and_set_vote(host, 0);
sdhci_msm_bus_unregister(&pdev->dev, msm_host);
}
sdhci_pltfm_free(pdev);
@ -3394,11 +3906,19 @@ static __maybe_unused int sdhci_msm_runtime_suspend(struct device *dev)
struct sdhci_host *host = dev_get_drvdata(dev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host);
struct sdhci_msm_qos_req *qos_req = msm_host->sdhci_qos;
if (!qos_req)
goto skip_qos;
queue_delayed_work(msm_host->workq,
&msm_host->pmqos_unvote_work,
msecs_to_jiffies(msm_host->pm_qos_delay));
skip_qos:
queue_delayed_work(msm_host->workq,
&msm_host->clk_gating_work,
msecs_to_jiffies(msm_host->clk_gating_delay));
sdhci_msm_registers_save(host);
clk_bulk_disable_unprepare(ARRAY_SIZE(msm_host->bulk_clks),
msm_host->bulk_clks);
sdhci_msm_bus_voting(host, false);
return 0;
}
@ -3407,22 +3927,36 @@ static __maybe_unused int sdhci_msm_runtime_resume(struct device *dev)
struct sdhci_host *host = dev_get_drvdata(dev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host);
struct sdhci_msm_qos_req *qos_req = msm_host->sdhci_qos;
int ret;
ret = clk_bulk_prepare_enable(ARRAY_SIZE(msm_host->bulk_clks),
msm_host->bulk_clks);
if (ret)
return ret;
ret = cancel_delayed_work_sync(&msm_host->clk_gating_work);
if (!ret) {
sdhci_msm_bus_voting(host, true);
ret = clk_bulk_prepare_enable(ARRAY_SIZE(msm_host->bulk_clks),
msm_host->bulk_clks);
if (ret) {
dev_err(dev, "Failed to enable clocks %d\n", ret);
sdhci_msm_bus_voting(host, false);
return ret;
}
sdhci_msm_registers_restore(host);
/*
* Whenever core-clock is gated dynamically, it's needed to
* restore the SDR DLL settings when the clock is ungated.
*/
if (msm_host->restore_dll_config && msm_host->clk_rate)
return sdhci_msm_restore_sdr_dll_config(host);
sdhci_msm_registers_restore(host);
/*
* Whenever core-clock is gated dynamically, it's needed to
* restore the SDR DLL settings when the clock is ungated.
*/
if (msm_host->restore_dll_config && msm_host->clk_rate)
sdhci_msm_restore_sdr_dll_config(host);
}
if (!qos_req)
return 0;
ret = cancel_delayed_work_sync(&msm_host->pmqos_unvote_work);
if (!ret)
sdhci_msm_vote_pmqos(msm_host->mmc,
msm_host->sdhci_qos->active_mask);
sdhci_msm_bus_voting(host, true);
return 0;
}