diff --git a/include/sound/memalloc.h b/include/sound/memalloc.h
index 96d0dc171459c58cb519993e3a5625d0c7b4cafb..d787a6b4a10123006f1384ffc61153ec9c9a733c 100644
--- a/include/sound/memalloc.h
+++ b/include/sound/memalloc.h
@@ -97,7 +97,9 @@ static inline unsigned int snd_sgbuf_aligned_pages(size_t size)
  */
 static inline dma_addr_t snd_sgbuf_get_addr(struct snd_sg_buf *sgbuf, size_t offset)
 {
-	return sgbuf->table[offset >> PAGE_SHIFT].addr + offset % PAGE_SIZE;
+	dma_addr_t addr = sgbuf->table[offset >> PAGE_SHIFT].addr;
+	addr &= PAGE_MASK;
+	return addr + offset % PAGE_SIZE;
 }
 
 /*
diff --git a/include/sound/pcm.h b/include/sound/pcm.h
index 8db89630c82140b4b1da492180e1628ab8eadfd4..40c5a6fa6bcd3635334f0405ee0d53f37c3944c7 100644
--- a/include/sound/pcm.h
+++ b/include/sound/pcm.h
@@ -996,7 +996,8 @@ snd_pcm_sgbuf_get_ptr(struct snd_pcm_substream *substream, unsigned int ofs)
 
 struct page *snd_pcm_sgbuf_ops_page(struct snd_pcm_substream *substream,
 				    unsigned long offset);
-
+unsigned int snd_pcm_sgbuf_get_chunk_size(struct snd_pcm_substream *substream,
+					  unsigned int ofs, unsigned int size);
 
 /* handle mmap counter - PCM mmap callback should handle this counter properly */
 static inline void snd_pcm_mmap_data_open(struct vm_area_struct *area)
diff --git a/sound/core/pcm_memory.c b/sound/core/pcm_memory.c
index 859b1185e69a953f7e212953930c147a6f9d8a76..a6d42808828c7d87d233f61782e753c7e99ba646 100644
--- a/sound/core/pcm_memory.c
+++ b/sound/core/pcm_memory.c
@@ -324,6 +324,32 @@ struct page *snd_pcm_sgbuf_ops_page(struct snd_pcm_substream *substream, unsigne
 
 EXPORT_SYMBOL(snd_pcm_sgbuf_ops_page);
 
+/*
+ * compute the max chunk size with continuous pages on sg-buffer
+ */
+unsigned int snd_pcm_sgbuf_get_chunk_size(struct snd_pcm_substream *substream,
+					  unsigned int ofs, unsigned int size)
+{
+	struct snd_sg_buf *sg = snd_pcm_substream_sgbuf(substream);
+	unsigned int start, end, pg;
+
+	start = ofs >> PAGE_SHIFT;
+	end = (ofs + size - 1) >> PAGE_SHIFT;
+	/* check page continuity */
+	pg = sg->table[start].addr >> PAGE_SHIFT;
+	for (;;) {
+		start++;
+		if (start > end)
+			break;
+		pg++;
+		if ((sg->table[start].addr >> PAGE_SHIFT) != pg)
+			return (start << PAGE_SHIFT) - ofs;
+	}
+	/* ok, all on continuous pages */
+	return size;
+}
+EXPORT_SYMBOL(snd_pcm_sgbuf_get_chunk_size);
+
 /**
  * snd_pcm_lib_malloc_pages - allocate the DMA buffer
  * @substream: the substream to allocate the DMA buffer to
diff --git a/sound/core/sgbuf.c b/sound/core/sgbuf.c
index cefd228cd2aafb964b61150a7a2d9931459a7fe8..d4564edd61d744b19fa98e6cc6a06fcd26d20b68 100644
--- a/sound/core/sgbuf.c
+++ b/sound/core/sgbuf.c
@@ -41,9 +41,11 @@ int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab)
 	tmpb.dev.type = SNDRV_DMA_TYPE_DEV;
 	tmpb.dev.dev = sgbuf->dev;
 	for (i = 0; i < sgbuf->pages; i++) {
+		if (!(sgbuf->table[i].addr & ~PAGE_MASK))
+			continue; /* continuous pages */
 		tmpb.area = sgbuf->table[i].buf;
-		tmpb.addr = sgbuf->table[i].addr;
-		tmpb.bytes = PAGE_SIZE;
+		tmpb.addr = sgbuf->table[i].addr & PAGE_MASK;
+		tmpb.bytes = (sgbuf->table[i].addr & ~PAGE_MASK) << PAGE_SHIFT;
 		snd_dma_free_pages(&tmpb);
 	}
 	if (dmab->area)
