public inbox for linux-omap@vger.kernel.org
 help / color / mirror / Atom feed
From: Paul Walmsley <paul@pwsan.com>
To: linux-omap@vger.kernel.org
Cc: Paul Walmsley <paul@pwsan.com>
Subject: [PATCH 1/7] OMAP2/3 clock: implement clock notifier infrastructure
Date: Tue, 23 Dec 2008 03:13:51 -0700	[thread overview]
Message-ID: <20081223101349.22979.20585.stgit@localhost.localdomain> (raw)
In-Reply-To: <20081223101116.22979.53805.stgit@localhost.localdomain>

This patch implements the remaining code for notification of clock
rate changes via the clock framework:

- a notifier registration function, clk_notifier_register()

- a notifier unregistration function, clk_notifier_unregister()

The implementation is via an atomic notifier, called with the clockfw
spinlock held.  Callback functions must not sleep and must not re-enter
the clock framework, and should execute quickly.

There are likely to be few users of these notifiers, compared to the
total number of clocks.  So, rather than storing one notifier per
struct clk, notifiers are stored in a separate, dynamically allocated
list, effectively trading execution speed (in terms of a sequential
scan of the notifier list) for memory savings.  The implementation is
completely hidden from the callbacks and is easily changed if
necessary.

Until prototypes for these functions are made available in
include/linux/clk.h, drivers should pass function pointers to
clk_notifier_register() and clk_notifier_unregister() via their
platform_data struct.

Signed-off-by: Paul Walmsley <paul@pwsan.com>
---
 arch/arm/plat-omap/clock.c              |  122 +++++++++++++++++++++++++++++++
 arch/arm/plat-omap/include/mach/clock.h |   82 +++++++++++++++++++++
 2 files changed, 204 insertions(+), 0 deletions(-)

diff --git a/arch/arm/plat-omap/clock.c b/arch/arm/plat-omap/clock.c
index 8d43d78..4c2ed56 100644
--- a/arch/arm/plat-omap/clock.c
+++ b/arch/arm/plat-omap/clock.c
@@ -21,6 +21,7 @@
 #include <linux/mutex.h>
 #include <linux/platform_device.h>
 #include <linux/cpufreq.h>
+#include <linux/notifier.h>
 #include <linux/debugfs.h>
 #include <linux/io.h>
 #include <linux/bootmem.h>
@@ -34,6 +35,8 @@ static DEFINE_SPINLOCK(clockfw_lock);
 
 static struct clk_functions *arch_clock;
 
