Fedora kernel-2.6.17-1.2142_FC4 patched with stable patch-2.6.17.4-vs2.0.2-rc26.diff
[linux-2.6.git] / drivers / media / dvb / dvb-core / dvb_frontend.c
index 2f1cf83..a051790 100644 (file)
@@ -3,9 +3,9 @@
  *
  *
  * Copyright (C) 1999-2001 Ralph  Metzler
- *                         Marcus Metzler
- *                         Holger Waechtler 
- *                                    for convergence integrated media GmbH
+ *                        Marcus Metzler
+ *                        Holger Waechtler
+ *                                   for convergence integrated media GmbH
  *
  * Copyright (C) 2004 Andrew de Quincey (tuning thread cleanup)
  *
@@ -16,7 +16,7 @@
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.         See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
@@ -35,8 +35,8 @@
 #include <linux/moduleparam.h>
 #include <linux/list.h>
 #include <linux/suspend.h>
+#include <linux/jiffies.h>
 #include <asm/processor.h>
-#include <asm/semaphore.h>
 
 #include "dvb_frontend.h"
 #include "dvbdev.h"
@@ -48,14 +48,14 @@ static int dvb_override_tune_delay;
 static int dvb_powerdown_on_sleep = 1;
 
 module_param_named(frontend_debug, dvb_frontend_debug, int, 0644);
-MODULE_PARM_DESC(dvb_frontend_debug, "Turn on/off frontend core debugging (default:off).");
-module_param(dvb_shutdown_timeout, int, 0444);
+MODULE_PARM_DESC(frontend_debug, "Turn on/off frontend core debugging (default:off).");
+module_param(dvb_shutdown_timeout, int, 0644);
 MODULE_PARM_DESC(dvb_shutdown_timeout, "wait <shutdown_timeout> seconds after close() before suspending hardware");
-module_param(dvb_force_auto_inversion, int, 0444);
+module_param(dvb_force_auto_inversion, int, 0644);
 MODULE_PARM_DESC(dvb_force_auto_inversion, "0: normal (default), 1: INVERSION_AUTO forced always");
-module_param(dvb_override_tune_delay, int, 0444);
+module_param(dvb_override_tune_delay, int, 0644);
 MODULE_PARM_DESC(dvb_override_tune_delay, "0: normal (default), >0 => delay in milliseconds to wait for lock after a tune attempt");
-module_param(dvb_powerdown_on_sleep, int, 0444);
+module_param(dvb_powerdown_on_sleep, int, 0644);
 MODULE_PARM_DESC(dvb_powerdown_on_sleep, "0: do not power down, 1: turn LNB volatage off on sleep (default)");
 
 #define dprintk if (dvb_frontend_debug) printk
@@ -87,10 +87,11 @@ MODULE_PARM_DESC(dvb_powerdown_on_sleep, "0: do not power down, 1: turn LNB vola
  * FESTATE_LOSTLOCK. When the lock has been lost, and we're searching it again.
  */
 
-static DECLARE_MUTEX(frontend_mutex);
+static DEFINE_MUTEX(frontend_mutex);
 
 struct dvb_frontend_private {
 
+       /* thread/frontend values */
        struct dvb_device *dvbdev;
        struct dvb_frontend_parameters parameters;
        struct dvb_fe_events events;
@@ -99,25 +100,35 @@ struct dvb_frontend_private {
        wait_queue_head_t wait_queue;
        pid_t thread_pid;
        unsigned long release_jiffies;
-       int state;
-       int bending;
-       int lnb_drift;
-       int inversion;
-       int auto_step;
-       int auto_sub_step;
-       int started_auto_step;
-       int min_delay;
-       int max_drift;
-       int step_size;
-       int exit;
-       int wakeup;
+       unsigned int exit;
+       unsigned int wakeup;
        fe_status_t status;
+       unsigned long tune_mode_flags;
+       unsigned int delay;
+       unsigned int reinitialise;
+       int tone;
+       int voltage;
+
+       /* swzigzag values */
+       unsigned int state;
+       unsigned int bending;
+       int lnb_drift;
+       unsigned int inversion;
+       unsigned int auto_step;
+       unsigned int auto_sub_step;
+       unsigned int started_auto_step;
+       unsigned int min_delay;
+       unsigned int max_drift;
+       unsigned int step_size;
+       int quality;
+       unsigned int check_wrapped;
 };
 
+static void dvb_frontend_wakeup(struct dvb_frontend *fe);
 
 static void dvb_frontend_add_event(struct dvb_frontend *fe, fe_status_t status)
 {
-       struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv;
+       struct dvb_frontend_private *fepriv = fe->frontend_priv;
        struct dvb_fe_events *events = &fepriv->events;
        struct dvb_frontend_event *e;
        int wp;
@@ -155,45 +166,45 @@ static void dvb_frontend_add_event(struct dvb_frontend *fe, fe_status_t status)
 static int dvb_frontend_get_event(struct dvb_frontend *fe,
                            struct dvb_frontend_event *event, int flags)
 {
-       struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv;
+       struct dvb_frontend_private *fepriv = fe->frontend_priv;
        struct dvb_fe_events *events = &fepriv->events;
 
        dprintk ("%s\n", __FUNCTION__);
 
        if (events->overflow) {
-                events->overflow = 0;
-                return -EOVERFLOW;
-        }
+               events->overflow = 0;
+               return -EOVERFLOW;
+       }
 
-        if (events->eventw == events->eventr) {
+       if (events->eventw == events->eventr) {
                int ret;
 
-                if (flags & O_NONBLOCK)
-                        return -EWOULDBLOCK;
+               if (flags & O_NONBLOCK)
+                       return -EWOULDBLOCK;
 
                up(&fepriv->sem);
 
-                ret = wait_event_interruptible (events->wait_queue,
-                                                events->eventw != events->eventr);
+               ret = wait_event_interruptible (events->wait_queue,
+                                               events->eventw != events->eventr);
 
                if (down_interruptible (&fepriv->sem))
                        return -ERESTARTSYS;
 
-                if (ret < 0)
-                        return ret;
-        }
+               if (ret < 0)
+                       return ret;
+       }
 
-        if (down_interruptible (&events->sem))
+       if (down_interruptible (&events->sem))
                return -ERESTARTSYS;
 
-               memcpy (event, &events->events[events->eventr],
+       memcpy (event, &events->events[events->eventr],
                sizeof(struct dvb_frontend_event));
 
-        events->eventr = (events->eventr + 1) % MAX_EVENT;
+       events->eventr = (events->eventr + 1) % MAX_EVENT;
 
-               up (&events->sem);
+       up (&events->sem);
 
-        return 0;
+       return 0;
 }
 
 static void dvb_frontend_init(struct dvb_frontend *fe)
@@ -206,35 +217,44 @@ static void dvb_frontend_init(struct dvb_frontend *fe)
                fe->ops->init(fe);
 }
 
-static void update_delay (int *quality, int *delay, int min_delay, int locked)
+void dvb_frontend_reinitialise(struct dvb_frontend *fe)
+{
+       struct dvb_frontend_private *fepriv = fe->frontend_priv;
+
+       fepriv->reinitialise = 1;
+       dvb_frontend_wakeup(fe);
+}
+EXPORT_SYMBOL(dvb_frontend_reinitialise);
+
+static void dvb_frontend_swzigzag_update_delay(struct dvb_frontend_private *fepriv, int locked)
 {
        int q2;
 
        dprintk ("%s\n", __FUNCTION__);
 
        if (locked)
-               (*quality) = (*quality * 220 + 36*256) / 256;
+               (fepriv->quality) = (fepriv->quality * 220 + 36*256) / 256;
        else
-               (*quality) = (*quality * 220 + 0) / 256;
+               (fepriv->quality) = (fepriv->quality * 220 + 0) / 256;
 
-       q2 = *quality - 128;
+       q2 = fepriv->quality - 128;
        q2 *= q2;
 
-           *delay = min_delay + q2 * HZ / (128*128);
+       fepriv->delay = fepriv->min_delay + q2 * HZ / (128*128);
 }
 
 /**
  * Performs automatic twiddling of frontend parameters.
- * 
+ *
  * @param fe The frontend concerned.
  * @param check_wrapped Checks if an iteration has completed. DO NOT SET ON THE FIRST ATTEMPT
  * @returns Number of complete iterations that have been performed.
  */
-static int dvb_frontend_autotune(struct dvb_frontend *fe, int check_wrapped)
+static int dvb_frontend_swzigzag_autotune(struct dvb_frontend *fe, int check_wrapped)
 {
        int autoinversion;
        int ready = 0;
-       struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv;
+       struct dvb_frontend_private *fepriv = fe->frontend_priv;
        int original_inversion = fepriv->parameters.inversion;
        u32 original_frequency = fepriv->parameters.frequency;
 
@@ -270,26 +290,26 @@ static int dvb_frontend_autotune(struct dvb_frontend *fe, int check_wrapped)
 
                case 2:
                        if (fepriv->lnb_drift == 0) break;
-                   
+
                        fepriv->lnb_drift = -fepriv->lnb_drift;
                        ready = 1;
                        break;
-           
+
                case 3:
                        if (fepriv->lnb_drift == 0) break;
                        if (!autoinversion) break;
-                   
+
                        fepriv->inversion = (fepriv->inversion == INVERSION_OFF) ? INVERSION_ON : INVERSION_OFF;
                        fepriv->lnb_drift = -fepriv->lnb_drift;
                        ready = 1;
                        break;
-                   
+
                default:
                        fepriv->auto_step++;
                        fepriv->auto_sub_step = -1; /* it'll be incremented to 0 in a moment */
                        break;
                }
-           
+
                if (!ready) fepriv->auto_sub_step++;
        }
 
@@ -298,13 +318,13 @@ static int dvb_frontend_autotune(struct dvb_frontend *fe, int check_wrapped)
        if ((fepriv->auto_step == fepriv->started_auto_step) &&
            (fepriv->auto_sub_step == 0) && check_wrapped) {
                return 1;
-               }
+       }
 
        dprintk("%s: drift:%i inversion:%i auto_step:%i "
                "auto_sub_step:%i started_auto_step:%i\n",
                __FUNCTION__, fepriv->lnb_drift, fepriv->inversion,
                fepriv->auto_step, fepriv->auto_sub_step, fepriv->started_auto_step);
