]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - kernel/time/tick-broadcast.c
Merge remote-tracking branch 'asoc/topic/arizona' into asoc-next
[karo-tx-linux.git] / kernel / time / tick-broadcast.c
index 20d6fba70652094324c13ef6f8e2fcf9bba26a50..218bcb565fed0f6e8f867b9e3e2264812d9797bf 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/profile.h>
 #include <linux/sched.h>
 #include <linux/smp.h>
+#include <linux/module.h>
 
 #include "tick-internal.h"
 
@@ -29,6 +30,7 @@
 
 static struct tick_device tick_broadcast_device;
 static cpumask_var_t tick_broadcast_mask;
+static cpumask_var_t tick_broadcast_on;
 static cpumask_var_t tmpmask;
 static DEFINE_RAW_SPINLOCK(tick_broadcast_lock);
 static int tick_broadcast_force;
@@ -64,17 +66,34 @@ static void tick_broadcast_start_periodic(struct clock_event_device *bc)
 /*
  * Check, if the device can be utilized as broadcast device:
  */
-int tick_check_broadcast_device(struct clock_event_device *dev)
+static bool tick_check_broadcast_device(struct clock_event_device *curdev,
+                                       struct clock_event_device *newdev)
+{
+       if ((newdev->features & CLOCK_EVT_FEAT_DUMMY) ||
+           (newdev->features & CLOCK_EVT_FEAT_C3STOP))
+               return false;
+
+       if (tick_broadcast_device.mode == TICKDEV_MODE_ONESHOT &&
+           !(newdev->features & CLOCK_EVT_FEAT_ONESHOT))
+               return false;
+
+       return !curdev || newdev->rating > curdev->rating;
+}
+
+/*
+ * Conditionally install/replace broadcast device
+ */
+void tick_install_broadcast_device(struct clock_event_device *dev)
 {
        struct clock_event_device *cur = tick_broadcast_device.evtdev;
 
-       if ((dev->features & CLOCK_EVT_FEAT_DUMMY) ||
-           (tick_broadcast_device.evtdev &&
-            tick_broadcast_device.evtdev->rating >= dev->rating) ||
-            (dev->features & CLOCK_EVT_FEAT_C3STOP))
-               return 0;
+       if (!tick_check_broadcast_device(cur, dev))
+               return;
+
+       if (!try_module_get(dev->owner))
+               return;
 
-       clockevents_exchange_device(tick_broadcast_device.evtdev, dev);
+       clockevents_exchange_device(cur, dev);
        if (cur)
                cur->event_handler = clockevents_handle_noop;
        tick_broadcast_device.evtdev = dev;
@@ -90,7 +109,6 @@ int tick_check_broadcast_device(struct clock_event_device *dev)
         */
        if (dev->features & CLOCK_EVT_FEAT_ONESHOT)
                tick_clock_notify();
-       return 1;
 }
 
 /*
@@ -123,8 +141,9 @@ static void tick_device_setup_broadcast_func(struct clock_event_device *dev)
  */
 int tick_device_uses_broadcast(struct clock_event_device *dev, int cpu)
 {
+       struct clock_event_device *bc = tick_broadcast_device.evtdev;
        unsigned long flags;
-       int ret = 0;
+       int ret;
 
        raw_spin_lock_irqsave(&tick_broadcast_lock, flags);
 
@@ -138,20 +157,62 @@ int tick_device_uses_broadcast(struct clock_event_device *dev, int cpu)
                dev->event_handler = tick_handle_periodic;
                tick_device_setup_broadcast_func(dev);
                cpumask_set_cpu(cpu, tick_broadcast_mask);
-               tick_broadcast_start_periodic(tick_broadcast_device.evtdev);
+               if (tick_broadcast_device.mode == TICKDEV_MODE_PERIODIC)
+                       tick_broadcast_start_periodic(bc);
+               else
+                       tick_broadcast_setup_oneshot(bc);
                ret = 1;
        } else {
                /*
-                * When the new device is not affected by the stop
-                * feature and the cpu is marked in the broadcast mask
-                * then clear the broadcast bit.
+                * Clear the broadcast bit for this cpu if the
+                * device is not power state affected.
                 */
-               if (!(dev->features & CLOCK_EVT_FEAT_C3STOP)) {
-                       int cpu = smp_processor_id();
+               if (!(dev->features & CLOCK_EVT_FEAT_C3STOP))
                        cpumask_clear_cpu(cpu, tick_broadcast_mask);
-                       tick_broadcast_clear_oneshot(cpu);
-               } else {
+               else
                        tick_device_setup_broadcast_func(dev);
+
+               /*
+                * Clear the broadcast bit if the CPU is not in
+                * periodic broadcast on state.
+                */
+               if (!cpumask_test_cpu(cpu, tick_broadcast_on))
+                       cpumask_clear_cpu(cpu, tick_broadcast_mask);
+
+               switch (tick_broadcast_device.mode) {
+               case TICKDEV_MODE_ONESHOT:
+                       /*
+                        * If the system is in oneshot mode we can
+                        * unconditionally clear the oneshot mask bit,
+                        * because the CPU is running and therefore
+                        * not in an idle state which causes the power
+                        * state affected device to stop. Let the
+                        * caller initialize the device.
+                        */
+                       tick_broadcast_clear_oneshot(cpu);
+                       ret = 0;
+                       break;
+
+               case TICKDEV_MODE_PERIODIC:
+                       /*
+                        * If the system is in periodic mode, check
+                        * whether the broadcast device can be
+                        * switched off now.
+                        */
+                       if (cpumask_empty(tick_broadcast_mask) && bc)
+                               clockevents_shutdown(bc);
+                       /*
+                        * If we kept the cpu in the broadcast mask,
+                        * tell the caller to leave the per cpu device
+                        * in shutdown state. The periodic interrupt
+                        * is delivered by the broadcast device.
+                        */
+                       ret = cpumask_test_cpu(cpu, tick_broadcast_mask);
+                       break;
+               default:
+                       /* Nothing to do */
+                       ret = 0;
+                       break;
                }
        }
        raw_spin_unlock_irqrestore(&tick_broadcast_lock, flags);
@@ -281,6 +342,7 @@ static void tick_do_broadcast_on_off(unsigned long *reason)
        switch (*reason) {
        case CLOCK_EVT_NOTIFY_BROADCAST_ON:
        case CLOCK_EVT_NOTIFY_BROADCAST_FORCE:
+               cpumask_set_cpu(cpu, tick_broadcast_on);
                if (!cpumask_test_and_set_cpu(cpu, tick_broadcast_mask)) {
                        if (tick_broadcast_device.mode ==
                            TICKDEV_MODE_PERIODIC)
@@ -290,8 +352,12 @@ static void tick_do_broadcast_on_off(unsigned long *reason)
                        tick_broadcast_force = 1;
                break;
        case CLOCK_EVT_NOTIFY_BROADCAST_OFF:
-               if (!tick_broadcast_force &&
-                   cpumask_test_and_clear_cpu(cpu, tick_broadcast_mask)) {
+               if (tick_broadcast_force)
+                       break;
+               cpumask_clear_cpu(cpu, tick_broadcast_on);
+               if (!tick_device_is_functional(dev))
+                       break;
+               if (cpumask_test_and_clear_cpu(cpu, tick_broadcast_mask)) {
                        if (tick_broadcast_device.mode ==
                            TICKDEV_MODE_PERIODIC)
                                tick_setup_periodic(dev, 0);
@@ -349,6 +415,7 @@ void tick_shutdown_broadcast(unsigned int *cpup)
 
        bc = tick_broadcast_device.evtdev;
        cpumask_clear_cpu(cpu, tick_broadcast_mask);
+       cpumask_clear_cpu(cpu, tick_broadcast_on);
 
        if (tick_broadcast_device.mode == TICKDEV_MODE_PERIODIC) {
                if (bc && cpumask_empty(tick_broadcast_mask))
@@ -475,7 +542,15 @@ void tick_check_oneshot_broadcast(int cpu)
        if (cpumask_test_cpu(cpu, tick_broadcast_oneshot_mask)) {
                struct tick_device *td = &per_cpu(tick_cpu_device, cpu);
 
-               clockevents_set_mode(td->evtdev, CLOCK_EVT_MODE_ONESHOT);
+               /*
+                * We might be in the middle of switching over from
+                * periodic to oneshot. If the CPU has not yet
+                * switched over, leave the device alone.
+                */
+               if (td->mode == TICKDEV_MODE_ONESHOT) {
+                       clockevents_set_mode(td->evtdev,
+                                            CLOCK_EVT_MODE_ONESHOT);
+               }
        }
 }
 
@@ -521,6 +596,13 @@ again:
        cpumask_or(tmpmask, tmpmask, tick_broadcast_force_mask);
        cpumask_clear(tick_broadcast_force_mask);
 
+       /*
+        * Sanity check. Catch the case where we try to broadcast to
+        * offline cpus.
+        */
+       if (WARN_ON_ONCE(!cpumask_subset(tmpmask, cpu_online_mask)))
+               cpumask_and(tmpmask, tmpmask, cpu_online_mask);
+
        /*
         * Wakeup the cpus which have an expired event.
         */
@@ -761,10 +843,12 @@ void tick_shutdown_broadcast_oneshot(unsigned int *cpup)
        raw_spin_lock_irqsave(&tick_broadcast_lock, flags);
 
        /*
-        * Clear the broadcast mask flag for the dead cpu, but do not
-        * stop the broadcast device!
+        * Clear the broadcast masks for the dead cpu, but do not stop
+        * the broadcast device!
         */
        cpumask_clear_cpu(cpu, tick_broadcast_oneshot_mask);
+       cpumask_clear_cpu(cpu, tick_broadcast_pending_mask);
+       cpumask_clear_cpu(cpu, tick_broadcast_force_mask);
 
        raw_spin_unlock_irqrestore(&tick_broadcast_lock, flags);
 }
@@ -792,6 +876,7 @@ bool tick_broadcast_oneshot_available(void)
 void __init tick_broadcast_init(void)
 {
        zalloc_cpumask_var(&tick_broadcast_mask, GFP_NOWAIT);
+       zalloc_cpumask_var(&tick_broadcast_on, GFP_NOWAIT);
        zalloc_cpumask_var(&tmpmask, GFP_NOWAIT);
 #ifdef CONFIG_TICK_ONESHOT
        zalloc_cpumask_var(&tick_broadcast_oneshot_mask, GFP_NOWAIT);