|
@@ -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,188 @@ const struct clk_ops clk_rcg2_shared_ops = {
|
|
|
.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 void clk_rcg2_dfs_populate_freq(struct clk_hw *hw, unsigned int l,
|
|
|
+ struct freq_tbl *f)
|
|
|
+{
|
|
|
+ struct clk_rcg2 *rcg = to_clk_rcg2(hw);
|
|
|
+ struct clk_hw *p;
|
|
|
+ unsigned long prate = 0;
|
|
|
+ u32 val, mask, cfg, mode;
|
|
|
+ int i, num_parents;
|
|
|
+
|
|
|
+ regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + SE_PERF_DFSR(l), &cfg);
|
|
|
+
|
|
|
+ mask = BIT(rcg->hid_width) - 1;
|
|
|
+ f->pre_div = 1;
|
|
|
+ if (cfg & mask)
|
|
|
+ f->pre_div = cfg & mask;
|
|
|
+
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ mode = cfg & CFG_MODE_MASK;
|
|
|
+ mode >>= CFG_MODE_SHIFT;
|
|
|
+ if (mode) {
|
|
|
+ mask = BIT(rcg->mnd_width) - 1;
|
|
|
+ regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + SE_PERF_M_DFSR(l),
|
|
|
+ &val);
|
|
|
+ val &= mask;
|
|
|
+ f->m = val;
|
|
|
+
|
|
|
+ regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + SE_PERF_N_DFSR(l),
|
|
|
+ &val);
|
|
|
+ val = ~val;
|
|
|
+ val &= mask;
|
|
|
+ val += f->m;
|
|
|
+ f->n = val;
|
|
|
+ }
|
|
|
+
|
|
|
+ f->freq = 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;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ freq_tbl = kcalloc(MAX_PERF_LEVEL, sizeof(*freq_tbl), GFP_KERNEL);
|
|
|
+ if (!freq_tbl)
|
|
|
+ return -ENOMEM;
|
|
|
+ rcg->freq_tbl = freq_tbl;
|
|
|
+
|
|
|
+ for (i = 0; i < MAX_PERF_LEVEL; i++)
|
|
|
+ clk_rcg2_dfs_populate_freq(&rcg->clkr.hw, i, freq_tbl + i);
|
|
|
+
|
|
|
+ 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_determine_rate(hw, req);
|
|
|
+}
|
|
|
+
|
|
|
+static unsigned long
|
|
|
+clk_rcg2_dfs_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
|
|
|
+{
|
|
|
+ struct clk_rcg2 *rcg = to_clk_rcg2(hw);
|
|
|
+ u32 level, mask, cfg, m = 0, n = 0, mode, pre_div;
|
|
|
+
|
|
|
+ regmap_read(rcg->clkr.regmap,
|
|
|
+ rcg->cmd_rcgr + SE_CMD_DFSR_OFFSET, &level);
|
|
|
+ level &= GENMASK(4, 1);
|
|
|
+ level >>= 1;
|
|
|
+
|
|
|
+ if (rcg->freq_tbl)
|
|
|
+ return rcg->freq_tbl[level].freq;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Assume that parent_rate is actually the parent because
|
|
|
+ * we can't do any better at figuring it out when the table
|
|
|
+ * hasn't been populated yet. We only populate the table
|
|
|
+ * in determine_rate because we can't guarantee the parents
|
|
|
+ * will be registered with the framework until then.
|
|
|
+ */
|
|
|
+ regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + SE_PERF_DFSR(level),
|
|
|
+ &cfg);
|
|
|
+
|
|
|
+ mask = BIT(rcg->hid_width) - 1;
|
|
|
+ pre_div = 1;
|
|
|
+ if (cfg & mask)
|
|
|
+ pre_div = cfg & mask;
|
|
|
+
|
|
|
+ mode = cfg & CFG_MODE_MASK;
|
|
|
+ mode >>= CFG_MODE_SHIFT;
|
|
|
+ if (mode) {
|
|
|
+ mask = BIT(rcg->mnd_width) - 1;
|
|
|
+ regmap_read(rcg->clkr.regmap,
|
|
|
+ rcg->cmd_rcgr + SE_PERF_M_DFSR(level), &m);
|
|
|
+ m &= mask;
|
|
|
+
|
|
|
+ regmap_read(rcg->clkr.regmap,
|
|
|
+ rcg->cmd_rcgr + SE_PERF_N_DFSR(level), &n);
|
|
|
+ n = ~n;
|
|
|
+ n &= mask;
|
|
|
+ n += m;
|
|
|
+ }
|
|
|
+
|
|
|
+ return calc_rate(parent_rate, m, n, mode, pre_div);
|
|
|
+}
|
|
|
+
|
|
|
+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,
|
|
|
+ .recalc_rate = clk_rcg2_dfs_recalc_rate,
|
|
|
+};
|
|
|
+
|
|
|
+static int clk_rcg2_enable_dfs(const struct clk_rcg_dfs_data *data,
|
|
|
+ struct regmap *regmap)
|
|
|
+{
|
|
|
+ struct clk_rcg2 *rcg = data->rcg;
|
|
|
+ struct clk_init_data *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;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Rate changes with consumer writing a register in
|
|
|
+ * their own I/O region
|
|
|
+ */
|
|
|
+ init->flags |= CLK_GET_RATE_NOCACHE;
|
|
|
+ init->ops = &clk_rcg2_dfs_ops;
|
|
|
+
|
|
|
+ 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,
|
|
|
+ const struct clk_rcg_dfs_data *rcgs, size_t len)
|
|
|
+{
|
|
|
+ int i, ret;
|
|
|
+
|
|
|
+ for (i = 0; i < len; i++) {
|
|
|
+ ret = clk_rcg2_enable_dfs(&rcgs[i], regmap);
|
|
|
+ if (ret) {
|
|
|
+ const char *name = rcgs[i].init->name;
|
|
|
+
|
|
|
+ pr_err("DFS register failed for clk %s\n", name);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(qcom_cc_register_rcg_dfs);
|