Commit eea0579f authored by Takashi Iwai's avatar Takashi Iwai

ALSA: pcsp - Fix starting the stream with HRTIMER_CB_IRQSAFE_UNLOCK

With the callback mode HRTIMER_CB_IRQSAFE_UNLOCK, the start of the
stream with zero delay doesn't work.  Since IRQSAFE mode is removed,
we have to change the pcsp start-up code.

This patch splits the callback function to two parts, the triggering
of the port and the calculation of the expire time, and the update of
the ALSA PCM core.  The first part is called both from the trigger-start
and the hrtimer callback while the latter is handled only in the
hrtimer callback.
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent e7dd8c1b
...@@ -62,6 +62,8 @@ struct snd_pcsp { ...@@ -62,6 +62,8 @@ struct snd_pcsp {
unsigned short port, irq, dma; unsigned short port, irq, dma;
spinlock_t substream_lock; spinlock_t substream_lock;
struct snd_pcm_substream *playback_substream; struct snd_pcm_substream *playback_substream;
unsigned int fmt_size;
unsigned int is_signed;
size_t playback_ptr; size_t playback_ptr;
size_t period_ptr; size_t period_ptr;
atomic_t timer_active; atomic_t timer_active;
......
...@@ -36,12 +36,13 @@ static void pcsp_call_pcm_elapsed(unsigned long priv) ...@@ -36,12 +36,13 @@ static void pcsp_call_pcm_elapsed(unsigned long priv)
static DECLARE_TASKLET(pcsp_pcm_tasklet, pcsp_call_pcm_elapsed, 0); static DECLARE_TASKLET(pcsp_pcm_tasklet, pcsp_call_pcm_elapsed, 0);
enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) /* write the port and returns the next expire time in ns;
* called at the trigger-start and in hrtimer callback
*/
static unsigned long pcsp_timer_update(struct hrtimer *handle)
{ {
unsigned char timer_cnt, val; unsigned char timer_cnt, val;
int fmt_size, periods_elapsed;
u64 ns; u64 ns;
size_t period_bytes, buffer_bytes;
struct snd_pcm_substream *substream; struct snd_pcm_substream *substream;
struct snd_pcm_runtime *runtime; struct snd_pcm_runtime *runtime;
struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer); struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer);
...@@ -51,28 +52,25 @@ enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) ...@@ -51,28 +52,25 @@ enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle)
outb(chip->val61, 0x61); outb(chip->val61, 0x61);
chip->thalf = 0; chip->thalf = 0;
if (!atomic_read(&chip->timer_active)) if (!atomic_read(&chip->timer_active))
goto stop; return 0;
hrtimer_forward(&chip->timer, hrtimer_get_expires(&chip->timer), return chip->ns_rem;
ktime_set(0, chip->ns_rem));
return HRTIMER_RESTART;
} }
if (!atomic_read(&chip->timer_active)) if (!atomic_read(&chip->timer_active))
goto stop; return 0;
substream = chip->playback_substream; substream = chip->playback_substream;
if (!substream) if (!substream)
goto stop; return 0;
runtime = substream->runtime; runtime = substream->runtime;
fmt_size = snd_pcm_format_physical_width(runtime->format) >> 3;
/* assume it is mono! */ /* assume it is mono! */
val = runtime->dma_area[chip->playback_ptr + fmt_size - 1]; val = runtime->dma_area[chip->playback_ptr + chip->fmt_size - 1];
if (snd_pcm_format_signed(runtime->format)) if (chip->is_signed)
val ^= 0x80; val ^= 0x80;
timer_cnt = val * CUR_DIV() / 256; timer_cnt = val * CUR_DIV() / 256;
if (timer_cnt && chip->enable) { if (timer_cnt && chip->enable) {
spin_lock(&i8253_lock); spin_lock_irqsave(&i8253_lock, flags);
if (!nforce_wa) { if (!nforce_wa) {
outb_p(chip->val61, 0x61); outb_p(chip->val61, 0x61);
outb_p(timer_cnt, 0x42); outb_p(timer_cnt, 0x42);
...@@ -81,14 +79,39 @@ enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) ...@@ -81,14 +79,39 @@ enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle)
outb(chip->val61 ^ 2, 0x61); outb(chip->val61 ^ 2, 0x61);
chip->thalf = 1; chip->thalf = 1;
} }
spin_unlock(&i8253_lock); spin_unlock_irqrestore(&i8253_lock, flags);
} }
chip->ns_rem = PCSP_PERIOD_NS();
ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem);
chip->ns_rem -= ns;
return ns;
}
enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle)
{
struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer);
struct snd_pcm_substream *substream;
int periods_elapsed, pointer_update;
size_t period_bytes, buffer_bytes;
unsigned long ns;
unsigned long flags;
pointer_update = !chip->thalf;
ns = pcsp_timer_update(handle);
if (!ns)
return HRTIMER_NORESTART;
/* update the playback position */
substream = chip->playback_substream;
if (!substream)
return HRTIMER_NORESTART;
period_bytes = snd_pcm_lib_period_bytes(substream); period_bytes = snd_pcm_lib_period_bytes(substream);
buffer_bytes = snd_pcm_lib_buffer_bytes(substream); buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
spin_lock_irqsave(&chip->substream_lock, flags); spin_lock_irqsave(&chip->substream_lock, flags);
chip->playback_ptr += PCSP_INDEX_INC() * fmt_size; chip->playback_ptr += PCSP_INDEX_INC() * chip->fmt_size;
periods_elapsed = chip->playback_ptr - chip->period_ptr; periods_elapsed = chip->playback_ptr - chip->period_ptr;
if (periods_elapsed < 0) { if (periods_elapsed < 0) {
#if PCSP_DEBUG #if PCSP_DEBUG
...@@ -106,32 +129,27 @@ enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) ...@@ -106,32 +129,27 @@ enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle)
if (periods_elapsed) { if (periods_elapsed) {
chip->period_ptr += periods_elapsed * period_bytes; chip->period_ptr += periods_elapsed * period_bytes;
chip->period_ptr %= buffer_bytes; chip->period_ptr %= buffer_bytes;
tasklet_schedule(&pcsp_pcm_tasklet);
} }
spin_unlock_irqrestore(&chip->substream_lock, flags); spin_unlock_irqrestore(&chip->substream_lock, flags);
if (!atomic_read(&chip->timer_active)) if (periods_elapsed)
goto stop; tasklet_schedule(&pcsp_pcm_tasklet);
chip->ns_rem = PCSP_PERIOD_NS(); hrtimer_forward(handle, hrtimer_get_expires(handle), ns_to_ktime(ns));
ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem);
chip->ns_rem -= ns;
hrtimer_forward(&chip->timer, hrtimer_get_expires(&chip->timer),
ktime_set(0, ns));
return HRTIMER_RESTART;
stop: return HRTIMER_RESTART;
return HRTIMER_NORESTART;
} }
static void pcsp_start_playing(struct snd_pcsp *chip) static int pcsp_start_playing(struct snd_pcsp *chip)
{ {
unsigned long ns;
#if PCSP_DEBUG #if PCSP_DEBUG
printk(KERN_INFO "PCSP: start_playing called\n"); printk(KERN_INFO "PCSP: start_playing called\n");
#endif #endif
if (atomic_read(&chip->timer_active)) { if (atomic_read(&chip->timer_active)) {
printk(KERN_ERR "PCSP: Timer already active\n"); printk(KERN_ERR "PCSP: Timer already active\n");
return; return -EIO;
} }
spin_lock(&i8253_lock); spin_lock(&i8253_lock);
...@@ -141,7 +159,12 @@ static void pcsp_start_playing(struct snd_pcsp *chip) ...@@ -141,7 +159,12 @@ static void pcsp_start_playing(struct snd_pcsp *chip)
atomic_set(&chip->timer_active, 1); atomic_set(&chip->timer_active, 1);
chip->thalf = 0; chip->thalf = 0;
hrtimer_start(&pcsp_chip.timer, ktime_set(0, 0), HRTIMER_MODE_REL); ns = pcsp_timer_update(&pcsp_chip.timer);
if (!ns)
return -EIO;
hrtimer_start(&pcsp_chip.timer, ktime_set(0, ns), HRTIMER_MODE_REL);
return 0;
} }
static void pcsp_stop_playing(struct snd_pcsp *chip) static void pcsp_stop_playing(struct snd_pcsp *chip)
...@@ -221,6 +244,9 @@ static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream) ...@@ -221,6 +244,9 @@ static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream)
pcsp_sync_stop(chip); pcsp_sync_stop(chip);
chip->playback_ptr = 0; chip->playback_ptr = 0;
chip->period_ptr = 0; chip->period_ptr = 0;
chip->fmt_size =
snd_pcm_format_physical_width(substream->runtime->format) >> 3;
chip->is_signed = snd_pcm_format_signed(substream->runtime->format);
return 0; return 0;
} }
...@@ -233,8 +259,7 @@ static int snd_pcsp_trigger(struct snd_pcm_substream *substream, int cmd) ...@@ -233,8 +259,7 @@ static int snd_pcsp_trigger(struct snd_pcm_substream *substream, int cmd)
switch (cmd) { switch (cmd) {
case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_RESUME:
pcsp_start_playing(chip); return pcsp_start_playing(chip);
break;
case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_SUSPEND:
pcsp_stop_playing(chip); pcsp_stop_playing(chip);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment