ALSA: aloop: Avoid unexpected timer event callback tasklets
[sfrench/cifs-2.6.git] / sound / drivers / aloop.c
index 9ccdad89c288d30d3b6853323098813186ebaf14..0ebfbe70db00607b1502791fd611fda5871ed400 100644 (file)
@@ -28,6 +28,7 @@
 #include <sound/pcm_params.h>
 #include <sound/info.h>
 #include <sound/initval.h>
+#include <sound/timer.h>
 
 MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
 MODULE_DESCRIPTION("A loopback soundcard");
@@ -41,6 +42,7 @@ static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;     /* ID for this card */
 static bool enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0};
 static int pcm_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8};
 static int pcm_notify[SNDRV_CARDS];
+static char *timer_source[SNDRV_CARDS];
 
 module_param_array(index, int, NULL, 0444);
 MODULE_PARM_DESC(index, "Index value for loopback soundcard.");
@@ -52,11 +54,48 @@ module_param_array(pcm_substreams, int, NULL, 0444);
 MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-8) for loopback driver.");
 module_param_array(pcm_notify, int, NULL, 0444);
 MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels changes.");
+module_param_array(timer_source, charp, NULL, 0444);
+MODULE_PARM_DESC(timer_source, "Sound card name or number and device/subdevice number of timer to be used. Empty string for jiffies timer [default].");
 
 #define NO_PITCH 100000
 
+#define CABLE_VALID_PLAYBACK   BIT(SNDRV_PCM_STREAM_PLAYBACK)
+#define CABLE_VALID_CAPTURE    BIT(SNDRV_PCM_STREAM_CAPTURE)
+#define CABLE_VALID_BOTH       (CABLE_VALID_PLAYBACK | CABLE_VALID_CAPTURE)
+
+struct loopback_cable;
 struct loopback_pcm;
 
