ALSA: use linux/io.h to fix compile warnings
[linux-2.6.git] / sound / drivers / aloop.c
index 040030a..12b44b0 100644 (file)
@@ -39,6 +39,7 @@
 #include <sound/core.h>
 #include <sound/control.h>
 #include <sound/pcm.h>
+#include <sound/info.h>
 #include <sound/initval.h>
 
 MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
@@ -76,6 +77,7 @@ struct loopback_cable {
        /* flags */
        unsigned int valid;
        unsigned int running;
+       unsigned int pause;
 };
 
 struct loopback_setup {
@@ -171,6 +173,10 @@ static void loopback_timer_start(struct loopback_pcm *dpcm)
                dpcm->pcm_rate_shift = rate_shift;
                dpcm->period_size_frac = frac_pos(dpcm, dpcm->pcm_period_size);
        }
+       if (dpcm->period_size_frac <= dpcm->irq_pos) {
+               dpcm->irq_pos %= dpcm->period_size_frac;
+               dpcm->period_update_pending = 1;
+       }
        tick = dpcm->period_size_frac - dpcm->irq_pos;
        tick = (tick + dpcm->pcm_bps - 1) / dpcm->pcm_bps;
        dpcm->timer.expires = jiffies + tick;
@@ -180,6 +186,7 @@ static void loopback_timer_start(struct loopback_pcm *dpcm)
 static inline void loopback_timer_stop(struct loopback_pcm *dpcm)
 {
        del_timer(&dpcm->timer);
+       dpcm->timer.expires = 0;
 }
 
 #define CABLE_VALID_PLAYBACK   (1 << SNDRV_PCM_STREAM_PLAYBACK)
@@ -188,7 +195,7 @@ static inline void loopback_timer_stop(struct loopback_pcm *dpcm)
 
 static int loopback_check_format(struct loopback_cable *cable, int stream)
 {
-       struct snd_pcm_runtime *runtime;
+       struct snd_pcm_runtime *runtime, *cruntime;
        struct loopback_setup *setup;
        struct snd_card *card;
        int check;
@@ -200,11 +207,11 @@ static int loopback_check_format(struct loopback_cable *cable, int stream)
        }
        runtime = cable->streams[SNDRV_PCM_STREAM_PLAYBACK]->
                                                        substream->runtime;
-       check = cable->hw.formats != (1ULL << runtime->format) ||
-               cable->hw.rate_min != runtime->rate ||
-               cable->hw.rate_max != runtime->rate ||
-               cable->hw.channels_min != runtime->channels ||
-               cable->hw.channels_max != runtime->channels;
+       cruntime = cable->streams[SNDRV_PCM_STREAM_CAPTURE]->
+                                                       substream->runtime;
+       check = runtime->format != cruntime->format ||
+               runtime->rate != cruntime->rate ||
+               runtime->channels != cruntime->channels;
        if (!check)
                return 0;
        if (stream == SNDRV_PCM_STREAM_CAPTURE) {
@@ -248,7 +255,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;
+       int err, stream = 1 << substream->stream;
 
        switch (cmd) {
        case SNDRV_PCM_TRIGGER_START:
@@ -257,29 +264,78 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
                        return err;
                dpcm->last_jiffies = jiffies;
                dpcm->pcm_rate_shift = 0;
+               spin_lock(&cable->lock);        
+               cable->running |= stream;
+               cable->pause &= ~stream;
+               spin_unlock(&cable->lock);
                loopback_timer_start(dpcm);
-               cable->running |= (1 << substream->stream);
                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
                        loopback_active_notify(dpcm);
                break;
        case SNDRV_PCM_TRIGGER_STOP:
-               cable->running &= ~(1 << substream->stream);
+               spin_lock(&cable->lock);        
+               cable->running &= ~stream;
+               cable->pause &= ~stream;
+               spin_unlock(&cable->lock);
                loopback_timer_stop(dpcm);
                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
                        loopback_active_notify(dpcm);
                break;
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               spin_lock(&cable->lock);        
+               cable->pause |= stream;
+               spin_unlock(&cable->lock);
+               loopback_timer_stop(dpcm);
+               break;
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               spin_lock(&cable->lock);
+               dpcm->last_jiffies = jiffies;
+               cable->pause &= ~stream;
+               spin_unlock(&cable->lock);
+               loopback_timer_start(dpcm);
+               break;
        default:
                return -EINVAL;
        }
        return 0;
 }
 
