vserver 1.9.5.x5
[linux-2.6.git] / sound / core / pcm_native.c
index b352535..d051946 100644 (file)
@@ -329,8 +329,8 @@ out:
        return err;
 }
 
-static int snd_pcm_hw_params(snd_pcm_substream_t *substream,
-                            snd_pcm_hw_params_t *params)
+int snd_pcm_hw_params(snd_pcm_substream_t *substream,
+                     snd_pcm_hw_params_t *params)
 {
        snd_pcm_runtime_t *runtime;
        int err;
@@ -451,7 +451,7 @@ out:
 static int snd_pcm_hw_free(snd_pcm_substream_t * substream)
 {
        snd_pcm_runtime_t *runtime;
-       int result;
+       int result = 0;
 
        snd_assert(substream != NULL, return -ENXIO);
        runtime = substream->runtime;
@@ -468,11 +468,8 @@ static int snd_pcm_hw_free(snd_pcm_substream_t * substream)
        snd_pcm_stream_unlock_irq(substream);
        if (atomic_read(&runtime->mmap_count))
                return -EBADFD;
-       if (substream->ops->hw_free == NULL) {
-               runtime->status->state = SNDRV_PCM_STATE_OPEN;
-               return 0;
-       }
-       result = substream->ops->hw_free(substream);
+       if (substream->ops->hw_free)
+               result = substream->ops->hw_free(substream);
        runtime->status->state = SNDRV_PCM_STATE_OPEN;
        return result;
 }
@@ -652,6 +649,7 @@ static void snd_pcm_trigger_tstamp(snd_pcm_substream_t *substream)
 struct action_ops {
        int (*pre_action)(snd_pcm_substream_t *substream, int state);
        int (*do_action)(snd_pcm_substream_t *substream, int state);
+       void (*undo_action)(snd_pcm_substream_t *substream, int state);
        void (*post_action)(snd_pcm_substream_t *substream, int state);
 };
 
@@ -666,7 +664,8 @@ static int snd_pcm_action_group(struct action_ops *ops,
 {
        struct list_head *pos;
        snd_pcm_substream_t *s = NULL;
-       int err, res = 0;
+       snd_pcm_substream_t *s1;
+       int res = 0;
 
        snd_pcm_group_for_each(pos, substream) {
                s = snd_pcm_group_substream_entry(pos);
@@ -674,24 +673,31 @@ static int snd_pcm_action_group(struct action_ops *ops,
                        spin_lock(&s->self_group.lock);
                res = ops->pre_action(s, state);
                if (res < 0)
-                       break;
+                       goto _unlock;
        }
-       if (res >= 0) {
-               snd_pcm_group_for_each(pos, substream) {
-                       s = snd_pcm_group_substream_entry(pos);
-                       err = ops->do_action(s, state);
-                       if (err < 0) {
-                               if (res == 0)
-                                       res = err;
-                       } else {
-                               ops->post_action(s, state);
+       snd_pcm_group_for_each(pos, substream) {
+               s = snd_pcm_group_substream_entry(pos);
+               res = ops->do_action(s, state);
+               if (res < 0) {
+                       if (ops->undo_action) {
+                               snd_pcm_group_for_each(pos, substream) {
+                                       s1 = snd_pcm_group_substream_entry(pos);
+                                       if (s1 == s) /* failed stream */
+                                               break;
+                                       ops->undo_action(s1, state);
+                               }
                        }
-                       if (do_lock && s != substream)
-                               spin_unlock(&s->self_group.lock);
+                       s = NULL; /* unlock all */
+                       goto _unlock;
                }
-       } else if (do_lock) {
-               snd_pcm_substream_t *s1;
-               /* unlock all streams */
+       }
+       snd_pcm_group_for_each(pos, substream) {
+               s = snd_pcm_group_substream_entry(pos);
+               ops->post_action(s, state);
+       }
+ _unlock:
+       if (do_lock) {
+               /* unlock streams */
                snd_pcm_group_for_each(pos, substream) {
                        s1 = snd_pcm_group_substream_entry(pos);
                        if (s1 != substream)
@@ -716,9 +722,10 @@ static int snd_pcm_action_single(struct action_ops *ops,
        if (res < 0)
                return res;
        res = ops->do_action(substream, state);
-       if (res == 0) {
+       if (res == 0)
                ops->post_action(substream, state);
-       }
+       else if (ops->undo_action)
+               ops->undo_action(substream, state);
        return res;
 }
 
@@ -787,6 +794,9 @@ static int snd_pcm_action_nonatomic(struct action_ops *ops,
        return res;
 }
 
+/*
+ * start callbacks
+ */
 static int snd_pcm_pre_start(snd_pcm_substream_t *substream, int state)
 {
        snd_pcm_runtime_t *runtime = substream->runtime;
@@ -803,14 +813,20 @@ static int snd_pcm_do_start(snd_pcm_substream_t *substream, int state)
 {
        if (substream->runtime->trigger_master != substream)
                return 0;
-        return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);
+       return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);
+}
+
+static void snd_pcm_undo_start(snd_pcm_substream_t *substream, int state)
+{
+       if (substream->runtime->trigger_master == substream)
+               substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP);
 }
 
 static void snd_pcm_post_start(snd_pcm_substream_t *substream, int state)
 {
        snd_pcm_runtime_t *runtime = substream->runtime;
        snd_pcm_trigger_tstamp(substream);
-       runtime->status->state = SNDRV_PCM_STATE_RUNNING;
+       runtime->status->state = state;
        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
            runtime->silence_size > 0)
                snd_pcm_playback_silence(substream, ULONG_MAX);
@@ -823,22 +839,27 @@ static void snd_pcm_post_start(snd_pcm_substream_t *substream, int state)
 static struct action_ops snd_pcm_action_start = {
        .pre_action = snd_pcm_pre_start,
        .do_action = snd_pcm_do_start,
+       .undo_action = snd_pcm_undo_start,
        .post_action = snd_pcm_post_start
 };
 
 /**
  * snd_pcm_start
+ *
+ * Start all linked streams.
  */
 int snd_pcm_start(snd_pcm_substream_t *substream)
 {
-       return snd_pcm_action(&snd_pcm_action_start, substream, 0);
+       return snd_pcm_action(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
 }
 
+/*
+ * stop callbacks
+ */
 static int snd_pcm_pre_stop(snd_pcm_substream_t *substream, int state)
 {
        snd_pcm_runtime_t *runtime = substream->runtime;
-       if (substream->runtime->status->state != SNDRV_PCM_STATE_RUNNING &&
-           substream->runtime->status->state != SNDRV_PCM_STATE_DRAINING)
+       if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
                return -EBADFD;
        runtime->trigger_master = substream;
        return 0;
@@ -846,19 +867,22 @@ static int snd_pcm_pre_stop(snd_pcm_substream_t *substream, int state)
 
 static int snd_pcm_do_stop(snd_pcm_substream_t *substream, int state)
 {
-       if (substream->runtime->trigger_master != substream)
-               return 0;
-       return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP);
+       if (substream->runtime->trigger_master == substream &&
+           snd_pcm_running(substream))
+               substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP);
+       return 0; /* unconditonally stop all substreams */
 }
 
 static void snd_pcm_post_stop(snd_pcm_substream_t *substream, int state)
 {
        snd_pcm_runtime_t *runtime = substream->runtime;
-       snd_pcm_trigger_tstamp(substream);
-       if (substream->timer)
-               snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSTOP, &runtime->trigger_tstamp);
-       runtime->status->state = state;
-       snd_pcm_tick_set(substream, 0);
+       if (runtime->status->state != state) {
+               snd_pcm_trigger_tstamp(substream);
+               if (substream->timer)
+                       snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSTOP, &runtime->trigger_tstamp);
+               runtime->status->state = state;
+               snd_pcm_tick_set(substream, 0);
+       }
        wake_up(&runtime->sleep);
 }
 
@@ -870,12 +894,30 @@ static struct action_ops snd_pcm_action_stop = {
 
 /**
  * snd_pcm_stop
+ *
+ * Try to stop all running streams in the substream group.
+ * The state of each stream is changed to the given value after that unconditionally.
  */
 int snd_pcm_stop(snd_pcm_substream_t *substream, int state)
 {
        return snd_pcm_action(&snd_pcm_action_stop, substream, state);
 }
 
+/**
+ * snd_pcm_drain_done
+ *
+ * Stop the DMA only when the given stream is playback.
+ * The state is changed to SETUP.
+ * Unlike snd_pcm_stop(), this affects only the given stream.
+ */
+int snd_pcm_drain_done(snd_pcm_substream_t *substream)
+{
+       return snd_pcm_action_single(&snd_pcm_action_stop, substream, SNDRV_PCM_STATE_SETUP);
+}
+
+/*
+ * pause callbacks
+ */
 static int snd_pcm_pre_pause(snd_pcm_substream_t *substream, int push)
 {
        snd_pcm_runtime_t *runtime = substream->runtime;
@@ -899,6 +941,14 @@ static int snd_pcm_do_pause(snd_pcm_substream_t *substream, int push)
                                              SNDRV_PCM_TRIGGER_PAUSE_RELEASE);
 }
 
+static void snd_pcm_undo_pause(snd_pcm_substream_t *substream, int push)
+{
+       if (substream->runtime->trigger_master == substream)
+               substream->ops->trigger(substream,
+                                       push ? SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
+                                       SNDRV_PCM_TRIGGER_PAUSE_PUSH);
+}
+
 static void snd_pcm_post_pause(snd_pcm_substream_t *substream, int push)
 {
        snd_pcm_runtime_t *runtime = substream->runtime;
@@ -921,9 +971,13 @@ static void snd_pcm_post_pause(snd_pcm_substream_t *substream, int push)
 static struct action_ops snd_pcm_action_pause = {
        .pre_action = snd_pcm_pre_pause,
        .do_action = snd_pcm_do_pause,
+       .undo_action = snd_pcm_undo_pause,
        .post_action = snd_pcm_post_pause
 };
 
+/*
+ * Push/release the pause for all linked streams.
+ */
 static int snd_pcm_pause(snd_pcm_substream_t *substream, int push)
 {
        return snd_pcm_action(&snd_pcm_action_pause, substream, push);
@@ -937,7 +991,6 @@ static int snd_pcm_pre_suspend(snd_pcm_substream_t *substream, int state)
        snd_pcm_runtime_t *runtime = substream->runtime;
        if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED)
                return -EBUSY;
-       runtime->status->suspended_state = runtime->status->state;
        runtime->trigger_master = substream;
        return 0;
 }
@@ -947,10 +1000,10 @@ static int snd_pcm_do_suspend(snd_pcm_substream_t *substream, int state)
        snd_pcm_runtime_t *runtime = substream->runtime;
        if (runtime->trigger_master != substream)
                return 0;
-       if (runtime->status->suspended_state != SNDRV_PCM_STATE_RUNNING &&
-           runtime->status->suspended_state != SNDRV_PCM_STATE_DRAINING)
+       if (! snd_pcm_running(substream))
                return 0;
-       return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_SUSPEND);
+       substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_SUSPEND);
+       return 0; /* suspend unconditionally */
 }
 
 static void snd_pcm_post_suspend(snd_pcm_substream_t *substream, int state)
@@ -959,6 +1012,7 @@ static void snd_pcm_post_suspend(snd_pcm_substream_t *substream, int state)
        snd_pcm_trigger_tstamp(substream);
        if (substream->timer)
                snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MPAUSE, &runtime->trigger_tstamp);
+       runtime->status->suspended_state = runtime->status->state;
        runtime->status->state = SNDRV_PCM_STATE_SUSPENDED;
        snd_pcm_tick_set(substream, 0);
        wake_up(&runtime->sleep);
@@ -972,6 +1026,9 @@ static struct action_ops snd_pcm_action_suspend = {
 
 /**
  * snd_pcm_suspend
+ *
+ * Trigger SUSPEND to all linked streams.
+ * After this call, all streams are changed to SUSPENDED state.
  */
 int snd_pcm_suspend(snd_pcm_substream_t *substream)
 {
@@ -980,11 +1037,14 @@ int snd_pcm_suspend(snd_pcm_substream_t *substream)
 
 /**
  * snd_pcm_suspend_all
+ *
+ * Trigger SUSPEND to all substreams in the given pcm.
+ * After this call, all streams are changed to SUSPENDED state.
  */
 int snd_pcm_suspend_all(snd_pcm_t *pcm)
 {
        snd_pcm_substream_t *substream;
-       int stream, err;
+       int stream, err = 0;
 
        for (stream = 0; stream < 2; stream++) {
                for (substream = pcm->streams[stream].substream; substream; substream = substream->next) {
@@ -992,15 +1052,11 @@ int snd_pcm_suspend_all(snd_pcm_t *pcm)
                        if (substream->runtime == NULL)
                                continue;
                        snd_pcm_stream_lock(substream);
-                       if (substream->runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
-                               snd_pcm_stream_unlock(substream);
-                               continue;
-                       }
-                       if ((err = snd_pcm_suspend(substream)) < 0) {
-                               snd_pcm_stream_unlock(substream);
-                               return err;
-                       }
+                       if (substream->runtime->status->state != SNDRV_PCM_STATE_SUSPENDED)
+                               err = snd_pcm_suspend(substream);
                        snd_pcm_stream_unlock(substream);
+                       if (err < 0)
+                               return err;
                }
        }
        return 0;
@@ -1022,12 +1078,21 @@ static int snd_pcm_do_resume(snd_pcm_substream_t *substream, int state)
        snd_pcm_runtime_t *runtime = substream->runtime;
        if (runtime->trigger_master != substream)
                return 0;
+       /* DMA not running previously? */
        if (runtime->status->suspended_state != SNDRV_PCM_STATE_RUNNING &&
-           runtime->status->suspended_state != SNDRV_PCM_STATE_DRAINING)
+           (runtime->status->suspended_state != SNDRV_PCM_STATE_DRAINING ||
+            substream->stream != SNDRV_PCM_STREAM_PLAYBACK))
                return 0;
        return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_RESUME);
 }
 
+static void snd_pcm_undo_resume(snd_pcm_substream_t *substream, int state)
+{
+       if (substream->runtime->trigger_master == substream &&
+           snd_pcm_running(substream))
+               substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_SUSPEND);
+}
+
 static void snd_pcm_post_resume(snd_pcm_substream_t *substream, int state)
 {
        snd_pcm_runtime_t *runtime = substream->runtime;
@@ -1042,6 +1107,7 @@ static void snd_pcm_post_resume(snd_pcm_substream_t *substream, int state)
 static struct action_ops snd_pcm_action_resume = {
        .pre_action = snd_pcm_pre_resume,
        .do_action = snd_pcm_do_resume,
+       .undo_action = snd_pcm_undo_resume,
        .post_action = snd_pcm_post_resume
 };
 
@@ -1066,6 +1132,11 @@ static int snd_pcm_resume(snd_pcm_substream_t *substream)
 
 #endif /* CONFIG_PM */
 
+/*
+ * xrun ioctl
+ *
+ * Change the RUNNING stream(s) to XRUN state.
+ */
 static int snd_pcm_xrun(snd_pcm_substream_t *substream)
 {
        snd_card_t *card = substream->pcm->card;
@@ -1073,8 +1144,13 @@ static int snd_pcm_xrun(snd_pcm_substream_t *substream)
        int result;
 
        snd_power_lock(card);
+       if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+               result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
+               if (result < 0)
+                       goto _unlock;
+       }
+
        snd_pcm_stream_lock_irq(substream);
-       _xrun_recovery:
        switch (runtime->status->state) {
        case SNDRV_PCM_STATE_XRUN:
                result = 0;     /* already there */
@@ -1082,21 +1158,18 @@ static int snd_pcm_xrun(snd_pcm_substream_t *substream)
        case SNDRV_PCM_STATE_RUNNING:
                result = snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
                break;
-       case SNDRV_PCM_STATE_SUSPENDED:
-               snd_pcm_stream_unlock_irq(substream);
-               result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
-               snd_pcm_stream_lock_irq(substream);
-               if (result >= 0)
-                       goto _xrun_recovery;
-               break;
        default:
                result = -EBADFD;
        }
        snd_pcm_stream_unlock_irq(substream);
+ _unlock:
        snd_power_unlock(card);
        return result;
 }
 