+struct loopback_ops {
+       /* optional
+        * call in loopback->cable_lock
+        */
+       int (*open)(struct loopback_pcm *dpcm);
+       /* required
+        * call in cable->lock
+        */
+       int (*start)(struct loopback_pcm *dpcm);
+       /* required
+        * call in cable->lock
+        */
+       int (*stop)(struct loopback_pcm *dpcm);
+       /* optional */
+       int (*stop_sync)(struct loopback_pcm *dpcm);
+       /* optional */
+       int (*close_substream)(struct loopback_pcm *dpcm);
+       /* optional
+        * call in loopback->cable_lock
+        */
+       int (*close_cable)(struct loopback_pcm *dpcm);
+       /* optional
+        * call in cable->lock
+        */
+       unsigned int (*pos_update)(struct loopback_cable *cable);
+       /* optional */
+       void (*dpcm_info)(struct loopback_pcm *dpcm,
+                         struct snd_info_buffer *buffer);
+};
+
 struct loopback_cable {
        spinlock_t lock;
        struct loopback_pcm *streams[2];
@@ -65,6 +104,15 @@ struct loopback_cable {
        unsigned int valid;
        unsigned int running;
        unsigned int pause;
+       /* timer specific */
+       struct loopback_ops *ops;
+       /* If sound timer is used */
+       struct {
+               int stream;
+               struct snd_timer_id id;
+               struct tasklet_struct event_tasklet;
+               struct snd_timer_instance *instance;
+       } snd_timer;
 };
 
 struct loopback_setup {
@@ -85,6 +133,7 @@ struct loopback {
        struct loopback_cable *cables[MAX_PCM_SUBSTREAMS][2];
        struct snd_pcm *pcm[2];
        struct loopback_setup setup[MAX_PCM_SUBSTREAMS][2];
+       const char *timer_source;
 };
 
 struct loopback_pcm {
@@ -102,10 +151,13 @@ struct loopback_pcm {
        /* flags */
        unsigned int period_update_pending :1;
        /* timer stuff */
-       unsigned int irq_pos;           /* fractional IRQ position */
-       unsigned int period_size_frac;
+       unsigned int irq_pos;           /* fractional IRQ position in jiffies
+                                        * ticks
+                                        */
+       unsigned int period_size_frac;  /* period size in jiffies ticks */
        unsigned int last_drift;
        unsigned long last_jiffies;
+       /* If jiffies timer is used */
        struct timer_list timer;
 };
 
@@ -153,7 +205,7 @@ static inline unsigned int get_rate_shift(struct loopback_pcm *dpcm)
 }
 
 /* call in cable->lock */
-static void loopback_timer_start(struct loopback_pcm *dpcm)
+static int loopback_jiffies_timer_start(struct loopback_pcm *dpcm)
 {
        unsigned long tick;
        unsigned int rate_shift = get_rate_shift(dpcm);
@@ -169,23 +221,102 @@ static void loopback_timer_start(struct loopback_pcm *dpcm)
        tick = dpcm->period_size_frac - dpcm->irq_pos;
        tick = (tick + dpcm->pcm_bps - 1) / dpcm->pcm_bps;
        mod_timer(&dpcm->timer, jiffies + tick);
+
+       return 0;
 }
 
 /* call in cable->lock */
-static inline void loopback_timer_stop(struct loopback_pcm *dpcm)
+static int loopback_snd_timer_start(struct loopback_pcm *dpcm)
+{
+       struct loopback_cable *cable = dpcm->cable;
+       int err;
+
+       /* Loopback device has to use same period as timer card. Therefore
+        * wake up for each snd_pcm_period_elapsed() call of timer card.
+        */
+       err = snd_timer_start(cable->snd_timer.instance, 1);
+       if (err < 0) {
+               /* do not report error if trying to start but already
+                * running. For example called by opposite substream
+                * of the same cable
+                */
+               if (err == -EBUSY)
+                       return 0;
+
+               pcm_err(dpcm->substream->pcm,
+                       "snd_timer_start(%d,%d,%d) failed with %d",
+                       cable->snd_timer.id.card,
+                       cable->snd_timer.id.device,
+                       cable->snd_timer.id.subdevice,
+                       err);
+       }
+
+       return err;
+}
+
+/* call in cable->lock */
+static inline int loopback_jiffies_timer_stop(struct loopback_pcm *dpcm)
 {
        del_timer(&dpcm->timer);
        dpcm->timer.expires = 0;
+
+       return 0;
 }
 
-static inline void loopback_timer_stop_sync(struct loopback_pcm *dpcm)
+/* call in cable->lock */
+static int loopback_snd_timer_stop(struct loopback_pcm *dpcm)
+{
+       struct loopback_cable *cable = dpcm->cable;
+       int err;
+
+       /* only stop if both devices (playback and capture) are not running */
+       if (cable->running ^ cable->pause)
+               return 0;
+
+       err = snd_timer_stop(cable->snd_timer.instance);
+       if (err < 0) {
+               pcm_err(dpcm->substream->pcm,
+                       "snd_timer_stop(%d,%d,%d) failed with %d",
+                       cable->snd_timer.id.card,
+                       cable->snd_timer.id.device,
+                       cable->snd_timer.id.subdevice,
+                       err);
+       }
+
+       return err;
+}
+
+static inline int loopback_jiffies_timer_stop_sync(struct loopback_pcm *dpcm)
 {
        del_timer_sync(&dpcm->timer);
+
+       return 0;
 }
 
-#define CABLE_VALID_PLAYBACK   (1 << SNDRV_PCM_STREAM_PLAYBACK)
-#define CABLE_VALID_CAPTURE    (1 << SNDRV_PCM_STREAM_CAPTURE)
-#define CABLE_VALID_BOTH       (CABLE_VALID_PLAYBACK|CABLE_VALID_CAPTURE)
+/* call in loopback->cable_lock */
+static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm)
+{
+       struct loopback_cable *cable = dpcm->cable;
+
+       /* snd_timer was not opened */
+       if (!cable->snd_timer.instance)
+               return 0;
+
+       /* will only be called from free_cable() when other stream was
+        * already closed. Other stream cannot be reopened as long as
+        * loopback->cable_lock is locked. Therefore no need to lock
+        * cable->lock;
+        */
+       snd_timer_close(cable->snd_timer.instance);
+
+       /* wait till drain tasklet has finished if requested */
+       tasklet_kill(&cable->snd_timer.event_tasklet);
+
+       snd_timer_instance_free(cable->snd_timer.instance);
+       memset(&cable->snd_timer, 0, sizeof(cable->snd_timer));
+
+       return 0;
+}
 
 static int loopback_check_format(struct loopback_cable *cable, int stream)
 {
@@ -249,7 +380,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
        struct snd_pcm_runtime *runtime = substream->runtime;
        struct loopback_pcm *dpcm = runtime->private_data;
        struct loopback_cable *cable = dpcm->cable;
-       int err, stream = 1 << substream->stream;
+       int err = 0, stream = 1 << substream->stream;
 
        switch (cmd) {
        case SNDRV_PCM_TRIGGER_START:
@@ -262,7 +393,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
                spin_lock(&cable->lock);        
                cable->running |= stream;
                cable->pause &= ~stream;
-               loopback_timer_start(dpcm);
+               err = cable->ops->start(dpcm);
                spin_unlock(&cable->lock);
                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
                        loopback_active_notify(dpcm);
@@ -271,7 +402,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
                spin_lock(&cable->lock);        
                cable->running &= ~stream;
                cable->pause &= ~stream;
-               loopback_timer_stop(dpcm);
+               err = cable->ops->stop(dpcm);
                spin_unlock(&cable->lock);
                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
                        loopback_active_notify(dpcm);
@@ -280,7 +411,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
        case SNDRV_PCM_TRIGGER_SUSPEND:
                spin_lock(&cable->lock);        
                cable->pause |= stream;
-               loopback_timer_stop(dpcm);
+               err = cable->ops->stop(dpcm);
                spin_unlock(&cable->lock);
                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
                        loopback_active_notify(dpcm);
@@ -290,7 +421,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
                spin_lock(&cable->lock);
                dpcm->last_jiffies = jiffies;
                cable->pause &= ~stream;
-               loopback_timer_start(dpcm);
+               err = cable->ops->start(dpcm);
                spin_unlock(&cable->lock);
                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
                        loopback_active_notify(dpcm);
@@ -298,7 +429,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
        default:
                return -EINVAL;
        }
-       return 0;
+       return err;
 }
 
 static void params_change(struct snd_pcm_substream *substream)
@@ -312,6 +443,13 @@ static void params_change(struct snd_pcm_substream *substream)
        cable->hw.rate_max = runtime->rate;
        cable->hw.channels_min = runtime->channels;
        cable->hw.channels_max = runtime->channels;
+
+       if (cable->snd_timer.instance) {
+               cable->hw.period_bytes_min =
+                               frames_to_bytes(runtime, runtime->period_size);
+               cable->hw.period_bytes_max = cable->hw.period_bytes_min;
+       }
+
 }
 
 static int loopback_prepare(struct snd_pcm_substream *substream)