+static void params_change_substream(struct loopback_pcm *dpcm,
+                                   struct snd_pcm_runtime *runtime)
+{
+       struct snd_pcm_runtime *dst_runtime;
+
+       if (dpcm == NULL || dpcm->substream == NULL)
+               return;
+       dst_runtime = dpcm->substream->runtime;
+       if (dst_runtime == NULL)
+               return;
+       dst_runtime->hw = dpcm->cable->hw;
+}
+
+static void params_change(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;
+
+       cable->hw.formats = (1ULL << runtime->format);
+       cable->hw.rate_min = runtime->rate;
+       cable->hw.rate_max = runtime->rate;
+       cable->hw.channels_min = runtime->channels;
+       cable->hw.channels_max = runtime->channels;
+       params_change_substream(cable->streams[SNDRV_PCM_STREAM_PLAYBACK],
+                               runtime);
+       params_change_substream(cable->streams[SNDRV_PCM_STREAM_CAPTURE],
+                               runtime);
+}
+
 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;
-       unsigned int bps, salign;
+       int bps, salign;
 
        salign = (snd_pcm_format_width(runtime->format) *
                                                runtime->channels) / 8;
@@ -303,13 +359,10 @@ static int loopback_prepare(struct snd_pcm_substream *substream)
        dpcm->pcm_period_size = frames_to_bytes(runtime, runtime->period_size);
 
        mutex_lock(&dpcm->loopback->cable_lock);
-       if (!(cable->valid & ~(1 << substream->stream))) {
-               cable->hw.formats = (1ULL << runtime->format);
-               cable->hw.rate_min = runtime->rate;
-               cable->hw.rate_max = runtime->rate;
-               cable->hw.channels_min = runtime->channels;
-               cable->hw.channels_max = runtime->channels;
-       }
+       if (!(cable->valid & ~(1 << substream->stream)) ||
+            (get_setup(dpcm)->notify &&
+            substream->stream == SNDRV_PCM_STREAM_PLAYBACK))
+               params_change(substream);
        cable->valid |= 1 << substream->stream;
        mutex_unlock(&dpcm->loopback->cable_lock);
 
@@ -421,28 +474,30 @@ static void loopback_bytepos_update(struct loopback_pcm *dpcm,
        }
 }
 