+/*
+ * reset ioctl
+ */
 static int snd_pcm_pre_reset(snd_pcm_substream_t * substream, int state)
 {
        snd_pcm_runtime_t *runtime = substream->runtime;
@@ -1145,17 +1218,17 @@ static int snd_pcm_reset(snd_pcm_substream_t *substream)
        return snd_pcm_action_nonatomic(&snd_pcm_action_reset, substream, 0);
 }
 
+/*
+ * prepare ioctl
+ */
 static int snd_pcm_pre_prepare(snd_pcm_substream_t * substream, int state)
 {
        snd_pcm_runtime_t *runtime = substream->runtime;
-       switch (runtime->status->state) {
-       case SNDRV_PCM_STATE_OPEN:
+       if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
                return -EBADFD;
-       case SNDRV_PCM_STATE_RUNNING:
+       if (snd_pcm_running(substream))
                return -EBUSY;
-       default:
-               return 0;
-       }
+       return 0;
 }
 
 static int snd_pcm_do_prepare(snd_pcm_substream_t * substream, int state)
@@ -1195,111 +1268,147 @@ int snd_pcm_prepare(snd_pcm_substream_t *substream)
        return res;
 }
 
-static void snd_pcm_change_state(snd_pcm_substream_t *substream, int state)
+/*
+ * drain ioctl
+ */
+
+static int snd_pcm_pre_drain_init(snd_pcm_substream_t * substream, int state)
 {
-       struct list_head *pos;
-       snd_pcm_substream_t *s;
+       if (substream->ffile->f_flags & O_NONBLOCK)
+               return -EAGAIN;
+       substream->runtime->trigger_master = substream;
+       return 0;
+}
 
