Merge branch 'master' of git://git.alsa-project.org/alsa-kernel into for-linus
authorTakashi Iwai <tiwai@suse.de>
Wed, 15 Apr 2009 09:21:13 +0000 (11:21 +0200)
committerTakashi Iwai <tiwai@suse.de>
Wed, 15 Apr 2009 09:21:13 +0000 (11:21 +0200)
* 'master' of git://git.alsa-project.org/alsa-kernel:
  [ALSA] intel8x0: add one retry to the ac97_clock measurement routine
  [ALSA] intel8x0: fix wrong conditions in ac97_clock measure routine
  [ALSA] intel8x0: do not use zero value from PICB register
  [ALSA] intel8x0: an attempt to make ac97_clock measurement more reliable
  [ALSA] pcm-midlevel: Add more strict buffer position checks based on jiffies
  [ALSA] hda_intel: fix unexpected ring buffer positions

include/sound/pcm.h
sound/core/pcm_lib.c
sound/pci/hda/hda_intel.c
sound/pci/intel8x0.c

index 8904b1900d7ffa7969bf82603a9142dc8d2f7807..c172968916170b3e3c4e42aa1793b729f21e3670 100644 (file)
@@ -268,7 +268,8 @@ struct snd_pcm_runtime {
        int overrange;
        snd_pcm_uframes_t avail_max;
        snd_pcm_uframes_t hw_ptr_base;  /* Position at buffer restart */
-       snd_pcm_uframes_t hw_ptr_interrupt; /* Position at interrupt time*/
+       snd_pcm_uframes_t hw_ptr_interrupt; /* Position at interrupt time */
+       unsigned long hw_ptr_jiffies;   /* Time when hw_ptr is updated */
 
        /* -- HW params -- */
        snd_pcm_access_t access;        /* access mode */
index fbb2e391591ea2c1a5e1933c65f6b946e7a0bd2c..63d088f2265f5c33ce8a26d707ff220bd61c2a1e 100644 (file)
@@ -209,9 +209,11 @@ static int snd_pcm_update_hw_ptr_interrupt(struct snd_pcm_substream *substream)
 {
        struct snd_pcm_runtime *runtime = substream->runtime;
        snd_pcm_uframes_t pos;
-       snd_pcm_uframes_t new_hw_ptr, hw_ptr_interrupt, hw_base;
-       snd_pcm_sframes_t delta;
+       snd_pcm_uframes_t old_hw_ptr, new_hw_ptr, hw_ptr_interrupt, hw_base;
+       snd_pcm_sframes_t hdelta, delta;
+       unsigned long jdelta;
 
+       old_hw_ptr = runtime->status->hw_ptr;
        pos = snd_pcm_update_hw_ptr_pos(substream, runtime);
        if (pos == SNDRV_PCM_POS_XRUN) {
                xrun(substream);
@@ -247,7 +249,30 @@ static int snd_pcm_update_hw_ptr_interrupt(struct snd_pcm_substream *substream)
                        new_hw_ptr = hw_base + pos;
                }
        }
-       if (delta > runtime->period_size) {
+       hdelta = new_hw_ptr - old_hw_ptr;
+       jdelta = jiffies - runtime->hw_ptr_jiffies;
+       if (((hdelta * HZ) / runtime->rate) > jdelta + HZ/100) {
+               delta = jdelta /
+                       (((runtime->period_size * HZ) / runtime->rate)
+                                                               + HZ/100);
+               hw_ptr_error(substream,
+                            "hw_ptr skipping! [Q] "
+                            "(pos=%ld, delta=%ld, period=%ld, "
+                            "jdelta=%lu/%lu/%lu)\n",
+                            (long)pos, (long)hdelta,
+                            (long)runtime->period_size, jdelta,
+                            ((hdelta * HZ) / runtime->rate), delta);
+               hw_ptr_interrupt = runtime->hw_ptr_interrupt +
+                                  runtime->period_size * delta;
+               if (hw_ptr_interrupt >= runtime->boundary)
+                       hw_ptr_interrupt -= runtime->boundary;
+               /* rebase to interrupt position */
+               hw_base = new_hw_ptr = hw_ptr_interrupt;
+               /* align hw_base to buffer_size */
+               hw_base -= hw_base % runtime->buffer_size;
+               delta = 0;
+       }
+       if (delta > runtime->period_size + runtime->period_size / 2) {
                hw_ptr_error(substream,
                             "Lost interrupts? "
                             "(stream=%i, delta=%ld, intr_ptr=%ld)\n",
@@ -263,6 +288,7 @@ static int snd_pcm_update_hw_ptr_interrupt(struct snd_pcm_substream *substream)
 
        runtime->hw_ptr_base = hw_base;
        runtime->status->hw_ptr = new_hw_ptr;
+       runtime->hw_ptr_jiffies = jiffies;
        runtime->hw_ptr_interrupt = hw_ptr_interrupt;
 
        return snd_pcm_update_hw_ptr_post(substream, runtime);
@@ -275,6 +301,7 @@ int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)
        snd_pcm_uframes_t pos;
        snd_pcm_uframes_t old_hw_ptr, new_hw_ptr, hw_base;
        snd_pcm_sframes_t delta;
+       unsigned long jdelta;
 
        old_hw_ptr = runtime->status->hw_ptr;
        pos = snd_pcm_update_hw_ptr_pos(substream, runtime);
@@ -286,14 +313,15 @@ int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)
        new_hw_ptr = hw_base + pos;
 
        delta = new_hw_ptr - old_hw_ptr;
+       jdelta = jiffies - runtime->hw_ptr_jiffies;
        if (delta < 0) {
                delta += runtime->buffer_size;
                if (delta < 0) {
                        hw_ptr_error(substream, 
                                     "Unexpected hw_pointer value [2] "
-                                    "(stream=%i, pos=%ld, old_ptr=%ld)\n",
+                                    "(stream=%i, pos=%ld, old_ptr=%ld, jdelta=%li)\n",
                                     substream->stream, (long)pos,
-                                    (long)old_hw_ptr);
+                                    (long)old_hw_ptr, jdelta);
                        return 0;
                }
                hw_base += runtime->buffer_size;
@@ -301,12 +329,13 @@ int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)
                        hw_base = 0;
                new_hw_ptr = hw_base + pos;
        }
-       if (delta > runtime->period_size && runtime->periods > 1) {
+       if (((delta * HZ) / runtime->rate) > jdelta + HZ/100) {
                hw_ptr_error(substream,
                             "hw_ptr skipping! "
-                            "(pos=%ld, delta=%ld, period=%ld)\n",
+                            "(pos=%ld, delta=%ld, period=%ld, jdelta=%lu/%lu)\n",
                             (long)pos, (long)delta,
-                            (long)runtime->period_size);
+                            (long)runtime->period_size, jdelta,
+                            ((delta * HZ) / runtime->rate));
                return 0;
        }
        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
@@ -315,6 +344,7 @@ int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)
 
        runtime->hw_ptr_base = hw_base;
        runtime->status->hw_ptr = new_hw_ptr;
+       runtime->hw_ptr_jiffies = jiffies;
 
        return snd_pcm_update_hw_ptr_post(substream, runtime);
 }
@@ -1441,6 +1471,7 @@ static int snd_pcm_lib_ioctl_reset(struct snd_pcm_substream *substream,
                runtime->status->hw_ptr %= runtime->buffer_size;
        else
                runtime->status->hw_ptr = 0;
+       runtime->hw_ptr_jiffies = jiffies;
        snd_pcm_stream_unlock_irqrestore(substream, flags);
        return 0;
 }
index 7ba8db5d4c42176787b283aea0246685d63342f9..bc882f8f163c4f9843d9f594c81d5b90036af2ae 100644 (file)
@@ -312,6 +312,9 @@ struct azx_dev {
        unsigned int period_bytes; /* size of the period in bytes */
        unsigned int frags;     /* number for period in the play buffer */
        unsigned int fifo_size; /* FIFO size */
+       unsigned int start_flag: 1;     /* stream full start flag */
+       unsigned long start_jiffies;    /* start + minimum jiffies */
+       unsigned long min_jiffies;      /* minimum jiffies before position is valid */
 
        void __iomem *sd_addr;  /* stream descriptor pointer */
 
@@ -330,7 +333,6 @@ struct azx_dev {
        unsigned int opened :1;
        unsigned int running :1;
        unsigned int irq_pending :1;
-       unsigned int irq_ignore :1;
        /*
         * For VIA:
         *  A flag to ensure DMA position is 0
@@ -975,7 +977,7 @@ static irqreturn_t azx_interrupt(int irq, void *dev_id)
        struct azx *chip = dev_id;
        struct azx_dev *azx_dev;
        u32 status;
-       int i;
+       int i, ok;
 
        spin_lock(&chip->reg_lock);
 
@@ -991,18 +993,14 @@ static irqreturn_t azx_interrupt(int irq, void *dev_id)
                        azx_sd_writeb(azx_dev, SD_STS, SD_INT_MASK);
                        if (!azx_dev->substream || !azx_dev->running)
                                continue;
-                       /* ignore the first dummy IRQ (due to pos_adj) */
-                       if (azx_dev->irq_ignore) {
-                               azx_dev->irq_ignore = 0;
-                               continue;
-                       }
                        /* check whether this IRQ is really acceptable */
-                       if (azx_position_ok(chip, azx_dev)) {
+                       ok = azx_position_ok(chip, azx_dev);
+                       if (ok == 1) {
                                azx_dev->irq_pending = 0;
                                spin_unlock(&chip->reg_lock);
                                snd_pcm_period_elapsed(azx_dev->substream);
                                spin_lock(&chip->reg_lock);
-                       } else if (chip->bus && chip->bus->workq) {
+                       } else if (ok == 0 && chip->bus && chip->bus->workq) {
                                /* bogus IRQ, process it later */
                                azx_dev->irq_pending = 1;
                                queue_work(chip->bus->workq,
@@ -1088,7 +1086,6 @@ static int azx_setup_periods(struct azx *chip,
        bdl = (u32 *)azx_dev->bdl.area;
        ofs = 0;
        azx_dev->frags = 0;
-       azx_dev->irq_ignore = 0;
        pos_adj = bdl_pos_adj[chip->dev_index];
        if (pos_adj > 0) {
                struct snd_pcm_runtime *runtime = substream->runtime;
@@ -1109,7 +1106,6 @@ static int azx_setup_periods(struct azx *chip,
                                         &bdl, ofs, pos_adj, 1);
                        if (ofs < 0)
                                goto error;
-                       azx_dev->irq_ignore = 1;
                }
        } else
                pos_adj = 0;
@@ -1155,6 +1151,9 @@ static void azx_stream_reset(struct azx *chip, struct azx_dev *azx_dev)
        while (((val = azx_sd_readb(azx_dev, SD_CTL)) & SD_CTL_STREAM_RESET) &&
               --timeout)
                ;
+
+       /* reset first position - may not be synced with hw at this time */
+       *azx_dev->posbuf = 0;
 }
 
 /*
@@ -1409,7 +1408,6 @@ static int azx_pcm_open(struct snd_pcm_substream *substream)
        snd_pcm_set_sync(substream);
        mutex_unlock(&chip->open_mutex);
 
-       azx_stream_reset(chip, azx_dev);
        return 0;
 }
 
@@ -1474,6 +1472,7 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream)
        unsigned int bufsize, period_bytes, format_val;
        int err;
 
+       azx_stream_reset(chip, azx_dev);
        format_val = snd_hda_calc_stream_format(runtime->rate,
                                                runtime->channels,
                                                runtime->format,
@@ -1502,6 +1501,8 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream)
                        return err;
        }
 
+       azx_dev->min_jiffies = (runtime->period_size * HZ) /
+                                               (runtime->rate * 2);
        azx_setup_controller(chip, azx_dev);
        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
                azx_dev->fifo_size = azx_sd_readw(azx_dev, SD_FIFOSIZE) + 1;
@@ -1518,13 +1519,14 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
        struct azx *chip = apcm->chip;
        struct azx_dev *azx_dev;
        struct snd_pcm_substream *s;
-       int start, nsync = 0, sbits = 0;
+       int rstart = 0, start, nsync = 0, sbits = 0;
        int nwait, timeout;
 
        switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+               rstart = 1;
        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
        case SNDRV_PCM_TRIGGER_RESUME:
-       case SNDRV_PCM_TRIGGER_START:
                start = 1;
                break;
        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
@@ -1554,6 +1556,10 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
                if (s->pcm->card != substream->pcm->card)
                        continue;
                azx_dev = get_azx_dev(s);
+               if (rstart) {
+                       azx_dev->start_flag = 1;
+                       azx_dev->start_jiffies = jiffies + azx_dev->min_jiffies;
+               }
                if (start)
                        azx_stream_start(chip, azx_dev);
                else
@@ -1703,6 +1709,11 @@ static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev)
 {
        unsigned int pos;
 
+       if (azx_dev->start_flag &&
+           time_before_eq(jiffies, azx_dev->start_jiffies))
+               return -1;      /* bogus (too early) interrupt */
+       azx_dev->start_flag = 0;
+
        pos = azx_get_position(chip, azx_dev);
        if (chip->position_fix == POS_FIX_AUTO) {
                if (!pos) {
index 57648810eaf16430049f9e3300441e2a58954b9f..5dced5b793873409f32bd20f3e06588de8dfb662 100644 (file)
@@ -355,6 +355,9 @@ struct ichdev {
         unsigned int fragsize1;
         unsigned int position;
        unsigned int pos_shift;
+       unsigned int last_pos;
+       unsigned long last_pos_jiffies;
+       unsigned int jiffy_to_bytes;
         int frags;
         int lvi;
         int lvi_frag;
@@ -838,7 +841,10 @@ static int snd_intel8x0_pcm_trigger(struct snd_pcm_substream *substream, int cmd
                ichdev->suspended = 0;
                /* fallthru */
        case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
                val = ICH_IOCE | ICH_STARTBM;
+               ichdev->last_pos = ichdev->position;
+               ichdev->last_pos_jiffies = jiffies;
                break;
        case SNDRV_PCM_TRIGGER_SUSPEND:
                ichdev->suspended = 1;
@@ -849,9 +855,6 @@ static int snd_intel8x0_pcm_trigger(struct snd_pcm_substream *substream, int cmd
        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
                val = ICH_IOCE;
                break;
-       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
-               val = ICH_IOCE | ICH_STARTBM;
-               break;
        default:
                return -EINVAL;
        }
@@ -1045,6 +1048,7 @@ static int snd_intel8x0_pcm_prepare(struct snd_pcm_substream *substream)
                        ichdev->pos_shift = (runtime->sample_bits > 16) ? 2 : 1;
        }
        snd_intel8x0_setup_periods(chip, ichdev);
+       ichdev->jiffy_to_bytes = (runtime->rate * 4 * ichdev->pos_shift) / HZ;
        return 0;
 }
 
@@ -1053,7 +1057,7 @@ static snd_pcm_uframes_t snd_intel8x0_pcm_pointer(struct snd_pcm_substream *subs
        struct intel8x0 *chip = snd_pcm_substream_chip(substream);
        struct ichdev *ichdev = get_ichdev(substream);
        size_t ptr1, ptr;
-       int civ, timeout = 100;
+       int civ, timeout = 10;
        unsigned int position;
 
        spin_lock(&chip->reg_lock);
@@ -1069,9 +1073,19 @@ static snd_pcm_uframes_t snd_intel8x0_pcm_pointer(struct snd_pcm_substream *subs
                    ptr1 == igetword(chip, ichdev->reg_offset + ichdev->roff_picb))
                        break;
        } while (timeout--);
-       ptr1 <<= ichdev->pos_shift;
-       ptr = ichdev->fragsize1 - ptr1;
-       ptr += position;
+       if (ptr1 != 0) {
+               ptr1 <<= ichdev->pos_shift;
+               ptr = ichdev->fragsize1 - ptr1;
+               ptr += position;
+               ichdev->last_pos = ptr;
+               ichdev->last_pos_jiffies = jiffies;
+       } else {
+               ptr1 = jiffies - ichdev->last_pos_jiffies;
+               if (ptr1)
+                       ptr1 -= 1;
+               ptr = ichdev->last_pos + ptr1 * ichdev->jiffy_to_bytes;
+               ptr %= ichdev->size;
+       }
        spin_unlock(&chip->reg_lock);
        if (ptr >= ichdev->size)
                return 0;
@@ -2661,12 +2675,14 @@ static void __devinit intel8x0_measure_ac97_clock(struct intel8x0 *chip)
        struct snd_pcm_substream *subs;
        struct ichdev *ichdev;
        unsigned long port;
-       unsigned long pos, t;
-       struct timeval start_time, stop_time;
+       unsigned long pos, pos1, t;
+       int civ, timeout = 1000, attempt = 1;
+       struct timespec start_time, stop_time;
 
        if (chip->ac97_bus->clock != 48000)
                return; /* specified in module option */
 
+      __again:
        subs = chip->pcm[0]->streams[0].substream;
        if (! subs || subs->dma_buffer.bytes < INTEL8X0_TESTBUF_SIZE) {
                snd_printk(KERN_WARNING "no playback buffer allocated - aborting measure ac97 clock\n");
@@ -2674,7 +2690,7 @@ static void __devinit intel8x0_measure_ac97_clock(struct intel8x0 *chip)
        }
        ichdev = &chip->ichd[ICHD_PCMOUT];
        ichdev->physbuf = subs->dma_buffer.addr;
-       ichdev->size = chip->ichd[ICHD_PCMOUT].fragsize = INTEL8X0_TESTBUF_SIZE;
+       ichdev->size = ichdev->fragsize = INTEL8X0_TESTBUF_SIZE;
        ichdev->substream = NULL; /* don't process interrupts */
 
        /* set rate */
@@ -2693,16 +2709,31 @@ static void __devinit intel8x0_measure_ac97_clock(struct intel8x0 *chip)
                iputbyte(chip, port + ICH_REG_OFF_CR, ICH_IOCE);
                iputdword(chip, ICHREG(ALI_DMACR), 1 << ichdev->ali_slot);
        }
-       do_gettimeofday(&start_time);
+       do_posix_clock_monotonic_gettime(&start_time);
        spin_unlock_irq(&chip->reg_lock);
        msleep(50);
        spin_lock_irq(&chip->reg_lock);
        /* check the position */
-       pos = ichdev->fragsize1;
-       pos -= igetword(chip, ichdev->reg_offset + ichdev->roff_picb) << ichdev->pos_shift;
-       pos += ichdev->position;
+       do {
+               civ = igetbyte(chip, ichdev->reg_offset + ICH_REG_OFF_CIV);
+               pos1 = igetword(chip, ichdev->reg_offset + ichdev->roff_picb);
+               if (pos1 == 0) {
+                       udelay(10);
+                       continue;
+               }
+               if (civ == igetbyte(chip, ichdev->reg_offset + ICH_REG_OFF_CIV) &&
+                   pos1 == igetword(chip, ichdev->reg_offset + ichdev->roff_picb))
+                       break;
+       } while (timeout--);
+       if (pos1 == 0) {        /* oops, this value is not reliable */
+               pos = 0;
+       } else {
+               pos = ichdev->fragsize1;
+               pos -= pos1 << ichdev->pos_shift;
+               pos += ichdev->position;
+       }
        chip->in_measurement = 0;
-       do_gettimeofday(&stop_time);
+       do_posix_clock_monotonic_gettime(&stop_time);
        /* stop */
        if (chip->device_type == DEVICE_ALI) {
                iputdword(chip, ICHREG(ALI_DMACR), 1 << (ichdev->ali_slot + 16));
@@ -2717,19 +2748,37 @@ static void __devinit intel8x0_measure_ac97_clock(struct intel8x0 *chip)
        iputbyte(chip, port + ICH_REG_OFF_CR, ICH_RESETREGS);
        spin_unlock_irq(&chip->reg_lock);
 
+       if (pos == 0) {
+               snd_printk(KERN_ERR "intel8x0: measure - unreliable DMA position..\n");
+             __retry:
+               if (attempt < 2) {
+                       attempt++;
+                       goto __again;
+               }
+               return;
+       }
+
+       pos /= 4;
        t = stop_time.tv_sec - start_time.tv_sec;
        t *= 1000000;
-       t += stop_time.tv_usec - start_time.tv_usec;
-       printk(KERN_INFO "%s: measured %lu usecs\n", __func__, t);
+       t += (stop_time.tv_nsec - start_time.tv_nsec) / 1000;
+       printk(KERN_INFO "%s: measured %lu usecs (%lu samples)\n", __func__, t, pos);
        if (t == 0) {
-               snd_printk(KERN_ERR "?? calculation error..\n");
-               return;
+               snd_printk(KERN_ERR "intel8x0: ?? calculation error..\n");
+               goto __retry;
        }
-       pos = (pos / 4) * 1000;
+       pos *= 1000;
        pos = (pos / t) * 1000 + ((pos % t) * 1000) / t;
-       if (pos < 40000 || pos >= 60000) 
+       if (pos < 40000 || pos >= 60000) {
                /* abnormal value. hw problem? */
                printk(KERN_INFO "intel8x0: measured clock %ld rejected\n", pos);
+               goto __retry;
+       } else if (pos > 40500 && pos < 41500)
+               /* first exception - 41000Hz reference clock */
+               chip->ac97_bus->clock = 41000;
+       else if (pos > 43600 && pos < 44600)
+               /* second exception - 44100HZ reference clock */
+               chip->ac97_bus->clock = 44100;
        else if (pos < 47500 || pos > 48500)
                /* not 48000Hz, tuning the clock.. */
                chip->ac97_bus->clock = (chip->ac97_bus->clock * 48000) / pos;