diff options
Diffstat (limited to 'drivers/clk')
-rw-r--r-- | drivers/clk/clk-si514.c | 38 | ||||
-rw-r--r-- | drivers/clk/clk-si544.c | 38 | ||||
-rw-r--r-- | drivers/clk/clk.c | 215 | ||||
-rw-r--r-- | drivers/clk/qcom/gcc-ipq806x.c | 3 | ||||
-rw-r--r-- | drivers/clk/qcom/gcc-mdm9615.c | 2 | ||||
-rw-r--r-- | drivers/clk/qcom/gcc-msm8660.c | 5 | ||||
-rw-r--r-- | drivers/clk/qcom/gcc-msm8960.c | 5 |
7 files changed, 281 insertions, 25 deletions
diff --git a/drivers/clk/clk-si514.c b/drivers/clk/clk-si514.c index 09b6718956bd..153b3a2b5857 100644 --- a/drivers/clk/clk-si514.c +++ b/drivers/clk/clk-si514.c @@ -74,6 +74,33 @@ static int si514_enable_output(struct clk_si514 *data, bool enable) SI514_CONTROL_OE, enable ? SI514_CONTROL_OE : 0); } +static int si514_prepare(struct clk_hw *hw) +{ + struct clk_si514 *data = to_clk_si514(hw); + + return si514_enable_output(data, true); +} + +static void si514_unprepare(struct clk_hw *hw) +{ + struct clk_si514 *data = to_clk_si514(hw); + + si514_enable_output(data, false); +} + +static int si514_is_prepared(struct clk_hw *hw) +{ + struct clk_si514 *data = to_clk_si514(hw); + unsigned int val; + int err; + + err = regmap_read(data->regmap, SI514_REG_CONTROL, &val); + if (err < 0) + return err; + + return !!(val & SI514_CONTROL_OE); +} + /* Retrieve clock multiplier and dividers from hardware */ static int si514_get_muldiv(struct clk_si514 *data, struct clk_si514_muldiv *settings) @@ -235,12 +262,17 @@ static int si514_set_rate(struct clk_hw *hw, unsigned long rate, { struct clk_si514 *data = to_clk_si514(hw); struct clk_si514_muldiv settings; + unsigned int old_oe_state; int err; err = si514_calc_muldiv(&settings, rate); if (err) return err; + err = regmap_read(data->regmap, SI514_REG_CONTROL, &old_oe_state); + if (err) + return err; + si514_enable_output(data, false); err = si514_set_muldiv(data, &settings); @@ -255,12 +287,16 @@ static int si514_set_rate(struct clk_hw *hw, unsigned long rate, /* Applying a new frequency can take up to 10ms */ usleep_range(10000, 12000); - si514_enable_output(data, true); + if (old_oe_state & SI514_CONTROL_OE) + si514_enable_output(data, true); return err; } static const struct clk_ops si514_clk_ops = { + .prepare = si514_prepare, + .unprepare = si514_unprepare, + .is_prepared = si514_is_prepared, .recalc_rate = si514_recalc_rate, .round_rate = si514_round_rate, .set_rate = si514_set_rate, diff --git a/drivers/clk/clk-si544.c b/drivers/clk/clk-si544.c index 1e2a3b8f9454..64e607f3232a 100644 --- a/drivers/clk/clk-si544.c +++ b/drivers/clk/clk-si544.c @@ -86,6 +86,33 @@ static int si544_enable_output(struct clk_si544 *data, bool enable) SI544_OE_STATE_ODC_OE, enable ? SI544_OE_STATE_ODC_OE : 0); } +static int si544_prepare(struct clk_hw *hw) +{ + struct clk_si544 *data = to_clk_si544(hw); + + return si544_enable_output(data, true); +} + +static void si544_unprepare(struct clk_hw *hw) +{ + struct clk_si544 *data = to_clk_si544(hw); + + si544_enable_output(data, false); +} + +static int si544_is_prepared(struct clk_hw *hw) +{ + struct clk_si544 *data = to_clk_si544(hw); + unsigned int val; + int err; + + err = regmap_read(data->regmap, SI544_REG_OE_STATE, &val); + if (err < 0) + return err; + + return !!(val & SI544_OE_STATE_ODC_OE); +} + /* Retrieve clock multiplier and dividers from hardware */ static int si544_get_muldiv(struct clk_si544 *data, struct clk_si544_muldiv *settings) @@ -273,6 +300,7 @@ static int si544_set_rate(struct clk_hw *hw, unsigned long rate, { struct clk_si544 *data = to_clk_si544(hw); struct clk_si544_muldiv settings; + unsigned int old_oe_state; int err; if (!is_valid_frequency(data, rate)) @@ -282,6 +310,10 @@ static int si544_set_rate(struct clk_hw *hw, unsigned long rate, if (err) return err; + err = regmap_read(data->regmap, SI544_REG_OE_STATE, &old_oe_state); + if (err) + return err; + si544_enable_output(data, false); /* Allow FCAL for this frequency update */ @@ -303,12 +335,16 @@ static int si544_set_rate(struct clk_hw *hw, unsigned long rate, /* Applying a new frequency can take up to 10ms */ usleep_range(10000, 12000); - si544_enable_output(data, true); + if (old_oe_state & SI544_OE_STATE_ODC_OE) + si544_enable_output(data, true); return err; } static const struct clk_ops si544_clk_ops = { + .prepare = si544_prepare, + .unprepare = si544_unprepare, + .is_prepared = si544_is_prepared, .recalc_rate = si544_recalc_rate, .round_rate = si544_round_rate, .set_rate = si544_set_rate, diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 9760b526ca31..e108f591d84a 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -68,6 +68,7 @@ struct clk_core { unsigned long max_rate; unsigned long accuracy; int phase; + struct clk_duty duty; struct hlist_head children; struct hlist_node child_node; struct hlist_head clks; @@ -691,6 +692,9 @@ static void clk_core_unprepare(struct clk_core *core) "Unpreparing critical %s\n", core->name)) return; + if (core->flags & CLK_SET_RATE_GATE) + clk_core_rate_unprotect(core); + if (--core->prepare_count > 0) return; @@ -765,6 +769,16 @@ static int clk_core_prepare(struct clk_core *core) core->prepare_count++; + /* + * CLK_SET_RATE_GATE is a special case of clock protection + * Instead of a consumer claiming exclusive rate control, it is + * actually the provider which prevents any consumer from making any + * operation which could result in a rate change or rate glitch while + * the clock is prepared. + */ + if (core->flags & CLK_SET_RATE_GATE) + clk_core_rate_protect(core); + return 0; unprepare: clk_core_unprepare(core->parent); @@ -1888,9 +1902,6 @@ static int clk_core_set_rate_nolock(struct clk_core *core, if (clk_core_rate_is_protected(core)) return -EBUSY; - if ((core->flags & CLK_SET_RATE_GATE) && core->prepare_count) - return -EBUSY; - /* calculate new rates and get the topmost changed clock */ top = clk_calc_new_rates(core, req_rate); if (!top) @@ -2402,6 +2413,172 @@ int clk_get_phase(struct clk *clk) } EXPORT_SYMBOL_GPL(clk_get_phase); +static void clk_core_reset_duty_cycle_nolock(struct clk_core *core) +{ + /* Assume a default value of 50% */ + core->duty.num = 1; + core->duty.den = 2; +} + +static int clk_core_update_duty_cycle_parent_nolock(struct clk_core *core); + +static int clk_core_update_duty_cycle_nolock(struct clk_core *core) +{ + struct clk_duty *duty = &core->duty; + int ret = 0; + + if (!core->ops->get_duty_cycle) + return clk_core_update_duty_cycle_parent_nolock(core); + + ret = core->ops->get_duty_cycle(core->hw, duty); + if (ret) + goto reset; + + /* Don't trust the clock provider too much */ + if (duty->den == 0 || duty->num > duty->den) { + ret = -EINVAL; + goto reset; + } + + return 0; + +reset: + clk_core_reset_duty_cycle_nolock(core); + return ret; +} + +static int clk_core_update_duty_cycle_parent_nolock(struct clk_core *core) +{ + int ret = 0; + + if (core->parent && + core->flags & CLK_DUTY_CYCLE_PARENT) { + ret = clk_core_update_duty_cycle_nolock(core->parent); + memcpy(&core->duty, &core->parent->duty, sizeof(core->duty)); + } else { + clk_core_reset_duty_cycle_nolock(core); + } + + return ret; +} + +static int clk_core_set_duty_cycle_parent_nolock(struct clk_core *core, + struct clk_duty *duty); + +static int clk_core_set_duty_cycle_nolock(struct clk_core *core, + struct clk_duty *duty) +{ + int ret; + + lockdep_assert_held(&prepare_lock); + + if (clk_core_rate_is_protected(core)) + return -EBUSY; + + trace_clk_set_duty_cycle(core, duty); + + if (!core->ops->set_duty_cycle) + return clk_core_set_duty_cycle_parent_nolock(core, duty); + + ret = core->ops->set_duty_cycle(core->hw, duty); + if (!ret) + memcpy(&core->duty, duty, sizeof(*duty)); + + trace_clk_set_duty_cycle_complete(core, duty); + + return ret; +} + +static int clk_core_set_duty_cycle_parent_nolock(struct clk_core *core, + struct clk_duty *duty) +{ + int ret = 0; + + if (core->parent && + core->flags & (CLK_DUTY_CYCLE_PARENT | CLK_SET_RATE_PARENT)) { + ret = clk_core_set_duty_cycle_nolock(core->parent, duty); + memcpy(&core->duty, &core->parent->duty, sizeof(core->duty)); + } + + return ret; +} + +/** + * clk_set_duty_cycle - adjust the duty cycle ratio of a clock signal + * @clk: clock signal source + * @num: numerator of the duty cycle ratio to be applied + * @den: denominator of the duty cycle ratio to be applied + * + * Apply the duty cycle ratio if the ratio is valid and the clock can + * perform this operation + * + * Returns (0) on success, a negative errno otherwise. + */ +int clk_set_duty_cycle(struct clk *clk, unsigned int num, unsigned int den) +{ + int ret; + struct clk_duty duty; + + if (!clk) + return 0; + + /* sanity check the ratio */ + if (den == 0 || num > den) + return -EINVAL; + + duty.num = num; + duty.den = den; + + clk_prepare_lock(); + + if (clk->exclusive_count) + clk_core_rate_unprotect(clk->core); + + ret = clk_core_set_duty_cycle_nolock(clk->core, &duty); + + if (clk->exclusive_count) + clk_core_rate_protect(clk->core); + + clk_prepare_unlock(); + + return ret; +} +EXPORT_SYMBOL_GPL(clk_set_duty_cycle); + +static int clk_core_get_scaled_duty_cycle(struct clk_core *core, + unsigned int scale) +{ + struct clk_duty *duty = &core->duty; + int ret; + + clk_prepare_lock(); + + ret = clk_core_update_duty_cycle_nolock(core); + if (!ret) + ret = mult_frac(scale, duty->num, duty->den); + + clk_prepare_unlock(); + + return ret; +} + +/** + * clk_get_scaled_duty_cycle - return the duty cycle ratio of a clock signal + * @clk: clock signal source + * @scale: scaling factor to be applied to represent the ratio as an integer + * + * Returns the duty cycle ratio of a clock node multiplied by the provided + * scaling factor, or negative errno on error. + */ +int clk_get_scaled_duty_cycle(struct clk *clk, unsigned int scale) +{ + if (!clk) + return 0; + + return clk_core_get_scaled_duty_cycle(clk->core, scale); +} +EXPORT_SYMBOL_GPL(clk_get_scaled_duty_cycle); + /** * clk_is_match - check if two clk's point to the same hardware clock * @p: clk compared against q @@ -2455,12 +2632,13 @@ static void clk_summary_show_one(struct seq_file *s, struct clk_core *c, if (!c) return; - seq_printf(s, "%*s%-*s %7d %8d %8d %11lu %10lu %-3d\n", + seq_printf(s, "%*s%-*s %7d %8d %8d %11lu %10lu %5d %6d\n", level * 3 + 1, "", 30 - level * 3, c->name, c->enable_count, c->prepare_count, c->protect_count, clk_core_get_rate(c), clk_core_get_accuracy(c), - clk_core_get_phase(c)); + clk_core_get_phase(c), + clk_core_get_scaled_duty_cycle(c, 100000)); } static void clk_summary_show_subtree(struct seq_file *s, struct clk_core *c, @@ -2482,9 +2660,9 @@ static int clk_summary_show(struct seq_file *s, void *data) struct clk_core *c; struct hlist_head **lists = (struct hlist_head **)s->private; - seq_puts(s, " enable prepare protect \n"); - seq_puts(s, " clock count count count rate accuracy phase\n"); - seq_puts(s, "----------------------------------------------------------------------------------------\n"); + seq_puts(s, " enable prepare protect duty\n"); + seq_puts(s, " clock count count count rate accuracy phase cycle\n"); + seq_puts(s, "---------------------------------------------------------------------------------------------\n"); clk_prepare_lock(); @@ -2511,6 +2689,8 @@ static void clk_dump_one(struct seq_file *s, struct clk_core *c, int level) seq_printf(s, "\"rate\": %lu,", clk_core_get_rate(c)); seq_printf(s, "\"accuracy\": %lu,", clk_core_get_accuracy(c)); seq_printf(s, "\"phase\": %d", clk_core_get_phase(c)); + seq_printf(s, "\"duty_cycle\": %u", + clk_core_get_scaled_duty_cycle(c, 100000)); } static void clk_dump_subtree(struct seq_file *s, struct clk_core *c, int level) @@ -2572,6 +2752,7 @@ static const struct { ENTRY(CLK_SET_RATE_UNGATE), ENTRY(CLK_IS_CRITICAL), ENTRY(CLK_OPS_PARENT_ENABLE), + ENTRY(CLK_DUTY_CYCLE_PARENT), #undef ENTRY }; @@ -2610,6 +2791,17 @@ static int possible_parents_show(struct seq_file *s, void *data) } DEFINE_SHOW_ATTRIBUTE(possible_parents); +static int clk_duty_cycle_show(struct seq_file *s, void *data) +{ + struct clk_core *core = s->private; + struct clk_duty *duty = &core->duty; + + seq_printf(s, "%u/%u\n", duty->num, duty->den); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(clk_duty_cycle); + static void clk_debug_create_one(struct clk_core *core, struct dentry *pdentry) { struct dentry *root; @@ -2628,6 +2820,8 @@ static void clk_debug_create_one(struct clk_core *core, struct dentry *pdentry) debugfs_create_u32("clk_enable_count", 0444, root, &core->enable_count); debugfs_create_u32("clk_protect_count", 0444, root, &core->protect_count); debugfs_create_u32("clk_notifier_count", 0444, root, &core->notifier_count); + debugfs_create_file("clk_duty_cycle", 0444, root, core, + &clk_duty_cycle_fops); if (core->num_parents > 1) debugfs_create_file("clk_possible_parents", 0444, root, core, @@ -2846,6 +3040,11 @@ static int __clk_core_init(struct clk_core *core) core->phase = 0; /* + * Set clk's duty cycle. + */ + clk_core_update_duty_cycle_nolock(core); + + /* * Set clk's rate. The preferred method is to use .recalc_rate. For * simple clocks and lazy developers the default fallback is to use the * parent's rate. If a clock doesn't have a parent (or is orphaned) diff --git a/drivers/clk/qcom/gcc-ipq806x.c b/drivers/clk/qcom/gcc-ipq806x.c index 28eb200d0f1e..5f61225657ab 100644 --- a/drivers/clk/qcom/gcc-ipq806x.c +++ b/drivers/clk/qcom/gcc-ipq806x.c @@ -1220,7 +1220,6 @@ static struct clk_rcg sdc1_src = { .parent_names = gcc_pxo_pll8, .num_parents = 2, .ops = &clk_rcg_ops, - .flags = CLK_SET_RATE_GATE, }, } }; @@ -1269,7 +1268,6 @@ static struct clk_rcg sdc3_src = { .parent_names = gcc_pxo_pll8, .num_parents = 2, .ops = &clk_rcg_ops, - .flags = CLK_SET_RATE_GATE, }, } }; @@ -1353,7 +1351,6 @@ static struct clk_rcg tsif_ref_src = { .parent_names = gcc_pxo_pll8, .num_parents = 2, .ops = &clk_rcg_ops, - .flags = CLK_SET_RATE_GATE, }, } }; diff --git a/drivers/clk/qcom/gcc-mdm9615.c b/drivers/clk/qcom/gcc-mdm9615.c index b99dd406e907..849046fbed6d 100644 --- a/drivers/clk/qcom/gcc-mdm9615.c +++ b/drivers/clk/qcom/gcc-mdm9615.c @@ -947,7 +947,6 @@ static struct clk_rcg sdc1_src = { .parent_names = gcc_cxo_pll8, .num_parents = 2, .ops = &clk_rcg_ops, - .flags = CLK_SET_RATE_GATE, }, } }; @@ -996,7 +995,6 @@ static struct clk_rcg sdc2_src = { .parent_names = gcc_cxo_pll8, .num_parents = 2, .ops = &clk_rcg_ops, - .flags = CLK_SET_RATE_GATE, }, } }; diff --git a/drivers/clk/qcom/gcc-msm8660.c b/drivers/clk/qcom/gcc-msm8660.c index c347a0d44bc8..7e930e25c79f 100644 --- a/drivers/clk/qcom/gcc-msm8660.c +++ b/drivers/clk/qcom/gcc-msm8660.c @@ -1558,7 +1558,6 @@ static struct clk_rcg sdc1_src = { .parent_names = gcc_pxo_pll8, .num_parents = 2, .ops = &clk_rcg_ops, - .flags = CLK_SET_RATE_GATE, }, } }; @@ -1607,7 +1606,6 @@ static struct clk_rcg sdc2_src = { .parent_names = gcc_pxo_pll8, .num_parents = 2, .ops = &clk_rcg_ops, - .flags = CLK_SET_RATE_GATE, }, } }; @@ -1656,7 +1654,6 @@ static struct clk_rcg sdc3_src = { .parent_names = gcc_pxo_pll8, .num_parents = 2, .ops = &clk_rcg_ops, - .flags = CLK_SET_RATE_GATE, }, } }; @@ -1705,7 +1702,6 @@ static struct clk_rcg sdc4_src = { .parent_names = gcc_pxo_pll8, .num_parents = 2, .ops = &clk_rcg_ops, - .flags = CLK_SET_RATE_GATE, }, } }; @@ -1754,7 +1750,6 @@ static struct clk_rcg sdc5_src = { .parent_names = gcc_pxo_pll8, .num_parents = 2, .ops = &clk_rcg_ops, - .flags = CLK_SET_RATE_GATE, }, } }; diff --git a/drivers/clk/qcom/gcc-msm8960.c b/drivers/clk/qcom/gcc-msm8960.c index eb551c75fba6..fd495e0471bb 100644 --- a/drivers/clk/qcom/gcc-msm8960.c +++ b/drivers/clk/qcom/gcc-msm8960.c @@ -1628,7 +1628,6 @@ static struct clk_rcg sdc1_src = { .parent_names = gcc_pxo_pll8, .num_parents = 2, .ops = &clk_rcg_ops, - .flags = CLK_SET_RATE_GATE, }, } }; @@ -1677,7 +1676,6 @@ static struct clk_rcg sdc2_src = { .parent_names = gcc_pxo_pll8, .num_parents = 2, .ops = &clk_rcg_ops, - .flags = CLK_SET_RATE_GATE, }, } }; @@ -1726,7 +1724,6 @@ static struct clk_rcg sdc3_src = { .parent_names = gcc_pxo_pll8, .num_parents = 2, .ops = &clk_rcg_ops, - .flags = CLK_SET_RATE_GATE, }, } }; @@ -1775,7 +1772,6 @@ static struct clk_rcg sdc4_src = { .parent_names = gcc_pxo_pll8, .num_parents = 2, .ops = &clk_rcg_ops, - .flags = CLK_SET_RATE_GATE, }, } }; @@ -1824,7 +1820,6 @@ static struct clk_rcg sdc5_src = { .parent_names = gcc_pxo_pll8, .num_parents = 2, .ops = &clk_rcg_ops, - .flags = CLK_SET_RATE_GATE, }, } }; |