Add a new entity - clock controller - so the global clock prepare lock could be fine-grained per controller. The controller is an abstract way of representing a hardware block. It overlaps a little with clock provider so there is a potential of merging them. The clock hierarchy might span between many controllers so add necessary locking primitives for locking children, parents or everything. Add a global controller for drivers not converted to new API. This will be removed once everything uses per-device/per-driver clock controller. Signed-off-by: Krzysztof Kozlowski <k.kozlowski@xxxxxxxxxxx> --- drivers/clk/clk.c | 300 +++++++++++++++++++++++++++++++++++++++++-- include/linux/clk-provider.h | 25 +++- include/linux/clk.h | 1 + 3 files changed, 310 insertions(+), 16 deletions(-) diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 238b989bf778..ee1cedfbaa29 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -35,6 +35,7 @@ static struct task_struct *enable_owner; static int prepare_refcnt; static int enable_refcnt; +static LIST_HEAD(clk_ctrl_list); static HLIST_HEAD(clk_root_list); static HLIST_HEAD(clk_orphan_list); static LIST_HEAD(clk_notifier_list); @@ -46,6 +47,7 @@ struct clk_core { const struct clk_ops *ops; struct clk_hw *hw; struct module *owner; + struct clk_ctrl *ctrl; struct clk_core *parent; const char **parent_names; struct clk_core **parents; @@ -87,6 +89,24 @@ struct clk { struct hlist_node clks_node; }; +struct clk_ctrl { + struct device *dev; /* Needed? */ + struct mutex prepare_lock; + struct task_struct *prepare_owner; + int prepare_refcnt; + struct list_head node; +}; + +/* + * As a temporary solution, register all clocks which pass NULL as clock + * controller under this one. This should be removed after converting + * all users to new clock controller aware API. + */ +static struct clk_ctrl global_ctrl = { + .prepare_lock = __MUTEX_INITIALIZER(global_ctrl.prepare_lock), + .node = LIST_HEAD_INIT(global_ctrl.node), +}; + /*** locking ***/ static void clk_prepare_lock(void) { @@ -148,6 +168,228 @@ static void clk_enable_unlock(unsigned long flags) spin_unlock_irqrestore(&enable_lock, flags); } +static void clk_ctrl_prepare_lock(struct clk_ctrl *ctrl) +{ + if (!ctrl) + return; + + if (!mutex_trylock(&ctrl->prepare_lock)) { + if (ctrl->prepare_owner == current) { + ctrl->prepare_refcnt++; + return; + } + mutex_lock(&ctrl->prepare_lock); + } + WARN_ON_ONCE(ctrl->prepare_owner != NULL); + WARN_ON_ONCE(ctrl->prepare_refcnt != 0); + ctrl->prepare_owner = current; + ctrl->prepare_refcnt = 1; +} + +static void clk_ctrl_prepare_unlock(struct clk_ctrl *ctrl) +{ + if (!ctrl) + return; + + WARN_ON_ONCE(ctrl->prepare_owner != current); + WARN_ON_ONCE(ctrl->prepare_refcnt == 0); + + if (--ctrl->prepare_refcnt) + return; + ctrl->prepare_owner = NULL; + mutex_unlock(&ctrl->prepare_lock); +} + +static void clk_prepare_lock_ctrl(struct clk_core *core) +{ + if (!core) + return; + + clk_ctrl_prepare_lock(core->ctrl); +} + +static void clk_prepare_unlock_ctrl(struct clk_core *core) +{ + if (!core) + return; + + clk_ctrl_prepare_unlock(core->ctrl); +} + +static void clk_prepare_lock_parents_locked(struct clk_core *core) +{ + struct clk_ctrl *prev = NULL; + + // lockdep_assert_held(&prepare_lock); // tmp comment? + + if (!core) + return; + + do { + if (core->ctrl != prev) { + clk_ctrl_prepare_lock(core->ctrl); + prev = core->ctrl; + } + } while ((core = core->parent)); +} + +static void clk_prepare_lock_parents(struct clk_core *core) +{ + if (!core) + return; + + clk_prepare_lock(); + clk_prepare_lock_parents_locked(core); + clk_prepare_unlock(); +} + +static void clk_prepare_unlock_parents_recur(struct clk_core *core, + struct clk_ctrl *prev) +{ + if (!core) + return; + + clk_prepare_unlock_parents_recur(core->parent, core->ctrl); + if (core->ctrl != prev) + clk_ctrl_prepare_unlock(core->ctrl); +} + +static void clk_prepare_unlock_parents(struct clk_core *core) +{ + if (!core) + return; + + clk_prepare_unlock_parents_recur(core, NULL); +} + +// FIXME: important note - will skip first lock +static void clk_prepare_lock_children_locked(struct clk_core *core) +{ + struct clk_core *child; + + lockdep_assert_held(&prepare_lock); + + if (!core) + return; + + hlist_for_each_entry(child, &core->children, child_node) { + clk_prepare_lock_children_locked(child); + + /* No need to double lock the same controller */ + if (child->ctrl != core->ctrl) + clk_ctrl_prepare_lock(child->ctrl); + } +} + +static void clk_prepare_lock_children(struct clk_core *core) +{ + if (!core) + return; + + clk_prepare_lock(); + clk_prepare_lock_children_locked(core); + /* Initial lock because children recurrency skiped first one */ + clk_ctrl_prepare_lock(core->ctrl); +} + +static void clk_prepare_unlock_children_locked(struct clk_core *core) +{ + struct clk_core *child; + + if (!core) + return; + + hlist_for_each_entry(child, &core->children, child_node) { + /* No need to double unlock the same controller */ + if (child->ctrl != core->ctrl) + clk_ctrl_prepare_unlock(child->ctrl); + + clk_prepare_unlock_children_locked(child); + } +} + +static void clk_prepare_unlock_children(struct clk_core *core) +{ + if (!core) + return; + + /* Unlock the initial controller, skipped in children recurrency */ + clk_ctrl_prepare_unlock(core->ctrl); + clk_prepare_unlock_children_locked(core); + clk_prepare_unlock(); +} + +/* Locks prepare lock, children and parents */ +static void clk_prepare_lock_tree(struct clk_core *core) +{ + if (!core) + return; + + clk_prepare_lock(); + clk_prepare_lock_children_locked(core); + /* Children recurrency skiped locking first one */ + clk_ctrl_prepare_lock(core->ctrl); + clk_prepare_lock_parents_locked(core); +} + +static void clk_prepare_unlock_tree(struct clk_core *core) +{ + if (!core) + return; + + clk_prepare_unlock_parents(core); + /* Unlock the initial controller, skipped in children recurrency */ + clk_ctrl_prepare_unlock(core->ctrl); + clk_prepare_unlock_children_locked(core); + clk_prepare_unlock(); +} + +/* + * Unlocks the controller hierarchy (children and parents) but going from + * old parent. Used in case of reparenting. + * If (core->parent == old_parent), this is equal to clk_prepare_unlock_tree(). + */ +static void clk_prepare_unlock_oldtree(struct clk_core *core, + struct clk_core *old_parent) +{ + if (!core) + return; + + clk_prepare_unlock_parents(old_parent); + /* + * Lock parents was called on 'core', but we unlock starting from + * 'old_parent'. In the same time locking did not lock the same + * controller twice but this check will be skipped for 'core'. + */ + if (old_parent->ctrl != core->ctrl) + clk_ctrl_prepare_unlock(core->ctrl); + + /* Unlock the initial controller, skipped in children recurrency */ + clk_ctrl_prepare_unlock(core->ctrl); + clk_prepare_unlock_children_locked(core); + clk_prepare_unlock(); +} + +/* Locks everything */ +/* FIXME: order of locking, it does not follow child-parent */ +static void clk_prepare_lock_all(void) +{ + struct clk_ctrl *ctrl; + + clk_prepare_lock(); + list_for_each_entry(ctrl, &clk_ctrl_list, node) + clk_ctrl_prepare_lock(ctrl); +} + +static void clk_prepare_unlock_all(void) +{ + struct clk_ctrl *ctrl; + + list_for_each_entry(ctrl, &clk_ctrl_list, node) + clk_ctrl_prepare_unlock(ctrl); + clk_prepare_unlock(); +} + static bool clk_core_is_prepared(struct clk_core *core) { /* @@ -2526,6 +2768,34 @@ void __clk_free_clk(struct clk *clk) kfree(clk); } +struct clk_ctrl *clk_ctrl_register(struct device *dev) +{ + struct clk_ctrl *ctrl; + + ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return ERR_PTR(-ENOMEM); + + mutex_init(&ctrl->prepare_lock); + + clk_prepare_lock(); + list_add(&ctrl->node, &clk_ctrl_list); + clk_prepare_unlock(); + + return ctrl; +} +EXPORT_SYMBOL_GPL(clk_ctrl_register); + +void clk_ctrl_unregister(struct clk_ctrl *ctrl) +{ + clk_prepare_lock(); + list_del(&ctrl->node); + clk_prepare_unlock(); + + kfree(ctrl); +} +EXPORT_SYMBOL_GPL(clk_ctrl_unregister); + /** * clk_register - allocate a new clock, register it and return an opaque cookie * @dev: device that is registering this clock @@ -2537,7 +2807,8 @@ void __clk_free_clk(struct clk *clk) * rest of the clock API. In the event of an error clk_register will return an * error code; drivers must test for an error code after calling clk_register. */ -struct clk *clk_register(struct device *dev, struct clk_hw *hw) +struct clk *clk_register_with_ctrl(struct device *dev, struct clk_ctrl *ctrl, + struct clk_hw *hw) { int i, ret; struct clk_core *core; @@ -2561,6 +2832,10 @@ struct clk *clk_register(struct device *dev, struct clk_hw *hw) core->num_parents = hw->init->num_parents; core->min_rate = 0; core->max_rate = ULONG_MAX; + if (ctrl) + core->ctrl = ctrl; + else + core->ctrl = &global_ctrl; hw->core = core; /* allocate local copy in case parent_names is __initdata */ @@ -2619,7 +2894,7 @@ fail_name: fail_out: return ERR_PTR(ret); } -EXPORT_SYMBOL_GPL(clk_register); +EXPORT_SYMBOL_GPL(clk_register_with_ctrl); /** * clk_hw_register - register a clk_hw and return an error code @@ -2631,11 +2906,12 @@ EXPORT_SYMBOL_GPL(clk_register); * less than zero indicating failure. Drivers must test for an error code after * calling clk_hw_register(). */ -int clk_hw_register(struct device *dev, struct clk_hw *hw) +int clk_hw_register_with_ctrl(struct device *dev, struct clk_ctrl *ctrl, + struct clk_hw *hw) { - return PTR_ERR_OR_ZERO(clk_register(dev, hw)); + return PTR_ERR_OR_ZERO(clk_register_with_ctrl(dev, ctrl, hw)); } -EXPORT_SYMBOL_GPL(clk_hw_register); +EXPORT_SYMBOL_GPL(clk_hw_register_with_ctrl); /* Free memory allocated for a clock. */ static void __clk_release(struct kref *ref) @@ -2644,6 +2920,7 @@ static void __clk_release(struct kref *ref) int i = core->num_parents; lockdep_assert_held(&prepare_lock); + // lockdep_assert_not_held(&core->ctrl->prepare_lock); // TODO? kfree(core->parents); while (--i >= 0) @@ -2767,7 +3044,7 @@ static void devm_clk_hw_release(struct device *dev, void *res) * automatically clk_unregister()ed on driver detach. See clk_register() for * more information. */ -struct clk *devm_clk_register(struct device *dev, struct clk_hw *hw) +struct clk *devm_clk_register_with_ctrl(struct device *dev, struct clk_ctrl *ctrl, struct clk_hw *hw) { struct clk *clk; struct clk **clkp; @@ -2776,7 +3053,7 @@ struct clk *devm_clk_register(struct device *dev, struct clk_hw *hw) if (!clkp) return ERR_PTR(-ENOMEM); - clk = clk_register(dev, hw); + clk = clk_register_with_ctrl(dev, ctrl, hw); if (!IS_ERR(clk)) { *clkp = clk; devres_add(dev, clkp); @@ -2786,7 +3063,7 @@ struct clk *devm_clk_register(struct device *dev, struct clk_hw *hw) return clk; } -EXPORT_SYMBOL_GPL(devm_clk_register); +EXPORT_SYMBOL_GPL(devm_clk_register_with_ctrl); /** * devm_clk_hw_register - resource managed clk_hw_register() @@ -2797,7 +3074,8 @@ EXPORT_SYMBOL_GPL(devm_clk_register); * automatically clk_hw_unregister()ed on driver detach. See clk_hw_register() * for more information. */ -int devm_clk_hw_register(struct device *dev, struct clk_hw *hw) +int devm_clk_hw_register_with_ctrl(struct device *dev, struct clk_ctrl *ctrl, + struct clk_hw *hw) { struct clk_hw **hwp; int ret; @@ -2806,7 +3084,7 @@ int devm_clk_hw_register(struct device *dev, struct clk_hw *hw) if (!hwp) return -ENOMEM; - ret = clk_hw_register(dev, hw); + ret = clk_hw_register_with_ctrl(dev, ctrl, hw); if (!ret) { *hwp = hw; devres_add(dev, hwp); @@ -2816,7 +3094,7 @@ int devm_clk_hw_register(struct device *dev, struct clk_hw *hw) return ret; } -EXPORT_SYMBOL_GPL(devm_clk_hw_register); +EXPORT_SYMBOL_GPL(devm_clk_hw_register_with_ctrl); static int devm_clk_match(struct device *dev, void *res, void *data) { diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index a39c0c530778..3589f164ff94 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -39,6 +39,7 @@ struct clk; struct clk_hw; struct clk_core; +struct clk_ctrl; struct dentry; /** @@ -703,6 +704,8 @@ struct clk_hw *clk_hw_register_gpio_mux(struct device *dev, const char *name, bool active_low, unsigned long flags); void clk_hw_unregister_gpio_mux(struct clk_hw *hw); +struct clk_ctrl *clk_ctrl_register(struct device *dev); +void clk_ctrl_unregister(struct clk_ctrl *ctrl); /** * clk_register - allocate a new clock, register it and return an opaque cookie * @dev: device that is registering this clock @@ -714,11 +717,23 @@ void clk_hw_unregister_gpio_mux(struct clk_hw *hw); * rest of the clock API. In the event of an error clk_register will return an * error code; drivers must test for an error code after calling clk_register. */ -struct clk *clk_register(struct device *dev, struct clk_hw *hw); -struct clk *devm_clk_register(struct device *dev, struct clk_hw *hw); - -int __must_check clk_hw_register(struct device *dev, struct clk_hw *hw); -int __must_check devm_clk_hw_register(struct device *dev, struct clk_hw *hw); +struct clk *clk_register_with_ctrl(struct device *dev, struct clk_ctrl *ctrl, + struct clk_hw *hw); +struct clk *devm_clk_register_with_ctrl(struct device *dev, struct clk_ctrl *ctrl, + struct clk_hw *hw); + +#define clk_register(dev, hw) clk_register_with_ctrl(dev, NULL, hw) +#define devm_clk_register(dev, hw) devm_clk_register_with_ctrl(dev, NULL, hw) + +int __must_check clk_hw_register_with_ctrl(struct device *dev, + struct clk_ctrl *ctrl, + struct clk_hw *hw); +int __must_check devm_clk_hw_register_with_ctrl(struct device *dev, + struct clk_ctrl *ctrl, + struct clk_hw *hw); + +#define clk_hw_register(dev, hw) clk_hw_register_with_ctrl(dev, NULL, hw) +#define devm_clk_hw_register(dev, hw) devm_clk_hw_register_with_ctrl(dev, NULL, hw) void clk_unregister(struct clk *clk); void devm_clk_unregister(struct device *dev, struct clk *clk); diff --git a/include/linux/clk.h b/include/linux/clk.h index 123c02788807..8f751d1eb1df 100644 --- a/include/linux/clk.h +++ b/include/linux/clk.h @@ -19,6 +19,7 @@ struct device; struct clk; +struct clk_ctrl; /** * DOC: clk notifier callback types -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html