-       if (snd_pcm_stream_linked(substream)) {
-               if (!spin_trylock(&substream->group->lock)) {
-                       spin_unlock(&substream->self_group.lock);
-                       spin_lock(&substream->group->lock);
-                       spin_lock(&substream->self_group.lock);
-               }
-               snd_pcm_group_for_each(pos, substream) {
-                       s = snd_pcm_group_substream_entry(pos);
-                       if (s != substream)
-                               spin_lock(&s->self_group.lock);
-                       s->runtime->status->state = state;
-                       if (s != substream)
-                               spin_unlock(&s->self_group.lock);
+static int snd_pcm_do_drain_init(snd_pcm_substream_t * substream, int state)
+{
+       snd_pcm_runtime_t *runtime = substream->runtime;
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+               switch (runtime->status->state) {
+               case SNDRV_PCM_STATE_PREPARED:
+                       /* start playback stream if possible */
+                       if (! snd_pcm_playback_empty(substream)) {
+                               snd_pcm_do_start(substream, SNDRV_PCM_STATE_DRAINING);
+                               snd_pcm_post_start(substream, SNDRV_PCM_STATE_DRAINING);
+                       }
+                       break;
+               case SNDRV_PCM_STATE_RUNNING:
+                       runtime->status->state = SNDRV_PCM_STATE_DRAINING;
+                       break;
+               default:
+                       break;
                }
-               spin_unlock(&substream->group->lock);
        } else {
-               substream->runtime->status->state = state;
+               /* stop running stream */
+               if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) {
+                       int state = snd_pcm_capture_avail(runtime) > 0 ?
+                               SNDRV_PCM_STATE_DRAINING : SNDRV_PCM_STATE_SETUP;
+                       snd_pcm_do_stop(substream, state);
+                       snd_pcm_post_stop(substream, state);
+               }
        }
+       return 0;
 }
 