@@ -319,9 +457,13 @@ static int loopback_prepare(struct snd_pcm_substream *substream)
        struct snd_pcm_runtime *runtime = substream->runtime;
        struct loopback_pcm *dpcm = runtime->private_data;
        struct loopback_cable *cable = dpcm->cable;
-       int bps, salign;
+       int err, bps, salign;
 
-       loopback_timer_stop_sync(dpcm);
+       if (cable->ops->stop_sync) {
+               err = cable->ops->stop_sync(dpcm);
+               if (err < 0)
+                       return err;
+       }
 
        salign = (snd_pcm_format_physical_width(runtime->format) *
                                                runtime->channels) / 8;
@@ -457,7 +599,8 @@ static inline void bytepos_finish(struct loopback_pcm *dpcm,
 }
 
 /* call in cable->lock */
-static unsigned int loopback_pos_update(struct loopback_cable *cable)
+static unsigned int loopback_jiffies_timer_pos_update
+               (struct loopback_cable *cable)
 {
        struct loopback_pcm *dpcm_play =
                        cable->streams[SNDRV_PCM_STREAM_PLAYBACK];
@@ -510,14 +653,15 @@ static unsigned int loopback_pos_update(struct loopback_cable *cable)
        return running;
 }
 
-static void loopback_timer_function(struct timer_list *t)
+static void loopback_jiffies_timer_function(struct timer_list *t)
 {
        struct loopback_pcm *dpcm = from_timer(dpcm, t, timer);
        unsigned long flags;
 
        spin_lock_irqsave(&dpcm->cable->lock, flags);
-       if (loopback_pos_update(dpcm->cable) & (1 << dpcm->substream->stream)) {
-               loopback_timer_start(dpcm);
+       if (loopback_jiffies_timer_pos_update(dpcm->cable) &
+                       (1 << dpcm->substream->stream)) {
+               loopback_jiffies_timer_start(dpcm);
                if (dpcm->period_update_pending) {
                        dpcm->period_update_pending = 0;
                        spin_unlock_irqrestore(&dpcm->cable->lock, flags);
@@ -529,6 +673,193 @@ static void loopback_timer_function(struct timer_list *t)
        spin_unlock_irqrestore(&dpcm->cable->lock, flags);
 }
 
+/* call in cable->lock */
+static int loopback_snd_timer_check_resolution(struct snd_pcm_runtime *runtime,
+                                              unsigned long resolution)
+{
+       if (resolution != runtime->timer_resolution) {
+               struct loopback_pcm *dpcm = runtime->private_data;
+               struct loopback_cable *cable = dpcm->cable;
+               /* Worst case estimation of possible values for resolution
+                * resolution <= (512 * 1024) frames / 8kHz in nsec
+                * resolution <= 65.536.000.000 nsec
+                *
+                * period_size <= 65.536.000.000 nsec / 1000nsec/usec * 192kHz +
+                *  500.000
+                * period_size <= 12.582.912.000.000  <64bit
+                *  / 1.000.000 usec/sec
+                */
+               snd_pcm_uframes_t period_size_usec =
+                               resolution / 1000 * runtime->rate;
+               /* round to nearest sample rate */
+               snd_pcm_uframes_t period_size =
+                               (period_size_usec + 500 * 1000) / (1000 * 1000);
+
+               pcm_err(dpcm->substream->pcm,
+                       "Period size (%lu frames) of loopback device is not corresponding to timer resolution (%lu nsec = %lu frames) of card timer %d,%d,%d. Use period size of %lu frames for loopback device.",
+                       runtime->period_size, resolution, period_size,
+                       cable->snd_timer.id.card,
+                       cable->snd_timer.id.device,
+                       cable->snd_timer.id.subdevice,
+                       period_size);
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static void loopback_snd_timer_period_elapsed(struct loopback_cable *cable,
+                                             int event,
+                                             unsigned long resolution)
+{
+       struct loopback_pcm *dpcm_play, *dpcm_capt;
+       struct snd_pcm_substream *substream_play, *substream_capt;
+       struct snd_pcm_runtime *valid_runtime;
+       unsigned int running, elapsed_bytes;
+       unsigned long flags;
+
+       spin_lock_irqsave(&cable->lock, flags);
+       running = cable->running ^ cable->pause;
+       /* no need to do anything if no stream is running */
+       if (!running) {
+               spin_unlock_irqrestore(&cable->lock, flags);
+               return;
+       }
+
+       dpcm_play = cable->streams[SNDRV_PCM_STREAM_PLAYBACK];
+       dpcm_capt = cable->streams[SNDRV_PCM_STREAM_CAPTURE];
+       substream_play = (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) ?
+                       dpcm_play->substream : NULL;
+       substream_capt = (running & (1 << SNDRV_PCM_STREAM_CAPTURE)) ?
+                       dpcm_capt->substream : NULL;
+
+       if (event == SNDRV_TIMER_EVENT_MSTOP) {
+               if (!dpcm_play ||
+                   dpcm_play->substream->runtime->status->state !=
+                               SNDRV_PCM_STATE_DRAINING) {
+                       spin_unlock_irqrestore(&cable->lock, flags);
+                       return;
+               }
+       }
+
+       valid_runtime = (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) ?
+                               dpcm_play->substream->runtime :
+                               dpcm_capt->substream->runtime;
+
+       /* resolution is only valid for SNDRV_TIMER_EVENT_TICK events */
+       if (event == SNDRV_TIMER_EVENT_TICK) {
+               /* The hardware rules guarantee that playback and capture period
+                * are the same. Therefore only one device has to be checked
+                * here.
+                */
+               if (loopback_snd_timer_check_resolution(valid_runtime,
+                                                       resolution) < 0) {
+                       spin_unlock_irqrestore(&cable->lock, flags);
+                       if (substream_play)
+                               snd_pcm_stop_xrun(substream_play);
+                       if (substream_capt)
+                               snd_pcm_stop_xrun(substream_capt);
+                       return;
+               }
+       }
+
+       elapsed_bytes = frames_to_bytes(valid_runtime,
+                                       valid_runtime->period_size);
+       /* The same timer interrupt is used for playback and capture device */
+       if ((running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) &&
+           (running & (1 << SNDRV_PCM_STREAM_CAPTURE))) {
+               copy_play_buf(dpcm_play, dpcm_capt, elapsed_bytes);
+               bytepos_finish(dpcm_play, elapsed_bytes);
+               bytepos_finish(dpcm_capt, elapsed_bytes);
+       } else if (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) {
+               bytepos_finish(dpcm_play, elapsed_bytes);
+       } else if (running & (1 << SNDRV_PCM_STREAM_CAPTURE)) {
+               clear_capture_buf(dpcm_capt, elapsed_bytes);
+               bytepos_finish(dpcm_capt, elapsed_bytes);
+       }
+       spin_unlock_irqrestore(&cable->lock, flags);
+
+       if (substream_play)
+               snd_pcm_period_elapsed(substream_play);
+       if (substream_capt)
+               snd_pcm_period_elapsed(substream_capt);
+}
+
+static void loopback_snd_timer_function(struct snd_timer_instance *timeri,
+                                       unsigned long resolution,
+                                       unsigned long ticks)
+{
+       struct loopback_cable *cable = timeri->callback_data;
+
+       loopback_snd_timer_period_elapsed(cable, SNDRV_TIMER_EVENT_TICK,
+                                         resolution);
+}
+
+static void loopback_snd_timer_tasklet(unsigned long arg)
+{
+       struct snd_timer_instance *timeri = (struct snd_timer_instance *)arg;
+       struct loopback_cable *cable = timeri->callback_data;
+
+       loopback_snd_timer_period_elapsed(cable, SNDRV_TIMER_EVENT_MSTOP, 0);
+}
+
+static void loopback_snd_timer_event(struct snd_timer_instance *timeri,
+                                    int event,
+                                    struct timespec *tstamp,
+                                    unsigned long resolution)
+{
+       /* Do not lock cable->lock here because timer->lock is already hold.
+        * There are other functions which first lock cable->lock and than
+        * timer->lock e.g.
+        * loopback_trigger()
+        * spin_lock(&cable->lock)
+        * loopback_snd_timer_start()
+        * snd_timer_start()
+        * spin_lock(&timer->lock)
+        * Therefore when using the oposit order of locks here it could result
+        * in a deadlock.
+        */
+
+       if (event == SNDRV_TIMER_EVENT_MSTOP) {
+               struct loopback_cable *cable = timeri->callback_data;
+
+               /* sound card of the timer was stopped. Therefore there will not
+                * be any further timer callbacks. Due to this forward audio
+                * data from here if in draining state. When still in running
+                * state the streaming will be aborted by the usual timeout. It
+                * should not be aborted here because may be the timer sound
+                * card does only a recovery and the timer is back soon.
+                * This tasklet triggers loopback_snd_timer_tasklet()
+                */
+               tasklet_schedule(&cable->snd_timer.event_tasklet);
+       }
+}
+
+static void loopback_jiffies_timer_dpcm_info(struct loopback_pcm *dpcm,
+                                            struct snd_info_buffer *buffer)
+{
+       snd_iprintf(buffer, "    update_pending:\t%u\n",
+                   dpcm->period_update_pending);
+       snd_iprintf(buffer, "    irq_pos:\t\t%u\n", dpcm->irq_pos);
+       snd_iprintf(buffer, "    period_frac:\t%u\n", dpcm->period_size_frac);
+       snd_iprintf(buffer, "    last_jiffies:\t%lu (%lu)\n",
+                   dpcm->last_jiffies, jiffies);
+       snd_iprintf(buffer, "    timer_expires:\t%lu\n", dpcm->timer.expires);
+}
+
+static void loopback_snd_timer_dpcm_info(struct loopback_pcm *dpcm,
+                                        struct snd_info_buffer *buffer)
+{
+       struct loopback_cable *cable = dpcm->cable;
+
+       snd_iprintf(buffer, "    sound timer:\thw:%d,%d,%d\n",
+                   cable->snd_timer.id.card,
+                   cable->snd_timer.id.device,
+                   cable->snd_timer.id.subdevice);
+       snd_iprintf(buffer, "    timer open:\t\t%s\n",
+                   (cable->snd_timer.stream == SNDRV_PCM_STREAM_CAPTURE) ?
+                           "capture" : "playback");
+}
+
 static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
 {
        struct snd_pcm_runtime *runtime = substream->runtime;
@@ -536,7 +867,8 @@ static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
        snd_pcm_uframes_t pos;
 
        spin_lock(&dpcm->cable->lock);
-       loopback_pos_update(dpcm->cable);
+       if (dpcm->cable->ops->pos_update)
+               dpcm->cable->ops->pos_update(dpcm->cable);
        pos = dpcm->buf_pos;
        spin_unlock(&dpcm->cable->lock);
        return bytes_to_frames(runtime, pos);
@@ -576,8 +908,7 @@ static void loopback_runtime_free(struct snd_pcm_runtime *runtime)
 static int loopback_hw_params(struct snd_pcm_substream *substream,
                              struct snd_pcm_hw_params *params)
 {
-       return snd_pcm_lib_alloc_vmalloc_buffer(substream,
-                                               params_buffer_bytes(params));
+       return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
 }
 
 static int loopback_hw_free(struct snd_pcm_substream *substream)
@@ -589,7 +920,7 @@ static int loopback_hw_free(struct snd_pcm_substream *substream)
        mutex_lock(&dpcm->loopback->cable_lock);
        cable->valid &= ~(1 << substream->stream);
        mutex_unlock(&dpcm->loopback->cable_lock);
-       return snd_pcm_lib_free_vmalloc_buffer(substream);
+       return snd_pcm_lib_free_pages(substream);
 }
 
 static unsigned int get_cable_index(struct snd_pcm_substream *substream)
@@ -647,6 +978,23 @@ static int rule_channels(struct snd_pcm_hw_params *params,
        return snd_interval_refine(hw_param_interval(params, rule->var), &t);
 }
 
+static int rule_period_bytes(struct snd_pcm_hw_params *params,
+                            struct snd_pcm_hw_rule *rule)
+{
+       struct loopback_pcm *dpcm = rule->private;
+       struct loopback_cable *cable = dpcm->cable;
+       struct snd_interval t;
+
+       mutex_lock(&dpcm->loopback->cable_lock);
+       t.min = cable->hw.period_bytes_min;
+       t.max = cable->hw.period_bytes_max;
+       mutex_unlock(&dpcm->loopback->cable_lock);
+       t.openmin = 0;
+       t.openmax = 0;
+       t.integer = 0;
+       return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
 static void free_cable(struct snd_pcm_substream *substream)
 {
        struct loopback *loopback = substream->private_data;
@@ -662,12 +1010,183 @@ static void free_cable(struct snd_pcm_substream *substream)
                cable->streams[substream->stream] = NULL;
                spin_unlock_irq(&cable->lock);
        } else {
+               struct loopback_pcm *dpcm = substream->runtime->private_data;
+
+               if (cable->ops && cable->ops->close_cable && dpcm)
+                       cable->ops->close_cable(dpcm);
                /* free the cable */
                loopback->cables[substream->number][dev] = NULL;
                kfree(cable);
        }
 }
 
+static int loopback_jiffies_timer_open(struct loopback_pcm *dpcm)
+{
+       timer_setup(&dpcm->timer, loopback_jiffies_timer_function, 0);
+
+       return 0;
+}
+
+static struct loopback_ops loopback_jiffies_timer_ops = {
+       .open = loopback_jiffies_timer_open,
+       .start = loopback_jiffies_timer_start,
+       .stop = loopback_jiffies_timer_stop,
+       .stop_sync = loopback_jiffies_timer_stop_sync,
+       .close_substream = loopback_jiffies_timer_stop_sync,
+       .pos_update = loopback_jiffies_timer_pos_update,
+       .dpcm_info = loopback_jiffies_timer_dpcm_info,
+};
+
+static int loopback_parse_timer_id(const char *str,
+                                  struct snd_timer_id *tid)
+{
+       /* [<pref>:](<card name>|<card idx>)[{.,}<dev idx>[{.,}<subdev idx>]] */
+       const char * const sep_dev = ".,";
+       const char * const sep_pref = ":";
+       const char *name = str;
+       char *sep, save = '\0';
+       int card_idx = 0, dev = 0, subdev = 0;
+       int err;
+
+       sep = strpbrk(str, sep_pref);
+       if (sep)
+               name = sep + 1;
+       sep = strpbrk(name, sep_dev);
+       if (sep) {
+               save = *sep;
+               *sep = '\0';
+       }
+       err = kstrtoint(name, 0, &card_idx);
+       if (err == -EINVAL) {
+               /* Must be the name, not number */
+               for (card_idx = 0; card_idx < snd_ecards_limit; card_idx++) {
+                       struct snd_card *card = snd_card_ref(card_idx);
+
+                       if (card) {
+                               if (!strcmp(card->id, name))
+                                       err = 0;
+                               snd_card_unref(card);
+                       }
+                       if (!err)
+                               break;
+               }
+       }
+       if (sep) {
+               *sep = save;
+               if (!err) {
+                       char *sep2, save2 = '\0';
+
+                       sep2 = strpbrk(sep + 1, sep_dev);
+                       if (sep2) {
+                               save2 = *sep2;
+                               *sep2 = '\0';
+                       }
+                       err = kstrtoint(sep + 1, 0, &dev);
+                       if (sep2) {
+                               *sep2 = save2;
+                               if (!err)
+                                       err = kstrtoint(sep2 + 1, 0, &subdev);
+                       }
+               }
+       }
+       if (!err && tid) {
+               tid->card = card_idx;
+               tid->device = dev;
+               tid->subdevice = subdev;
+       }
+       return err;
+}
+
+/* call in loopback->cable_lock */
+static int loopback_snd_timer_open(struct loopback_pcm *dpcm)
+{
+       int err = 0;
+       struct snd_timer_id tid = {
+               .dev_class = SNDRV_TIMER_CLASS_PCM,
+               .dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION,
+       };
+       struct snd_timer_instance *timeri;
+       struct loopback_cable *cable = dpcm->cable;
+
+       /* check if timer was already opened. It is only opened once
+        * per playback and capture subdevice (aka cable).
+        */
+       if (cable->snd_timer.instance)
+               goto exit;
+
+       err = loopback_parse_timer_id(dpcm->loopback->timer_source, &tid);
+       if (err < 0) {
+               pcm_err(dpcm->substream->pcm,
+                       "Parsing timer source \'%s\' failed with %d",
+                       dpcm->loopback->timer_source, err);
+               goto exit;
+       }
+
+       cable->snd_timer.stream = dpcm->substream->stream;
+       cable->snd_timer.id = tid;
+
+       timeri = snd_timer_instance_new(dpcm->loopback->card->id);
+       if (!timeri) {
+               err = -ENOMEM;
+               goto exit;
+       }
+       /* The callback has to be called from another tasklet. If
+        * SNDRV_TIMER_IFLG_FAST is specified it will be called from the
+        * snd_pcm_period_elapsed() call of the selected sound card.
+        * snd_pcm_period_elapsed() helds snd_pcm_stream_lock_irqsave().
+        * Due to our callback loopback_snd_timer_function() also calls
+        * snd_pcm_period_elapsed() which calls snd_pcm_stream_lock_irqsave().
+        * This would end up in a dead lock.
+        */
+       timeri->flags |= SNDRV_TIMER_IFLG_AUTO;
+       timeri->callback = loopback_snd_timer_function;
+       timeri->callback_data = (void *)cable;
+       timeri->ccallback = loopback_snd_timer_event;
+
+       /* initialise a tasklet used for draining */
+       tasklet_init(&cable->snd_timer.event_tasklet,
+                    loopback_snd_timer_tasklet, (unsigned long)timeri);
+
+       /* The mutex loopback->cable_lock is kept locked.
+        * Therefore snd_timer_open() cannot be called a second time
+        * by the other device of the same cable.
+        * Therefore the following issue cannot happen:
+        * [proc1] Call loopback_timer_open() ->
+        *         Unlock cable->lock for snd_timer_close/open() call
+        * [proc2] Call loopback_timer_open() -> snd_timer_open(),
+        *         snd_timer_start()
+        * [proc1] Call snd_timer_open() and overwrite running timer
+        *         instance
+        */
+       err = snd_timer_open(timeri, &cable->snd_timer.id, current->pid);
+       if (err < 0) {
+               pcm_err(dpcm->substream->pcm,
+                       "snd_timer_open (%d,%d,%d) failed with %d",
+                       cable->snd_timer.id.card,
+                       cable->snd_timer.id.device,
+                       cable->snd_timer.id.subdevice,
+                       err);
+               snd_timer_instance_free(timeri);
+               goto exit;
+       }
+
+       cable->snd_timer.instance = timeri;
+
+exit:
+       return err;
+}
+
+/* stop_sync() is not required for sound timer because it does not need to be
+ * restarted in loopback_prepare() on Xrun recovery
+ */
+static struct loopback_ops loopback_snd_timer_ops = {
+       .open = loopback_snd_timer_open,
+       .start = loopback_snd_timer_start,
+       .stop = loopback_snd_timer_stop,
+       .close_cable = loopback_snd_timer_close_cable,
+       .dpcm_info = loopback_snd_timer_dpcm_info,
+};
+
 static int loopback_open(struct snd_pcm_substream *substream)
 {
        struct snd_pcm_runtime *runtime = substream->runtime;
@@ -685,7 +1204,6 @@ static int loopback_open(struct snd_pcm_substream *substream)
        }
        dpcm->loopback = loopback;
        dpcm->substream = substream;
-       timer_setup(&dpcm->timer, loopback_timer_function, 0);
 
        cable = loopback->cables[substream->number][dev];
        if (!cable) {
@@ -696,9 +1214,20 @@ static int loopback_open(struct snd_pcm_substream *substream)
                }
                spin_lock_init(&cable->lock);
                cable->hw = loopback_pcm_hardware;
+               if (loopback->timer_source)
+                       cable->ops = &loopback_snd_timer_ops;
+               else
+                       cable->ops = &loopback_jiffies_timer_ops;
                loopback->cables[substream->number][dev] = cable;
        }
        dpcm->cable = cable;
+       runtime->private_data = dpcm;
+
+       if (cable->ops->open) {
+               err = cable->ops->open(dpcm);
+               if (err < 0)
+                       goto unlock;
+       }
 
        snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
 
@@ -724,7 +1253,22 @@ static int loopback_open(struct snd_pcm_substream *substream)
        if (err < 0)
                goto unlock;
 
-       runtime->private_data = dpcm;
+       /* In case of sound timer the period time of both devices of the same
+        * loop has to be the same.
+        * This rule only takes effect if a sound timer was chosen
+        */
+       if (cable->snd_timer.instance) {
+               err = snd_pcm_hw_rule_add(runtime, 0,
+                                         SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+                                         rule_period_bytes, dpcm,
+                                         SNDRV_PCM_HW_PARAM_PERIOD_BYTES, -1);
+               if (err < 0)
+                       goto unlock;
+       }
+
+       /* loopback_runtime_free() has not to be called if kfree(dpcm) was
+        * already called here. Otherwise it will end up with a double free.
+        */
        runtime->private_free = loopback_runtime_free;
        if (get_notify(dpcm))
                runtime->hw = loopback_pcm_hardware;
@@ -748,12 +1292,14 @@ static int loopback_close(struct snd_pcm_substream *substream)
 {
        struct loopback *loopback = substream->private_data;
        struct loopback_pcm *dpcm = substream->runtime->private_data;
+       int err = 0;
 
-       loopback_timer_stop_sync(dpcm);
+       if (dpcm->cable->ops->close_substream)
+               err = dpcm->cable->ops->close_substream(dpcm);
        mutex_lock(&loopback->cable_lock);
        free_cable(substream);
        mutex_unlock(&loopback->cable_lock);
-       return 0;
+       return err;
 }
 
 static const struct snd_pcm_ops loopback_pcm_ops = {
@@ -765,7 +1311,6 @@ static const struct snd_pcm_ops loopback_pcm_ops = {
        .prepare =      loopback_prepare,
        .trigger =      loopback_trigger,
        .pointer =      loopback_pointer,
-       .page =         snd_pcm_lib_get_vmalloc_page,
 };
 
 static int loopback_pcm_new(struct loopback *loopback,
@@ -780,6 +1325,8 @@ static int loopback_pcm_new(struct loopback *loopback,
                return err;
        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &loopback_pcm_ops);
        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &loopback_pcm_ops);
+       snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_VMALLOC,
+                                             NULL, 0, 0);
 
        pcm->private_data = loopback;
        pcm->info_flags = 0;
@@ -1076,13 +1623,8 @@ static void print_dpcm_info(struct snd_info_buffer *buffer,
        snd_iprintf(buffer, "    bytes_per_sec:\t%u\n", dpcm->pcm_bps);
        snd_iprintf(buffer, "    sample_align:\t%u\n", dpcm->pcm_salign);
        snd_iprintf(buffer, "    rate_shift:\t\t%u\n", dpcm->pcm_rate_shift);
-       snd_iprintf(buffer, "    update_pending:\t%u\n",
-                                               dpcm->period_update_pending);
-       snd_iprintf(buffer, "    irq_pos:\t\t%u\n", dpcm->irq_pos);
-       snd_iprintf(buffer, "    period_frac:\t%u\n", dpcm->period_size_frac);
-       snd_iprintf(buffer, "    last_jiffies:\t%lu (%lu)\n",
-                                       dpcm->last_jiffies, jiffies);
-       snd_iprintf(buffer, "    timer_expires:\t%lu\n", dpcm->timer.expires);
+       if (dpcm->cable->ops->dpcm_info)
+               dpcm->cable->ops->dpcm_info(dpcm, buffer);
 }
 
 static void print_substream_info(struct snd_info_buffer *buffer,
@@ -1118,7 +1660,7 @@ static void print_cable_info(struct snd_info_entry *entry,
        mutex_unlock(&loopback->cable_lock);
 }
 
-static int loopback_proc_new(struct loopback *loopback, int cidx)
+static int loopback_cable_proc_new(struct loopback *loopback, int cidx)
 {
        char name[32];
 
@@ -1127,6 +1669,48 @@ static int loopback_proc_new(struct loopback *loopback, int cidx)
                                    print_cable_info);
 }
 
+static void loopback_set_timer_source(struct loopback *loopback,
+                                     const char *value)
+{
+       if (loopback->timer_source) {
+               devm_kfree(loopback->card->dev, loopback->timer_source);
+               loopback->timer_source = NULL;
+       }
+       if (value && *value)
+               loopback->timer_source = devm_kstrdup(loopback->card->dev,
+                                                     value, GFP_KERNEL);
+}
+
+static void print_timer_source_info(struct snd_info_entry *entry,
+                                   struct snd_info_buffer *buffer)
+{
+       struct loopback *loopback = entry->private_data;
+
+       mutex_lock(&loopback->cable_lock);
+       snd_iprintf(buffer, "%s\n",
+                   loopback->timer_source ? loopback->timer_source : "");
+       mutex_unlock(&loopback->cable_lock);
+}
+
+static void change_timer_source_info(struct snd_info_entry *entry,
+                                    struct snd_info_buffer *buffer)
+{
+       struct loopback *loopback = entry->private_data;
+       char line[64];
+
+       mutex_lock(&loopback->cable_lock);
+       if (!snd_info_get_line(buffer, line, sizeof(line)))
+               loopback_set_timer_source(loopback, strim(line));
+       mutex_unlock(&loopback->cable_lock);
+}
+
+static int loopback_timer_source_proc_new(struct loopback *loopback)
+{
+       return snd_card_rw_proc_new(loopback->card, "timer_source", loopback,
+                                   print_timer_source_info,
+                                   change_timer_source_info);
+}
+
 static int loopback_probe(struct platform_device *devptr)
 {
        struct snd_card *card;
@@ -1146,6 +1730,8 @@ static int loopback_probe(struct platform_device *devptr)
                pcm_substreams[dev] = MAX_PCM_SUBSTREAMS;
        
        loopback->card = card;
+       loopback_set_timer_source(loopback, timer_source[dev]);
+
        mutex_init(&loopback->cable_lock);
 
        err = loopback_pcm_new(loopback, 0, pcm_substreams[dev]);
@@ -1157,8 +1743,9 @@ static int loopback_probe(struct platform_device *devptr)
        err = loopback_mixer_new(loopback, pcm_notify[dev] ? 1 : 0);
        if (err < 0)
                goto __nodev;
-       loopback_proc_new(loopback, 0);
-       loopback_proc_new(loopback, 1);
+       loopback_cable_proc_new(loopback, 0);
+       loopback_cable_proc_new(loopback, 1);
+       loopback_timer_source_proc_new(loopback);
        strcpy(card->driver, "Loopback");
        strcpy(card->shortname, "Loopback");
        sprintf(card->longname, "Loopback %i", dev + 1);