mirror of
https://github.com/linux-sunxi/meta-sunxi.git
synced 2024-12-27 05:28:22 +01:00
198 lines
5.9 KiB
Diff
198 lines
5.9 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Samuel Holland <samuel@sholland.org>
|
|
Date: Sun, 29 Dec 2019 20:23:28 -0600
|
|
Subject: [PATCH] clk: Implement protected-clocks for all OF clock providers
|
|
|
|
This is a generic implementation of the "protected-clocks" property from
|
|
the common clock binding. It allows firmware to inform the OS about
|
|
clocks that must not be disabled while the OS is running.
|
|
|
|
This implementation comes with some caveats:
|
|
|
|
1) Clocks that have CLK_IS_CRITICAL in their init data are prepared/
|
|
enabled before they are attached to the clock tree. protected-clocks are
|
|
only protected once the clock provider is added, which is generally
|
|
after all of the clocks it provides have been registered. This leaves a
|
|
window of opportunity where something could disable or modify the clock,
|
|
such as a driver running on another CPU, or the clock core itself. There
|
|
is a comment to this effect in __clk_core_init():
|
|
|
|
/*
|
|
* Enable CLK_IS_CRITICAL clocks so newly added critical clocks
|
|
* don't get accidentally disabled when walking the orphan tree and
|
|
* reparenting clocks
|
|
*/
|
|
|
|
Similarly, these clocks will be enabled after they are first reparented,
|
|
unlike other CLK_IS_CRITICAL clocks. See the comment in
|
|
clk_core_reparent_orphans_nolock():
|
|
|
|
/*
|
|
* We need to use __clk_set_parent_before() and _after() to
|
|
* to properly migrate any prepare/enable count of the orphan
|
|
* clock. This is important for CLK_IS_CRITICAL clocks, which
|
|
* are enabled during init but might not have a parent yet.
|
|
*/
|
|
|
|
Ideally we could detect protected clocks before they are reparented, but
|
|
there are two problems with that:
|
|
|
|
a) From the clock core's perspective, hw->init is const.
|
|
|
|
b) The clock core doesn't see the device_node until __clk_register is
|
|
called on the first clock.
|
|
|
|
So the only "race-free" way to detect protected-clocks is to do it in
|
|
the middle of __clk_register, between when core->flags is initialized
|
|
and calling __clk_core_init(). That requires scanning the device tree
|
|
again for each clock, which is part of why I didn't do it that way.
|
|
|
|
2) __clk_protect needs to be idempotent, for two reasons:
|
|
|
|
a) Clocks with CLK_IS_CRITICAL in their init data are already
|
|
prepared/enabled, and we don't want to prepare/enable them again.
|
|
|
|
b) of_clk_set_defaults() is called twice for (at least some) clock
|
|
controllers registered with CLK_OF_DECLARE. It is called first in
|
|
of_clk_add_provider()/of_clk_add_hw_provider() inside clk_init_cb,
|
|
and again afterward in of_clk_init(). The second call in
|
|
of_clk_init() may be unnecessary, but verifying that would require
|
|
auditing all users of CLK_OF_DECLARE to ensure they called one of
|
|
the of_clk_add{,_hw}_provider functions.
|
|
|
|
Signed-off-by: Samuel Holland <samuel@sholland.org>
|
|
---
|
|
drivers/clk/clk-conf.c | 54 ++++++++++++++++++++++++++++++++++++++++++
|
|
drivers/clk/clk.c | 31 ++++++++++++++++++++++++
|
|
drivers/clk/clk.h | 2 ++
|
|
3 files changed, 87 insertions(+)
|
|
|
|
--- a/drivers/clk/clk-conf.c
|
|
+++ b/drivers/clk/clk-conf.c
|
|
@@ -11,6 +11,54 @@
|
|
#include <linux/of.h>
|
|
#include <linux/printk.h>
|
|
|
|
+#include "clk.h"
|
|
+
|
|
+static int __set_clk_flags(struct device_node *node)
|
|
+{
|
|
+ struct of_phandle_args clkspec;
|
|
+ struct property *prop;
|
|
+ int i, index = 0, rc;
|
|
+ const __be32 *cur;
|
|
+ struct clk *clk;
|
|
+ u32 nr_cells;
|
|
+
|
|
+ rc = of_property_read_u32(node, "#clock-cells", &nr_cells);
|
|
+ if (rc < 0) {
|
|
+ pr_err("clk: missing #clock-cells property on %pOF\n", node);
|
|
+ return rc;
|
|
+ }
|
|
+
|
|
+ clkspec.np = node;
|
|
+ clkspec.args_count = nr_cells;
|
|
+
|
|
+ of_property_for_each_u32(node, "protected-clocks", prop, cur, clkspec.args[0]) {
|
|
+ /* read the remainder of the clock specifier */
|
|
+ for (i = 1; i < nr_cells; ++i) {
|
|
+ cur = of_prop_next_u32(prop, cur, &clkspec.args[i]);
|
|
+ if (!cur) {
|
|
+ pr_err("clk: invalid value of protected-clocks"
|
|
+ " property at %pOF\n", node);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+ clk = of_clk_get_from_provider(&clkspec);
|
|
+ if (IS_ERR(clk)) {
|
|
+ if (PTR_ERR(clk) != -EPROBE_DEFER)
|
|
+ pr_err("clk: couldn't get protected clock"
|
|
+ " %u for %pOF\n", index, node);
|
|
+ return PTR_ERR(clk);
|
|
+ }
|
|
+
|
|
+ rc = __clk_protect(clk);
|
|
+ if (rc < 0)
|
|
+ pr_warn("clk: failed to protect %s: %d\n",
|
|
+ __clk_get_name(clk), rc);
|
|
+ clk_put(clk);
|
|
+ index++;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
static int __set_clk_parents(struct device_node *node, bool clk_supplier)
|
|
{
|
|
struct of_phandle_args clkspec;
|
|
@@ -135,6 +183,12 @@ int of_clk_set_defaults(struct device_no
|
|
if (!node)
|
|
return 0;
|
|
|
|
+ if (clk_supplier) {
|
|
+ rc = __set_clk_flags(node);
|
|
+ if (rc < 0)
|
|
+ return rc;
|
|
+ }
|
|
+
|
|
rc = __set_clk_parents(node, clk_supplier);
|
|
if (rc < 0)
|
|
return rc;
|
|
--- a/drivers/clk/clk.c
|
|
+++ b/drivers/clk/clk.c
|
|
@@ -4271,6 +4271,37 @@ struct clk *devm_clk_hw_get_clk(struct d
|
|
EXPORT_SYMBOL_GPL(devm_clk_hw_get_clk);
|
|
|
|
/*
|
|
+ * clk-conf helpers
|
|
+ */
|
|
+
|
|
+int __clk_protect(struct clk *clk)
|
|
+{
|
|
+ struct clk_core *core = clk->core;
|
|
+ int ret = 0;
|
|
+
|
|
+ clk_prepare_lock();
|
|
+
|
|
+ /*
|
|
+ * If CLK_IS_CRITICAL was set in the clock's init data, then
|
|
+ * the clock was already prepared/enabled when it was added.
|
|
+ */
|
|
+ if (core->flags & CLK_IS_CRITICAL)
|
|
+ goto out;
|
|
+
|
|
+ core->flags |= CLK_IS_CRITICAL;
|
|
+ ret = clk_core_prepare(core);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+
|
|
+ ret = clk_core_enable_lock(core);
|
|
+
|
|
+out:
|
|
+ clk_prepare_unlock();
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/*
|
|
* clkdev helpers
|
|
*/
|
|
|
|
--- a/drivers/clk/clk.h
|
|
+++ b/drivers/clk/clk.h
|
|
@@ -24,6 +24,7 @@ struct clk_hw *clk_find_hw(const char *d
|
|
#ifdef CONFIG_COMMON_CLK
|
|
struct clk *clk_hw_create_clk(struct device *dev, struct clk_hw *hw,
|
|
const char *dev_id, const char *con_id);
|
|
+int __clk_protect(struct clk *clk);
|
|
void __clk_put(struct clk *clk);
|
|
#else
|
|
/* All these casts to avoid ifdefs in clkdev... */
|
|
@@ -33,6 +34,7 @@ clk_hw_create_clk(struct device *dev, st
|
|
{
|
|
return (struct clk *)hw;
|
|
}
|
|
+static inline int __clk_protect(struct clk *clk) { return 0; }
|
|
static inline void __clk_put(struct clk *clk) { }
|
|
|
|
#endif
|