-static int snd_pcm_playback_drop(snd_pcm_substream_t *substream);
+static void snd_pcm_post_drain_init(snd_pcm_substream_t * substream, int state)
+{
+}
+
+static struct action_ops snd_pcm_action_drain_init = {
+       .pre_action = snd_pcm_pre_drain_init,
+       .do_action = snd_pcm_do_drain_init,
+       .post_action = snd_pcm_post_drain_init
+};
+
+struct drain_rec {
+       snd_pcm_substream_t *substream;
+       wait_queue_t wait;
+       snd_pcm_uframes_t stop_threshold;
+};
+
+static int snd_pcm_drop(snd_pcm_substream_t *substream);
 
-static int snd_pcm_playback_drain(snd_pcm_substream_t * substream)
+/*
+ * Drain the stream(s).
+ * When the substream is linked, sync until the draining of all playback streams
+ * is finished.
+ * After this call, all streams are supposed to be either SETUP or DRAINING
+ * (capture only) state.
+ */
+static int snd_pcm_drain(snd_pcm_substream_t *substream)
 {
        snd_card_t *card;
        snd_pcm_runtime_t *runtime;
-       int err, result = 0;
-       wait_queue_t wait;
-       enum { READY, EXPIRED, SUSPENDED, SIGNALED } state = READY;
-       snd_pcm_uframes_t stop_threshold;
+       struct list_head *pos;
+       int result = 0;
+       int i, num_drecs;
+       struct drain_rec *drec, drec_tmp, *d;
 
        snd_assert(substream != NULL, return -ENXIO);
-       snd_assert(substream->stream == SNDRV_PCM_STREAM_PLAYBACK, return -EINVAL);
-       runtime = substream->runtime;
        card = substream->pcm->card;
+       runtime = substream->runtime;
+
+       if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+               return -EBADFD;
 
+       down_read(&snd_pcm_link_rwsem);
        snd_power_lock(card);
-       snd_pcm_stream_lock_irq(substream);
+       if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+               result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
+               if (result < 0)
+                       goto _unlock;
+       }
 
-       /* stop_threshold fixup to avoid endless loop when */
-       /* stop_threshold > buffer_size */
-       stop_threshold = runtime->stop_threshold;
-       if (runtime->stop_threshold > runtime->buffer_size)
-               runtime->stop_threshold = runtime->buffer_size;
+       /* allocate temporary record for drain sync */
+       if (snd_pcm_stream_linked(substream)) {
+               drec = kmalloc(substream->group->count * sizeof(*drec), GFP_KERNEL);
+               if (! drec) {
+                       result = -ENOMEM;
+                       goto _unlock;
+               }
+       } else
+               drec = &drec_tmp;
 
-       switch (runtime->status->state) {
-       case SNDRV_PCM_STATE_PAUSED:
+       snd_pcm_stream_lock_irq(substream);
+       /* resume pause */
+       if (runtime->status->state == SNDRV_PCM_STATE_PAUSED)
                snd_pcm_pause(substream, 0);
-               /* Fall through */
-       case SNDRV_PCM_STATE_RUNNING:
-       case SNDRV_PCM_STATE_DRAINING:
-               break;
-       case SNDRV_PCM_STATE_SUSPENDED:
-               snd_pcm_stream_unlock_irq(substream);
-               result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
-               snd_pcm_stream_lock_irq(substream);
-               if (result >= 0)
-                       snd_pcm_change_state(substream, SNDRV_PCM_STATE_SETUP);
-               goto _end;
-       case SNDRV_PCM_STATE_OPEN:
-               result = -EBADFD;
-               goto _end;
-       case SNDRV_PCM_STATE_PREPARED:
-               if (!snd_pcm_playback_empty(substream)) {
-                       err = snd_pcm_start(substream);
-                       if (err < 0) {
-                               result = err;
-                               goto _end;
-                       }
-                       break;
-               }
-               /* Fall through */
-       case SNDRV_PCM_STATE_XRUN:
-               snd_pcm_change_state(substream, SNDRV_PCM_STATE_SETUP);
-               /* Fall through */
-       case SNDRV_PCM_STATE_SETUP:
+
+       /* pre-start/stop - all running streams are changed to DRAINING state */
+       result = snd_pcm_action(&snd_pcm_action_drain_init, substream, 0);
+       if (result < 0)
                goto _end;
-       default: 
-               break; 
-       }
 