+static LIST_HEAD(clk_notifier_list);
+
 /**
  * omap_clk_for_each_child - call callback on each child clock of clk
  * @clk: struct clk * to use as the "parent"
@@ -526,6 +529,125 @@ void clk_init_cpufreq_table(struct cpufreq_frequency_table **table)
 EXPORT_SYMBOL(clk_init_cpufreq_table);
 #endif
 
+/**
+ * clk_notifier_register - add a clock parameter change notifier
+ * @clk: struct clk * to watch
+ * @nb: struct notifier_block * with callback info
+ *
+ * Request notification for changes to the clock 'clk'.  This uses an
+ * atomic notifier.  The callback will be called with interrupts
+ * disabled; therefore callback code should be very lightweight.
+ * Callback code must not call back into the clock framework.
+ * Callback code will be passed the previous and new rate of the
+ * clock.
+ *
+ * clk_notifier_register() must be called from process
+ * context.  Returns -EINVAL if called with null arguments, -ENOMEM
+ * upon allocation failure; otherwise, passes along the return value
+ * of atomic_notifier_chain_register().
+ */
+int clk_notifier_register(struct clk *clk, struct notifier_block *nb)
+{
+	struct clk_notifier *cn = NULL, *cn_new = NULL;
+	int r;
+	unsigned long flags;
+	struct clk *clkp;
+
+	if (!clk || !nb)
+		return -EINVAL;
+
+	/* Allocate this here speculatively so we can avoid GFP_ATOMIC */
+	cn_new = kzalloc(sizeof(struct clk_notifier), GFP_KERNEL);
+	if (!cn_new)
+		return -ENOMEM;
+
+	spin_lock_irqsave(&clockfw_lock, flags);
+
+	list_for_each_entry(cn, &clk_notifier_list, node) {
+		if (cn->clk == clk)
+			break;
+	}
+
+	if (cn->clk != clk) {
+		cn_new->clk = clk;
+		ATOMIC_INIT_NOTIFIER_HEAD(&cn_new->notifier_head);
+
+		list_add(&cn_new->node, &clk_notifier_list);
+		cn = cn_new;
+	} else {
+		kfree(cn_new); /* didn't need it after all */
+	}
+
+	r = atomic_notifier_chain_register(&cn->notifier_head, nb);
+	if (!r) {
+		clkp = clk;
+		do {
+			clkp->notifier_count++;
+		} while ((clkp = clkp->parent));
+	}
+
+	spin_unlock_irqrestore(&clockfw_lock, flags);
+
+	return r;
+}
+
+/**
+ * clk_notifier_unregister - remove a clock change notifier
+ * @clk: struct clk *
+ * @nb: struct notifier_block * with callback info
+ *
+ * Request no further notification for changes to clock 'clk'.  This
+ * function presently does not release memory allocated by its
+ * corresponding _register function; see inline comments for more
+ * information.  Returns -EINVAL if called with null arguments;
+ * otherwise, passes along the return value of
+ * atomic_notifier_chain_unregister().
+ */
+int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb)
+{
+	struct clk_notifier *cn = NULL;
+	struct clk *clkp;
+	int r = -EINVAL;
+	unsigned long flags;
+
+	if (!clk || !nb)
+		return -EINVAL;
+
+	spin_lock_irqsave(&clockfw_lock, flags);
+
+	list_for_each_entry(cn, &clk_notifier_list, node) {
+		if (cn->clk == clk)
+			break;
+	}
+
+	if (cn->clk == clk) {
+		r = atomic_notifier_chain_unregister(&cn->notifier_head, nb);
+
+		if (!r) {
+			clkp = clk;
+			do {
+				clkp->notifier_count--;
+			} while ((clkp = clkp->parent));
+		}
+
+		/*
+		 * XXX ugh, layering violation.  there should be some
+		 * support in the notifier code for this.
+		 */
+		if (!cn->notifier_head.head)
+			kfree(cn);
+
+	} else {
+		r = -ENOENT;
+	}
+
+	spin_unlock_irqrestore(&clockfw_lock, flags);
+
+	return r;
+}
+
+
+
 /*-------------------------------------------------------------------------*/
 
 #ifdef CONFIG_OMAP_RESET_CLOCKS
diff --git a/arch/arm/plat-omap/include/mach/clock.h b/arch/arm/plat-omap/include/mach/clock.h
index f0194bc..d08f16c 100644
--- a/arch/arm/plat-omap/include/mach/clock.h
+++ b/arch/arm/plat-omap/include/mach/clock.h
@@ -10,6 +10,8 @@
  * published by the Free Software Foundation.
  */
 
+#include <linux/notifier.h>
+
 #ifndef __ARCH_ARM_OMAP_CLOCK_H
 #define __ARCH_ARM_OMAP_CLOCK_H
 
@@ -71,6 +73,40 @@ struct clk_child {
 	u8			flags;
 };
 
