diff options
Diffstat (limited to 'drivers/clk/qcom/clk-rcg2.c')
| -rw-r--r-- | drivers/clk/qcom/clk-rcg2.c | 662 |
1 files changed, 605 insertions, 57 deletions
diff --git a/drivers/clk/qcom/clk-rcg2.c b/drivers/clk/qcom/clk-rcg2.c index e1b1b426fae4..e18cb8807d73 100644 --- a/drivers/clk/qcom/clk-rcg2.c +++ b/drivers/clk/qcom/clk-rcg2.c @@ -8,11 +8,14 @@ #include <linux/err.h> #include <linux/bug.h> #include <linux/export.h> +#include <linux/clk.h> #include <linux/clk-provider.h> #include <linux/delay.h> #include <linux/rational.h> #include <linux/regmap.h> #include <linux/math64.h> +#include <linux/gcd.h> +#include <linux/minmax.h> #include <linux/slab.h> #include <asm/div64.h> @@ -31,6 +34,7 @@ #define CFG_REG 0x4 #define CFG_SRC_DIV_SHIFT 0 +#define CFG_SRC_DIV_LENGTH 8 #define CFG_SRC_SEL_SHIFT 8 #define CFG_SRC_SEL_MASK (0x7 << CFG_SRC_SEL_SHIFT) #define CFG_MODE_SHIFT 12 @@ -73,16 +77,11 @@ static int clk_rcg2_is_enabled(struct clk_hw *hw) 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; @@ -91,12 +90,27 @@ static u8 clk_rcg2_get_parent(struct clk_hw *hw) 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; @@ -137,45 +151,57 @@ static int clk_rcg2_set_parent(struct clk_hw *hw, u8 index) return update_config(rcg); } -/* - * Calculate m/n:d rate +/** + * convert_to_reg_val() - Convert divisor values to hardware values. + * + * @f: Frequency table with pure m/n/pre_div parameters. + */ +static void convert_to_reg_val(struct freq_tbl *f) +{ + f->pre_div *= 2; + f->pre_div -= 1; +} + +/** + * calc_rate() - Calculate rate based on m/n:d values + * + * @rate: Parent rate. + * @m: Multiplier. + * @n: Divisor. + * @mode: Use zero to ignore m/n calculation. + * @hid_div: Pre divisor register value. Pre divisor value + * relates to hid_div as pre_div = (hid_div + 1) / 2. + * + * Return calculated rate according to formula: * * parent_rate m * rate = ----------- x --- - * hid_div n + * pre_div n */ static unsigned long calc_rate(unsigned long rate, u32 m, u32 n, u32 mode, u32 hid_div) { - if (hid_div) { - rate *= 2; - rate /= hid_div + 1; - } + if (hid_div) + rate = mult_frac(rate, 2, hid_div + 1); - if (mode) { - u64 tmp = rate; - tmp *= m; - do_div(tmp, n); - rate = tmp; - } + if (mode) + rate = mult_frac(rate, m, n); return rate; } 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; regmap_read(rcg->clkr.regmap, RCG_M_OFFSET(rcg), &m); m &= mask; regmap_read(rcg->clkr.regmap, RCG_N_OFFSET(rcg), &n); - n = ~n; + n = ~n; n &= mask; n += m; mode = cfg & CFG_MODE_MASK; @@ -189,6 +215,17 @@ clk_rcg2_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) 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) @@ -237,7 +274,7 @@ static int _freq_tbl_determine_rate(struct clk_hw *hw, const struct freq_tbl *f, rate = tmp; } } else { - rate = clk_hw_get_rate(p); + rate = clk_hw_get_rate(p); } req->best_parent_hw = p; req->best_parent_rate = rate; @@ -246,6 +283,115 @@ static int _freq_tbl_determine_rate(struct clk_hw *hw, const struct freq_tbl *f, return 0; } +static const struct freq_conf * +__clk_rcg2_select_conf(struct clk_hw *hw, const struct freq_multi_tbl *f, + unsigned long req_rate) +{ + unsigned long rate_diff, best_rate_diff = ULONG_MAX; + const struct freq_conf *conf, *best_conf = NULL; + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + const char *name = clk_hw_get_name(hw); + unsigned long parent_rate, rate; + struct clk_hw *p; + int index, i; + + /* Exit early if only one config is defined */ + if (f->num_confs == 1) { + best_conf = f->confs; + goto exit; + } + + /* Search in each provided config the one that is near the wanted rate */ + for (i = 0, conf = f->confs; i < f->num_confs; i++, conf++) { + index = qcom_find_src_index(hw, rcg->parent_map, conf->src); + if (index < 0) + continue; + + p = clk_hw_get_parent_by_index(hw, index); + if (!p) + continue; + + parent_rate = clk_hw_get_rate(p); + rate = calc_rate(parent_rate, conf->n, conf->m, conf->n, conf->pre_div); + + if (rate == req_rate) { + best_conf = conf; + goto exit; + } + + rate_diff = abs_diff(req_rate, rate); + if (rate_diff < best_rate_diff) { + best_rate_diff = rate_diff; + best_conf = conf; + } + } + + /* + * Very unlikely. Warn if we couldn't find a correct config + * due to parent not found in every config. + */ + if (unlikely(!best_conf)) { + WARN(1, "%s: can't find a configuration for rate %lu\n", + name, req_rate); + return ERR_PTR(-EINVAL); + } + +exit: + return best_conf; +} + +static int _freq_tbl_fm_determine_rate(struct clk_hw *hw, const struct freq_multi_tbl *f, + struct clk_rate_request *req) +{ + unsigned long clk_flags, rate = req->rate; + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + const struct freq_conf *conf; + struct clk_hw *p; + int index; + + f = qcom_find_freq_multi(f, rate); + if (!f || !f->confs) + return -EINVAL; + + conf = __clk_rcg2_select_conf(hw, f, rate); + if (IS_ERR(conf)) + return PTR_ERR(conf); + index = qcom_find_src_index(hw, rcg->parent_map, conf->src); + if (index < 0) + return index; + + clk_flags = clk_hw_get_flags(hw); + p = clk_hw_get_parent_by_index(hw, index); + if (!p) + return -EINVAL; + + if (clk_flags & CLK_SET_RATE_PARENT) { + rate = f->freq; + if (conf->pre_div) { + if (!rate) + rate = req->rate; + rate /= 2; + rate *= conf->pre_div + 1; + } + + if (conf->n) { + u64 tmp = rate; + + tmp = tmp * conf->n; + do_div(tmp, conf->m); + rate = tmp; + } + } else { + rate = clk_hw_get_rate(p); + } + + req->best_parent_hw = p; + req->best_parent_rate = rate; + req->rate = f->freq; + + return 0; +} + static int clk_rcg2_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { @@ -262,15 +408,118 @@ static int clk_rcg2_determine_floor_rate(struct clk_hw *hw, 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_fm_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + + return _freq_tbl_fm_determine_rate(hw, rcg->freq_multi_tbl, req); +} + +/** + * clk_rcg2_split_div() - Split multiplier that doesn't fit in n neither in pre_div. + * + * @multiplier: Multiplier to split between n and pre_div. + * @pre_div: Pointer to pre divisor value. + * @n: Pointer to n divisor value. + * @pre_div_max: Pre divisor maximum value. + */ +static inline void clk_rcg2_split_div(int multiplier, unsigned int *pre_div, + u16 *n, unsigned int pre_div_max) +{ + *n = mult_frac(multiplier * *n, *pre_div, pre_div_max); + *pre_div = pre_div_max; +} + +static void clk_rcg2_calc_mnd(u64 parent_rate, u64 rate, struct freq_tbl *f, + unsigned int mnd_max, unsigned int pre_div_max) +{ + int i = 2; + unsigned int pre_div = 1; + unsigned long rates_gcd, scaled_parent_rate; + u16 m, n = 1, n_candidate = 1, n_max; + + rates_gcd = gcd(parent_rate, rate); + m = div64_u64(rate, rates_gcd); + scaled_parent_rate = div64_u64(parent_rate, rates_gcd); + while (scaled_parent_rate > (mnd_max + m) * pre_div_max) { + // we're exceeding divisor's range, trying lower scale. + if (m > 1) { + m--; + scaled_parent_rate = mult_frac(scaled_parent_rate, m, (m + 1)); + } else { + // cannot lower scale, just set max divisor values. + f->n = mnd_max + m; + f->pre_div = pre_div_max; + f->m = m; + return; + } + } + + n_max = m + mnd_max; + + while (scaled_parent_rate > 1) { + while (scaled_parent_rate % i == 0) { + n_candidate *= i; + if (n_candidate < n_max) + n = n_candidate; + else if (pre_div * i < pre_div_max) + pre_div *= i; + else + clk_rcg2_split_div(i, &pre_div, &n, pre_div_max); + + scaled_parent_rate /= i; + } + i++; + } + + f->m = m; + f->n = n; + f->pre_div = pre_div > 1 ? pre_div : 0; +} + +static int clk_rcg2_determine_gp_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + struct freq_tbl f_tbl = {}, *f = &f_tbl; + int mnd_max = BIT(rcg->mnd_width) - 1; + int hid_max = BIT(rcg->hid_width) - 1; + struct clk_hw *parent; + u64 parent_rate; + + parent = clk_hw_get_parent(hw); + parent_rate = clk_get_rate(parent->clk); + if (!parent_rate) + return -EINVAL; + + clk_rcg2_calc_mnd(parent_rate, req->rate, f, mnd_max, hid_max / 2); + convert_to_reg_val(f); + req->rate = calc_rate(parent_rate, f->m, f->n, f->n, f->pre_div); + + return 0; +} + +static int __clk_rcg2_configure_parent(struct clk_rcg2 *rcg, u8 src, u32 *_cfg) { - u32 cfg, mask; struct clk_hw *hw = &rcg->clkr.hw; - int ret, index = qcom_find_src_index(hw, rcg->parent_map, f->src); + int index = qcom_find_src_index(hw, rcg->parent_map, src); if (index < 0) return index; + *_cfg &= ~CFG_SRC_SEL_MASK; + *_cfg |= rcg->parent_map[index].cfg << CFG_SRC_SEL_SHIFT; + + return 0; +} + +static int __clk_rcg2_configure_mnd(struct clk_rcg2 *rcg, const struct freq_tbl *f, + u32 *_cfg) +{ + u32 cfg, mask, d_val, not2d_val, n_minus_m; + int ret; + if (rcg->mnd_width && f->n) { mask = BIT(rcg->mnd_width) - 1; ret = regmap_update_bits(rcg->clkr.regmap, @@ -283,27 +532,85 @@ static int __clk_rcg2_configure(struct clk_rcg2 *rcg, const struct freq_tbl *f) if (ret) return ret; + /* Calculate 2d value */ + d_val = f->n; + + n_minus_m = f->n - f->m; + n_minus_m *= 2; + + d_val = clamp_t(u32, d_val, f->m, n_minus_m); + not2d_val = ~d_val & mask; + ret = regmap_update_bits(rcg->clkr.regmap, - RCG_D_OFFSET(rcg), mask, ~f->n); + RCG_D_OFFSET(rcg), mask, not2d_val); if (ret) return ret; } mask = BIT(rcg->hid_width) - 1; - mask |= CFG_SRC_SEL_MASK | CFG_MODE_MASK | CFG_HW_CLK_CTRL_MASK; + mask |= CFG_MODE_MASK | CFG_HW_CLK_CTRL_MASK; cfg = f->pre_div << CFG_SRC_DIV_SHIFT; - 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); + if (rcg->hw_clk_ctrl) + cfg |= CFG_HW_CLK_CTRL_MASK; + + *_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_parent(rcg, f->src, _cfg); + if (ret) + return ret; + + ret = __clk_rcg2_configure_mnd(rcg, f, _cfg); + if (ret) + return ret; + + 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; + + return update_config(rcg); +} + +static int clk_rcg2_configure_gp(struct clk_rcg2 *rcg, const struct freq_tbl *f) +{ + u32 cfg; + int ret; + + ret = regmap_read(rcg->clkr.regmap, RCG_CFG_OFFSET(rcg), &cfg); + if (ret) + return ret; + + ret = __clk_rcg2_configure_mnd(rcg, f, &cfg); + if (ret) + return ret; + + ret = regmap_write(rcg->clkr.regmap, RCG_CFG_OFFSET(rcg), cfg); if (ret) return ret; @@ -333,18 +640,64 @@ static int __clk_rcg2_set_rate(struct clk_hw *hw, unsigned long rate, return clk_rcg2_configure(rcg, f); } +static int __clk_rcg2_fm_set_rate(struct clk_hw *hw, unsigned long rate) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + const struct freq_multi_tbl *f; + const struct freq_conf *conf; + struct freq_tbl f_tbl = {}; + + f = qcom_find_freq_multi(rcg->freq_multi_tbl, rate); + if (!f || !f->confs) + return -EINVAL; + + conf = __clk_rcg2_select_conf(hw, f, rate); + if (IS_ERR(conf)) + return PTR_ERR(conf); + + f_tbl.freq = f->freq; + f_tbl.src = conf->src; + f_tbl.pre_div = conf->pre_div; + f_tbl.m = conf->m; + f_tbl.n = conf->n; + + return clk_rcg2_configure(rcg, &f_tbl); +} + static int clk_rcg2_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { return __clk_rcg2_set_rate(hw, rate, CEIL); } +static int clk_rcg2_set_gp_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + int mnd_max = BIT(rcg->mnd_width) - 1; + int hid_max = BIT(rcg->hid_width) - 1; + struct freq_tbl f_tbl = {}, *f = &f_tbl; + int ret; + + clk_rcg2_calc_mnd(parent_rate, rate, f, mnd_max, hid_max / 2); + convert_to_reg_val(f); + ret = clk_rcg2_configure_gp(rcg, f); + + return ret; +} + static int clk_rcg2_set_floor_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { return __clk_rcg2_set_rate(hw, rate, FLOOR); } +static int clk_rcg2_fm_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + return __clk_rcg2_fm_set_rate(hw, rate); +} + static int clk_rcg2_set_rate_and_parent(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate, u8 index) { @@ -357,6 +710,12 @@ static int clk_rcg2_set_floor_rate_and_parent(struct clk_hw *hw, return __clk_rcg2_set_rate(hw, rate, FLOOR); } +static int clk_rcg2_fm_set_rate_and_parent(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate, u8 index) +{ + return __clk_rcg2_fm_set_rate(hw, rate); +} + static int clk_rcg2_get_duty_cycle(struct clk_hw *hw, struct clk_duty *duty) { struct clk_rcg2 *rcg = to_clk_rcg2(hw); @@ -396,7 +755,7 @@ static int clk_rcg2_get_duty_cycle(struct clk_hw *hw, struct clk_duty *duty) static int clk_rcg2_set_duty_cycle(struct clk_hw *hw, struct clk_duty *duty) { struct clk_rcg2 *rcg = to_clk_rcg2(hw); - u32 notn_m, n, m, d, not2d, mask, duty_per; + u32 notn_m, n, m, d, not2d, mask, duty_per, cfg; int ret; /* Duty-cycle cannot be modified for non-MND RCGs */ @@ -407,6 +766,11 @@ static int clk_rcg2_set_duty_cycle(struct clk_hw *hw, struct clk_duty *duty) regmap_read(rcg->clkr.regmap, RCG_N_OFFSET(rcg), ¬n_m); regmap_read(rcg->clkr.regmap, RCG_M_OFFSET(rcg), &m); + regmap_read(rcg->clkr.regmap, RCG_CFG_OFFSET(rcg), &cfg); + + /* Duty-cycle cannot be modified if MND divider is in bypass mode. */ + if (!(cfg & CFG_MODE_MASK)) + return -EINVAL; n = (~(notn_m) + m) & mask; @@ -415,9 +779,11 @@ static int clk_rcg2_set_duty_cycle(struct clk_hw *hw, struct clk_duty *duty) /* Calculate 2d value */ d = DIV_ROUND_CLOSEST(n * duty_per * 2, 100); - /* Check bit widths of 2d. If D is too big reduce duty cycle. */ - if (d > mask) - d = mask; + /* + * Check bit widths of 2d. If D is too big reduce duty cycle. + * Also make sure it is never zero. + */ + d = clamp_val(d, 1, mask); if ((d / 2) > (n - m)) d = (n - m) * 2; @@ -447,6 +813,18 @@ const struct clk_ops clk_rcg2_ops = { }; EXPORT_SYMBOL_GPL(clk_rcg2_ops); +const struct clk_ops clk_rcg2_gp_ops = { + .is_enabled = clk_rcg2_is_enabled, + .get_parent = clk_rcg2_get_parent, + .set_parent = clk_rcg2_set_parent, + .recalc_rate = clk_rcg2_recalc_rate, + .determine_rate = clk_rcg2_determine_gp_rate, + .set_rate = clk_rcg2_set_gp_rate, + .get_duty_cycle = clk_rcg2_get_duty_cycle, + .set_duty_cycle = clk_rcg2_set_duty_cycle, +}; +EXPORT_SYMBOL_GPL(clk_rcg2_gp_ops); + const struct clk_ops clk_rcg2_floor_ops = { .is_enabled = clk_rcg2_is_enabled, .get_parent = clk_rcg2_get_parent, @@ -460,6 +838,26 @@ const struct clk_ops clk_rcg2_floor_ops = { }; EXPORT_SYMBOL_GPL(clk_rcg2_floor_ops); +const struct clk_ops clk_rcg2_fm_ops = { + .is_enabled = clk_rcg2_is_enabled, + .get_parent = clk_rcg2_get_parent, + .set_parent = clk_rcg2_set_parent, + .recalc_rate = clk_rcg2_recalc_rate, + .determine_rate = clk_rcg2_fm_determine_rate, + .set_rate = clk_rcg2_fm_set_rate, + .set_rate_and_parent = clk_rcg2_fm_set_rate_and_parent, + .get_duty_cycle = clk_rcg2_get_duty_cycle, + .set_duty_cycle = clk_rcg2_set_duty_cycle, +}; +EXPORT_SYMBOL_GPL(clk_rcg2_fm_ops); + +const struct clk_ops clk_rcg2_mux_closest_ops = { + .determine_rate = __clk_mux_determine_rate_closest, + .get_parent = clk_rcg2_get_parent, + .set_parent = clk_rcg2_set_parent, +}; +EXPORT_SYMBOL_GPL(clk_rcg2_mux_closest_ops); + struct frac_entry { int num; int den; @@ -720,6 +1118,7 @@ static const struct frac_entry frac_table_pixel[] = { { 2, 9 }, { 4, 9 }, { 1, 1 }, + { 2, 3 }, { } }; @@ -808,7 +1207,7 @@ EXPORT_SYMBOL_GPL(clk_pixel_ops); static int clk_gfx3d_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { - struct clk_rate_request parent_req = { }; + struct clk_rate_request parent_req = { .min_rate = 0, .max_rate = ULONG_MAX }; struct clk_rcg2_gfx3d *cgfx = to_clk_rcg2_gfx3d(hw); struct clk_hw *xo, *p0, *p1, *p2; unsigned long p0_rate; @@ -858,6 +1257,15 @@ static int clk_gfx3d_determine_rate(struct clk_hw *hw, req->best_parent_hw = p2; } + clk_hw_get_rate_range(req->best_parent_hw, + &parent_req.min_rate, &parent_req.max_rate); + + if (req->min_rate > parent_req.min_rate) + parent_req.min_rate = req->min_rate; + + if (req->max_rate < parent_req.max_rate) + parent_req.max_rate = req->max_rate; + ret = __clk_determine_rate(req->best_parent_hw, &parent_req); if (ret) return ret; @@ -958,30 +1366,57 @@ clk_rcg2_shared_force_enable_clear(struct clk_hw *hw, const struct freq_tbl *f) return clk_rcg2_clear_force_enable(hw); } -static int clk_rcg2_shared_set_rate(struct clk_hw *hw, unsigned long rate, - unsigned long parent_rate) +static int __clk_rcg2_shared_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate, + enum freq_policy policy) { struct clk_rcg2 *rcg = to_clk_rcg2(hw); const struct freq_tbl *f; - f = qcom_find_freq(rcg->freq_tbl, rate); - if (!f) + switch (policy) { + case FLOOR: + f = qcom_find_freq_floor(rcg->freq_tbl, rate); + break; + case CEIL: + f = qcom_find_freq(rcg->freq_tbl, rate); + break; + default: 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); } +static int clk_rcg2_shared_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + return __clk_rcg2_shared_set_rate(hw, rate, parent_rate, CEIL); +} + static int clk_rcg2_shared_set_rate_and_parent(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate, u8 index) { - return clk_rcg2_shared_set_rate(hw, rate, parent_rate); + return __clk_rcg2_shared_set_rate(hw, rate, parent_rate, CEIL); +} + +static int clk_rcg2_shared_set_floor_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + return __clk_rcg2_shared_set_rate(hw, rate, parent_rate, FLOOR); +} + +static int clk_rcg2_shared_set_floor_rate_and_parent(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate, u8 index) +{ + return __clk_rcg2_shared_set_rate(hw, rate, parent_rate, FLOOR); } static int clk_rcg2_shared_enable(struct clk_hw *hw) @@ -997,6 +1432,11 @@ static int clk_rcg2_shared_enable(struct clk_hw *hw) 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; @@ -1007,13 +1447,12 @@ static int clk_rcg2_shared_enable(struct clk_hw *hw) 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. @@ -1031,23 +1470,132 @@ static void clk_rcg2_shared_disable(struct clk_hw *hw) 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); +} + +static int clk_rcg2_shared_init(struct clk_hw *hw) +{ + /* + * This does a few things: + * + * 1. Sets rcg->parked_cfg to reflect the value at probe so that the + * proper parent is reported from clk_rcg2_shared_get_parent(). + * + * 2. Clears the force enable bit of the RCG because we rely on child + * clks (branches) to turn the RCG on/off with a hardware feedback + * mechanism and only set the force enable bit in the RCG when we + * want to make sure the clk stays on for parent switches or + * parking. + * + * 3. Parks shared RCGs on the safe source at registration because we + * can't be certain that the parent clk will stay on during boot, + * especially if the parent is shared. If this RCG is enabled at + * boot, and the parent is turned off, the RCG will get stuck on. A + * GDSC can wedge if is turned on and the RCG is stuck on because + * the GDSC's controller will hang waiting for the clk status to + * toggle on when it never does. + * + * The safest option here is to "park" the RCG at init so that the clk + * can never get stuck on or off. This ensures the GDSC can't get + * wedged. + */ + clk_rcg2_shared_disable(hw); + + return 0; } const struct clk_ops clk_rcg2_shared_ops = { + .init = clk_rcg2_shared_init, .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, }; EXPORT_SYMBOL_GPL(clk_rcg2_shared_ops); +const struct clk_ops clk_rcg2_shared_floor_ops = { + .enable = clk_rcg2_shared_enable, + .disable = clk_rcg2_shared_disable, + .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_floor_rate, + .set_rate = clk_rcg2_shared_set_floor_rate, + .set_rate_and_parent = clk_rcg2_shared_set_floor_rate_and_parent, +}; +EXPORT_SYMBOL_GPL(clk_rcg2_shared_floor_ops); + +static int clk_rcg2_shared_no_init_park(struct clk_hw *hw) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + + /* + * Read the config register so that the parent is properly mapped at + * registration time. + */ + regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + CFG_REG, &rcg->parked_cfg); + + return 0; +} + +/* + * Like clk_rcg2_shared_ops but skip the init so that the clk frequency is left + * unchanged at registration time. + */ +const struct clk_ops clk_rcg2_shared_no_init_park_ops = { + .init = clk_rcg2_shared_no_init_park, + .enable = clk_rcg2_shared_enable, + .disable = clk_rcg2_shared_disable, + .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, +}; +EXPORT_SYMBOL_GPL(clk_rcg2_shared_no_init_park_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) |
