* [PATCH v3 0/2] clk: qcom: Add support for RCG to register for DFS @ 2018-07-16 5:37 Taniya Das 2018-07-16 5:37 ` [PATCH v3 1/2] " Taniya Das 2018-07-16 5:37 ` [PATCH v3 2/2] clk: qcom: gcc: Register QUPv3 RCGs for DFS on SDM845 Taniya Das 0 siblings, 2 replies; 4+ messages in thread From: Taniya Das @ 2018-07-16 5:37 UTC (permalink / raw) To: Stephen Boyd, Michael Turquette Cc: Andy Gross, David Brown, Rajendra Nayak, Amit Nischal, linux-arm-msm, linux-soc, linux-clk, linux-kernel, Taniya Das [v3] * Rename clk_rcg2_calculate_m_and_n with clk_rcg2_calculate_freq, as this function would now calculate the frequency. * Rename dfs_freq_tbl to freq_tbl. * Remove the logic to remove duplicate frequencies. * Remove recalc_rate & set_rate clock ops. * clk_rcg2_dfs_ops clock ops is static. * Override the clock ops only if DFS mode is enabled. * qcom_cc_register_rcg_dfs uses regmap instead of device. * Few cleanups : Remove DFS probing after registering clocks. sizeof(*init), sizeof(*freq_tbl). [v2] * Move the dfs register function 'qcom_cc_register_rcg_dfs' to clk-rcg2.c instead of common.c * At boot read the DFS enable register and override the clk_ops to be used for dfs or non-dfs RCGs. * Remove flag 'dfs_enabled'. * Remove functions 'clk_rcg2_dfs_determine_rate_lazy' * Remove 'struct dfs_table *dfs_entry' * Remove '_freq_tbl_determine_dfs_rate' * Combine the function 'clk_index_pre_div_and_mode' and 'calculate_m_and_n' to a single function and named it 'clk_rcg2_calculate_m_and_n'. * Remove taking M/N/PERF offsets as function arguments. * Add clocks in gcc-sdm845.c the DFS clock array to register. [v1] * Update SPDX for files. * Add new clk_ops for DFS mode which would be used if dfs is enabled, else fall back to the clk_rcg2_shared_ops. * Use kcalloc in place kzalloc. * Fixed the return type for 'clk_parent_index_pre_div_and_mode' which is now renamed to 'clk_index_pre_div_and_mode'. * Removed return of -EPERM from 'clk_rcg2_set_rate' and new dfs clk_ops is introduced. * Pass frequency table entry structure to function calculate_m_and_n. * Remove desc from qcom_cc_register_rcg_dfs and instead pass array of clk_rcg2. * Add a dfs_enable flag to identify if dfs mode is enabled. In the cases where a RCG requires a Dynamic Frequency switch support requires to register which would at runtime read the clock perf level registers to identify the frequencies supported and update the frequency table accordingly. Taniya Das (2): clk: qcom: Add support for RCG to register for DFS clk: qcom: gcc: Register QUPv3 RCGs for DFS on SDM845 drivers/clk/qcom/clk-rcg.h | 2 + drivers/clk/qcom/clk-rcg2.c | 173 ++++++++++++++++++++++++++++++++++++++++++ drivers/clk/qcom/gcc-sdm845.c | 25 ++++++ 3 files changed, 200 insertions(+) -- Qualcomm INDIA, on behalf of Qualcomm Innovation Center, Inc.is a member of the Code Aurora Forum, hosted by the Linux Foundation. ^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v3 1/2] clk: qcom: Add support for RCG to register for DFS 2018-07-16 5:37 [PATCH v3 0/2] clk: qcom: Add support for RCG to register for DFS Taniya Das @ 2018-07-16 5:37 ` Taniya Das 2018-07-26 20:11 ` Stephen Boyd 2018-07-16 5:37 ` [PATCH v3 2/2] clk: qcom: gcc: Register QUPv3 RCGs for DFS on SDM845 Taniya Das 1 sibling, 1 reply; 4+ messages in thread From: Taniya Das @ 2018-07-16 5:37 UTC (permalink / raw) To: Stephen Boyd, Michael Turquette Cc: Andy Gross, David Brown, Rajendra Nayak, Amit Nischal, linux-arm-msm, linux-soc, linux-clk, linux-kernel, Taniya Das Dynamic Frequency switch is a feature of clock controller by which request from peripherals allows automatic switching frequency of input clock without SW intervention. There are various performance levels associated with a root clock. When the input performance state changes, the source clocks and division ratios of the new performance state are loaded on to RCG via HW and the RCG switches to new clock frequency when the RCG is in DFS HW enabled mode. Register the root clock generators(RCG) to switch to use the dfs clock ops in the cases where DFS is enabled. The clk_round_rate() called by the clock consumer would invoke the dfs determine clock ops and would read the DFS performance level registers to identify all the frequencies supported and update the frequency table. The DFS clock consumers would maintain these frequency mapping and request the desired performance levels. Signed-off-by: Taniya Das <tdas@codeaurora.org> --- drivers/clk/qcom/clk-rcg.h | 2 + drivers/clk/qcom/clk-rcg2.c | 173 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+) diff --git a/drivers/clk/qcom/clk-rcg.h b/drivers/clk/qcom/clk-rcg.h index b209a2f..bffb625 100644 --- a/drivers/clk/qcom/clk-rcg.h +++ b/drivers/clk/qcom/clk-rcg.h @@ -161,4 +161,6 @@ struct clk_rcg2 { extern const struct clk_ops clk_gfx3d_ops; extern const struct clk_ops clk_rcg2_shared_ops; +extern int qcom_cc_register_rcg_dfs(struct regmap *regmap, + struct clk_rcg2 **rcgs, int num_clks); #endif diff --git a/drivers/clk/qcom/clk-rcg2.c b/drivers/clk/qcom/clk-rcg2.c index 52208d4..f8f1417 100644 --- a/drivers/clk/qcom/clk-rcg2.c +++ b/drivers/clk/qcom/clk-rcg2.c @@ -12,6 +12,7 @@ #include <linux/delay.h> #include <linux/regmap.h> #include <linux/math64.h> +#include <linux/slab.h> #include <asm/div64.h> @@ -40,6 +41,14 @@ #define N_REG 0xc #define D_REG 0x10 +/* Dynamic Frequency Scaling */ +#define MAX_PERF_LEVEL 8 +#define SE_CMD_DFSR_OFFSET 0x14 +#define SE_CMD_DFS_EN BIT(0) +#define SE_PERF_DFSR(level) (0x1c + 0x4 * (level)) +#define SE_PERF_M_DFSR(level) (0x5c + 0x4 * (level)) +#define SE_PERF_N_DFSR(level) (0x9c + 0x4 * (level)) + enum freq_policy { FLOOR, CEIL, @@ -929,3 +938,167 @@ static void clk_rcg2_shared_disable(struct clk_hw *hw) .set_rate_and_parent = clk_rcg2_shared_set_rate_and_parent, }; EXPORT_SYMBOL_GPL(clk_rcg2_shared_ops); + +/* Common APIs to be used for DFS based RCGR */ +static unsigned long clk_rcg2_calculate_freq(struct clk_hw *hw, + int level, struct freq_tbl *f) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + struct clk_hw *p; + unsigned long prate = 0; + u32 val, mask, cfg, m_off, n_off, offset, mode; + int i, ret, num_parents; + + offset = SE_PERF_DFSR(level); + ret = regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + offset, &cfg); + if (ret) + return ret; + + mask = BIT(rcg->hid_width) - 1; + f->pre_div = cfg & mask ? (cfg & mask) : 1; + + mode = cfg & CFG_MODE_MASK; + mode >>= CFG_MODE_SHIFT; + + cfg &= CFG_SRC_SEL_MASK; + cfg >>= CFG_SRC_SEL_SHIFT; + + num_parents = clk_hw_get_num_parents(hw); + for (i = 0; i < num_parents; i++) { + if (cfg == rcg->parent_map[i].cfg) { + f->src = rcg->parent_map[i].src; + p = clk_hw_get_parent_by_index(&rcg->clkr.hw, i); + prate = clk_hw_get_rate(p); + } + } + + if (!mode) + goto done; + + /* Calculate M & N values */ + m_off = SE_PERF_M_DFSR(level); + n_off = SE_PERF_N_DFSR(level); + + mask = BIT(rcg->mnd_width) - 1; + ret = regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + m_off, &val); + if (ret) { + pr_err("Failed to read M offset register\n"); + return ret; + } + val &= mask; + f->m = val; + + ret = regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + n_off, &val); + if (ret) { + pr_err("Failed to read N offset register\n"); + return ret; + } + /* val ~(N-M) */ + val = ~val; + val &= mask; + val += f->m; + f->n = val; +done: + return calc_rate(prate, f->m, f->n, mode, f->pre_div); +} + +static int clk_rcg2_dfs_populate_freq_table(struct clk_rcg2 *rcg) +{ + struct freq_tbl *freq_tbl; + unsigned long calc_freq; + int i; + + freq_tbl = kcalloc(MAX_PERF_LEVEL, sizeof(*freq_tbl), + GFP_KERNEL); + if (!freq_tbl) + return -ENOMEM; + + for (i = 0; i < MAX_PERF_LEVEL; i++) { + calc_freq = clk_rcg2_calculate_freq(&rcg->clkr.hw, + i, &freq_tbl[i]); + if (calc_freq < 0) { + kfree(freq_tbl); + return calc_freq; + } + + freq_tbl[i].freq = calc_freq; + } + rcg->freq_tbl = freq_tbl; + + return 0; +} + +static int clk_rcg2_dfs_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + int ret; + + if (!rcg->freq_tbl) { + ret = clk_rcg2_dfs_populate_freq_table(rcg); + if (ret) { + pr_err("Failed to update DFS tables for %s\n", + clk_hw_get_name(hw)); + return ret; + } + } + + return clk_rcg2_shared_ops.determine_rate(hw, req); +} + +static const struct clk_ops clk_rcg2_dfs_ops = { + .is_enabled = clk_rcg2_is_enabled, + .get_parent = clk_rcg2_get_parent, + .determine_rate = clk_rcg2_dfs_determine_rate, +}; + +static int clk_rcg2_enable_dfs(struct clk_rcg2 *rcg, struct regmap *regmap) +{ + struct clk_init_data *init; + u32 val; + int ret; + + ret = regmap_read(regmap, rcg->cmd_rcgr + SE_CMD_DFSR_OFFSET, + &val); + if (ret) + return -EINVAL; + + if (!(val & SE_CMD_DFS_EN)) + return 0; + + init = kzalloc(sizeof(*init), GFP_KERNEL); + if (!init) + return -ENOMEM; + + init->name = rcg->clkr.hw.init->name; + init->flags = rcg->clkr.hw.init->flags; + init->parent_names = rcg->clkr.hw.init->parent_names; + init->num_parents = rcg->clkr.hw.init->num_parents; + init->ops = &clk_rcg2_dfs_ops; + + rcg->clkr.hw.init = init; + rcg->freq_tbl = NULL; + + pr_debug("DFS registered for clk %s\n", init->name); + + return 0; +} + +int qcom_cc_register_rcg_dfs(struct regmap *regmap, + struct clk_rcg2 **rcgs, int num_clks) +{ + int i, ret = 0; + + for (i = 0; i < num_clks; i++) { + ret = clk_rcg2_enable_dfs(rcgs[i], regmap); + if (ret) { + const char *name = rcgs[i]->clkr.hw.init->name; + + pr_err("DFS register failed for clk %s\n", name); + break; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(qcom_cc_register_rcg_dfs); -- Qualcomm INDIA, on behalf of Qualcomm Innovation Center, Inc.is a member of the Code Aurora Forum, hosted by the Linux Foundation. ^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH v3 1/2] clk: qcom: Add support for RCG to register for DFS 2018-07-16 5:37 ` [PATCH v3 1/2] " Taniya Das @ 2018-07-26 20:11 ` Stephen Boyd 0 siblings, 0 replies; 4+ messages in thread From: Stephen Boyd @ 2018-07-26 20:11 UTC (permalink / raw) To: Michael Turquette, Taniya Das Cc: Andy Gross, David Brown, Rajendra Nayak, Amit Nischal, linux-arm-msm, linux-soc, linux-clk, linux-kernel, Taniya Das Quoting Taniya Das (2018-07-15 22:37:47) > diff --git a/drivers/clk/qcom/clk-rcg2.c b/drivers/clk/qcom/clk-rcg2.c > index 52208d4..f8f1417 100644 > --- a/drivers/clk/qcom/clk-rcg2.c > +++ b/drivers/clk/qcom/clk-rcg2.c > @@ -929,3 +938,167 @@ static void clk_rcg2_shared_disable(struct clk_hw *hw) > .set_rate_and_parent = clk_rcg2_shared_set_rate_and_parent, > }; > EXPORT_SYMBOL_GPL(clk_rcg2_shared_ops); > + > +/* Common APIs to be used for DFS based RCGR */ > +static unsigned long clk_rcg2_calculate_freq(struct clk_hw *hw, > + int level, struct freq_tbl *f) > +{ > + struct clk_rcg2 *rcg = to_clk_rcg2(hw); > + struct clk_hw *p; > + unsigned long prate = 0; > + u32 val, mask, cfg, m_off, n_off, offset, mode; > + int i, ret, num_parents; > + > + offset = SE_PERF_DFSR(level); > + ret = regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + offset, &cfg); > + if (ret) > + return ret; > + > + mask = BIT(rcg->hid_width) - 1; > + f->pre_div = cfg & mask ? (cfg & mask) : 1; > + > + mode = cfg & CFG_MODE_MASK; > + mode >>= CFG_MODE_SHIFT; > + > + cfg &= CFG_SRC_SEL_MASK; > + cfg >>= CFG_SRC_SEL_SHIFT; > + > + num_parents = clk_hw_get_num_parents(hw); > + for (i = 0; i < num_parents; i++) { > + if (cfg == rcg->parent_map[i].cfg) { > + f->src = rcg->parent_map[i].src; > + p = clk_hw_get_parent_by_index(&rcg->clkr.hw, i); > + prate = clk_hw_get_rate(p); > + } > + } > + > + if (!mode) > + goto done; Please remove the goto and indent all the below code when we need to do the mode check. > + > + /* Calculate M & N values */ > + m_off = SE_PERF_M_DFSR(level); > + n_off = SE_PERF_N_DFSR(level); > + > + mask = BIT(rcg->mnd_width) - 1; > + ret = regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + m_off, &val); > + if (ret) { > + pr_err("Failed to read M offset register\n"); > + return ret; > + } > + val &= mask; > + f->m = val; > + > + ret = regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + n_off, &val); > + if (ret) { > + pr_err("Failed to read N offset register\n"); > + return ret; > + } > + /* val ~(N-M) */ > + val = ~val; > + val &= mask; > + val += f->m; > + f->n = val; > +done: > + return calc_rate(prate, f->m, f->n, mode, f->pre_div); > +} > + > +static int clk_rcg2_dfs_populate_freq_table(struct clk_rcg2 *rcg) > +{ > + struct freq_tbl *freq_tbl; > + unsigned long calc_freq; > + int i; > + > + freq_tbl = kcalloc(MAX_PERF_LEVEL, sizeof(*freq_tbl), > + GFP_KERNEL); > + if (!freq_tbl) > + return -ENOMEM; > + > + for (i = 0; i < MAX_PERF_LEVEL; i++) { > + calc_freq = clk_rcg2_calculate_freq(&rcg->clkr.hw, > + i, &freq_tbl[i]); > + if (calc_freq < 0) { > + kfree(freq_tbl); > + return calc_freq; > + } > + > + freq_tbl[i].freq = calc_freq; > + } > + rcg->freq_tbl = freq_tbl; > + > + return 0; > +} > + > +static int clk_rcg2_dfs_determine_rate(struct clk_hw *hw, > + struct clk_rate_request *req) > +{ > + struct clk_rcg2 *rcg = to_clk_rcg2(hw); > + int ret; > + > + if (!rcg->freq_tbl) { > + ret = clk_rcg2_dfs_populate_freq_table(rcg); > + if (ret) { > + pr_err("Failed to update DFS tables for %s\n", > + clk_hw_get_name(hw)); > + return ret; > + } > + } > + > + return clk_rcg2_shared_ops.determine_rate(hw, req); > +} > + > +static const struct clk_ops clk_rcg2_dfs_ops = { > + .is_enabled = clk_rcg2_is_enabled, > + .get_parent = clk_rcg2_get_parent, > + .determine_rate = clk_rcg2_dfs_determine_rate, It seems very risky to do this without being able to read the frequency out of the hardware. Taking a peek at the DFS registers I see that there is some sort of 'CURR_PERF_STATE' field in the DFSR register. Wouldn't that correspond to the index in the table that the clk is currently using? Why can't we mark these clks as CLK_GET_RATE_NOCACHE and then read this field when we're using DFS? In fact, I just tested this on my device and I see that changing the DFS bit on the QUP register that controls the DFS used causes the DFSR register in GCC to update the CURR_PERF_STATE field to match what is chosen in the QUP. So there is definitely a way to implement recalc_rate() for this clk. Please do so. > +}; > + > +static int clk_rcg2_enable_dfs(struct clk_rcg2 *rcg, struct regmap *regmap) > +{ > + struct clk_init_data *init; > + u32 val; > + int ret; > + > + ret = regmap_read(regmap, rcg->cmd_rcgr + SE_CMD_DFSR_OFFSET, > + &val); > + if (ret) > + return -EINVAL; > + > + if (!(val & SE_CMD_DFS_EN)) > + return 0; > + > + init = kzalloc(sizeof(*init), GFP_KERNEL); > + if (!init) > + return -ENOMEM; > + > + init->name = rcg->clkr.hw.init->name; > + init->flags = rcg->clkr.hw.init->flags; > + init->parent_names = rcg->clkr.hw.init->parent_names; > + init->num_parents = rcg->clkr.hw.init->num_parents; > + init->ops = &clk_rcg2_dfs_ops; > + > + rcg->clkr.hw.init = init; > + rcg->freq_tbl = NULL; > + > + pr_debug("DFS registered for clk %s\n", init->name); Ok > + > + return 0; > +} > + > +int qcom_cc_register_rcg_dfs(struct regmap *regmap, > + struct clk_rcg2 **rcgs, int num_clks) > +{ > + int i, ret = 0; > + > + for (i = 0; i < num_clks; i++) { > + ret = clk_rcg2_enable_dfs(rcgs[i], regmap); > + if (ret) { > + const char *name = rcgs[i]->clkr.hw.init->name; > + > + pr_err("DFS register failed for clk %s\n", name); > + break; Just return ret please. > + } > + } > + > + return ret; And return 0 here. > +} > +EXPORT_SYMBOL_GPL(qcom_cc_register_rcg_dfs); ^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v3 2/2] clk: qcom: gcc: Register QUPv3 RCGs for DFS on SDM845 2018-07-16 5:37 [PATCH v3 0/2] clk: qcom: Add support for RCG to register for DFS Taniya Das 2018-07-16 5:37 ` [PATCH v3 1/2] " Taniya Das @ 2018-07-16 5:37 ` Taniya Das 1 sibling, 0 replies; 4+ messages in thread From: Taniya Das @ 2018-07-16 5:37 UTC (permalink / raw) To: Stephen Boyd, Michael Turquette Cc: Andy Gross, David Brown, Rajendra Nayak, Amit Nischal, linux-arm-msm, linux-soc, linux-clk, linux-kernel, Taniya Das QUPv3 clocks support DFS and thus register the RCGs which require support for the same. Signed-off-by: Taniya Das <tdas@codeaurora.org> --- drivers/clk/qcom/gcc-sdm845.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/drivers/clk/qcom/gcc-sdm845.c b/drivers/clk/qcom/gcc-sdm845.c index 0f694ed..305deca 100644 --- a/drivers/clk/qcom/gcc-sdm845.c +++ b/drivers/clk/qcom/gcc-sdm845.c @@ -3460,9 +3460,29 @@ enum { }; MODULE_DEVICE_TABLE(of, gcc_sdm845_match_table); +static struct clk_rcg2 *gcc_dfs_clocks[] = { + &gcc_qupv3_wrap0_s0_clk_src, + &gcc_qupv3_wrap0_s1_clk_src, + &gcc_qupv3_wrap0_s2_clk_src, + &gcc_qupv3_wrap0_s3_clk_src, + &gcc_qupv3_wrap0_s4_clk_src, + &gcc_qupv3_wrap0_s5_clk_src, + &gcc_qupv3_wrap0_s6_clk_src, + &gcc_qupv3_wrap0_s7_clk_src, + &gcc_qupv3_wrap1_s0_clk_src, + &gcc_qupv3_wrap1_s1_clk_src, + &gcc_qupv3_wrap1_s2_clk_src, + &gcc_qupv3_wrap1_s3_clk_src, + &gcc_qupv3_wrap1_s4_clk_src, + &gcc_qupv3_wrap1_s5_clk_src, + &gcc_qupv3_wrap1_s6_clk_src, + &gcc_qupv3_wrap1_s7_clk_src, +}; + static int gcc_sdm845_probe(struct platform_device *pdev) { struct regmap *regmap; + int ret; regmap = qcom_cc_map(pdev, &gcc_sdm845_desc); if (IS_ERR(regmap)) @@ -3472,6 +3492,11 @@ static int gcc_sdm845_probe(struct platform_device *pdev) regmap_update_bits(regmap, 0x09ffc, 0x3, 0x3); regmap_update_bits(regmap, 0x71028, 0x3, 0x3); + ret = qcom_cc_register_rcg_dfs(regmap, gcc_dfs_clocks, + ARRAY_SIZE(gcc_dfs_clocks)); + if (ret) + return ret; + return qcom_cc_really_probe(pdev, &gcc_sdm845_desc, regmap); } -- Qualcomm INDIA, on behalf of Qualcomm Innovation Center, Inc.is a member of the Code Aurora Forum, hosted by the Linux Foundation. ^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2018-07-26 20:11 UTC | newest] Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2018-07-16 5:37 [PATCH v3 0/2] clk: qcom: Add support for RCG to register for DFS Taniya Das 2018-07-16 5:37 ` [PATCH v3 1/2] " Taniya Das 2018-07-26 20:11 ` Stephen Boyd 2018-07-16 5:37 ` [PATCH v3 2/2] clk: qcom: gcc: Register QUPv3 RCGs for DFS on SDM845 Taniya Das
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).