+/**
+ * struct clk_notifier - associate a clk with a notifier
+ * @clk: struct clk * to associate the notifier with
+ * @notifier_head: an atomic_notifier_head for this clk
+ * @node: linked list pointers
+ *
+ * A list of struct clk_notifier is maintained by the notifier code.
+ * An entry is created whenever code registers the first notifier on a
+ * particular @clk.  Future notifiers on that @clk are added to the
+ * @notifier_head.
+ */
+struct clk_notifier {
+	struct clk			*clk;
+	struct atomic_notifier_head	notifier_head;
+	struct list_head		node;
+};
+
+/**
+ * struct clk_notifier_data - XXX documentation here
+ * @clk: struct clk * to associate the notifier with
+ * @old_rate: previous rate of this clock
+ * @new_rate: new rate of this clock
+ *
+ * new_rate is what the rate will be in the future if this is called
+ * in a pre-notifier, and is what the rate is now set to if called in
+ * a post-notifier.  old_rate is always the clock's rate before this
+ * particular rate change.
+ */
+struct clk_notifier_data {
+	struct clk		*clk;
+	unsigned long		old_rate;
+	unsigned long		new_rate;
+};
+
 struct clk {
 	struct list_head	node;
 	const char		*name;
@@ -87,6 +123,7 @@ struct clk {
 	void			(*init)(struct clk *);
 	int			(*enable)(struct clk *);
 	void			(*disable)(struct clk *);
+	u16			notifier_count;
 	__u8			enable_bit;
 	__s8			usecount;
 	u8			idlest_bit;
@@ -139,6 +176,8 @@ extern void followparent_recalc(struct clk *clk, unsigned long parent_rate,
 extern void clk_allow_idle(struct clk *clk);
 extern void clk_deny_idle(struct clk *clk);
 extern void clk_enable_init_clocks(void);
+extern int clk_notifier_register(struct clk *clk, struct notifier_block *nb);
+extern int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb);
 #ifdef CONFIG_CPU_FREQ
 extern void clk_init_cpufreq_table(struct cpufreq_frequency_table **table);
 #endif
@@ -196,4 +235,47 @@ void omap_clk_del_child(struct clk *clk, struct clk *clk2);
 #define CLK_REG_IN_PRM		(1 << 0)
 #define CLK_REG_IN_SCM		(1 << 1)
 
+/*
+ * Clk notifier callback types
+ *
+ * Since the notifier is called with interrupts disabled, any actions
+ * taken by callbacks must be extremely fast and lightweight.
+ *
+ * CLK_PREPARE_RATE_CHANGE: called by clock code to get pre-approval
+ *     for a rate change.  Upon receiving this notification, device
+ *     drivers should expect either a CLK_PRE_RATE_CHANGE event or a
+ *     CLK_ABORT_RATE_CHANGE event to follow shortly.  One example of
+ *     a possible action might be to switch to PIO mode for future
+ *     transfers until a CLK_ABORT_RATE_CHANGE or CLK_POST_RATE_CHANGE
+ *     message is received.  Drivers should return NOTIFY_DONE (*not*
+ *     NOTIFY_OK) if they approve the rate change, or return
+ *     NOTIFY_BAD if they do not approve the change.
+ *
+ * CLK_ABORT_RATE_CHANGE: called if one of the notifier callbacks
+ *     called with CLK_PREPARE_RATE_CHANGE refuses the rate change, or
+ *     if the rate change failed for some reason after
+ *     CLK_PRE_RATE_CHANGE.  In this case, all registered notifiers on
+ *     the clock will be called with CLK_ABORT_RATE_CHANGE -- even if
+ *     they had not yet received the CLK_PREPARE_RATE_CHANGE
+ *     notification. Callbacks must always return NOTIFY_DONE.
+ *
+ * CLK_PRE_RATE_CHANGE - called after all callbacks have approved the
+ *     rate change, immediately before the clock rate is changed, to
+ *     indicate that the rate change will proceed.  Drivers must
+ *     immediately terminate any operations that will be affected by
+ *     the rate change.  Note that the rate change could still fail,
+ *     at which point the driver should receive a
+ *     CLK_ABORT_RATE_CHANGE message.  Callbacks must always return
+ *     NOTIFY_DONE.
+ *
+ * CLK_POST_RATE_CHANGE - called after the clock rate change has
+ *     successfully completed.  Callbacks must always return
+ *     NOTIFY_DONE.
+ *
+ */
+#define CLK_PREPARE_RATE_CHANGE		1
+#define CLK_ABORT_RATE_CHANGE		2
+#define CLK_PRE_RATE_CHANGE		3
+#define CLK_POST_RATE_CHANGE		4
+
 #endif



  reply	other threads:[~2008-12-23 10:14 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2008-12-23 10:13 [PATCH 0/7] OMAP2/3 clock: implement clock rate change notifiers Paul Walmsley
2008-12-23 10:13 ` Paul Walmsley [this message]
2008-12-23 10:13 ` [PATCH 2/7] OMAP clock: add notifier infrastructure Paul Walmsley
2008-12-23 10:13 ` [PATCH 3/7] OMAP2/3 clock: store planned clock rates into temporary rate storage Paul Walmsley
2008-12-23 10:13 ` [PATCH 4/7] OMAP2/3 clock: add clk post-rate-change notifiers Paul Walmsley
2008-12-23 10:13 ` [PATCH 6/7] OMAP2/3 clock: add clock prepare-rate-change notifications Paul Walmsley
2008-12-23 10:13 ` [PATCH 5/7] OMAP2/3 clock: add clock pre-rate-change notification Paul Walmsley
2008-12-23 10:13 ` [PATCH 7/7] OMAP2/3 clock: add clock abort-rate-change notifications Paul Walmsley
2009-01-13 17:01 ` [PATCH 0/7] OMAP2/3 clock: implement clock rate change notifiers Kevin Hilman

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20081223101349.22979.20585.stgit@localhost.localdomain \
    --to=paul@pwsan.com \
    --cc=linux-omap@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox