* [PATCH v3 1/7] time: Serialize calls to 'clockevents_update_freq' in the timing core
2014-02-03 22:34 [PATCH v3 0/7] ARM: zynq: cpufreq support Soren Brinkmann
@ 2014-02-03 22:34 ` Soren Brinkmann
2014-02-07 14:40 ` [tip:timers/core] clockevents: Serialize calls to clockevents_update_freq() in the core tip-bot for Thomas Gleixner
2014-02-03 22:34 ` [PATCH v3 2/7] time: clockevents: Adjust timer interval when frequency changes Soren Brinkmann
` (5 subsequent siblings)
6 siblings, 1 reply; 12+ messages in thread
From: Soren Brinkmann @ 2014-02-03 22:34 UTC (permalink / raw)
To: Thomas Gleixner, Daniel Lezcano, Michal Simek
Cc: linux-kernel, linux-arm-kernel, Sören Brinkmann
From: Thomas Gleixner <tglx@linutronix.de>
We can identify the broadcast device in the core and serialize all
callers including interrupts on a different CPU against the update.
Also, disabling interrupts is moved into the core allowing callers to
leave interrutps enabled when calling clockevents_update_freq().
Cc: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Soren Brinkmann <soren.brinkmann@xilinx.com>
---
kernel/time/clockevents.c | 29 ++++++++++++++++++++++-------
kernel/time/tick-broadcast.c | 25 +++++++++++++++++++------
kernel/time/tick-internal.h | 4 ++++
3 files changed, 45 insertions(+), 13 deletions(-)
diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c
index 086ad6043bcb..641d91003a45 100644
--- a/kernel/time/clockevents.c
+++ b/kernel/time/clockevents.c
@@ -439,6 +439,16 @@ void clockevents_config_and_register(struct clock_event_device *dev,
}
EXPORT_SYMBOL_GPL(clockevents_config_and_register);
+int __clockevents_update_freq(struct clock_event_device *dev, u32 freq)
+{
+ clockevents_config(dev, freq);
+
+ if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
+ return 0;
+
+ return clockevents_program_event(dev, dev->next_event, false);
+}
+
/**
* clockevents_update_freq - Update frequency and reprogram a clock event device.
* @dev: device to modify
@@ -446,17 +456,22 @@ EXPORT_SYMBOL_GPL(clockevents_config_and_register);
*
* Reconfigure and reprogram a clock event device in oneshot
* mode. Must be called on the cpu for which the device delivers per
- * cpu timer events with interrupts disabled! Returns 0 on success,
- * -ETIME when the event is in the past.
+ * cpu timer events. If called for the broadcast device the core takes
+ * care of serialization.
+ *
+ * Returns 0 on success, -ETIME when the event is in the past.
*/
int clockevents_update_freq(struct clock_event_device *dev, u32 freq)
{
- clockevents_config(dev, freq);
-
- if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
- return 0;
+ unsigned long flags;
+ int ret;
- return clockevents_program_event(dev, dev->next_event, false);
+ local_irq_save(flags);
+ ret = tick_broadcast_update_freq(dev, freq);
+ if (ret == -ENODEV)
+ ret = __clockevents_update_freq(dev, freq);
+ local_irq_restore(flags);
+ return ret;
}
/*
diff --git a/kernel/time/tick-broadcast.c b/kernel/time/tick-broadcast.c
index 43780ab5e279..003e6c3663b1 100644
--- a/kernel/time/tick-broadcast.c
+++ b/kernel/time/tick-broadcast.c
@@ -120,6 +120,19 @@ int tick_is_broadcast_device(struct clock_event_device *dev)
return (dev && tick_broadcast_device.evtdev == dev);
}
+int tick_broadcast_update_freq(struct clock_event_device *dev, u32 freq)
+{
+ int ret = -ENODEV;
+
+ if (tick_is_broadcast_device(dev)) {
+ raw_spin_lock(&tick_broadcast_lock);
+ ret = __clockevents_update_freq(dev, freq);
+ raw_spin_unlock(&tick_broadcast_lock);
+ }
+ return ret;
+}
+
+
static void err_broadcast(const struct cpumask *mask)
{
pr_crit_once("Failed to broadcast timer tick. Some CPUs may be unresponsive.\n");
@@ -272,12 +285,8 @@ static void tick_do_broadcast(struct cpumask *mask)
*/
static void tick_do_periodic_broadcast(void)
{
- raw_spin_lock(&tick_broadcast_lock);
-
cpumask_and(tmpmask, cpu_online_mask, tick_broadcast_mask);
tick_do_broadcast(tmpmask);
-
- raw_spin_unlock(&tick_broadcast_lock);
}
/*
@@ -287,13 +296,15 @@ static void tick_handle_periodic_broadcast(struct clock_event_device *dev)
{
ktime_t next;
+ raw_spin_lock(&tick_broadcast_lock);
+
tick_do_periodic_broadcast();
/*
* The device is in periodic mode. No reprogramming necessary:
*/
if (dev->mode == CLOCK_EVT_MODE_PERIODIC)
- return;
+ goto unlock;
/*
* Setup the next period for devices, which do not have
@@ -306,9 +317,11 @@ static void tick_handle_periodic_broadcast(struct clock_event_device *dev)
next = ktime_add(next, tick_period);
if (!clockevents_program_event(dev, next, false))
- return;
+ goto unlock;
tick_do_periodic_broadcast();
}
+unlock:
+ raw_spin_unlock(&tick_broadcast_lock);
}
/*
diff --git a/kernel/time/tick-internal.h b/kernel/time/tick-internal.h
index 8329669b51ec..26f1c0ba9d81 100644
--- a/kernel/time/tick-internal.h
+++ b/kernel/time/tick-internal.h
@@ -111,6 +111,7 @@ extern int tick_resume_broadcast(void);
extern void tick_broadcast_init(void);
extern void
tick_set_periodic_handler(struct clock_event_device *dev, int broadcast);
+int tick_broadcast_update_freq(struct clock_event_device *dev, u32 freq);
#else /* !BROADCAST */
@@ -133,6 +134,8 @@ static inline void tick_shutdown_broadcast(unsigned int *cpup) { }
static inline void tick_suspend_broadcast(void) { }
static inline int tick_resume_broadcast(void) { return 0; }
static inline void tick_broadcast_init(void) { }
+static inline int tick_broadcast_update_freq(struct clock_event_device *dev,
+ u32 freq) { return -ENODEV; }
/*
* Set the periodic handler in non broadcast mode
@@ -154,5 +157,6 @@ static inline int tick_device_is_functional(struct clock_event_device *dev)
#endif
+int __clockevents_update_freq(struct clock_event_device *dev, u32 freq);
extern void do_timer(unsigned long ticks);
extern void update_wall_time(void);
--
1.8.5.3
^ permalink raw reply [flat|nested] 12+ messages in thread* [tip:timers/core] clockevents: Serialize calls to clockevents_update_freq() in the core
2014-02-03 22:34 ` [PATCH v3 1/7] time: Serialize calls to 'clockevents_update_freq' in the timing core Soren Brinkmann
@ 2014-02-07 14:40 ` tip-bot for Thomas Gleixner
0 siblings, 0 replies; 12+ messages in thread
From: tip-bot for Thomas Gleixner @ 2014-02-07 14:40 UTC (permalink / raw)
To: linux-tip-commits
Cc: linux-kernel, soren.brinkmann, hpa, mingo, tglx, michal.simek,
daniel.lezcano
Commit-ID: 627ee7947e2e83ba565c31c5c9373d6e364b1ecd
Gitweb: http://git.kernel.org/tip/627ee7947e2e83ba565c31c5c9373d6e364b1ecd
Author: Thomas Gleixner <tglx@linutronix.de>
AuthorDate: Mon, 3 Feb 2014 14:34:31 -0800
Committer: Thomas Gleixner <tglx@linutronix.de>
CommitDate: Fri, 7 Feb 2014 15:34:28 +0100
clockevents: Serialize calls to clockevents_update_freq() in the core
We can identify the broadcast device in the core and serialize all
callers including interrupts on a different CPU against the update.
Also, disabling interrupts is moved into the core allowing callers to
leave interrutps enabled when calling clockevents_update_freq().
Signed-off-by: Soren Brinkmann <soren.brinkmann@xilinx.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: Soeren Brinkmann <soren.brinkmann@xilinx.com>
Cc: Daniel Lezcano <daniel.lezcano@linaro.org>
Cc: Michal Simek <michal.simek@xilinx.com>
Link: http://lkml.kernel.org/r/1391466877-28908-2-git-send-email-soren.brinkmann@xilinx.com
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
---
kernel/time/clockevents.c | 29 ++++++++++++++++++++++-------
kernel/time/tick-broadcast.c | 25 +++++++++++++++++++------
kernel/time/tick-internal.h | 4 ++++
3 files changed, 45 insertions(+), 13 deletions(-)
diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c
index 086ad60..641d910 100644
--- a/kernel/time/clockevents.c
+++ b/kernel/time/clockevents.c
@@ -439,6 +439,16 @@ void clockevents_config_and_register(struct clock_event_device *dev,
}
EXPORT_SYMBOL_GPL(clockevents_config_and_register);
+int __clockevents_update_freq(struct clock_event_device *dev, u32 freq)
+{
+ clockevents_config(dev, freq);
+
+ if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
+ return 0;
+
+ return clockevents_program_event(dev, dev->next_event, false);
+}
+
/**
* clockevents_update_freq - Update frequency and reprogram a clock event device.
* @dev: device to modify
@@ -446,17 +456,22 @@ EXPORT_SYMBOL_GPL(clockevents_config_and_register);
*
* Reconfigure and reprogram a clock event device in oneshot
* mode. Must be called on the cpu for which the device delivers per
- * cpu timer events with interrupts disabled! Returns 0 on success,
- * -ETIME when the event is in the past.
+ * cpu timer events. If called for the broadcast device the core takes
+ * care of serialization.
+ *
+ * Returns 0 on success, -ETIME when the event is in the past.
*/
int clockevents_update_freq(struct clock_event_device *dev, u32 freq)
{
- clockevents_config(dev, freq);
-
- if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
- return 0;
+ unsigned long flags;
+ int ret;
- return clockevents_program_event(dev, dev->next_event, false);
+ local_irq_save(flags);
+ ret = tick_broadcast_update_freq(dev, freq);
+ if (ret == -ENODEV)
+ ret = __clockevents_update_freq(dev, freq);
+ local_irq_restore(flags);
+ return ret;
}
/*
diff --git a/kernel/time/tick-broadcast.c b/kernel/time/tick-broadcast.c
index 43780ab..003e6c3 100644
--- a/kernel/time/tick-broadcast.c
+++ b/kernel/time/tick-broadcast.c
@@ -120,6 +120,19 @@ int tick_is_broadcast_device(struct clock_event_device *dev)
return (dev && tick_broadcast_device.evtdev == dev);
}
+int tick_broadcast_update_freq(struct clock_event_device *dev, u32 freq)
+{
+ int ret = -ENODEV;
+
+ if (tick_is_broadcast_device(dev)) {
+ raw_spin_lock(&tick_broadcast_lock);
+ ret = __clockevents_update_freq(dev, freq);
+ raw_spin_unlock(&tick_broadcast_lock);
+ }
+ return ret;
+}
+
+
static void err_broadcast(const struct cpumask *mask)
{
pr_crit_once("Failed to broadcast timer tick. Some CPUs may be unresponsive.\n");
@@ -272,12 +285,8 @@ static void tick_do_broadcast(struct cpumask *mask)
*/
static void tick_do_periodic_broadcast(void)
{
- raw_spin_lock(&tick_broadcast_lock);
-
cpumask_and(tmpmask, cpu_online_mask, tick_broadcast_mask);
tick_do_broadcast(tmpmask);
-
- raw_spin_unlock(&tick_broadcast_lock);
}
/*
@@ -287,13 +296,15 @@ static void tick_handle_periodic_broadcast(struct clock_event_device *dev)
{
ktime_t next;
+ raw_spin_lock(&tick_broadcast_lock);
+
tick_do_periodic_broadcast();
/*
* The device is in periodic mode. No reprogramming necessary:
*/
if (dev->mode == CLOCK_EVT_MODE_PERIODIC)
- return;
+ goto unlock;
/*
* Setup the next period for devices, which do not have
@@ -306,9 +317,11 @@ static void tick_handle_periodic_broadcast(struct clock_event_device *dev)
next = ktime_add(next, tick_period);
if (!clockevents_program_event(dev, next, false))
- return;
+ goto unlock;
tick_do_periodic_broadcast();
}
+unlock:
+ raw_spin_unlock(&tick_broadcast_lock);
}
/*
diff --git a/kernel/time/tick-internal.h b/kernel/time/tick-internal.h
index 8329669..26f1c0b 100644
--- a/kernel/time/tick-internal.h
+++ b/kernel/time/tick-internal.h
@@ -111,6 +111,7 @@ extern int tick_resume_broadcast(void);
extern void tick_broadcast_init(void);
extern void
tick_set_periodic_handler(struct clock_event_device *dev, int broadcast);
+int tick_broadcast_update_freq(struct clock_event_device *dev, u32 freq);
#else /* !BROADCAST */
@@ -133,6 +134,8 @@ static inline void tick_shutdown_broadcast(unsigned int *cpup) { }
static inline void tick_suspend_broadcast(void) { }
static inline int tick_resume_broadcast(void) { return 0; }
static inline void tick_broadcast_init(void) { }
+static inline int tick_broadcast_update_freq(struct clock_event_device *dev,
+ u32 freq) { return -ENODEV; }
/*
* Set the periodic handler in non broadcast mode
@@ -154,5 +157,6 @@ static inline int tick_device_is_functional(struct clock_event_device *dev)
#endif
+int __clockevents_update_freq(struct clock_event_device *dev, u32 freq);
extern void do_timer(unsigned long ticks);
extern void update_wall_time(void);
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 2/7] time: clockevents: Adjust timer interval when frequency changes
2014-02-03 22:34 [PATCH v3 0/7] ARM: zynq: cpufreq support Soren Brinkmann
2014-02-03 22:34 ` [PATCH v3 1/7] time: Serialize calls to 'clockevents_update_freq' in the timing core Soren Brinkmann
@ 2014-02-03 22:34 ` Soren Brinkmann
2014-02-07 14:40 ` [tip:timers/core] " tip-bot for Soren Brinkmann
2014-02-03 22:34 ` [PATCH v3 3/7] clocksource/cadence_ttc: Call clockevents_update_freq() with IRQs enabled Soren Brinkmann
` (4 subsequent siblings)
6 siblings, 1 reply; 12+ messages in thread
From: Soren Brinkmann @ 2014-02-03 22:34 UTC (permalink / raw)
To: Thomas Gleixner, Daniel Lezcano, Michal Simek
Cc: linux-kernel, linux-arm-kernel, Sören Brinkmann
To adjust the timer's interval in periodic mode, the clockevent device
is put into periodic mode during clockevents_update_freq() in case the
timer is in periodic mode.
Cc: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Soren Brinkmann <soren.brinkmann@xilinx.com>
---
It is assumed that simply calling the timer's 'set_mode()' API takes care of
programming an appropriate interval based on the current timer frequency.
If that doesn't work for all timers, I guess it might require a new API
call.
---
kernel/time/clockevents.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c
index 641d91003a45..f85e5fda9c66 100644
--- a/kernel/time/clockevents.c
+++ b/kernel/time/clockevents.c
@@ -443,10 +443,13 @@ int __clockevents_update_freq(struct clock_event_device *dev, u32 freq)
{
clockevents_config(dev, freq);
- if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
- return 0;
+ if (dev->mode == CLOCK_EVT_MODE_ONESHOT)
+ return clockevents_program_event(dev, dev->next_event, false);
+
+ if (dev->mode == CLOCK_EVT_MODE_PERIODIC)
+ dev->set_mode(CLOCK_EVT_MODE_PERIODIC, dev);
- return clockevents_program_event(dev, dev->next_event, false);
+ return 0;
}
/**
--
1.8.5.3
^ permalink raw reply [flat|nested] 12+ messages in thread* [tip:timers/core] clockevents: Adjust timer interval when frequency changes
2014-02-03 22:34 ` [PATCH v3 2/7] time: clockevents: Adjust timer interval when frequency changes Soren Brinkmann
@ 2014-02-07 14:40 ` tip-bot for Soren Brinkmann
0 siblings, 0 replies; 12+ messages in thread
From: tip-bot for Soren Brinkmann @ 2014-02-07 14:40 UTC (permalink / raw)
To: linux-tip-commits
Cc: linux-kernel, soren.brinkmann, hpa, mingo, tglx, michal.simek,
daniel.lezcano
Commit-ID: fe79a9ba11962a603fb6af68fcb476e64031e46c
Gitweb: http://git.kernel.org/tip/fe79a9ba11962a603fb6af68fcb476e64031e46c
Author: Soren Brinkmann <soren.brinkmann@xilinx.com>
AuthorDate: Mon, 3 Feb 2014 14:34:32 -0800
Committer: Thomas Gleixner <tglx@linutronix.de>
CommitDate: Fri, 7 Feb 2014 15:34:29 +0100
clockevents: Adjust timer interval when frequency changes
clockevent devices in periodic mode are not updated when the frequency
of the device changes. Issue a dev->set_mode() callback which forces
the device to reevaluate the timer settings.
Signed-off-by: Soren Brinkmann <soren.brinkmann@xilinx.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: Daniel Lezcano <daniel.lezcano@linaro.org>
Cc: Michal Simek <michal.simek@xilinx.com>
Link: http://lkml.kernel.org/r/1391466877-28908-3-git-send-email-soren.brinkmann@xilinx.com
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
---
kernel/time/clockevents.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c
index 641d910..f85e5fd 100644
--- a/kernel/time/clockevents.c
+++ b/kernel/time/clockevents.c
@@ -443,10 +443,13 @@ int __clockevents_update_freq(struct clock_event_device *dev, u32 freq)
{
clockevents_config(dev, freq);
- if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
- return 0;
+ if (dev->mode == CLOCK_EVT_MODE_ONESHOT)
+ return clockevents_program_event(dev, dev->next_event, false);
+
+ if (dev->mode == CLOCK_EVT_MODE_PERIODIC)
+ dev->set_mode(CLOCK_EVT_MODE_PERIODIC, dev);
- return clockevents_program_event(dev, dev->next_event, false);
+ return 0;
}
/**
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 3/7] clocksource/cadence_ttc: Call clockevents_update_freq() with IRQs enabled
2014-02-03 22:34 [PATCH v3 0/7] ARM: zynq: cpufreq support Soren Brinkmann
2014-02-03 22:34 ` [PATCH v3 1/7] time: Serialize calls to 'clockevents_update_freq' in the timing core Soren Brinkmann
2014-02-03 22:34 ` [PATCH v3 2/7] time: clockevents: Adjust timer interval when frequency changes Soren Brinkmann
@ 2014-02-03 22:34 ` Soren Brinkmann
2014-02-03 22:34 ` [PATCH v3 4/7] clocksource/cadence_ttc: Overhaul clocksource frequency adjustment Soren Brinkmann
` (3 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Soren Brinkmann @ 2014-02-03 22:34 UTC (permalink / raw)
To: Thomas Gleixner, Daniel Lezcano, Michal Simek
Cc: linux-kernel, linux-arm-kernel, Sören Brinkmann
The timer core takes care of serialization and IRQs. Hence the driver is
no longer required to disable interrupts when calling
clockevents_update_freq().
Signed-off-by: Soren Brinkmann <soren.brinkmann@xilinx.com>
---
drivers/clocksource/cadence_ttc_timer.c | 17 ++---------------
1 file changed, 2 insertions(+), 15 deletions(-)
diff --git a/drivers/clocksource/cadence_ttc_timer.c b/drivers/clocksource/cadence_ttc_timer.c
index 63f176de0d02..f05b9fdc7425 100644
--- a/drivers/clocksource/cadence_ttc_timer.c
+++ b/drivers/clocksource/cadence_ttc_timer.c
@@ -321,25 +321,12 @@ static int ttc_rate_change_clockevent_cb(struct notifier_block *nb,
switch (event) {
case POST_RATE_CHANGE:
- {
- unsigned long flags;
-
- /*
- * clockevents_update_freq should be called with IRQ disabled on
- * the CPU the timer provides events for. The timer we use is
- * common to both CPUs, not sure if we need to run on both
- * cores.
- */
- local_irq_save(flags);
- clockevents_update_freq(&ttcce->ce,
- ndata->new_rate / PRESCALE);
- local_irq_restore(flags);
-
/* update cached frequency */
ttc->freq = ndata->new_rate;
+ clockevents_update_freq(&ttcce->ce, ndata->new_rate / PRESCALE);
+
/* fall through */
- }
case PRE_RATE_CHANGE:
case ABORT_RATE_CHANGE:
default:
--
1.8.5.3
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v3 4/7] clocksource/cadence_ttc: Overhaul clocksource frequency adjustment
2014-02-03 22:34 [PATCH v3 0/7] ARM: zynq: cpufreq support Soren Brinkmann
` (2 preceding siblings ...)
2014-02-03 22:34 ` [PATCH v3 3/7] clocksource/cadence_ttc: Call clockevents_update_freq() with IRQs enabled Soren Brinkmann
@ 2014-02-03 22:34 ` Soren Brinkmann
2014-02-03 22:34 ` [PATCH v3 5/7] clocksource/cadence_ttc: Use only one counter Soren Brinkmann
` (2 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Soren Brinkmann @ 2014-02-03 22:34 UTC (permalink / raw)
To: Thomas Gleixner, Daniel Lezcano, Michal Simek
Cc: linux-kernel, linux-arm-kernel, Sören Brinkmann
The currently used method adjusting the clocksource to a changing input
frequency does not work on kernels from 3.11 on.
The new approach is to keep the timer frequency as constant as possible.
I.e.
- due to the TTC's prescaler limitations, allow frequency changes
only if the frequency scales by a power of 2
- adjust the counter's divider on the fly when a frequency change
occurs
This limits cpufreq to scale by certain factors only.
But we may keep the time base somewhat constant, so that sleep() & co
keep working as expected, while supporting cpufreq.
Signed-off-by: Soren Brinkmann <soren.brinkmann@xilinx.com>
---
v3:
- Clock notifier data does not behave as documented. Adjust code
accordingly (https://lkml.org/lkml/2014/1/22/412)
v2:
- remove PM notifier and associated code
Signed-off-by: Soren Brinkmann <soren.brinkmann@xilinx.com>
---
drivers/clocksource/cadence_ttc_timer.c | 104 ++++++++++++++++++++++++++------
1 file changed, 84 insertions(+), 20 deletions(-)
diff --git a/drivers/clocksource/cadence_ttc_timer.c b/drivers/clocksource/cadence_ttc_timer.c
index f05b9fdc7425..49fbe2847c84 100644
--- a/drivers/clocksource/cadence_ttc_timer.c
+++ b/drivers/clocksource/cadence_ttc_timer.c
@@ -16,6 +16,7 @@
*/
#include <linux/clk.h>
+#include <linux/clk-provider.h>
#include <linux/interrupt.h>
#include <linux/clockchips.h>
#include <linux/of_address.h>
@@ -52,6 +53,8 @@
#define TTC_CNT_CNTRL_DISABLE_MASK 0x1
#define TTC_CLK_CNTRL_CSRC_MASK (1 << 5) /* clock source */
+#define TTC_CLK_CNTRL_PSV_MASK 0x1e
+#define TTC_CLK_CNTRL_PSV_SHIFT 1
/*
* Setup the timers to use pre-scaling, using a fixed value for now that will
@@ -63,6 +66,8 @@
#define CLK_CNTRL_PRESCALE_EN 1
#define CNT_CNTRL_RESET (1 << 4)
+#define MAX_F_ERR 50
+
/**
* struct ttc_timer - This definition defines local timer structure
*
@@ -82,6 +87,8 @@ struct ttc_timer {
container_of(x, struct ttc_timer, clk_rate_change_nb)
struct ttc_timer_clocksource {
+ u32 scale_clk_ctrl_reg_old;
+ u32 scale_clk_ctrl_reg_new;
struct ttc_timer ttc;
struct clocksource cs;
};
@@ -229,32 +236,89 @@ static int ttc_rate_change_clocksource_cb(struct notifier_block *nb,
struct ttc_timer_clocksource, ttc);
switch (event) {
- case POST_RATE_CHANGE:
+ case PRE_RATE_CHANGE:
+ {
+ u32 psv;
+ unsigned long factor, rate_low, rate_high;
+
+ if (ndata->new_rate > ndata->old_rate) {
+ factor = DIV_ROUND_CLOSEST(ndata->new_rate,
+ ndata->old_rate);
+ rate_low = ndata->old_rate;
+ rate_high = ndata->new_rate;
+ } else {
+ factor = DIV_ROUND_CLOSEST(ndata->old_rate,
+ ndata->new_rate);
+ rate_low = ndata->new_rate;
+ rate_high = ndata->old_rate;
+ }
+
+ if (!is_power_of_2(factor))
+ return NOTIFY_BAD;
+
+ if (abs(rate_high - (factor * rate_low)) > MAX_F_ERR)
+ return NOTIFY_BAD;
+
+ factor = __ilog2_u32(factor);
+
/*
- * Do whatever is necessary to maintain a proper time base
- *
- * I cannot find a way to adjust the currently used clocksource
- * to the new frequency. __clocksource_updatefreq_hz() sounds
- * good, but does not work. Not sure what's that missing.
- *
- * This approach works, but triggers two clocksource switches.
- * The first after unregister to clocksource jiffies. And
- * another one after the register to the newly registered timer.
- *
- * Alternatively we could 'waste' another HW timer to ping pong
- * between clock sources. That would also use one register and
- * one unregister call, but only trigger one clocksource switch
- * for the cost of another HW timer used by the OS.
+ * store timer clock ctrl register so we can restore it in case
+ * of an abort.
*/
- clocksource_unregister(&ttccs->cs);
- clocksource_register_hz(&ttccs->cs,
- ndata->new_rate / PRESCALE);
- /* fall through */
- case PRE_RATE_CHANGE:
+ ttccs->scale_clk_ctrl_reg_old =
+ __raw_readl(ttccs->ttc.base_addr +
+ TTC_CLK_CNTRL_OFFSET);
+
+ psv = (ttccs->scale_clk_ctrl_reg_old &
+ TTC_CLK_CNTRL_PSV_MASK) >>
+ TTC_CLK_CNTRL_PSV_SHIFT;
+ if (ndata->new_rate < ndata->old_rate)
+ psv -= factor;
+ else
+ psv += factor;
+
+ /* prescaler within legal range? */
+ if (psv & ~(TTC_CLK_CNTRL_PSV_MASK >> TTC_CLK_CNTRL_PSV_SHIFT))
+ return NOTIFY_BAD;
+
+ ttccs->scale_clk_ctrl_reg_new = ttccs->scale_clk_ctrl_reg_old &
+ ~TTC_CLK_CNTRL_PSV_MASK;
+ ttccs->scale_clk_ctrl_reg_new |= psv << TTC_CLK_CNTRL_PSV_SHIFT;
+
+
+ /* scale down: adjust divider in post-change notification */
+ if (ndata->new_rate < ndata->old_rate)
+ return NOTIFY_DONE;
+
+ /* scale up: adjust divider now - before frequency change */
+ __raw_writel(ttccs->scale_clk_ctrl_reg_new,
+ ttccs->ttc.base_addr + TTC_CLK_CNTRL_OFFSET);
+ break;
+ }
+ case POST_RATE_CHANGE:
+ /* scale up: pre-change notification did the adjustment */
+ if (ndata->new_rate > ndata->old_rate)
+ return NOTIFY_OK;
+
+ /* scale down: adjust divider now - after frequency change */
+ __raw_writel(ttccs->scale_clk_ctrl_reg_new,
+ ttccs->ttc.base_addr + TTC_CLK_CNTRL_OFFSET);
+ break;
+
case ABORT_RATE_CHANGE:
+ /* we have to undo the adjustment in case we scale up */
+ if (ndata->new_rate < ndata->old_rate)
+ return NOTIFY_OK;
+
+ /* restore original register value */
+ __raw_writel(ttccs->scale_clk_ctrl_reg_old,
+ ttccs->ttc.base_addr + TTC_CLK_CNTRL_OFFSET);
+ /* fall through */
default:
return NOTIFY_DONE;
}
+
+ return NOTIFY_DONE;
}
static void __init ttc_setup_clocksource(struct clk *clk, void __iomem *base)
--
1.8.5.3
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v3 5/7] clocksource/cadence_ttc: Use only one counter
2014-02-03 22:34 [PATCH v3 0/7] ARM: zynq: cpufreq support Soren Brinkmann
` (3 preceding siblings ...)
2014-02-03 22:34 ` [PATCH v3 4/7] clocksource/cadence_ttc: Overhaul clocksource frequency adjustment Soren Brinkmann
@ 2014-02-03 22:34 ` Soren Brinkmann
2014-02-04 20:41 ` Thomas Gleixner
2014-02-03 22:34 ` [PATCH v3 6/7] arm: zynq: Don't use arm_global_timer with cpufreq Soren Brinkmann
2014-02-03 22:34 ` [PATCH v3 7/7] arm: zynq: Add support for cpufreq Soren Brinkmann
6 siblings, 1 reply; 12+ messages in thread
From: Soren Brinkmann @ 2014-02-03 22:34 UTC (permalink / raw)
To: Thomas Gleixner, Daniel Lezcano, Michal Simek
Cc: linux-kernel, linux-arm-kernel, Sören Brinkmann
Currently the driver uses two of the three counters the TTC provides to
implement a clocksource and a clockevent device. By using the TTC's
match feature we can implement both use cases using a single counter
only.
The old approach is to use timer over-/underflow to generate an
interrupt. Using the match register allows to generate an interrupt on
arbitrary counter values. This way a dedicated clockevent counter can be
avoided.
Signed-off-by: Soren Brinkmann <soren.brinkmann@xilinx.com>
---
drivers/clocksource/cadence_ttc_timer.c | 92 +++++++++++----------------------
1 file changed, 31 insertions(+), 61 deletions(-)
diff --git a/drivers/clocksource/cadence_ttc_timer.c b/drivers/clocksource/cadence_ttc_timer.c
index 49fbe2847c84..d922e982af95 100644
--- a/drivers/clocksource/cadence_ttc_timer.c
+++ b/drivers/clocksource/cadence_ttc_timer.c
@@ -47,6 +47,7 @@
#define TTC_CNT_CNTRL_OFFSET 0x0C /* Counter Control Reg, RW */
#define TTC_COUNT_VAL_OFFSET 0x18 /* Counter Value Reg, RO */
#define TTC_INTR_VAL_OFFSET 0x24 /* Interval Count Reg, RW */
+#define TTC_MATCH1_OFFSET 0x30 /* Match reg, RW */
#define TTC_ISR_OFFSET 0x54 /* Interrupt Status Reg, RO */
#define TTC_IER_OFFSET 0x60 /* Interrupt Enable Reg, RW */
@@ -64,7 +65,10 @@
#define PRESCALE 2048 /* The exponent must match this */
#define CLK_CNTRL_PRESCALE ((PRESCALE_EXPONENT - 1) << 1)
#define CLK_CNTRL_PRESCALE_EN 1
-#define CNT_CNTRL_RESET (1 << 4)
+#define CNT_CNTRL_RESET BIT(4)
+#define CNT_CNTRL_MATCH BIT(3)
+
+#define TTC_INTERRUPT_MATCH1 BIT(1)
#define MAX_F_ERR 50
@@ -99,6 +103,7 @@ struct ttc_timer_clocksource {
struct ttc_timer_clockevent {
struct ttc_timer ttc;
struct clock_event_device ce;
+ unsigned long interval;
};
#define to_ttc_timer_clkevent(x) \
@@ -112,25 +117,20 @@ static void __iomem *ttc_sched_clock_val_reg;
* @timer: Pointer to the timer instance
* @cycles: Timer interval ticks
**/
-static void ttc_set_interval(struct ttc_timer *timer,
- unsigned long cycles)
+static void ttc_set_interval(struct ttc_timer *timer, unsigned long cycles)
{
- u32 ctrl_reg;
+ struct ttc_timer_clockevent *ttcce = container_of(timer,
+ struct ttc_timer_clockevent, ttc);
- /* Disable the counter, set the counter value and re-enable counter */
- ctrl_reg = __raw_readl(timer->base_addr + TTC_CNT_CNTRL_OFFSET);
- ctrl_reg |= TTC_CNT_CNTRL_DISABLE_MASK;
- __raw_writel(ctrl_reg, timer->base_addr + TTC_CNT_CNTRL_OFFSET);
+ /* set interval */
+ u32 reg = __raw_readl(timer->base_addr + TTC_COUNT_VAL_OFFSET);
+ reg += cycles;
+ __raw_writel(reg, timer->base_addr + TTC_MATCH1_OFFSET);
- __raw_writel(cycles, timer->base_addr + TTC_INTR_VAL_OFFSET);
+ /* enable match interrupt */
+ __raw_writel(TTC_INTERRUPT_MATCH1, timer->base_addr + TTC_IER_OFFSET);
- /*
- * Reset the counter (0x10) so that it starts from 0, one-shot
- * mode makes this needed for timing to be right.
- */
- ctrl_reg |= CNT_CNTRL_RESET;
- ctrl_reg &= ~TTC_CNT_CNTRL_DISABLE_MASK;
- __raw_writel(ctrl_reg, timer->base_addr + TTC_CNT_CNTRL_OFFSET);
+ ttcce->interval = cycles;
}
/**
@@ -148,6 +148,8 @@ static irqreturn_t ttc_clock_event_interrupt(int irq, void *dev_id)
/* Acknowledge the interrupt and call event handler */
__raw_readl(timer->base_addr + TTC_ISR_OFFSET);
+ if (ttce->ce.mode == CLOCK_EVT_MODE_PERIODIC)
+ ttc_set_interval(timer, ttce->interval);
ttce->ce.event_handler(&ttce->ce);
@@ -201,7 +203,6 @@ static void ttc_set_mode(enum clock_event_mode mode,
{
struct ttc_timer_clockevent *ttce = to_ttc_timer_clkevent(evt);
struct ttc_timer *timer = &ttce->ttc;
- u32 ctrl_reg;
switch (mode) {
case CLOCK_EVT_MODE_PERIODIC:
@@ -211,18 +212,9 @@ static void ttc_set_mode(enum clock_event_mode mode,
case CLOCK_EVT_MODE_ONESHOT:
case CLOCK_EVT_MODE_UNUSED:
case CLOCK_EVT_MODE_SHUTDOWN:
- ctrl_reg = __raw_readl(timer->base_addr +
- TTC_CNT_CNTRL_OFFSET);
- ctrl_reg |= TTC_CNT_CNTRL_DISABLE_MASK;
- __raw_writel(ctrl_reg,
- timer->base_addr + TTC_CNT_CNTRL_OFFSET);
+ __raw_writel(0, timer->base_addr + TTC_IER_OFFSET);
break;
case CLOCK_EVT_MODE_RESUME:
- ctrl_reg = __raw_readl(timer->base_addr +
- TTC_CNT_CNTRL_OFFSET);
- ctrl_reg &= ~TTC_CNT_CNTRL_DISABLE_MASK;
- __raw_writel(ctrl_reg,
- timer->base_addr + TTC_CNT_CNTRL_OFFSET);
break;
}
}
@@ -354,17 +346,6 @@ static void __init ttc_setup_clocksource(struct clk *clk, void __iomem *base)
ttccs->cs.mask = CLOCKSOURCE_MASK(16);
ttccs->cs.flags = CLOCK_SOURCE_IS_CONTINUOUS;
- /*
- * Setup the clock source counter to be an incrementing counter
- * with no interrupt and it rolls over at 0xFFFF. Pre-scale
- * it by 32 also. Let it start running now.
- */
- __raw_writel(0x0, ttccs->ttc.base_addr + TTC_IER_OFFSET);
- __raw_writel(CLK_CNTRL_PRESCALE | CLK_CNTRL_PRESCALE_EN,
- ttccs->ttc.base_addr + TTC_CLK_CNTRL_OFFSET);
- __raw_writel(CNT_CNTRL_RESET,
- ttccs->ttc.base_addr + TTC_CNT_CNTRL_OFFSET);
-
err = clocksource_register_hz(&ttccs->cs, ttccs->ttc.freq / PRESCALE);
if (WARN_ON(err)) {
kfree(ttccs);
@@ -433,16 +414,6 @@ static void __init ttc_setup_clockevent(struct clk *clk,
ttcce->ce.irq = irq;
ttcce->ce.cpumask = cpu_possible_mask;
- /*
- * Setup the clock event timer to be an interval timer which
- * is prescaled by 32 using the interval interrupt. Leave it
- * disabled for now.
- */
- __raw_writel(0x23, ttcce->ttc.base_addr + TTC_CNT_CNTRL_OFFSET);
- __raw_writel(CLK_CNTRL_PRESCALE | CLK_CNTRL_PRESCALE_EN,
- ttcce->ttc.base_addr + TTC_CLK_CNTRL_OFFSET);
- __raw_writel(0x1, ttcce->ttc.base_addr + TTC_IER_OFFSET);
-
err = request_irq(irq, ttc_clock_event_interrupt,
IRQF_TIMER, ttcce->ce.name, ttcce);
if (WARN_ON(err)) {
@@ -464,7 +435,7 @@ static void __init ttc_timer_init(struct device_node *timer)
{
unsigned int irq;
void __iomem *timer_baseaddr;
- struct clk *clk_cs, *clk_ce;
+ struct clk *clk;
static int initialized;
int clksel;
@@ -484,7 +455,7 @@ static void __init ttc_timer_init(struct device_node *timer)
BUG();
}
- irq = irq_of_parse_and_map(timer, 1);
+ irq = irq_of_parse_and_map(timer, 0);
if (irq <= 0) {
pr_err("ERROR: invalid interrupt number\n");
BUG();
@@ -492,22 +463,21 @@ static void __init ttc_timer_init(struct device_node *timer)
clksel = __raw_readl(timer_baseaddr + TTC_CLK_CNTRL_OFFSET);
clksel = !!(clksel & TTC_CLK_CNTRL_CSRC_MASK);
- clk_cs = of_clk_get(timer, clksel);
- if (IS_ERR(clk_cs)) {
+ clk = of_clk_get(timer, clksel);
+ if (IS_ERR(clk)) {
pr_err("ERROR: timer input clock not found\n");
BUG();
}
- clksel = __raw_readl(timer_baseaddr + 4 + TTC_CLK_CNTRL_OFFSET);
- clksel = !!(clksel & TTC_CLK_CNTRL_CSRC_MASK);
- clk_ce = of_clk_get(timer, clksel);
- if (IS_ERR(clk_ce)) {
- pr_err("ERROR: timer input clock not found\n");
- BUG();
- }
+ __raw_writel(CLK_CNTRL_PRESCALE | CLK_CNTRL_PRESCALE_EN,
+ timer_baseaddr + TTC_CLK_CNTRL_OFFSET);
+
+ /* start timer in overflow and match mode */
+ __raw_writel(CNT_CNTRL_RESET | CNT_CNTRL_MATCH,
+ timer_baseaddr + TTC_CNT_CNTRL_OFFSET);
- ttc_setup_clocksource(clk_cs, timer_baseaddr);
- ttc_setup_clockevent(clk_ce, timer_baseaddr + 4, irq);
+ ttc_setup_clocksource(clk, timer_baseaddr);
+ ttc_setup_clockevent(clk, timer_baseaddr, irq);
pr_info("%s #0 at %p, irq=%d\n", timer->name, timer_baseaddr, irq);
}
--
1.8.5.3
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH v3 5/7] clocksource/cadence_ttc: Use only one counter
2014-02-03 22:34 ` [PATCH v3 5/7] clocksource/cadence_ttc: Use only one counter Soren Brinkmann
@ 2014-02-04 20:41 ` Thomas Gleixner
2014-02-10 17:46 ` Sören Brinkmann
0 siblings, 1 reply; 12+ messages in thread
From: Thomas Gleixner @ 2014-02-04 20:41 UTC (permalink / raw)
To: Soren Brinkmann
Cc: Daniel Lezcano, Michal Simek, linux-kernel, linux-arm-kernel
On Mon, 3 Feb 2014, Soren Brinkmann wrote:
> Currently the driver uses two of the three counters the TTC provides to
> implement a clocksource and a clockevent device. By using the TTC's
> match feature we can implement both use cases using a single counter
> only.
Are you entirely sure that this match feature is free of the infamous
HPET match feature issues?
See arch/x86/kernel/hpet.c: hpet_next_event()
If yes, please add a comment. If no ....
Thanks,
tglx
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v3 5/7] clocksource/cadence_ttc: Use only one counter
2014-02-04 20:41 ` Thomas Gleixner
@ 2014-02-10 17:46 ` Sören Brinkmann
0 siblings, 0 replies; 12+ messages in thread
From: Sören Brinkmann @ 2014-02-10 17:46 UTC (permalink / raw)
To: Thomas Gleixner
Cc: Daniel Lezcano, Michal Simek, linux-kernel, linux-arm-kernel
Hi Thomas,
On Tue, Feb 04, 2014 at 09:41:53PM +0100, Thomas Gleixner wrote:
> On Mon, 3 Feb 2014, Soren Brinkmann wrote:
>
> > Currently the driver uses two of the three counters the TTC provides to
> > implement a clocksource and a clockevent device. By using the TTC's
> > match feature we can implement both use cases using a single counter
> > only.
>
> Are you entirely sure that this match feature is free of the infamous
> HPET match feature issues?
>
> See arch/x86/kernel/hpet.c: hpet_next_event()
>
> If yes, please add a comment. If no ....
Good catch. Looks like the comparator compares on == as well. So, I
assume it may show similar issues. I'll have to spend some more time
looking into this.
Thanks for applying the first two patches of the series.
Thanks,
Sören
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 6/7] arm: zynq: Don't use arm_global_timer with cpufreq
2014-02-03 22:34 [PATCH v3 0/7] ARM: zynq: cpufreq support Soren Brinkmann
` (4 preceding siblings ...)
2014-02-03 22:34 ` [PATCH v3 5/7] clocksource/cadence_ttc: Use only one counter Soren Brinkmann
@ 2014-02-03 22:34 ` Soren Brinkmann
2014-02-03 22:34 ` [PATCH v3 7/7] arm: zynq: Add support for cpufreq Soren Brinkmann
6 siblings, 0 replies; 12+ messages in thread
From: Soren Brinkmann @ 2014-02-03 22:34 UTC (permalink / raw)
To: Thomas Gleixner, Daniel Lezcano, Michal Simek
Cc: linux-kernel, linux-arm-kernel, Sören Brinkmann, Russell King
The timer frequency of the arm_global_timer depends on the CPU
frequency. With cpufreq altering that frequency the arm_global_timer
does not maintain a stable time base. Therefore don't enable that timer
in case cpufreq is enabled.
Cc: Russell King <linux@arm.linux.org.uk>
Signed-off-by: Soren Brinkmann <soren.brinkmann@xilinx.com>
---
arch/arm/mach-zynq/Kconfig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/arm/mach-zynq/Kconfig b/arch/arm/mach-zynq/Kconfig
index 6b04260aa142..f84fab14f0b7 100644
--- a/arch/arm/mach-zynq/Kconfig
+++ b/arch/arm/mach-zynq/Kconfig
@@ -13,6 +13,6 @@ config ARCH_ZYNQ
select HAVE_SMP
select SPARSE_IRQ
select CADENCE_TTC_TIMER
- select ARM_GLOBAL_TIMER
+ select ARM_GLOBAL_TIMER if !CPU_FREQ
help
Support for Xilinx Zynq ARM Cortex A9 Platform
--
1.8.5.3
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v3 7/7] arm: zynq: Add support for cpufreq
2014-02-03 22:34 [PATCH v3 0/7] ARM: zynq: cpufreq support Soren Brinkmann
` (5 preceding siblings ...)
2014-02-03 22:34 ` [PATCH v3 6/7] arm: zynq: Don't use arm_global_timer with cpufreq Soren Brinkmann
@ 2014-02-03 22:34 ` Soren Brinkmann
6 siblings, 0 replies; 12+ messages in thread
From: Soren Brinkmann @ 2014-02-03 22:34 UTC (permalink / raw)
To: Thomas Gleixner, Daniel Lezcano, Michal Simek
Cc: linux-kernel, linux-arm-kernel, Sören Brinkmann,
Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
Russell King, devicetree
The generic cpufreq-cpu0 driver can scale the CPU frequency on Zynq
SOCs. Add the required platform device to the BSP and appropriate
OPPs to the dts.
Cc: Rob Herring <robh+dt@kernel.org>
Cc: Pawel Moll <pawel.moll@arm.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Ian Campbell <ijc+devicetree@hellion.org.uk>
Cc: Kumar Gala <galak@codeaurora.org>
Cc: Russell King <linux@arm.linux.org.uk>
Cc: devicetree@vger.kernel.org
Signed-off-by: Soren Brinkmann <soren.brinkmann@xilinx.com>
---
v3:
- change lowest frequency to 222223 (F_max / 3)
---
arch/arm/boot/dts/zynq-7000.dtsi | 6 ++++++
arch/arm/mach-zynq/Kconfig | 2 ++
arch/arm/mach-zynq/common.c | 3 +++
3 files changed, 11 insertions(+)
diff --git a/arch/arm/boot/dts/zynq-7000.dtsi b/arch/arm/boot/dts/zynq-7000.dtsi
index 8b67b19392ec..789d0bacc110 100644
--- a/arch/arm/boot/dts/zynq-7000.dtsi
+++ b/arch/arm/boot/dts/zynq-7000.dtsi
@@ -24,6 +24,12 @@
device_type = "cpu";
reg = <0>;
clocks = <&clkc 3>;
+ operating-points = <
+ /* kHz uV */
+ 666667 1000000
+ 333334 1000000
+ 222223 1000000
+ >;
};
cpu@1 {
diff --git a/arch/arm/mach-zynq/Kconfig b/arch/arm/mach-zynq/Kconfig
index f84fab14f0b7..f03e75bd0b2b 100644
--- a/arch/arm/mach-zynq/Kconfig
+++ b/arch/arm/mach-zynq/Kconfig
@@ -2,6 +2,8 @@ config ARCH_ZYNQ
bool "Xilinx Zynq ARM Cortex A9 Platform" if ARCH_MULTI_V7
select ARM_AMBA
select ARM_GIC
+ select ARCH_HAS_CPUFREQ
+ select ARCH_HAS_OPP
select COMMON_CLK
select CPU_V7
select GENERIC_CLOCKEVENTS
diff --git a/arch/arm/mach-zynq/common.c b/arch/arm/mach-zynq/common.c
index 1db2a5ca9ab8..644468151c04 100644
--- a/arch/arm/mach-zynq/common.c
+++ b/arch/arm/mach-zynq/common.c
@@ -51,6 +51,8 @@ static struct platform_device zynq_cpuidle_device = {
*/
static void __init zynq_init_machine(void)
{
+ struct platform_device_info devinfo = { .name = "cpufreq-cpu0", };
+
/*
* 64KB way size, 8-way associativity, parity disabled
*/
@@ -59,6 +61,7 @@ static void __init zynq_init_machine(void)
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
platform_device_register(&zynq_cpuidle_device);
+ platform_device_register_full(&devinfo);
}
static void __init zynq_timer_init(void)
--
1.8.5.3
^ permalink raw reply [flat|nested] 12+ messages in thread