Changelog: https://cdn.kernel.org/pub/linux/kernel/v5.x/ChangeLog-5.15.139
Removed upstreamed:
x86/patches-5.15/120-hwrng-geode-fix-accessing-registers.patch[3]
All other patches automatically rebased.
3. https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?h=v5.15.139&id=a5c83c8043d70b9a28d1bd78a2dbbab340f43889
Build system: x86_64
Build-tested: ramips/tplink_archer-a6-v3
Run-tested: ramips/tplink_archer-a6-v3
Signed-off-by: John Audia <therealgraysky@proton.me>
[Refresh on top of OpenWrt 23.05]
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
(cherry picked from commit df167450a5)
289 lines
9.1 KiB
Diff
289 lines
9.1 KiB
Diff
From e8e7ce92a49dc87f0d006cfbfe419b8e0b25476d Mon Sep 17 00:00:00 2001
|
|
From: Bjorn Andersson <bjorn.andersson@linaro.org>
|
|
Date: Tue, 26 Apr 2022 14:21:36 -0700
|
|
Subject: [PATCH] clk: qcom: rcg2: Cache CFG register updates for parked RCGs
|
|
|
|
As GDSCs are turned on and off some associated clocks are momentarily
|
|
enabled for house keeping purposes. For this, and similar, purposes the
|
|
"shared RCGs" will park the RCG on a source clock which is known to be
|
|
available.
|
|
When the RCG is parked, a safe clock source will be selected and
|
|
committed, then the original source would be written back and upon enable
|
|
the change back to the unparked source would be committed.
|
|
|
|
But starting with SM8350 this fails, as the value in CFG is committed by
|
|
the GDSC handshake and without a ticking parent the GDSC enablement will
|
|
time out.
|
|
|
|
This becomes a concrete problem if the runtime supended state of a
|
|
device includes disabling such rcg's parent clock. As the device
|
|
attempts to power up the domain again the rcg will fail to enable and
|
|
hence the GDSC enablement will fail, preventing the device from
|
|
returning from the suspended state.
|
|
|
|
This can be seen in e.g. the display stack during probe on SM8350.
|
|
|
|
To avoid this problem, the software needs to ensure that the RCG is
|
|
configured to a active parent clock while it is disabled. This is done
|
|
by caching the CFG register content while the shared RCG is parked on
|
|
this safe source.
|
|
|
|
Writes to M, N and D registers are committed as they are requested. New
|
|
helpers for get_parent() and recalc_rate() are extracted from their
|
|
previous implementations and __clk_rcg2_configure() is modified to allow
|
|
it to operate on the cached value.
|
|
|
|
Fixes: 7ef6f11887bd ("clk: qcom: Configure the RCGs to a safe source as needed")
|
|
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
|
|
Reviewed-by: Stephen Boyd <sboyd@kernel.org>
|
|
Link: https://lore.kernel.org/r/20220426212136.1543984-1-bjorn.andersson@linaro.org
|
|
---
|
|
drivers/clk/qcom/clk-rcg.h | 2 +
|
|
drivers/clk/qcom/clk-rcg2.c | 126 ++++++++++++++++++++++++++++--------
|
|
2 files changed, 101 insertions(+), 27 deletions(-)
|
|
|
|
--- a/drivers/clk/qcom/clk-rcg.h
|
|
+++ b/drivers/clk/qcom/clk-rcg.h
|
|
@@ -139,6 +139,7 @@ extern const struct clk_ops clk_dyn_rcg_
|
|
* @freq_tbl: frequency table
|
|
* @clkr: regmap clock handle
|
|
* @cfg_off: defines the cfg register offset from the CMD_RCGR + CFG_REG
|
|
+ * @parked_cfg: cached value of the CFG register for parked RCGs
|
|
*/
|
|
struct clk_rcg2 {
|
|
u32 cmd_rcgr;
|
|
@@ -149,6 +150,7 @@ struct clk_rcg2 {
|
|
const struct freq_tbl *freq_tbl;
|
|
struct clk_regmap clkr;
|
|
u8 cfg_off;
|
|
+ u32 parked_cfg;
|
|
};
|
|
|
|
#define to_clk_rcg2(_hw) container_of(to_clk_regmap(_hw), struct clk_rcg2, clkr)
|
|
--- a/drivers/clk/qcom/clk-rcg2.c
|
|
+++ b/drivers/clk/qcom/clk-rcg2.c
|
|
@@ -74,16 +74,11 @@ static int clk_rcg2_is_enabled(struct cl
|
|
return (cmd & CMD_ROOT_OFF) == 0;
|
|
}
|
|
|
|
-static u8 clk_rcg2_get_parent(struct clk_hw *hw)
|
|
+static u8 __clk_rcg2_get_parent(struct clk_hw *hw, u32 cfg)
|
|
{
|
|
struct clk_rcg2 *rcg = to_clk_rcg2(hw);
|
|
int num_parents = clk_hw_get_num_parents(hw);
|
|
- u32 cfg;
|
|
- int i, ret;
|
|
-
|
|
- ret = regmap_read(rcg->clkr.regmap, RCG_CFG_OFFSET(rcg), &cfg);
|
|
- if (ret)
|
|
- goto err;
|
|
+ int i;
|
|
|
|
cfg &= CFG_SRC_SEL_MASK;
|
|
cfg >>= CFG_SRC_SEL_SHIFT;
|
|
@@ -92,12 +87,27 @@ static u8 clk_rcg2_get_parent(struct clk
|
|
if (cfg == rcg->parent_map[i].cfg)
|
|
return i;
|
|
|
|
-err:
|
|
pr_debug("%s: Clock %s has invalid parent, using default.\n",
|
|
__func__, clk_hw_get_name(hw));
|
|
return 0;
|
|
}
|
|
|
|
+static u8 clk_rcg2_get_parent(struct clk_hw *hw)
|
|
+{
|
|
+ struct clk_rcg2 *rcg = to_clk_rcg2(hw);
|
|
+ u32 cfg;
|
|
+ int ret;
|
|
+
|
|
+ ret = regmap_read(rcg->clkr.regmap, RCG_CFG_OFFSET(rcg), &cfg);
|
|
+ if (ret) {
|
|
+ pr_debug("%s: Unable to read CFG register for %s\n",
|
|
+ __func__, clk_hw_get_name(hw));
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ return __clk_rcg2_get_parent(hw, cfg);
|
|
+}
|
|
+
|
|
static int update_config(struct clk_rcg2 *rcg)
|
|
{
|
|
int count, ret;
|
|
@@ -158,12 +168,10 @@ calc_rate(unsigned long rate, u32 m, u32
|
|
}
|
|
|
|
static unsigned long
|
|
-clk_rcg2_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
|
|
+__clk_rcg2_recalc_rate(struct clk_hw *hw, unsigned long parent_rate, u32 cfg)
|
|
{
|
|
struct clk_rcg2 *rcg = to_clk_rcg2(hw);
|
|
- u32 cfg, hid_div, m = 0, n = 0, mode = 0, mask;
|
|
-
|
|
- regmap_read(rcg->clkr.regmap, RCG_CFG_OFFSET(rcg), &cfg);
|
|
+ u32 hid_div, m = 0, n = 0, mode = 0, mask;
|
|
|
|
if (rcg->mnd_width) {
|
|
mask = BIT(rcg->mnd_width) - 1;
|
|
@@ -184,6 +192,17 @@ clk_rcg2_recalc_rate(struct clk_hw *hw,
|
|
return calc_rate(parent_rate, m, n, mode, hid_div);
|
|
}
|
|
|
|
+static unsigned long
|
|
+clk_rcg2_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
|
|
+{
|
|
+ struct clk_rcg2 *rcg = to_clk_rcg2(hw);
|
|
+ u32 cfg;
|
|
+
|
|
+ regmap_read(rcg->clkr.regmap, RCG_CFG_OFFSET(rcg), &cfg);
|
|
+
|
|
+ return __clk_rcg2_recalc_rate(hw, parent_rate, cfg);
|
|
+}
|
|
+
|
|
static int _freq_tbl_determine_rate(struct clk_hw *hw, const struct freq_tbl *f,
|
|
struct clk_rate_request *req,
|
|
enum freq_policy policy)
|
|
@@ -257,7 +276,8 @@ static int clk_rcg2_determine_floor_rate
|
|
return _freq_tbl_determine_rate(hw, rcg->freq_tbl, req, FLOOR);
|
|
}
|
|
|
|
-static int __clk_rcg2_configure(struct clk_rcg2 *rcg, const struct freq_tbl *f)
|
|
+static int __clk_rcg2_configure(struct clk_rcg2 *rcg, const struct freq_tbl *f,
|
|
+ u32 *_cfg)
|
|
{
|
|
u32 cfg, mask, d_val, not2d_val, n_minus_m;
|
|
struct clk_hw *hw = &rcg->clkr.hw;
|
|
@@ -299,15 +319,27 @@ static int __clk_rcg2_configure(struct c
|
|
cfg |= rcg->parent_map[index].cfg << CFG_SRC_SEL_SHIFT;
|
|
if (rcg->mnd_width && f->n && (f->m != f->n))
|
|
cfg |= CFG_MODE_DUAL_EDGE;
|
|
- return regmap_update_bits(rcg->clkr.regmap, RCG_CFG_OFFSET(rcg),
|
|
- mask, cfg);
|
|
+
|
|
+ *_cfg &= ~mask;
|
|
+ *_cfg |= cfg;
|
|
+
|
|
+ return 0;
|
|
}
|
|
|
|
static int clk_rcg2_configure(struct clk_rcg2 *rcg, const struct freq_tbl *f)
|
|
{
|
|
+ u32 cfg;
|
|
int ret;
|
|
|
|
- ret = __clk_rcg2_configure(rcg, f);
|
|
+ ret = regmap_read(rcg->clkr.regmap, RCG_CFG_OFFSET(rcg), &cfg);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = __clk_rcg2_configure(rcg, f, &cfg);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = regmap_write(rcg->clkr.regmap, RCG_CFG_OFFSET(rcg), cfg);
|
|
if (ret)
|
|
return ret;
|
|
|
|
@@ -988,11 +1020,12 @@ static int clk_rcg2_shared_set_rate(stru
|
|
return -EINVAL;
|
|
|
|
/*
|
|
- * In case clock is disabled, update the CFG, M, N and D registers
|
|
- * and don't hit the update bit of CMD register.
|
|
+ * In case clock is disabled, update the M, N and D registers, cache
|
|
+ * the CFG value in parked_cfg and don't hit the update bit of CMD
|
|
+ * register.
|
|
*/
|
|
- if (!__clk_is_enabled(hw->clk))
|
|
- return __clk_rcg2_configure(rcg, f);
|
|
+ if (!clk_hw_is_enabled(hw))
|
|
+ return __clk_rcg2_configure(rcg, f, &rcg->parked_cfg);
|
|
|
|
return clk_rcg2_shared_force_enable_clear(hw, f);
|
|
}
|
|
@@ -1016,6 +1049,11 @@ static int clk_rcg2_shared_enable(struct
|
|
if (ret)
|
|
return ret;
|
|
|
|
+ /* Write back the stored configuration corresponding to current rate */
|
|
+ ret = regmap_write(rcg->clkr.regmap, rcg->cmd_rcgr + CFG_REG, rcg->parked_cfg);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
ret = update_config(rcg);
|
|
if (ret)
|
|
return ret;
|
|
@@ -1026,13 +1064,12 @@ static int clk_rcg2_shared_enable(struct
|
|
static void clk_rcg2_shared_disable(struct clk_hw *hw)
|
|
{
|
|
struct clk_rcg2 *rcg = to_clk_rcg2(hw);
|
|
- u32 cfg;
|
|
|
|
/*
|
|
* Store current configuration as switching to safe source would clear
|
|
* the SRC and DIV of CFG register
|
|
*/
|
|
- regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + CFG_REG, &cfg);
|
|
+ regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + CFG_REG, &rcg->parked_cfg);
|
|
|
|
/*
|
|
* Park the RCG at a safe configuration - sourced off of safe source.
|
|
@@ -1050,17 +1087,52 @@ static void clk_rcg2_shared_disable(stru
|
|
update_config(rcg);
|
|
|
|
clk_rcg2_clear_force_enable(hw);
|
|
+}
|
|
|
|
- /* Write back the stored configuration corresponding to current rate */
|
|
- regmap_write(rcg->clkr.regmap, rcg->cmd_rcgr + CFG_REG, cfg);
|
|
+static u8 clk_rcg2_shared_get_parent(struct clk_hw *hw)
|
|
+{
|
|
+ struct clk_rcg2 *rcg = to_clk_rcg2(hw);
|
|
+
|
|
+ /* If the shared rcg is parked use the cached cfg instead */
|
|
+ if (!clk_hw_is_enabled(hw))
|
|
+ return __clk_rcg2_get_parent(hw, rcg->parked_cfg);
|
|
+
|
|
+ return clk_rcg2_get_parent(hw);
|
|
+}
|
|
+
|
|
+static int clk_rcg2_shared_set_parent(struct clk_hw *hw, u8 index)
|
|
+{
|
|
+ struct clk_rcg2 *rcg = to_clk_rcg2(hw);
|
|
+
|
|
+ /* If the shared rcg is parked only update the cached cfg */
|
|
+ if (!clk_hw_is_enabled(hw)) {
|
|
+ rcg->parked_cfg &= ~CFG_SRC_SEL_MASK;
|
|
+ rcg->parked_cfg |= rcg->parent_map[index].cfg << CFG_SRC_SEL_SHIFT;
|
|
+
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ return clk_rcg2_set_parent(hw, index);
|
|
+}
|
|
+
|
|
+static unsigned long
|
|
+clk_rcg2_shared_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
|
|
+{
|
|
+ struct clk_rcg2 *rcg = to_clk_rcg2(hw);
|
|
+
|
|
+ /* If the shared rcg is parked use the cached cfg instead */
|
|
+ if (!clk_hw_is_enabled(hw))
|
|
+ return __clk_rcg2_recalc_rate(hw, parent_rate, rcg->parked_cfg);
|
|
+
|
|
+ return clk_rcg2_recalc_rate(hw, parent_rate);
|
|
}
|
|
|
|
const struct clk_ops clk_rcg2_shared_ops = {
|
|
.enable = clk_rcg2_shared_enable,
|
|
.disable = clk_rcg2_shared_disable,
|
|
- .get_parent = clk_rcg2_get_parent,
|
|
- .set_parent = clk_rcg2_set_parent,
|
|
- .recalc_rate = clk_rcg2_recalc_rate,
|
|
+ .get_parent = clk_rcg2_shared_get_parent,
|
|
+ .set_parent = clk_rcg2_shared_set_parent,
|
|
+ .recalc_rate = clk_rcg2_shared_recalc_rate,
|
|
.determine_rate = clk_rcg2_determine_rate,
|
|
.set_rate = clk_rcg2_shared_set_rate,
|
|
.set_rate_and_parent = clk_rcg2_shared_set_rate_and_parent,
|