-    
+
        /* set the frontend itself */
        fepriv->parameters.frequency += fepriv->lnb_drift;
        if (autoinversion)
@@ -319,15 +339,139 @@ static int dvb_frontend_autotune(struct dvb_frontend *fe, int check_wrapped)
        return 0;
 }
 
+static void dvb_frontend_swzigzag(struct dvb_frontend *fe)
+{
+       fe_status_t s;
+       struct dvb_frontend_private *fepriv = fe->frontend_priv;
+
+       /* if we've got no parameters, just keep idling */
+       if (fepriv->state & FESTATE_IDLE) {
+               fepriv->delay = 3*HZ;
+               fepriv->quality = 0;
+               return;
+       }
+
+       /* in SCAN mode, we just set the frontend when asked and leave it alone */
+       if (fepriv->tune_mode_flags & FE_TUNE_MODE_ONESHOT) {
+               if (fepriv->state & FESTATE_RETUNE) {
+                       if (fe->ops->set_frontend)
+                               fe->ops->set_frontend(fe, &fepriv->parameters);
+                       fepriv->state = FESTATE_TUNED;
+               }
+               fepriv->delay = 3*HZ;
+               fepriv->quality = 0;
+               return;
+       }
+
+       /* get the frontend status */
+       if (fepriv->state & FESTATE_RETUNE) {
+               s = 0;
+       } else {
+               if (fe->ops->read_status)
+                       fe->ops->read_status(fe, &s);
+               if (s != fepriv->status) {
+                       dvb_frontend_add_event(fe, s);
+                       fepriv->status = s;
+               }
+       }
+
+       /* if we're not tuned, and we have a lock, move to the TUNED state */
+       if ((fepriv->state & FESTATE_WAITFORLOCK) && (s & FE_HAS_LOCK)) {
+               dvb_frontend_swzigzag_update_delay(fepriv, s & FE_HAS_LOCK);
+               fepriv->state = FESTATE_TUNED;
+
+               /* if we're tuned, then we have determined the correct inversion */
+               if ((!(fe->ops->info.caps & FE_CAN_INVERSION_AUTO)) &&
+                   (fepriv->parameters.inversion == INVERSION_AUTO)) {
+                       fepriv->parameters.inversion = fepriv->inversion;
+               }
+               return;
+       }
+
+       /* if we are tuned already, check we're still locked */
+       if (fepriv->state & FESTATE_TUNED) {
+               dvb_frontend_swzigzag_update_delay(fepriv, s & FE_HAS_LOCK);
+
+               /* we're tuned, and the lock is still good... */
+               if (s & FE_HAS_LOCK) {
+                       return;
+               } else { /* if we _WERE_ tuned, but now don't have a lock */
+                       fepriv->state = FESTATE_ZIGZAG_FAST;
+                       fepriv->started_auto_step = fepriv->auto_step;
+                       fepriv->check_wrapped = 0;
+               }
+       }
+
+       /* don't actually do anything if we're in the LOSTLOCK state,
+        * the frontend is set to FE_CAN_RECOVER, and the max_drift is 0 */
+       if ((fepriv->state & FESTATE_LOSTLOCK) &&
+           (fe->ops->info.caps & FE_CAN_RECOVER) && (fepriv->max_drift == 0)) {
+               dvb_frontend_swzigzag_update_delay(fepriv, s & FE_HAS_LOCK);
+               return;
+       }
+
+       /* don't do anything if we're in the DISEQC state, since this
+        * might be someone with a motorized dish controlled by DISEQC.
+        * If its actually a re-tune, there will be a SET_FRONTEND soon enough. */
+       if (fepriv->state & FESTATE_DISEQC) {
+               dvb_frontend_swzigzag_update_delay(fepriv, s & FE_HAS_LOCK);
+               return;
+       }
+
+       /* if we're in the RETUNE state, set everything up for a brand
+        * new scan, keeping the current inversion setting, as the next
+        * tune is _very_ likely to require the same */
+       if (fepriv->state & FESTATE_RETUNE) {
+               fepriv->lnb_drift = 0;
+               fepriv->auto_step = 0;
+               fepriv->auto_sub_step = 0;
+               fepriv->started_auto_step = 0;
+               fepriv->check_wrapped = 0;
+       }
+
+       /* fast zigzag. */
+       if ((fepriv->state & FESTATE_SEARCHING_FAST) || (fepriv->state & FESTATE_RETUNE)) {
+               fepriv->delay = fepriv->min_delay;
+
+               /* peform a tune */
+               if (dvb_frontend_swzigzag_autotune(fe, fepriv->check_wrapped)) {
+                       /* OK, if we've run out of trials at the fast speed.
+                        * Drop back to slow for the _next_ attempt */
+                       fepriv->state = FESTATE_SEARCHING_SLOW;
+                       fepriv->started_auto_step = fepriv->auto_step;
+                       return;
+               }
+               fepriv->check_wrapped = 1;
+
+               /* if we've just retuned, enter the ZIGZAG_FAST state.
+                * This ensures we cannot return from an
+                * FE_SET_FRONTEND ioctl before the first frontend tune
+                * occurs */
+               if (fepriv->state & FESTATE_RETUNE) {
+                       fepriv->state = FESTATE_TUNING_FAST;
+               }
+       }
+
+       /* slow zigzag */
+       if (fepriv->state & FESTATE_SEARCHING_SLOW) {
+               dvb_frontend_swzigzag_update_delay(fepriv, s & FE_HAS_LOCK);
+
+               /* Note: don't bother checking for wrapping; we stay in this
+                * state until we get a lock */
+               dvb_frontend_swzigzag_autotune(fe, 0);
+       }
+}
+
 static int dvb_frontend_is_exiting(struct dvb_frontend *fe)
 {
-       struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv;
+       struct dvb_frontend_private *fepriv = fe->frontend_priv;
 
        if (fepriv->exit)
                return 1;
 
        if (fepriv->dvbdev->writers == 1)
-               if (jiffies - fepriv->release_jiffies > dvb_shutdown_timeout * HZ)
+               if (time_after(jiffies, fepriv->release_jiffies +
+                                 dvb_shutdown_timeout * HZ))
                        return 1;
 
        return 0;
@@ -335,7 +479,7 @@ static int dvb_frontend_is_exiting(struct dvb_frontend *fe)
 
 static int dvb_frontend_should_wakeup(struct dvb_frontend *fe)
 {
-       struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv;
+       struct dvb_frontend_private *fepriv = fe->frontend_priv;
 
        if (fepriv->wakeup) {
                fepriv->wakeup = 0;
@@ -346,159 +490,80 @@ static int dvb_frontend_should_wakeup(struct dvb_frontend *fe)
 
 static void dvb_frontend_wakeup(struct dvb_frontend *fe)
 {
-       struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv;
+       struct dvb_frontend_private *fepriv = fe->frontend_priv;
 
        fepriv->wakeup = 1;
        wake_up_interruptible(&fepriv->wait_queue);
 }
 
-/*
- * FIXME: use linux/kthread.h
- */
-static int dvb_frontend_thread (void *data)
+static int dvb_frontend_thread(void *data)
 {
-       struct dvb_frontend *fe = (struct dvb_frontend *) data;
-       struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv;
+       struct dvb_frontend *fe = data;
+       struct dvb_frontend_private *fepriv = fe->frontend_priv;
        unsigned long timeout;
        char name [15];
-       int quality = 0, delay = 3*HZ;
        fe_status_t s;
-       int check_wrapped = 0;
+       struct dvb_frontend_parameters *params;
 
-       dprintk ("%s\n", __FUNCTION__);
+       dprintk("%s\n", __FUNCTION__);
 
        snprintf (name, sizeof(name), "kdvb-fe-%i", fe->dvb->num);
 
-        lock_kernel ();
-        daemonize (name);
-        sigfillset (&current->blocked);
-        unlock_kernel ();
+       lock_kernel();
+       daemonize(name);
+       sigfillset(&current->blocked);
+       unlock_kernel();
 
+       fepriv->check_wrapped = 0;
+       fepriv->quality = 0;
+       fepriv->delay = 3*HZ;
        fepriv->status = 0;
-       dvb_frontend_init (fe);
        fepriv->wakeup = 0;
+       fepriv->reinitialise = 1;
 
        while (1) {
                up(&fepriv->sem);           /* is locked when we enter the thread... */
 
                timeout = wait_event_interruptible_timeout(fepriv->wait_queue,
                                                           dvb_frontend_should_wakeup(fe),
-                                                          delay);
-               if (0 != dvb_frontend_is_exiting (fe)) {
+                                                          fepriv->delay);
+               if (0 != dvb_frontend_is_exiting(fe)) {
                        /* got signal or quitting */
                        break;
                }
 
-               if (current->flags & PF_FREEZE)
-                       refrigerator(PF_FREEZE);
+               try_to_freeze();
 
                if (down_interruptible(&fepriv->sem))
                        break;
 
-               /* if we've got no parameters, just keep idling */
-               if (fepriv->state & FESTATE_IDLE) {
-                       delay = 3*HZ;
-                       quality = 0;
-                       continue;
-               }
-
-               /* get the frontend status */
-               if (fepriv->state & FESTATE_RETUNE) {
-                       s = 0;
-               } else {
-                       if (fe->ops->read_status)
-                               fe->ops->read_status(fe, &s);
-                       if (s != fepriv->status) {
-                       dvb_frontend_add_event (fe, s);
-                               fepriv->status = s;
+               if (fepriv->reinitialise) {
+                       dvb_frontend_init(fe);
+                       if (fepriv->tone != -1) {
+                               fe->ops->set_tone(fe, fepriv->tone);
                        }
-               }
-               /* if we're not tuned, and we have a lock, move to the TUNED state */
-               if ((fepriv->state & FESTATE_WAITFORLOCK) && (s & FE_HAS_LOCK)) {
-                       update_delay(&quality, &delay, fepriv->min_delay, s & FE_HAS_LOCK);
-                       fepriv->state = FESTATE_TUNED;
-
-                       /* if we're tuned, then we have determined the correct inversion */
-                       if ((!(fe->ops->info.caps & FE_CAN_INVERSION_AUTO)) &&
-                           (fepriv->parameters.inversion == INVERSION_AUTO)) {
-                               fepriv->parameters.inversion = fepriv->inversion;
+                       if (fepriv->voltage != -1) {
+                               fe->ops->set_voltage(fe, fepriv->voltage);
                        }
-                       continue;
-               }
-
-               /* if we are tuned already, check we're still locked */
-               if (fepriv->state & FESTATE_TUNED) {
-                       update_delay(&quality, &delay, fepriv->min_delay, s & FE_HAS_LOCK);
-
-                       /* we're tuned, and the lock is still good... */
-                       if (s & FE_HAS_LOCK)
-                               continue;
-                       else {
-                               /* if we _WERE_ tuned, but now don't have a lock,
-                                * need to zigzag */
-                               fepriv->state = FESTATE_ZIGZAG_FAST;
-                               fepriv->started_auto_step = fepriv->auto_step;
-                               check_wrapped = 0;
-                       }
-               }
-
-               /* don't actually do anything if we're in the LOSTLOCK state,
-                * the frontend is set to FE_CAN_RECOVER, and the max_drift is 0 */
-               if ((fepriv->state & FESTATE_LOSTLOCK) &&
-                   (fe->ops->info.caps & FE_CAN_RECOVER) && (fepriv->max_drift == 0)) {
-                       update_delay(&quality, &delay, fepriv->min_delay, s & FE_HAS_LOCK);
-                                               continue;
-                               }
-           
-               /* don't do anything if we're in the DISEQC state, since this
-                * might be someone with a motorized dish controlled by DISEQC.
-                * If its actually a re-tune, there will be a SET_FRONTEND soon enough. */
-               if (fepriv->state & FESTATE_DISEQC) {
-                       update_delay(&quality, &delay, fepriv->min_delay, s & FE_HAS_LOCK);
-                       continue;
-                               }
-
-               /* if we're in the RETUNE state, set everything up for a brand
-                * new scan, keeping the current inversion setting, as the next
-                * tune is _very_ likely to require the same */
-               if (fepriv->state & FESTATE_RETUNE) {
-                       fepriv->lnb_drift = 0;
-                       fepriv->auto_step = 0;
-                       fepriv->auto_sub_step = 0;
-                       fepriv->started_auto_step = 0;
-                       check_wrapped = 0;
+                       fepriv->reinitialise = 0;
                }
 
-               /* fast zigzag. */
-               if ((fepriv->state & FESTATE_SEARCHING_FAST) || (fepriv->state & FESTATE_RETUNE)) {
-                       delay = fepriv->min_delay;
-
-                       /* peform a tune */
-                       if (dvb_frontend_autotune(fe, check_wrapped)) {
-                               /* OK, if we've run out of trials at the fast speed.
-                                * Drop back to slow for the _next_ attempt */
-                               fepriv->state = FESTATE_SEARCHING_SLOW;
-                               fepriv->started_auto_step = fepriv->auto_step;
-                               continue;
-                       }
-                       check_wrapped = 1;
-
-                       /* if we've just retuned, enter the ZIGZAG_FAST state.
-                        * This ensures we cannot return from an
-                        * FE_SET_FRONTEND ioctl before the first frontend tune
-                        * occurs */
+               /* do an iteration of the tuning loop */
+               if (fe->ops->tune) {
+                       /* have we been asked to retune? */
+                       params = NULL;
                        if (fepriv->state & FESTATE_RETUNE) {
-                               fepriv->state = FESTATE_TUNING_FAST;
+                               params = &fepriv->parameters;
+                               fepriv->state = FESTATE_TUNED;
                        }
-               }
 
-               /* slow zigzag */
-               if (fepriv->state & FESTATE_SEARCHING_SLOW) {
-                       update_delay(&quality, &delay, fepriv->min_delay, s & FE_HAS_LOCK);
-                   
-                       /* Note: don't bother checking for wrapping; we stay in this
-                        * state until we get a lock */
-                       dvb_frontend_autotune(fe, 0);
+                       fe->ops->tune(fe, params, fepriv->tune_mode_flags, &fepriv->delay, &s);
+                       if (s != fepriv->status) {
+                               dvb_frontend_add_event(fe, s);
+                               fepriv->status = s;
+                       }
+               } else {
+                       dvb_frontend_swzigzag(fe);
                }
        }
 
@@ -520,7 +585,7 @@ static int dvb_frontend_thread (void *data)
 static void dvb_frontend_stop(struct dvb_frontend *fe)
 {
        unsigned long ret;
-       struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv;
+       struct dvb_frontend_private *fepriv = fe->frontend_priv;
 
        dprintk ("%s\n", __FUNCTION__);
 
@@ -556,10 +621,53 @@ static void dvb_frontend_stop(struct dvb_frontend *fe)
                                fepriv->thread_pid);
 }
 
+s32 timeval_usec_diff(struct timeval lasttime, struct timeval curtime)
+{
+       return ((curtime.tv_usec < lasttime.tv_usec) ?
+               1000000 - lasttime.tv_usec + curtime.tv_usec :
+               curtime.tv_usec - lasttime.tv_usec);
+}
+EXPORT_SYMBOL(timeval_usec_diff);
+
+static inline void timeval_usec_add(struct timeval *curtime, u32 add_usec)
+{
+       curtime->tv_usec += add_usec;
+       if (curtime->tv_usec >= 1000000) {
+               curtime->tv_usec -= 1000000;
+               curtime->tv_sec++;
+       }
+}
+
+/*
+ * Sleep until gettimeofday() > waketime + add_usec
+ * This needs to be as precise as possible, but as the delay is
+ * usually between 2ms and 32ms, it is done using a scheduled msleep
+ * followed by usleep (normally a busy-wait loop) for the remainder
+ */
+void dvb_frontend_sleep_until(struct timeval *waketime, u32 add_usec)
+{
+       struct timeval lasttime;
+       s32 delta, newdelta;
+
+       timeval_usec_add(waketime, add_usec);
+
+       do_gettimeofday(&lasttime);
+       delta = timeval_usec_diff(lasttime, *waketime);
+       if (delta > 2500) {
+               msleep((delta - 1500) / 1000);
+               do_gettimeofday(&lasttime);
+               newdelta = timeval_usec_diff(lasttime, *waketime);
+               delta = (newdelta > delta) ? 0 : newdelta;
+       }
+       if (delta > 0)
+               udelay(delta);
+}
+EXPORT_SYMBOL(dvb_frontend_sleep_until);
+
 static int dvb_frontend_start(struct dvb_frontend *fe)
 {
        int ret;
-       struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv;
+       struct dvb_frontend_private *fepriv = fe->frontend_priv;
 
        dprintk ("%s\n", __FUNCTION__);
 
@@ -567,7 +675,7 @@ static int dvb_frontend_start(struct dvb_frontend *fe)
                if (!fepriv->exit)
                        return 0;
                else
-               dvb_frontend_stop (fe);
+                       dvb_frontend_stop (fe);
        }
 
        if (signal_pending(current))
@@ -592,13 +700,12 @@ static int dvb_frontend_start(struct dvb_frontend *fe)
        return 0;
 }
 
-
-static int dvb_frontend_ioctl (struct inode *inode, struct file *file,
+static int dvb_frontend_ioctl(struct inode *inode, struct file *file,
                        unsigned int cmd, void *parg)
 {
        struct dvb_device *dvbdev = file->private_data;
        struct dvb_frontend *fe = dvbdev->priv;
-       struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv;
+       struct dvb_frontend_private *fepriv = fe->frontend_priv;
        int err = -EOPNOTSUPP;
 
        dprintk ("%s\n", __FUNCTION__);
@@ -616,7 +723,7 @@ static int dvb_frontend_ioctl (struct inode *inode, struct file *file,
 
        switch (cmd) {
        case FE_GET_INFO: {
-               struct dvb_frontend_info* info = (struct dvb_frontend_info*) parg;
+               struct dvb_frontend_info* info = parg;
                memcpy(info, &fe->ops->info, sizeof(struct dvb_frontend_info));
 
                /* Force the CAN_INVERSION_AUTO bit on. If the frontend doesn't
@@ -626,11 +733,21 @@ static int dvb_frontend_ioctl (struct inode *inode, struct file *file,
                break;
        }
 
-       case FE_READ_STATUS:
+       case FE_READ_STATUS: {
+               fe_status_t* status = parg;
+
+               /* if retune was requested but hasn't occured yet, prevent
+                * that user get signal state from previous tuning */
+               if(fepriv->state == FESTATE_RETUNE) {
+                       err=0;
+                       *status = 0;
+                       break;
+               }
+
                if (fe->ops->read_status)
-                       err = fe->ops->read_status(fe, (fe_status_t*) parg);
+                       err = fe->ops->read_status(fe, status);
                break;
-
+       }
        case FE_READ_BER:
                if (fe->ops->read_ber)
                        err = fe->ops->read_ber(fe, (__u32*) parg);
@@ -679,6 +796,7 @@ static int dvb_frontend_ioctl (struct inode *inode, struct file *file,
        case FE_SET_TONE:
                if (fe->ops->set_tone) {
                        err = fe->ops->set_tone(fe, (fe_sec_tone_mode_t) parg);
+                       fepriv->tone = (fe_sec_tone_mode_t) parg;
                        fepriv->state = FESTATE_DISEQC;
                        fepriv->status = 0;
                }
@@ -687,6 +805,7 @@ static int dvb_frontend_ioctl (struct inode *inode, struct file *file,
        case FE_SET_VOLTAGE:
                if (fe->ops->set_voltage) {
                        err = fe->ops->set_voltage(fe, (fe_sec_voltage_t) parg);
+                       fepriv->voltage = (fe_sec_voltage_t) parg;
                        fepriv->state = FESTATE_DISEQC;
                        fepriv->status = 0;
                }
@@ -694,7 +813,61 @@ static int dvb_frontend_ioctl (struct inode *inode, struct file *file,
 
        case FE_DISHNETWORK_SEND_LEGACY_CMD:
                if (fe->ops->dishnetwork_send_legacy_command) {
-                       err = fe->ops->dishnetwork_send_legacy_command(fe, (unsigned int) parg);
+                       err = fe->ops->dishnetwork_send_legacy_command(fe, (unsigned long) parg);
+                       fepriv->state = FESTATE_DISEQC;
+                       fepriv->status = 0;
+               } else if (fe->ops->set_voltage) {
+                       /*
+                        * NOTE: This is a fallback condition.  Some frontends
+                        * (stv0299 for instance) take longer than 8msec to
+                        * respond to a set_voltage command.  Those switches
+                        * need custom routines to switch properly.  For all
+                        * other frontends, the following shoule work ok.
+                        * Dish network legacy switches (as used by Dish500)
+                        * are controlled by sending 9-bit command words
+                        * spaced 8msec apart.
+                        * the actual command word is switch/port dependant
+                        * so it is up to the userspace application to send
+                        * the right command.
+                        * The command must always start with a '0' after
+                        * initialization, so parg is 8 bits and does not
+                        * include the initialization or start bit
+                        */
+                       unsigned long cmd = ((unsigned long) parg) << 1;
+                       struct timeval nexttime;
+                       struct timeval tv[10];
+                       int i;
+                       u8 last = 1;
+                       if (dvb_frontend_debug)
+                               printk("%s switch command: 0x%04lx\n", __FUNCTION__, cmd);
+                       do_gettimeofday(&nexttime);
+                       if (dvb_frontend_debug)
+                               memcpy(&tv[0], &nexttime, sizeof(struct timeval));
+                       /* before sending a command, initialize by sending
+                        * a 32ms 18V to the switch
+                        */
+                       fe->ops->set_voltage(fe, SEC_VOLTAGE_18);
+                       dvb_frontend_sleep_until(&nexttime, 32000);
+
+                       for (i = 0; i < 9; i++) {
+                               if (dvb_frontend_debug)
+                                       do_gettimeofday(&tv[i + 1]);
+                               if ((cmd & 0x01) != last) {
+                                       /* set voltage to (last ? 13V : 18V) */
+                                       fe->ops->set_voltage(fe, (last) ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18);
+                                       last = (last) ? 0 : 1;
+                               }
+                               cmd = cmd >> 1;
+                               if (i != 8)
+                                       dvb_frontend_sleep_until(&nexttime, 8000);
+                       }
+                       if (dvb_frontend_debug) {
+                               printk("%s(%d): switch delay (should be 32k followed by all 8k\n",
+                                       __FUNCTION__, fe->dvb->num);
+                               for (i = 1; i < 10; i++)
+                                       printk("%d: %d\n", i, timeval_usec_diff(tv[i-1] , tv[i]));
+                       }
+                       err = 0;
                        fepriv->state = FESTATE_DISEQC;
                        fepriv->status = 0;
                }
@@ -707,19 +880,19 @@ static int dvb_frontend_ioctl (struct inode *inode, struct file *file,
 
        case FE_ENABLE_HIGH_LNB_VOLTAGE:
                if (fe->ops->enable_high_lnb_voltage)
-                       err = fe->ops->enable_high_lnb_voltage(fe, (int) parg);
+                       err = fe->ops->enable_high_lnb_voltage(fe, (long) parg);
                break;
 
        case FE_SET_FRONTEND: {
                struct dvb_frontend_tune_settings fetunesettings;
-           
+
                memcpy (&fepriv->parameters, parg,
                        sizeof (struct dvb_frontend_parameters));
 
                memset(&fetunesettings, 0, sizeof(struct dvb_frontend_tune_settings));
                memcpy(&fetunesettings.parameters, parg,
                       sizeof (struct dvb_frontend_parameters));
-                   
+
                /* force auto frequency inversion if requested */
                if (dvb_force_auto_inversion) {
                        fepriv->parameters.inversion = INVERSION_AUTO;
@@ -745,14 +918,14 @@ static int dvb_frontend_ioctl (struct inode *inode, struct file *file,
                                fepriv->min_delay = HZ/20;
                                fepriv->step_size = fepriv->parameters.u.qpsk.symbol_rate / 16000;
                                fepriv->max_drift = fepriv->parameters.u.qpsk.symbol_rate / 2000;
-               break;
-                           
+                               break;
+
                        case FE_QAM:
                                fepriv->min_delay = HZ/20;
                                fepriv->step_size = 0; /* no zigzag */
                                fepriv->max_drift = 0;
                                break;
-                           
+
                        case FE_OFDM:
                                fepriv->min_delay = HZ/20;
                                fepriv->step_size = fe->ops->info.frequency_stepsize * 2;
@@ -768,7 +941,7 @@ static int dvb_frontend_ioctl (struct inode *inode, struct file *file,
 
                fepriv->state = FESTATE_RETUNE;
                dvb_frontend_wakeup(fe);
-               dvb_frontend_add_event (fe, 0);     
+               dvb_frontend_add_event(fe, 0);
                fepriv->status = 0;
                err = 0;
                break;
@@ -784,18 +957,21 @@ static int dvb_frontend_ioctl (struct inode *inode, struct file *file,
                        err = fe->ops->get_frontend(fe, (struct dvb_frontend_parameters*) parg);
                }
                break;
+
+       case FE_SET_FRONTEND_TUNE_MODE:
+               fepriv->tune_mode_flags = (unsigned long) parg;
+               break;
        };
 
        up (&fepriv->sem);
        return err;
 }
 
-
-static unsigned int dvb_frontend_poll (struct file *file, struct poll_table_struct *wait)
+static unsigned int dvb_frontend_poll(struct file *file, struct poll_table_struct *wait)
 {
        struct dvb_device *dvbdev = file->private_data;
        struct dvb_frontend *fe = dvbdev->priv;
-       struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv;
+       struct dvb_frontend_private *fepriv = fe->frontend_priv;
 
        dprintk ("%s\n", __FUNCTION__);
 
@@ -807,12 +983,11 @@ static unsigned int dvb_frontend_poll (struct file *file, struct poll_table_stru
        return 0;
 }
 
-
-static int dvb_frontend_open (struct inode *inode, struct file *file)
+static int dvb_frontend_open(struct inode *inode, struct file *file)
 {
        struct dvb_device *dvbdev = file->private_data;
        struct dvb_frontend *fe = dvbdev->priv;
-       struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv;
+       struct dvb_frontend_private *fepriv = fe->frontend_priv;
        int ret;
 
        dprintk ("%s\n", __FUNCTION__);
@@ -827,17 +1002,21 @@ static int dvb_frontend_open (struct inode *inode, struct file *file)
 
                /*  empty event queue */
                fepriv->events.eventr = fepriv->events.eventw = 0;
+
+               /* normal tune mode when opened R/W */
+               fepriv->tune_mode_flags &= ~FE_TUNE_MODE_ONESHOT;
+               fepriv->tone = -1;
+               fepriv->voltage = -1;
        }
-       
+
        return ret;
 }
 
-
-static int dvb_frontend_release (struct inode *inode, struct file *file)
+static int dvb_frontend_release(struct inode *inode, struct file *file)
 {
        struct dvb_device *dvbdev = file->private_data;
        struct dvb_frontend *fe = dvbdev->priv;
-       struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv;
+       struct dvb_frontend_private *fepriv = fe->frontend_priv;
 
        dprintk ("%s\n", __FUNCTION__);
 
@@ -847,7 +1026,6 @@ static int dvb_frontend_release (struct inode *inode, struct file *file)
        return dvb_generic_release (inode, file);
 }
 
-
 static struct file_operations dvb_frontend_fops = {
        .owner          = THIS_MODULE,
        .ioctl          = dvb_generic_ioctl,
@@ -870,16 +1048,15 @@ int dvb_register_frontend(struct dvb_adapter* dvb,
 
        dprintk ("%s\n", __FUNCTION__);
 
-       if (down_interruptible (&frontend_mutex))
+       if (mutex_lock_interruptible(&frontend_mutex))
                return -ERESTARTSYS;
 
-       fe->frontend_priv = kmalloc(sizeof(struct dvb_frontend_private), GFP_KERNEL);
+       fe->frontend_priv = kzalloc(sizeof(struct dvb_frontend_private), GFP_KERNEL);
        if (fe->frontend_priv == NULL) {
-               up(&frontend_mutex);
+               mutex_unlock(&frontend_mutex);
                return -ENOMEM;
        }
-       fepriv = (struct dvb_frontend_private*) fe->frontend_priv;
-       memset(fe->frontend_priv, 0, sizeof(struct dvb_frontend_private));
+       fepriv = fe->frontend_priv;
 
        init_MUTEX (&fepriv->sem);
        init_waitqueue_head (&fepriv->wait_queue);
@@ -895,27 +1072,26 @@ int dvb_register_frontend(struct dvb_adapter* dvb,
        dvb_register_device (fe->dvb, &fepriv->dvbdev, &dvbdev_template,
                             fe, DVB_DEVICE_FRONTEND);
 
-       up (&frontend_mutex);
+       mutex_unlock(&frontend_mutex);
        return 0;
 }
 EXPORT_SYMBOL(dvb_register_frontend);
 
 int dvb_unregister_frontend(struct dvb_frontend* fe)
 {
-       struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv;
+       struct dvb_frontend_private *fepriv = fe->frontend_priv;
        dprintk ("%s\n", __FUNCTION__);
 
-       down (&frontend_mutex);
+       mutex_lock(&frontend_mutex);
        dvb_unregister_device (fepriv->dvbdev);
-                       dvb_frontend_stop (fe);
+       dvb_frontend_stop (fe);
        if (fe->ops->release)
                fe->ops->release(fe);
        else
                printk("dvb_frontend: Demodulator (%s) does not have a release callback!\n", fe->ops->info.name);
        /* fe is invalid now */
-       if (fepriv)
-               kfree(fepriv);
-       up (&frontend_mutex);
+       kfree(fepriv);
+       mutex_unlock(&frontend_mutex);
        return 0;
 }
 EXPORT_SYMBOL(dvb_unregister_frontend);