summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorDong Aisheng <aisheng.dong@nxp.com>2017-12-07 20:23:53 +0800
committerLeonard Crestez <leonard.crestez@nxp.com>2018-08-24 12:41:33 +0300
commit7005580e0ae4ebbd7651d47ac070cd5361621e25 (patch)
tree01bff6b9170db87de0171161d190d142a8b11866 /drivers
parent1b6b4cbe55470009859207b54e232329cc5f8b7e (diff)
MLK-17124 imx8: pm-domains: fix clock rate may lost due to domain off during probe phase
With current design, there may be a clock state issue lost due to driver probe fail and power domain go to OFF. Then the next driver probe using the same domain and clocks may fail because the kernel already caches the last clk settings, the next retry will return directly. As a result, driver may believe the the clk setting is passed but actually no in HW. So a state mismatach happenes between SW and HW. This is actually a nature limitation with current design as there's no state alignment mechanism between clk SW status and HW status. Power Domain and CLK subsystem are two separate subsystems in current kernel design, re-architecure the kernel power domain and clk probably is the best way to handle this issue. However, this patch implements a quick workaround to trap the possible state lost case and give the driver one more chance to re-set the clk when power domain is enabled. This can tempororily fix this issue although may be not be so good from architecture point of view. One note is that as a parent clk rate restore will cause the clk recalc to all possible child clks which may result in child clk previous state lost due to power domain lost before, we have to first walk through all child clks to retrieve the state via clk_hw_get_rate which bypassed the clk recalc, then we can restore them one by one. Signed-off-by: Dong Aisheng <aisheng.dong@nxp.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/soc/imx/pm-domain-imx8.h4
-rw-r--r--drivers/soc/imx/pm-domains.c90
2 files changed, 76 insertions, 18 deletions
diff --git a/drivers/soc/imx/pm-domain-imx8.h b/drivers/soc/imx/pm-domain-imx8.h
index bbda879cf180..58b5328e47e5 100644
--- a/drivers/soc/imx/pm-domain-imx8.h
+++ b/drivers/soc/imx/pm-domain-imx8.h
@@ -39,6 +39,10 @@ struct imx8_pm_domain {
sc_rsrc_t rsrc_id;
bool runtime_idle_active;
struct list_head clks;
+
+ /* indicate the possible clk state lost */
+ bool clk_state_saved;
+ bool clk_state_may_lost;
};
static inline
diff --git a/drivers/soc/imx/pm-domains.c b/drivers/soc/imx/pm-domains.c
index d42602f4a967..d65c3de61740 100644
--- a/drivers/soc/imx/pm-domains.c
+++ b/drivers/soc/imx/pm-domains.c
@@ -52,6 +52,11 @@ enum imx_pd_state {
PD_OFF,
};
+struct clk_stat {
+ struct clk *clk;
+ unsigned long rate;
+};
+
static int imx8_pd_power(struct generic_pm_domain *domain, bool power_on)
{
struct imx8_pm_domain *pd;
@@ -92,26 +97,67 @@ static int imx8_pd_power_on(struct generic_pm_domain *domain)
ret = imx8_pd_power(domain, true);
if (!list_empty(&pd->clks) && (pd->pd.state_idx == PD_OFF)) {
- /*
- * The SS is powered on restore the clock rates that
- * may be lost.
- */
- list_for_each_entry(imx8_rsrc_clk, &pd->clks, node) {
- if (imx8_rsrc_clk->parent)
- clk_set_parent(imx8_rsrc_clk->clk,
- imx8_rsrc_clk->parent);
-
- if (imx8_rsrc_clk->rate) {
- /*
- * Need to read the clock so that rate in
- * Linux is reset.
- */
- clk_get_rate(imx8_rsrc_clk->clk);
- /* Restore the clock rate. */
- clk_set_rate(imx8_rsrc_clk->clk,
- imx8_rsrc_clk->rate);
+ if (pd->clk_state_saved) {
+ /*
+ * The SS is powered on restore the clock rates that
+ * may be lost.
+ */
+ list_for_each_entry(imx8_rsrc_clk, &pd->clks, node) {
+
+ if (imx8_rsrc_clk->parent)
+ clk_set_parent(imx8_rsrc_clk->clk,
+ imx8_rsrc_clk->parent);
+
+ if (imx8_rsrc_clk->rate) {
+ /*
+ * Need to read the clock so that rate in
+ * Linux is reset.
+ */
+ clk_get_rate(imx8_rsrc_clk->clk);
+ /* Restore the clock rate. */
+ clk_set_rate(imx8_rsrc_clk->clk,
+ imx8_rsrc_clk->rate);
+ }
+ }
+ } else if (pd->clk_state_may_lost) {
+ struct clk_stat *clk_stats;
+ int count = 0;
+ int i = 0;
+ /*
+ * The SS is powered down before without saving clk rates,
+ * try to restore the lost clock rates if any
+ *
+ * As a parent clk rate restore will cause the clk recalc
+ * to all possible child clks which may result in child clk
+ * previous state lost due to power domain lost before, we
+ * have to first walk through all child clks to retrieve the
+ * state via clk_hw_get_rate which bypassed the clk recalc,
+ * then we can restore them one by one.
+ */
+ list_for_each_entry(imx8_rsrc_clk, &pd->clks, node)
+ count++;
+
+ clk_stats = kzalloc(count * sizeof(*clk_stats), GFP_KERNEL);
+ if (!clk_stats) {
+ pr_warn("%s: failed to alloc mem for clk state recovery\n", pd->name);
+ return -ENOMEM;
}
+
+ list_for_each_entry(imx8_rsrc_clk, &pd->clks, node) {
+ clk_stats[i].clk = imx8_rsrc_clk->clk;
+ clk_stats[i].rate = clk_hw_get_rate(__clk_get_hw(imx8_rsrc_clk->clk));
+ i++;
+ }
+
+ for (i = 0; i <= count; i++) {
+ /* invalid cached rate first by get rate once */
+ clk_get_rate(clk_stats[i].clk);
+ /* restore the lost rate */
+ clk_set_rate(clk_stats[i].clk, clk_stats[i].rate);
+ }
+
+ kfree(clk_stats);
}
}
@@ -134,6 +180,14 @@ static int imx8_pd_power_off(struct generic_pm_domain *domain)
imx8_rsrc_clk->parent = clk_get_parent(imx8_rsrc_clk->clk);
imx8_rsrc_clk->rate = clk_hw_get_rate(__clk_get_hw(imx8_rsrc_clk->clk));
}
+ pd->clk_state_saved = true;
+ pd->clk_state_may_lost = false;
+ } else if (pd->pd.state_idx == PD_OFF) {
+ pd->clk_state_saved = false;
+ pd->clk_state_may_lost = true;
+ } else {
+ pd->clk_state_saved = false;
+ pd->clk_state_may_lost = false;
}
return imx8_pd_power(domain, false);
}