ALSA: hda - Add bdl_pos_adj option
[deliverable/linux.git] / sound / pci / hda / hda_intel.c
index dc68709e75694dedbf8626190472c84864e3665b..1ec3fd4c8940ce197af271e657b0bcea71ecb64f 100644 (file)
@@ -58,6 +58,7 @@ static int position_fix[SNDRV_CARDS];
 static int probe_mask[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1};
 static int single_cmd;
 static int enable_msi;
+static int bdl_pos_adj = 1;
 
 module_param_array(index, int, NULL, 0444);
 MODULE_PARM_DESC(index, "Index value for Intel HD audio interface.");
@@ -77,6 +78,8 @@ MODULE_PARM_DESC(single_cmd, "Use single command to communicate with codecs "
                 "(for debugging only).");
 module_param(enable_msi, int, 0444);
 MODULE_PARM_DESC(enable_msi, "Enable Message Signaled Interrupt (MSI)");
+module_param(bdl_pos_adj, int, 0644);
+MODULE_PARM_DESC(bdl_pos_adj, "BDL position adjustment offset");
 
 #ifdef CONFIG_SND_HDA_POWER_SAVE
 /* power_save option is defined in hda_codec.c */
@@ -309,7 +312,8 @@ struct azx_dev {
 
        unsigned int opened :1;
        unsigned int running :1;
-       unsigned int irq_pending: 1;
+       unsigned int irq_pending :1;
+       unsigned int irq_ignore :1;
 };
 
 /* CORB/RIRB */
@@ -943,6 +947,11 @@ 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)) {
                                azx_dev->irq_pending = 0;
@@ -976,15 +985,54 @@ static irqreturn_t azx_interrupt(int irq, void *dev_id)
 }
 
 
+/*
+ * set up a BDL entry
+ */
+static int setup_bdle(struct snd_pcm_substream *substream,
+                     struct azx_dev *azx_dev, u32 **bdlp,
+                     int ofs, int size, int with_ioc)
+{
+       struct snd_sg_buf *sgbuf = snd_pcm_substream_sgbuf(substream);
+       u32 *bdl = *bdlp;
+
+       while (size > 0) {
+               dma_addr_t addr;
+               int chunk;
+
+               if (azx_dev->frags >= AZX_MAX_BDL_ENTRIES)
+                       return -EINVAL;
+
+               addr = snd_pcm_sgbuf_get_addr(sgbuf, ofs);
+               /* program the address field of the BDL entry */
+               bdl[0] = cpu_to_le32((u32)addr);
+               bdl[1] = cpu_to_le32(upper_32bit(addr));
+               /* program the size field of the BDL entry */
+               chunk = PAGE_SIZE - (ofs % PAGE_SIZE);
+               if (size < chunk)
+                       chunk = size;
+               bdl[2] = cpu_to_le32(chunk);
+               /* program the IOC to enable interrupt
+                * only when the whole fragment is processed
+                */
+               size -= chunk;
+               bdl[3] = (size || !with_ioc) ? 0 : cpu_to_le32(0x01);
+               bdl += 4;
+               azx_dev->frags++;
+               ofs += chunk;
+       }
+       *bdlp = bdl;
+       return ofs;
+}
+
 /*
  * set up BDL entries
  */
 static int azx_setup_periods(struct snd_pcm_substream *substream,
                             struct azx_dev *azx_dev)
 {
-       struct snd_sg_buf *sgbuf = snd_pcm_substream_sgbuf(substream);
        u32 *bdl;
        int i, ofs, periods, period_bytes;
+       int pos_adj = 0;
 
        /* reset BDL address */
        azx_sd_writel(azx_dev, SD_BDLPL, 0);
@@ -998,39 +1046,44 @@ static int azx_setup_periods(struct snd_pcm_substream *substream,
        bdl = (u32 *)azx_dev->bdl.area;
        ofs = 0;
        azx_dev->frags = 0;
-       for (i = 0; i < periods; i++) {
-               int size, rest;
-               if (i >= AZX_MAX_BDL_ENTRIES) {
-                       snd_printk(KERN_ERR "Too many BDL entries: "
-                                  "buffer=%d, period=%d\n",
-                                  azx_dev->bufsize, period_bytes);
-                       /* reset */
-                       azx_sd_writel(azx_dev, SD_BDLPL, 0);
-                       azx_sd_writel(azx_dev, SD_BDLPU, 0);
-                       return -EINVAL;
+       azx_dev->irq_ignore = 0;
+       if (bdl_pos_adj > 0) {
+               struct snd_pcm_runtime *runtime = substream->runtime;
+               pos_adj = (bdl_pos_adj * runtime->rate + 47999) / 48000;
+               if (!pos_adj)
+                       pos_adj = 1;
+               pos_adj = frames_to_bytes(runtime, pos_adj);
+               if (pos_adj >= period_bytes) {
+                       snd_printk(KERN_WARNING "Too big adjustment %d\n",
+                                  bdl_pos_adj);
+                       pos_adj = 0;
+               } else {
+                       ofs = setup_bdle(substream, azx_dev,
+                                        &bdl, ofs, pos_adj, 1);
+                       if (ofs < 0)
+                               goto error;
+                       azx_dev->irq_ignore = 1;
                }
-               rest = period_bytes;
-               do {
-                       dma_addr_t addr = snd_pcm_sgbuf_get_addr(sgbuf, ofs);
-                       /* program the address field of the BDL entry */
-                       bdl[0] = cpu_to_le32((u32)addr);
-                       bdl[1] = cpu_to_le32(upper_32bit(addr));
-                       /* program the size field of the BDL entry */
-                       size = PAGE_SIZE - (ofs % PAGE_SIZE);
-                       if (rest < size)
-                               size = rest;
-                       bdl[2] = cpu_to_le32(size);
-                       /* program the IOC to enable interrupt
-                        * only when the whole fragment is processed
-                        */
-                       rest -= size;
-                       bdl[3] = rest ? 0 : cpu_to_le32(0x01);
-                       bdl += 4;
-                       azx_dev->frags++;
-                       ofs += size;
-               } while (rest > 0);
+       }
+       for (i = 0; i < periods; i++) {
+               if (i == periods - 1 && pos_adj)
+                       ofs = setup_bdle(substream, azx_dev, &bdl, ofs,
+                                        period_bytes - pos_adj, 0);
+               else
+                       ofs = setup_bdle(substream, azx_dev, &bdl, ofs,
+                                        period_bytes, 1);
+               if (ofs < 0)
+                       goto error;
        }
        return 0;
+
+ error:
+       snd_printk(KERN_ERR "Too many BDL entries: buffer=%d, period=%d\n",
+                  azx_dev->bufsize, period_bytes);
+       /* reset */
+       azx_sd_writel(azx_dev, SD_BDLPL, 0);
+       azx_sd_writel(azx_dev, SD_BDLPU, 0);
+       return -EINVAL;
 }
 
 /*
This page took 0.029091 seconds and 5 git commands to generate.