Loading drivers/clk/qcom/clk-rcg.h +11 −0 Original line number Diff line number Diff line Loading @@ -172,4 +172,15 @@ extern const struct clk_ops clk_gfx3d_ops; extern const struct clk_ops clk_rcg2_shared_ops; extern const struct clk_ops clk_dp_ops; struct clk_rcg_dfs_data { struct clk_rcg2 *rcg; struct clk_init_data *init; }; #define DEFINE_RCG_DFS(r) \ { .rcg = &r##_src, .init = &r##_init } extern int qcom_cc_register_rcg_dfs(struct regmap *regmap, const struct clk_rcg_dfs_data *rcgs, size_t len); #endif drivers/clk/qcom/clk-rcg2.c +200 −0 Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ #include <linux/rational.h> #include <linux/math64.h> #include <linux/clk.h> #include <linux/slab.h> #include <asm/div64.h> Loading Loading @@ -42,6 +43,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, Loading Loading @@ -1278,3 +1287,194 @@ const struct clk_ops clk_rcg2_shared_ops = { .list_registers = clk_rcg2_list_registers, }; EXPORT_SYMBOL_GPL(clk_rcg2_shared_ops); /* Common APIs to be used for DFS based RCGR */ static int 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); if (!p) return -EINVAL; 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); return 0; } static int clk_rcg2_dfs_populate_freq_table(struct clk_rcg2 *rcg) { struct freq_tbl *freq_tbl; int i, ret; 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++) { ret = clk_rcg2_dfs_populate_freq(&rcg->clkr.hw, i, freq_tbl + i); if (ret) return ret; } 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); Loading
drivers/clk/qcom/clk-rcg.h +11 −0 Original line number Diff line number Diff line Loading @@ -172,4 +172,15 @@ extern const struct clk_ops clk_gfx3d_ops; extern const struct clk_ops clk_rcg2_shared_ops; extern const struct clk_ops clk_dp_ops; struct clk_rcg_dfs_data { struct clk_rcg2 *rcg; struct clk_init_data *init; }; #define DEFINE_RCG_DFS(r) \ { .rcg = &r##_src, .init = &r##_init } extern int qcom_cc_register_rcg_dfs(struct regmap *regmap, const struct clk_rcg_dfs_data *rcgs, size_t len); #endif
drivers/clk/qcom/clk-rcg2.c +200 −0 Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ #include <linux/rational.h> #include <linux/math64.h> #include <linux/clk.h> #include <linux/slab.h> #include <asm/div64.h> Loading Loading @@ -42,6 +43,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, Loading Loading @@ -1278,3 +1287,194 @@ const struct clk_ops clk_rcg2_shared_ops = { .list_registers = clk_rcg2_list_registers, }; EXPORT_SYMBOL_GPL(clk_rcg2_shared_ops); /* Common APIs to be used for DFS based RCGR */ static int 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); if (!p) return -EINVAL; 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); return 0; } static int clk_rcg2_dfs_populate_freq_table(struct clk_rcg2 *rcg) { struct freq_tbl *freq_tbl; int i, ret; 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++) { ret = clk_rcg2_dfs_populate_freq(&rcg->clkr.hw, i, freq_tbl + i); if (ret) return ret; } 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);