Commit 675f25d4 authored by Takashi Iwai's avatar Takashi Iwai Committed by Jaroslav Kysela

ALSA: hda - Add bdl_pos_adj option

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: default avatarTakashi Iwai <tiwai@suse.de>
Signed-off-by: default avatarJaroslav Kysela <perex@perex.cz>
parent 0a1b42db
...@@ -58,6 +58,7 @@ static int position_fix[SNDRV_CARDS]; ...@@ -58,6 +58,7 @@ static int position_fix[SNDRV_CARDS];
static int probe_mask[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1}; static int probe_mask[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1};
static int single_cmd; static int single_cmd;
static int enable_msi; static int enable_msi;
static int bdl_pos_adj = 1;
module_param_array(index, int, NULL, 0444); module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for Intel HD audio interface."); 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 " ...@@ -77,6 +78,8 @@ MODULE_PARM_DESC(single_cmd, "Use single command to communicate with codecs "
"(for debugging only)."); "(for debugging only).");
module_param(enable_msi, int, 0444); module_param(enable_msi, int, 0444);
MODULE_PARM_DESC(enable_msi, "Enable Message Signaled Interrupt (MSI)"); 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 #ifdef CONFIG_SND_HDA_POWER_SAVE
/* power_save option is defined in hda_codec.c */ /* power_save option is defined in hda_codec.c */
...@@ -309,7 +312,8 @@ struct azx_dev { ...@@ -309,7 +312,8 @@ struct azx_dev {
unsigned int opened :1; unsigned int opened :1;
unsigned int running :1; unsigned int running :1;
unsigned int irq_pending: 1; unsigned int irq_pending :1;
unsigned int irq_ignore :1;
}; };
/* CORB/RIRB */ /* CORB/RIRB */
...@@ -943,6 +947,11 @@ static irqreturn_t azx_interrupt(int irq, void *dev_id) ...@@ -943,6 +947,11 @@ static irqreturn_t azx_interrupt(int irq, void *dev_id)
azx_sd_writeb(azx_dev, SD_STS, SD_INT_MASK); azx_sd_writeb(azx_dev, SD_STS, SD_INT_MASK);
if (!azx_dev->substream || !azx_dev->running) if (!azx_dev->substream || !azx_dev->running)
continue; 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 */ /* check whether this IRQ is really acceptable */
if (azx_position_ok(chip, azx_dev)) { if (azx_position_ok(chip, azx_dev)) {
azx_dev->irq_pending = 0; azx_dev->irq_pending = 0;
...@@ -976,15 +985,54 @@ static irqreturn_t azx_interrupt(int irq, void *dev_id) ...@@ -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 * set up BDL entries
*/ */
static int azx_setup_periods(struct snd_pcm_substream *substream, static int azx_setup_periods(struct snd_pcm_substream *substream,
struct azx_dev *azx_dev) struct azx_dev *azx_dev)
{ {
struct snd_sg_buf *sgbuf = snd_pcm_substream_sgbuf(substream);
u32 *bdl; u32 *bdl;
int i, ofs, periods, period_bytes; int i, ofs, periods, period_bytes;
int pos_adj = 0;
/* reset BDL address */ /* reset BDL address */
azx_sd_writel(azx_dev, SD_BDLPL, 0); azx_sd_writel(azx_dev, SD_BDLPL, 0);
...@@ -998,39 +1046,44 @@ static int azx_setup_periods(struct snd_pcm_substream *substream, ...@@ -998,39 +1046,44 @@ static int azx_setup_periods(struct snd_pcm_substream *substream,
bdl = (u32 *)azx_dev->bdl.area; bdl = (u32 *)azx_dev->bdl.area;
ofs = 0; ofs = 0;
azx_dev->frags = 0; azx_dev->frags = 0;
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;
}
}
for (i = 0; i < periods; i++) { for (i = 0; i < periods; i++) {
int size, rest; if (i == periods - 1 && pos_adj)
if (i >= AZX_MAX_BDL_ENTRIES) { ofs = setup_bdle(substream, azx_dev, &bdl, ofs,
snd_printk(KERN_ERR "Too many BDL entries: " period_bytes - pos_adj, 0);
"buffer=%d, period=%d\n", 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); azx_dev->bufsize, period_bytes);
/* reset */ /* reset */
azx_sd_writel(azx_dev, SD_BDLPL, 0); azx_sd_writel(azx_dev, SD_BDLPL, 0);
azx_sd_writel(azx_dev, SD_BDLPU, 0); azx_sd_writel(azx_dev, SD_BDLPU, 0);
return -EINVAL; return -EINVAL;
}
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);
}
return 0;
} }
/* /*
......
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