@@ -58,13 +60,17 @@ int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab)
 	return 0;
 }
 
+#define MAX_ALLOC_PAGES		32
+
 void *snd_malloc_sgbuf_pages(struct device *device,
 			     size_t size, struct snd_dma_buffer *dmab,
 			     size_t *res_size)
 {
 	struct snd_sg_buf *sgbuf;
-	unsigned int i, pages;
+	unsigned int i, pages, chunk, maxpages;
 	struct snd_dma_buffer tmpb;
+	struct snd_sg_page *table;
+	struct page **pgtable;
 
 	dmab->area = NULL;
 	dmab->addr = 0;
@@ -74,31 +80,55 @@ void *snd_malloc_sgbuf_pages(struct device *device,
 	sgbuf->dev = device;
 	pages = snd_sgbuf_aligned_pages(size);
 	sgbuf->tblsize = sgbuf_align_table(pages);
-	sgbuf->table = kcalloc(sgbuf->tblsize, sizeof(*sgbuf->table), GFP_KERNEL);
-	if (! sgbuf->table)
+	table = kcalloc(sgbuf->tblsize, sizeof(*table), GFP_KERNEL);
+	if (!table)
 		goto _failed;
-	sgbuf->page_table = kcalloc(sgbuf->tblsize, sizeof(*sgbuf->page_table), GFP_KERNEL);
-	if (! sgbuf->page_table)
+	sgbuf->table = table;
+	pgtable = kcalloc(sgbuf->tblsize, sizeof(*pgtable), GFP_KERNEL);
+	if (!pgtable)
 		goto _failed;
+	sgbuf->page_table = pgtable;
 
-	/* allocate each page */
-	for (i = 0; i < pages; i++) {
-		if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, device, PAGE_SIZE, &tmpb) < 0) {
-			if (res_size == NULL)
+	/* allocate pages */
+	maxpages = MAX_ALLOC_PAGES;
+	while (pages > 0) {
+		chunk = pages;
+		/* don't be too eager to take a huge chunk */
+		if (chunk > maxpages)
+			chunk = maxpages;
+		chunk <<= PAGE_SHIFT;
+		if (snd_dma_alloc_pages_fallback(SNDRV_DMA_TYPE_DEV, device,
+						 chunk, &tmpb) < 0) {
+			if (!sgbuf->pages)
+				return NULL;
+			if (!res_size)
 				goto _failed;
-			*res_size = size = sgbuf->pages * PAGE_SIZE;
+			size = sgbuf->pages * PAGE_SIZE;
 			break;
 		}
-		sgbuf->table[i].buf = tmpb.area;
-		sgbuf->table[i].addr = tmpb.addr;
-		sgbuf->page_table[i] = virt_to_page(tmpb.area);
-		sgbuf->pages++;
+		chunk = tmpb.bytes >> PAGE_SHIFT;
+		for (i = 0; i < chunk; i++) {
+			table->buf = tmpb.area;
+			table->addr = tmpb.addr;
+			if (!i)
+				table->addr |= chunk; /* mark head */
+			table++;
+			*pgtable++ = virt_to_page(tmpb.area);
+			tmpb.area += PAGE_SIZE;
+			tmpb.addr += PAGE_SIZE;
+		}
+		sgbuf->pages += chunk;
+		pages -= chunk;
+		if (chunk < maxpages)
+			maxpages = chunk;
 	}
 
 	sgbuf->size = size;
 	dmab->area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, PAGE_KERNEL);
 	if (! dmab->area)
 		goto _failed;
+	if (res_size)
+		*res_size = sgbuf->size;
 	return dmab->area;
 
  _failed: