Skip to content

Commit

Permalink
ALSA: hda - Add bdl_pos_adj option
Browse files Browse the repository at this point in the history
Added a new option, bdl_pos_adj, to adjust the delay of IRQ-wakeup
timing.

Most HD-audio hardwares have a problem that a BDL IRQ is issued before
actually the data and the DMA pointer are updated.
We have already a mechanism to force to delay snd_pcm_period_elapsed()
calls via workq, but this costs much CPU, and typically the delay is
within one sample.  Thus, it's more clever to adjust the BDL entries
instead.

The new option adds the size of the delay in frames.  As default,
it's set to 1 -- that is, one sample delay.  Even the hardware is
really correct, one sample delay is relatively harmless in comparison
with reporting wrong positions.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
  • Loading branch information
Takashi Iwai authored and Jaroslav Kysela committed Jun 13, 2008
1 parent 0a1b42d commit 675f25d
Showing 1 changed file with 85 additions and 32 deletions.
117 changes: 85 additions & 32 deletions sound/pci/hda/hda_intel.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
Expand All @@ -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 */
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}

/*
Expand Down

0 comments on commit 675f25d

Please sign in to comment.