-       if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) {
-               if (snd_pcm_playback_empty(substream)) {
-                       snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
-                       goto _end;
+       /* check streams with PLAYBACK & DRAINING */
+       num_drecs = 0;
+       snd_pcm_group_for_each(pos, substream) {
+               snd_pcm_substream_t *s = snd_pcm_group_substream_entry(pos);
+               runtime = s->runtime;
+               if (runtime->status->state != SNDRV_PCM_STATE_DRAINING) {
+                       runtime->status->state = SNDRV_PCM_STATE_SETUP;
+                       continue;
+               }
+               if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+                       d = &drec[num_drecs++];
+                       d->substream = s;
+                       init_waitqueue_entry(&d->wait, current);
+                       add_wait_queue(&runtime->sleep, &d->wait);
+                       /* stop_threshold fixup to avoid endless loop when
+                        * stop_threshold > buffer_size
+                        */
+                       d->stop_threshold = runtime->stop_threshold;
+                       if (runtime->stop_threshold > runtime->buffer_size)
+                               runtime->stop_threshold = runtime->buffer_size;
                }
-               snd_pcm_change_state(substream, SNDRV_PCM_STATE_DRAINING);
        }
 
-       if (substream->ffile->f_flags & O_NONBLOCK) {
-               result = -EAGAIN;
+       if (! num_drecs)
                goto _end;
-       }
 
-       init_waitqueue_entry(&wait, current);
-       add_wait_queue(&runtime->sleep, &wait);
-       while (1) {
+       for (;;) {
                long tout;
                if (signal_pending(current)) {
-                       state = SIGNALED;
+                       result = -ERESTARTSYS;
                        break;
                }
                set_current_state(TASK_INTERRUPTIBLE);
@@ -1309,177 +1418,81 @@ static int snd_pcm_playback_drain(snd_pcm_substream_t * substream)
                snd_power_lock(card);
                snd_pcm_stream_lock_irq(substream);
                if (tout == 0) {
-                       state = runtime->status->state == SNDRV_PCM_STATE_SUSPENDED ? SUSPENDED : EXPIRED;
+                       if (substream->runtime->status->state == SNDRV_PCM_STATE_SUSPENDED)
+                               result = -ESTRPIPE;
+                       else {
+                               snd_printd("playback drain error (DMA or IRQ trouble?)\n");
+                               snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
+                               result = -EIO;
+                       }
                        break;
                }
-               if (runtime->status->state != SNDRV_PCM_STATE_DRAINING) {
-                       state = READY;
-                       break;
+               /* all finished? */
+               for (i = 0; i < num_drecs; i++) {
+                       runtime = drec[i].substream->runtime;
+                       if (runtime->status->state == SNDRV_PCM_STATE_DRAINING)
+                               break;
                }
+               if (i == num_drecs)
+                       break;
        }
-       remove_wait_queue(&runtime->sleep, &wait);
-
-       switch (state) {
-       case SIGNALED:
-               result = -ERESTARTSYS;
-               goto _end;
-       case SUSPENDED:
-               result = -ESTRPIPE;
-               goto _end;
-       case EXPIRED:
-               snd_printd("playback drain error (DMA or IRQ trouble?)\n");
-               result = -EIO;
-               goto _end;
-       default:
-               break;
+       for (i = 0; i < num_drecs; i++) {
+               d = &drec[i];
+               runtime = d->substream->runtime;
+               remove_wait_queue(&runtime->sleep, &d->wait);
+               runtime->stop_threshold = d->stop_threshold;
        }
 
-      _end:
-       runtime->stop_threshold = stop_threshold;
+ _end:
        snd_pcm_stream_unlock_irq(substream);
+       if (drec != &drec_tmp)
+               kfree(drec);
+ _unlock:
        snd_power_unlock(card);
-       if (state == EXPIRED)
-               snd_pcm_playback_drop(substream);
+       up_read(&snd_pcm_link_rwsem);
 
        return result;
 }
 
-static int snd_pcm_playback_drop(snd_pcm_substream_t *substream)
+/*
+ * drop ioctl
+ *
+ * Immediately put all linked substreams into SETUP state.
+ */
+static int snd_pcm_drop(snd_pcm_substream_t *substream)
 {
-       snd_pcm_runtime_t *runtime = substream->runtime;
-       snd_card_t *card = substream->pcm->card;
-       int res = 0;
+       snd_pcm_runtime_t *runtime;
+       snd_card_t *card;
+       int result = 0;
        
-       snd_power_lock(card);
-       snd_pcm_stream_lock_irq(substream);
-       switch (runtime->status->state) {
-       case SNDRV_PCM_STATE_OPEN:
-               res = -EBADFD;
-               break;
-       case SNDRV_PCM_STATE_SETUP:
-               break;
-       case SNDRV_PCM_STATE_PAUSED:
-               snd_pcm_pause(substream, 0);
-               /* Fall through */
-       case SNDRV_PCM_STATE_RUNNING:
-       case SNDRV_PCM_STATE_DRAINING:
-               if (snd_pcm_update_hw_ptr(substream) >= 0) {
-                       snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
-                       break;
-               }
-               /* Fall through */
-       case SNDRV_PCM_STATE_PREPARED:
-       case SNDRV_PCM_STATE_XRUN:
-               snd_pcm_change_state(substream, SNDRV_PCM_STATE_SETUP);
-               break;
-       case SNDRV_PCM_STATE_SUSPENDED:
-               snd_pcm_stream_unlock_irq(substream);
-               res = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
-               snd_pcm_stream_lock_irq(substream);
-               if (res >= 0)
-                       snd_pcm_change_state(substream, SNDRV_PCM_STATE_SETUP);
-               break;
-       default:
-               break; 
-       }
-       runtime->control->appl_ptr = runtime->status->hw_ptr;
-       snd_pcm_stream_unlock_irq(substream);
-       snd_power_unlock(card);
-       return res;
-}
+       snd_assert(substream != NULL, return -ENXIO);
+       runtime = substream->runtime;
+       card = substream->pcm->card;
 
-static int snd_pcm_capture_drain(snd_pcm_substream_t * substream)
-{
-       snd_pcm_runtime_t *runtime = substream->runtime;
-       snd_card_t *card = substream->pcm->card;
-       int res = 0;
+       if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+               return -EBADFD;
 
        snd_power_lock(card);
-       snd_pcm_stream_lock_irq(substream);
-       switch (runtime->status->state) {
-       case SNDRV_PCM_STATE_OPEN:
-               res = -EBADFD;
-               break;
-       case SNDRV_PCM_STATE_PREPARED:
-               snd_pcm_change_state(substream, SNDRV_PCM_STATE_SETUP);
-               break;
-       case SNDRV_PCM_STATE_SETUP:
-       case SNDRV_PCM_STATE_DRAINING:
-               break;
-       case SNDRV_PCM_STATE_PAUSED:
-               snd_pcm_pause(substream, 0);
-               /* Fall through */
-       case SNDRV_PCM_STATE_RUNNING:
-               if (snd_pcm_update_hw_ptr(substream) >= 0) {
-                       snd_pcm_stop(substream, 
-                                    snd_pcm_capture_avail(runtime) > 0 ?
-                                    SNDRV_PCM_STATE_DRAINING : SNDRV_PCM_STATE_SETUP);
-                       break;
-               }
-               /* Fall through */
-       case SNDRV_PCM_STATE_XRUN:
-              _xrun_recovery:
-               snd_pcm_change_state(substream, 
-                                    snd_pcm_capture_avail(runtime) > 0 ?
-                                    SNDRV_PCM_STATE_DRAINING : SNDRV_PCM_STATE_SETUP);
-               break;
-       case SNDRV_PCM_STATE_SUSPENDED:
-               snd_pcm_stream_unlock_irq(substream);
-               res = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
-               snd_pcm_stream_lock_irq(substream);
-               if (res >= 0)
-                       goto _xrun_recovery;
-               break;
-       default: 
-               break; 
+       if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+               result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
+               if (result < 0)
+                       goto _unlock;
        }
-       snd_pcm_stream_unlock_irq(substream);
-       snd_power_unlock(card);
-       return res;
-}
-
-static int snd_pcm_capture_drop(snd_pcm_substream_t * substream)
-{
-       snd_pcm_runtime_t *runtime = substream->runtime;
-       snd_card_t *card = substream->pcm->card;
-       int res = 0;
 
-       snd_power_lock(card);
        snd_pcm_stream_lock_irq(substream);
-       switch (runtime->status->state) {
-       case SNDRV_PCM_STATE_OPEN:
-               res = -EBADFD;
-               break;
-       case SNDRV_PCM_STATE_SETUP:
-               break;
-       case SNDRV_PCM_STATE_PAUSED:
+       /* resume pause */
+       if (runtime->status->state == SNDRV_PCM_STATE_PAUSED)
                snd_pcm_pause(substream, 0);
-               /* Fall through */
-       case SNDRV_PCM_STATE_RUNNING:
-               snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
-               break;
-       case SNDRV_PCM_STATE_SUSPENDED:
-               snd_pcm_stream_unlock_irq(substream);
-               res = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile);
-               snd_pcm_stream_lock_irq(substream);
-               if (res < 0)
-                       goto _end;
-               /* Fall through */
-       case SNDRV_PCM_STATE_PREPARED:
-       case SNDRV_PCM_STATE_DRAINING:
-       case SNDRV_PCM_STATE_XRUN:
-               snd_pcm_change_state(substream, SNDRV_PCM_STATE_SETUP);
-               break;
-       default: 
-               break; 
-       }
-       runtime->control->appl_ptr = runtime->status->hw_ptr;
-       _end:
+
+       snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
+       /* runtime->control->appl_ptr = runtime->status->hw_ptr; */
        snd_pcm_stream_unlock_irq(substream);
+ _unlock:
        snd_power_unlock(card);
-       return res;
+       return result;
 }
 
+
 /* WARNING: Don't forget to fput back the file */
 extern int snd_major;
 static struct file *snd_pcm_file_fd(int fd)
@@ -1505,6 +1518,9 @@ static struct file *snd_pcm_file_fd(int fd)
        return file;
 }
 
+/*
+ * PCM link handling
+ */
 static int snd_pcm_link(snd_pcm_substream_t *substream, int fd)
 {
        int res = 0;
@@ -1512,12 +1528,6 @@ static int snd_pcm_link(snd_pcm_substream_t *substream, int fd)
        snd_pcm_file_t *pcm_file;
        snd_pcm_substream_t *substream1;
 
-       snd_pcm_stream_lock_irq(substream);
-       if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
-               snd_pcm_stream_unlock_irq(substream);
-               return -EBADFD;
-       }
-       snd_pcm_stream_unlock_irq(substream);
        file = snd_pcm_file_fd(fd);
        if (!file)
                return -EBADFD;
@@ -1525,7 +1535,8 @@ static int snd_pcm_link(snd_pcm_substream_t *substream, int fd)
        substream1 = pcm_file->substream;
        down_write(&snd_pcm_link_rwsem);
        write_lock_irq(&snd_pcm_link_rwlock);
-       if (substream->runtime->status->state != substream1->runtime->status->state) {
+       if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN ||
+           substream->runtime->status->state != substream1->runtime->status->state) {
                res = -EBADFD;
                goto _end;
        }
@@ -1542,8 +1553,10 @@ static int snd_pcm_link(snd_pcm_substream_t *substream, int fd)
                spin_lock_init(&substream->group->lock);
                INIT_LIST_HEAD(&substream->group->substreams);
                list_add_tail(&substream->link_list, &substream->group->substreams);
+               substream->group->count = 1;
        }
        list_add_tail(&substream1->link_list, &substream->group->substreams);
+       substream->group->count++;
        substream1->group = substream->group;
  _end:
        write_unlock_irq(&snd_pcm_link_rwlock);
@@ -1562,7 +1575,7 @@ static void relink_to_local(snd_pcm_substream_t *substream)
 static int snd_pcm_unlink(snd_pcm_substream_t *substream)
 {
        struct list_head *pos;
-       int res = 0, count = 0;
+       int res = 0;
 
        down_write(&snd_pcm_link_rwsem);
        write_lock_irq(&snd_pcm_link_rwlock);
@@ -1571,11 +1584,8 @@ static int snd_pcm_unlink(snd_pcm_substream_t *substream)
                goto _end;
        }
        list_del(&substream->link_list);
-       snd_pcm_group_for_each(pos, substream) {
-               if (++count > 1)
-                       break;
-       }
-       if (count == 1) {       /* detach the last stream, too */
+       substream->group->count--;
+       if (substream->group->count == 1) {     /* detach the last stream, too */
                snd_pcm_group_for_each(pos, substream) {
                        relink_to_local(snd_pcm_group_substream_entry(pos));
                        break;
@@ -1589,6 +1599,9 @@ static int snd_pcm_unlink(snd_pcm_substream_t *substream)
        return res;
 }
 
+/*
+ * hw configurator
+ */
 static int snd_pcm_hw_rule_mul(snd_pcm_hw_params_t *params,
                               snd_pcm_hw_rule_t *rule)
 {
@@ -1973,6 +1986,7 @@ static int snd_pcm_open_file(struct file *file,
 
        str = substream->pstr;
        substream->file = pcm_file;
+       substream->no_mmap_ctrl = 0;
 
        pcm_file->substream = substream;
 
@@ -2006,7 +2020,7 @@ static int snd_pcm_open_file(struct file *file,
        return 0;
 }
 
-int snd_pcm_open(struct inode *inode, struct file *file)
+static int snd_pcm_open(struct inode *inode, struct file *file)
 {
        int cardnum = SNDRV_MINOR_CARD(iminor(inode));
        int device = SNDRV_MINOR_DEVICE(iminor(inode));
@@ -2065,7 +2079,7 @@ int snd_pcm_open(struct inode *inode, struct file *file)
        return err;
 }
 
-int snd_pcm_release(struct inode *inode, struct file *file)
+static int snd_pcm_release(struct inode *inode, struct file *file)
 {
        snd_pcm_t *pcm;
        snd_pcm_substream_t *substream;
@@ -2076,10 +2090,7 @@ int snd_pcm_release(struct inode *inode, struct file *file)
        snd_assert(substream != NULL, return -ENXIO);
        snd_assert(!atomic_read(&substream->runtime->mmap_count), );
        pcm = substream->pcm;
-       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
-               snd_pcm_playback_drop(substream);
-       else
-               snd_pcm_capture_drop(substream);
+       snd_pcm_drop(substream);
        fasync_helper(-1, file, 0, &substream->runtime->fasync);
        down(&pcm->open_mutex);
        snd_pcm_release_file(pcm_file);
@@ -2090,7 +2101,7 @@ int snd_pcm_release(struct inode *inode, struct file *file)
        return 0;
 }
 
-snd_pcm_sframes_t snd_pcm_playback_rewind(snd_pcm_substream_t *substream, snd_pcm_uframes_t frames)
+static snd_pcm_sframes_t snd_pcm_playback_rewind(snd_pcm_substream_t *substream, snd_pcm_uframes_t frames)
 {
        snd_pcm_runtime_t *runtime = substream->runtime;
        snd_pcm_sframes_t appl_ptr;
@@ -2139,7 +2150,7 @@ snd_pcm_sframes_t snd_pcm_playback_rewind(snd_pcm_substream_t *substream, snd_pc
        return ret;
 }
 
-snd_pcm_sframes_t snd_pcm_capture_rewind(snd_pcm_substream_t *substream, snd_pcm_uframes_t frames)
+static snd_pcm_sframes_t snd_pcm_capture_rewind(snd_pcm_substream_t *substream, snd_pcm_uframes_t frames)
 {
        snd_pcm_runtime_t *runtime = substream->runtime;
        snd_pcm_sframes_t appl_ptr;
@@ -2188,7 +2199,7 @@ snd_pcm_sframes_t snd_pcm_capture_rewind(snd_pcm_substream_t *substream, snd_pcm
        return ret;
 }
 
-snd_pcm_sframes_t snd_pcm_playback_forward(snd_pcm_substream_t *substream, snd_pcm_uframes_t frames)
+static snd_pcm_sframes_t snd_pcm_playback_forward(snd_pcm_substream_t *substream, snd_pcm_uframes_t frames)
 {
        snd_pcm_runtime_t *runtime = substream->runtime;
        snd_pcm_sframes_t appl_ptr;
@@ -2238,7 +2249,7 @@ snd_pcm_sframes_t snd_pcm_playback_forward(snd_pcm_substream_t *substream, snd_p
        return ret;
 }
 
-snd_pcm_sframes_t snd_pcm_capture_forward(snd_pcm_substream_t *substream, snd_pcm_uframes_t frames)
+static snd_pcm_sframes_t snd_pcm_capture_forward(snd_pcm_substream_t *substream, snd_pcm_uframes_t frames)
 {
        snd_pcm_runtime_t *runtime = substream->runtime;
        snd_pcm_sframes_t appl_ptr;
@@ -2435,7 +2446,7 @@ static int snd_pcm_common_ioctl1(snd_pcm_substream_t *substream,
        case SNDRV_PCM_IOCTL_RESET:
                return snd_pcm_reset(substream);
        case SNDRV_PCM_IOCTL_START:
-               return snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, 0);
+               return snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
        case SNDRV_PCM_IOCTL_LINK:
                return snd_pcm_link(substream, (int)(unsigned long) arg);
        case SNDRV_PCM_IOCTL_UNLINK:
@@ -2454,6 +2465,10 @@ static int snd_pcm_common_ioctl1(snd_pcm_substream_t *substream,
                return snd_pcm_hw_refine_old_user(substream, arg);
        case SNDRV_PCM_IOCTL_HW_PARAMS_OLD:
                return snd_pcm_hw_params_old_user(substream, arg);
+       case SNDRV_PCM_IOCTL_DRAIN:
+               return snd_pcm_drain(substream);
+       case SNDRV_PCM_IOCTL_DROP:
+               return snd_pcm_drop(substream);
        }
        snd_printd("unknown ioctl = 0x%x\n", cmd);
        return -ENOTTY;
@@ -2542,10 +2557,6 @@ static int snd_pcm_playback_ioctl1(snd_pcm_substream_t *substream,
                snd_pcm_stream_unlock_irq(substream);
                return res;
        }
-       case SNDRV_PCM_IOCTL_DRAIN:
-               return snd_pcm_playback_drain(substream);
-       case SNDRV_PCM_IOCTL_DROP:
-               return snd_pcm_playback_drop(substream);
        }
        return snd_pcm_common_ioctl1(substream, cmd, arg);
 }
@@ -2625,10 +2636,6 @@ static int snd_pcm_capture_ioctl1(snd_pcm_substream_t *substream,
                __put_user(result, _frames);
                return result < 0 ? result : 0;
        }
-       case SNDRV_PCM_IOCTL_DRAIN:
-               return snd_pcm_capture_drain(substream);
-       case SNDRV_PCM_IOCTL_DROP:
-               return snd_pcm_capture_drop(substream);
        }
        return snd_pcm_common_ioctl1(substream, cmd, arg);
 }
@@ -2828,7 +2835,7 @@ static ssize_t snd_pcm_writev(struct file *file, const struct iovec *_vector,
        return result;
 }
 
-unsigned int snd_pcm_playback_poll(struct file *file, poll_table * wait)
+static unsigned int snd_pcm_playback_poll(struct file *file, poll_table * wait)
 {
        snd_pcm_file_t *pcm_file;
        snd_pcm_substream_t *substream;
@@ -2866,7 +2873,7 @@ unsigned int snd_pcm_playback_poll(struct file *file, poll_table * wait)
        return mask;
 }
 
-unsigned int snd_pcm_capture_poll(struct file *file, poll_table * wait)
+static unsigned int snd_pcm_capture_poll(struct file *file, poll_table * wait)
 {
        snd_pcm_file_t *pcm_file;
        snd_pcm_substream_t *substream;
@@ -3092,6 +3099,7 @@ int snd_pcm_lib_mmap_iomem(snd_pcm_substream_t *substream, struct vm_area_struct
        area->vm_page_prot = pgprot_noncached(area->vm_page_prot);
 #endif
        area->vm_ops = &snd_pcm_vm_ops_data_mmio;
+       area->vm_private_data = substream;
        area->vm_flags |= VM_IO;
        size = area->vm_end - area->vm_start;
        offset = area->vm_pgoff << PAGE_SHIFT;
@@ -3158,8 +3166,12 @@ static int snd_pcm_mmap(struct file *file, struct vm_area_struct *area)
        offset = area->vm_pgoff << PAGE_SHIFT;
        switch (offset) {
        case SNDRV_PCM_MMAP_OFFSET_STATUS:
+               if (substream->no_mmap_ctrl)
+                       return -ENXIO;
                return snd_pcm_mmap_status(substream, file, area);
        case SNDRV_PCM_MMAP_OFFSET_CONTROL:
+               if (substream->no_mmap_ctrl)
+                       return -ENXIO;
                return snd_pcm_mmap_control(substream, file, area);
        default:
                return snd_pcm_mmap_data(substream, file, area);