[ALSA] hda-intel - Use SG buffer
authorTakashi Iwai <tiwai@suse.de>
Wed, 6 Feb 2008 13:50:19 +0000 (14:50 +0100)
committerTakashi Iwai <tiwai@suse.de>
Thu, 24 Apr 2008 10:00:08 +0000 (12:00 +0200)
Use SG buffers for the HD-audio instead of linear buffers.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/pci/hda/hda_intel.c

index 18475de..b38a5a7 100644 (file)
@@ -206,8 +206,9 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 };
 #define MAX_AZX_DEV            16
 
 /* max number of fragments - we may use more if allocating more pages for BDL */
-#define BDL_SIZE               PAGE_ALIGN(8192)
-#define AZX_MAX_FRAG           (BDL_SIZE / (MAX_AZX_DEV * 16))
+#define BDL_SIZE               4096
+#define AZX_MAX_BDL_ENTRIES    (BDL_SIZE / 16)
+#define AZX_MAX_FRAG           32
 /* max buffer size - no h/w limit, you can increase as you like */
 #define AZX_MAX_BUF_SIZE       (1024*1024*1024)
 /* max number of PCM devics per card */
@@ -282,12 +283,10 @@ enum {
  */
 
 struct azx_dev {
-       u32 *bdl;               /* virtual address of the BDL */
-       dma_addr_t bdl_addr;    /* physical address of the BDL */
+       struct snd_dma_buffer bdl; /* BDL buffer */
        u32 *posbuf;            /* position buffer pointer */
 
        unsigned int bufsize;   /* size of the play buffer in bytes */
-       unsigned int fragsize;  /* size of each period in bytes */
        unsigned int frags;     /* number for period in the play buffer */
        unsigned int fifo_size; /* FIFO size */
 
@@ -358,8 +357,7 @@ struct azx {
        struct azx_rb corb;
        struct azx_rb rirb;
 
-       /* BDL, CORB/RIRB and position buffers */
-       struct snd_dma_buffer bdl;
+       /* CORB/RIRB and position buffers */
        struct snd_dma_buffer rb;
        struct snd_dma_buffer posbuf;
 
@@ -962,30 +960,57 @@ static irqreturn_t azx_interrupt(int irq, void *dev_id)
 /*
  * set up BDL entries
  */
-static void azx_setup_periods(struct azx_dev *azx_dev)
+static int azx_setup_periods(struct snd_pcm_substream *substream,
+                            struct azx_dev *azx_dev)
 {
-       u32 *bdl = azx_dev->bdl;
-       dma_addr_t dma_addr = azx_dev->substream->runtime->dma_addr;
-       int idx;
+       struct snd_sg_buf *sgbuf = snd_pcm_substream_sgbuf(substream);
+       u32 *bdl;
+       int i, ofs, periods, period_bytes;
 
        /* reset BDL address */
        azx_sd_writel(azx_dev, SD_BDLPL, 0);
        azx_sd_writel(azx_dev, SD_BDLPU, 0);
 
+       period_bytes = snd_pcm_lib_period_bytes(substream);
+       periods = azx_dev->bufsize / period_bytes;
+
        /* program the initial BDL entries */
-       for (idx = 0; idx < azx_dev->frags; idx++) {
-               unsigned int off = idx << 2; /* 4 dword step */
-               dma_addr_t addr = dma_addr + idx * azx_dev->fragsize;
-               /* program the address field of the BDL entry */
-               bdl[off] = cpu_to_le32((u32)addr);
-               bdl[off+1] = cpu_to_le32(upper_32bit(addr));
-
-               /* program the size field of the BDL entry */
-               bdl[off+2] = cpu_to_le32(azx_dev->fragsize);
-
-               /* program the IOC to enable interrupt when buffer completes */
-               bdl[off+3] = cpu_to_le32(0x01);
+       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;
+               }
+               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;
 }
 
 /*
@@ -1034,9 +1059,9 @@ static int azx_setup_controller(struct azx *chip, struct azx_dev *azx_dev)
 
        /* program the BDL address */
        /* lower BDL address */
-       azx_sd_writel(azx_dev, SD_BDLPL, (u32)azx_dev->bdl_addr);
+       azx_sd_writel(azx_dev, SD_BDLPL, (u32)azx_dev->bdl.addr);
        /* upper BDL address */
-       azx_sd_writel(azx_dev, SD_BDLPU, upper_32bit(azx_dev->bdl_addr));
+       azx_sd_writel(azx_dev, SD_BDLPU, upper_32bit(azx_dev->bdl.addr));
 
        /* enable the position buffer */
        if (!(azx_readl(chip, DPLBASE) & ICH6_DPLBASE_ENABLE))
@@ -1272,8 +1297,6 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream)
        struct snd_pcm_runtime *runtime = substream->runtime;
 
        azx_dev->bufsize = snd_pcm_lib_buffer_bytes(substream);
-       azx_dev->fragsize = snd_pcm_lib_period_bytes(substream);
-       azx_dev->frags = azx_dev->bufsize / azx_dev->fragsize;
        azx_dev->format_val = snd_hda_calc_stream_format(runtime->rate,
                                                         runtime->channels,
                                                         runtime->format,
@@ -1288,7 +1311,8 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream)
        snd_printdd("azx_pcm_prepare: bufsize=0x%x, fragsize=0x%x, "
                    "format=0x%x\n",
                    azx_dev->bufsize, azx_dev->fragsize, azx_dev->format_val);
-       azx_setup_periods(azx_dev);
+       if (azx_setup_periods(substream, azx_dev) < 0)
+               return -EINVAL;
        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;
@@ -1375,6 +1399,7 @@ static struct snd_pcm_ops azx_pcm_ops = {
        .prepare = azx_pcm_prepare,
        .trigger = azx_pcm_trigger,
        .pointer = azx_pcm_pointer,
+       .page = snd_pcm_sgbuf_ops_page,
 };
 
 static void azx_pcm_free(struct snd_pcm *pcm)
@@ -1417,7 +1442,7 @@ static int __devinit create_codec_pcm(struct azx *chip, struct hda_codec *codec,
                snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &azx_pcm_ops);
        if (cpcm->stream[1].substreams)
                snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &azx_pcm_ops);
-       snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+       snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
                                              snd_dma_pci_data(chip->pci),
                                              1024 * 64, 1024 * 1024);
        chip->pcm[cpcm->device] = pcm;