-static void loopback_pos_update(struct loopback_cable *cable)
+static unsigned int loopback_pos_update(struct loopback_cable *cable)
 {
        struct loopback_pcm *dpcm_play =
                        cable->streams[SNDRV_PCM_STREAM_PLAYBACK];
        struct loopback_pcm *dpcm_capt =
                        cable->streams[SNDRV_PCM_STREAM_CAPTURE];
        unsigned long delta_play = 0, delta_capt = 0;
+       unsigned int running;
 
        spin_lock(&cable->lock);        
-       if (cable->running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) {
+       running = cable->running ^ cable->pause;
+       if (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) {
                delta_play = jiffies - dpcm_play->last_jiffies;
                dpcm_play->last_jiffies += delta_play;
        }
 
-       if (cable->running & (1 << SNDRV_PCM_STREAM_CAPTURE)) {
+       if (running & (1 << SNDRV_PCM_STREAM_CAPTURE)) {
                delta_capt = jiffies - dpcm_capt->last_jiffies;
                dpcm_capt->last_jiffies += delta_capt;
        }
 
        if (delta_play == 0 && delta_capt == 0) {
                spin_unlock(&cable->lock);
-               return;
+               return running;
        }
                
        if (delta_play > delta_capt) {
@@ -457,27 +512,27 @@ static void loopback_pos_update(struct loopback_cable *cable)
 
        if (delta_play == 0 && delta_capt == 0) {
                spin_unlock(&cable->lock);
-               return;
+               return running;
        }
        /* note delta_capt == delta_play at this moment */
        loopback_bytepos_update(dpcm_capt, delta_capt, BYTEPOS_UPDATE_COPY);
        loopback_bytepos_update(dpcm_play, delta_play, BYTEPOS_UPDATE_POSONLY);
        spin_unlock(&cable->lock);
+       return running;
 }
 
 static void loopback_timer_function(unsigned long data)
 {
        struct loopback_pcm *dpcm = (struct loopback_pcm *)data;
-       int stream;
+       unsigned int running;
 
-       loopback_pos_update(dpcm->cable);
-       stream = dpcm->substream->stream;
-       if (dpcm->cable->running & (1 << stream))
+       running = loopback_pos_update(dpcm->cable);
+       if (running & (1 << dpcm->substream->stream)) {
                loopback_timer_start(dpcm);
-       if (dpcm->period_update_pending) {
-               dpcm->period_update_pending = 0;
-               if (dpcm->cable->running & (1 << stream))
+               if (dpcm->period_update_pending) {
+                       dpcm->period_update_pending = 0;
                        snd_pcm_period_elapsed(dpcm->substream);
+               }
        }
 }
 
@@ -493,7 +548,7 @@ static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
 static struct snd_pcm_hardware loopback_pcm_hardware =
 {
        .info =         (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP |
-                        SNDRV_PCM_INFO_MMAP_VALID),
+                        SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE),
        .formats =      (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |
                         SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE |
                         SNDRV_PCM_FMTBIT_FLOAT_LE | SNDRV_PCM_FMTBIT_FLOAT_BE),
@@ -504,7 +559,9 @@ static struct snd_pcm_hardware loopback_pcm_hardware =
        .channels_max =         32,
        .buffer_bytes_max =     2 * 1024 * 1024,
        .period_bytes_min =     64,
-       .period_bytes_max =     2 * 1024 * 1024,
+       /* note check overflow in frac_pos() using pcm_rate_shift before
+          changing period_bytes_max value */
+       .period_bytes_max =     1024 * 1024,
        .periods_min =          1,
        .periods_max =          1024,
        .fifo_size =            0,
@@ -542,6 +599,47 @@ static unsigned int get_cable_index(struct snd_pcm_substream *substream)
                return !substream->stream;
 }
 
+static int rule_format(struct snd_pcm_hw_params *params,
+                      struct snd_pcm_hw_rule *rule)
+{
+
+       struct snd_pcm_hardware *hw = rule->private;
+       struct snd_mask *maskp = hw_param_mask(params, rule->var);
+
+       maskp->bits[0] &= (u_int32_t)hw->formats;
+       maskp->bits[1] &= (u_int32_t)(hw->formats >> 32);
+       memset(maskp->bits + 2, 0, (SNDRV_MASK_MAX-64) / 8); /* clear rest */
+       if (! maskp->bits[0] && ! maskp->bits[1])
+               return -EINVAL;
+       return 0;
+}
+
+static int rule_rate(struct snd_pcm_hw_params *params,
+                    struct snd_pcm_hw_rule *rule)
+{
+       struct snd_pcm_hardware *hw = rule->private;
+       struct snd_interval t;
+
+        t.min = hw->rate_min;
+        t.max = hw->rate_max;
+        t.openmin = t.openmax = 0;
+        t.integer = 0;
+       return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
+static int rule_channels(struct snd_pcm_hw_params *params,
+                        struct snd_pcm_hw_rule *rule)
+{
+       struct snd_pcm_hardware *hw = rule->private;
+       struct snd_interval t;
+
+        t.min = hw->channels_min;
+        t.max = hw->channels_max;
+        t.openmin = t.openmax = 0;
+        t.integer = 0;
+       return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
 static int loopback_open(struct snd_pcm_substream *substream)
 {
        struct snd_pcm_runtime *runtime = substream->runtime;
@@ -579,14 +677,34 @@ static int loopback_open(struct snd_pcm_substream *substream)
 
        snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
 
+       /* use dynamic rules based on actual runtime->hw values */
+       /* note that the default rules created in the PCM midlevel code */
+       /* are cached -> they do not reflect the actual state */
+       err = snd_pcm_hw_rule_add(runtime, 0,
+                                 SNDRV_PCM_HW_PARAM_FORMAT,
+                                 rule_format, &runtime->hw,
+                                 SNDRV_PCM_HW_PARAM_FORMAT, -1);
+       if (err < 0)
+               goto unlock;
+       err = snd_pcm_hw_rule_add(runtime, 0,
+                                 SNDRV_PCM_HW_PARAM_RATE,
+                                 rule_rate, &runtime->hw,
+                                 SNDRV_PCM_HW_PARAM_RATE, -1);
+       if (err < 0)
+               goto unlock;
+       err = snd_pcm_hw_rule_add(runtime, 0,
+                                 SNDRV_PCM_HW_PARAM_CHANNELS,
+                                 rule_channels, &runtime->hw,
+                                 SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+       if (err < 0)
+               goto unlock;
+
        runtime->private_data = dpcm;
        runtime->private_free = loopback_runtime_free;
-       if (get_notify(dpcm) &&
-           substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+       if (get_notify(dpcm))
                runtime->hw = loopback_pcm_hardware;
-       } else {
+       else
                runtime->hw = cable->hw;
-       }
  unlock:
        mutex_unlock(&loopback->cable_lock);
        return err;
@@ -917,6 +1035,87 @@ static int __devinit loopback_mixer_new(struct loopback *loopback, int notify)
        return 0;
 }
 
+#ifdef CONFIG_PROC_FS
+
+static void print_dpcm_info(struct snd_info_buffer *buffer,
+                           struct loopback_pcm *dpcm,
+                           const char *id)
+{
+       snd_iprintf(buffer, "  %s\n", id);
+       if (dpcm == NULL) {
+               snd_iprintf(buffer, "    inactive\n");
+               return;
+       }
+       snd_iprintf(buffer, "    buffer_size:\t%u\n", dpcm->pcm_buffer_size);
+       snd_iprintf(buffer, "    buffer_pos:\t\t%u\n", dpcm->buf_pos);
+       snd_iprintf(buffer, "    silent_size:\t%u\n", dpcm->silent_size);
+       snd_iprintf(buffer, "    period_size:\t%u\n", dpcm->pcm_period_size);
+       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);
+}
+
+static void print_substream_info(struct snd_info_buffer *buffer,
+                                struct loopback *loopback,
+                                int sub,
+                                int num)
+{
+       struct loopback_cable *cable = loopback->cables[sub][num];
+
+       snd_iprintf(buffer, "Cable %i substream %i:\n", num, sub);
+       if (cable == NULL) {
+               snd_iprintf(buffer, "  inactive\n");
+               return;
+       }
+       snd_iprintf(buffer, "  valid: %u\n", cable->valid);
+       snd_iprintf(buffer, "  running: %u\n", cable->running);
+       snd_iprintf(buffer, "  pause: %u\n", cable->pause);
+       print_dpcm_info(buffer, cable->streams[0], "Playback");
+       print_dpcm_info(buffer, cable->streams[1], "Capture");
+}
+
+static void print_cable_info(struct snd_info_entry *entry,
+                            struct snd_info_buffer *buffer)
+{
+       struct loopback *loopback = entry->private_data;
+       int sub, num;
+
+       mutex_lock(&loopback->cable_lock);
+       num = entry->name[strlen(entry->name)-1];
+       num = num == '0' ? 0 : 1;
+       for (sub = 0; sub < MAX_PCM_SUBSTREAMS; sub++)
+               print_substream_info(buffer, loopback, sub, num);
+       mutex_unlock(&loopback->cable_lock);
+}
+
+static int __devinit loopback_proc_new(struct loopback *loopback, int cidx)
+{
+       char name[32];
+       struct snd_info_entry *entry;
+       int err;
+
+       snprintf(name, sizeof(name), "cable#%d", cidx);
+       err = snd_card_proc_new(loopback->card, name, &entry);
+       if (err < 0)
+               return err;
+
+       snd_info_set_text_ops(entry, loopback, print_cable_info);
+       return 0;
+}
+
+#else /* !CONFIG_PROC_FS */
+
+#define loopback_proc_new(loopback, cidx) do { } while (0)
+
+#endif
+
 static int __devinit loopback_probe(struct platform_device *devptr)
 {
        struct snd_card *card;
@@ -947,6 +1146,8 @@ static int __devinit 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);
        strcpy(card->driver, "Loopback");
        strcpy(card->shortname, "Loopback");
        sprintf(card->longname, "Loopback %i", dev + 1);