@@ -1507,10 +1532,7 @@ static int __devinit azx_init_stream(struct azx *chip)
         * and initialize
         */
        for (i = 0; i < chip->num_streams; i++) {
-               unsigned int off = sizeof(u32) * (i * AZX_MAX_FRAG * 4);
                struct azx_dev *azx_dev = &chip->azx_dev[i];
-               azx_dev->bdl = (u32 *)(chip->bdl.area + off);
-               azx_dev->bdl_addr = chip->bdl.addr + off;
                azx_dev->posbuf = (u32 __iomem *)(chip->posbuf.area + i * 8);
                /* offset: SDI0=0x80, SDI1=0xa0, ... SDO3=0x160 */
                azx_dev->sd_addr = chip->remap_addr + (0x20 * i + 0x80);
@@ -1646,8 +1668,9 @@ static int azx_resume(struct pci_dev *pci)
  */
 static int azx_free(struct azx *chip)
 {
+       int i;
+
        if (chip->initialized) {
-               int i;
                for (i = 0; i < chip->num_streams; i++)
                        azx_stream_stop(chip, &chip->azx_dev[i]);
                azx_stop_chip(chip);
@@ -1662,8 +1685,11 @@ static int azx_free(struct azx *chip)
        if (chip->remap_addr)
                iounmap(chip->remap_addr);
 
-       if (chip->bdl.area)
-               snd_dma_free_pages(&chip->bdl);
+       if (chip->azx_dev) {
+               for (i = 0; i < chip->num_streams; i++)
+                       if (chip->azx_dev[i].bdl.area)
+                               snd_dma_free_pages(&chip->azx_dev[i].bdl);
+       }
        if (chip->rb.area)
                snd_dma_free_pages(&chip->rb);
        if (chip->posbuf.area)
@@ -1745,7 +1771,7 @@ static int __devinit azx_create(struct snd_card *card, struct pci_dev *pci,
                                struct azx **rchip)
 {
        struct azx *chip;
-       int err;
+       int i, err;
        unsigned short gcap;
        static struct snd_device_ops ops = {
                .dev_free = azx_dev_free,
@@ -1857,13 +1883,15 @@ static int __devinit azx_create(struct snd_card *card, struct pci_dev *pci,
                goto errout;
        }
 
-       /* allocate memory for the BDL for each stream */
-       err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
-                                 snd_dma_pci_data(chip->pci),
-                                 BDL_SIZE, &chip->bdl);
-       if (err < 0) {
-               snd_printk(KERN_ERR SFX "cannot allocate BDL\n");
-               goto errout;
+       for (i = 0; i < chip->num_streams; i++) {
+               /* allocate memory for the BDL for each stream */
+               err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
+                                         snd_dma_pci_data(chip->pci),
+                                         BDL_SIZE, &chip->azx_dev[i].bdl);
+               if (err < 0) {
+                       snd_printk(KERN_ERR SFX "cannot allocate BDL\n");
+                       goto errout;
+               }
        }
        /* allocate memory for the position buffer */
        err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,