diff mbox

[V1,08/11] ASoc: DaVinci: i2s, Improve underrun, support mono

Message ID 1246761001-21982-9-git-send-email-troy.kisky@boundarydevices.com (mailing list archive)
State Not Applicable
Headers show

Commit Message

Troy Kisky July 5, 2009, 2:29 a.m. UTC
This patch will reduce the number of underruns by
shifting out 32 bit values instead of 16 bit. It also
adds mono support.

Signed-off-by: Troy Kisky <troy.kisky@boundarydevices.com>
---
 sound/soc/davinci/davinci-i2s.c |  124 +++++++++++++++++++++++++++------------
 sound/soc/davinci/davinci-pcm.c |   31 +++++++---
 sound/soc/davinci/davinci-pcm.h |    3 +-
 3 files changed, 110 insertions(+), 48 deletions(-)

Comments

Steve Chen July 6, 2009, 11:09 a.m. UTC | #1
On Sat, 2009-07-04 at 19:29 -0700, Troy Kisky wrote:
> This patch will reduce the number of underruns by
> shifting out 32 bit values instead of 16 bit. It also
> adds mono support.

Doesn't ALSA already automatically handle mono-stero conversions?  I
don't think we need to provide the same functionality in the driver.

> 
> Signed-off-by: Troy Kisky <troy.kisky@boundarydevices.com>
> ---
>  sound/soc/davinci/davinci-i2s.c |  124 +++++++++++++++++++++++++++------------
>  sound/soc/davinci/davinci-pcm.c |   31 +++++++---
>  sound/soc/davinci/davinci-pcm.h |    3 +-
>  3 files changed, 110 insertions(+), 48 deletions(-)
> 
> diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c
> index 6fa1b6a..a2ad53e 100644
> --- a/sound/soc/davinci/davinci-i2s.c
> +++ b/sound/soc/davinci/davinci-i2s.c
> @@ -356,26 +356,29 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream,
>  	struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data;
>  	struct snd_interval *i = NULL;
>  	int mcbsp_word_length;
> +	int bits_per_sample;
> +	int bits_per_frame;
>  	unsigned int rcr, xcr, srgr;
> +	int channels;
> +	int format;
> +	int element_cnt = 1;
>  	u32 spcr;
>  
> -	/* general line settings */
> -	spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
> -	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
> -		spcr |= DAVINCI_MCBSP_SPCR_RINTM(3) | DAVINCI_MCBSP_SPCR_FREE;
> -		davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
> -	} else {
> -		spcr |= DAVINCI_MCBSP_SPCR_XINTM(3) | DAVINCI_MCBSP_SPCR_FREE;
> -		davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
> -	}
> -
>  	i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
> -	srgr = DAVINCI_MCBSP_SRGR_FSGM;
> -	srgr |= DAVINCI_MCBSP_SRGR_FWID(snd_interval_value(i) - 1);
> +	bits_per_sample = snd_interval_value(i);
> +	/* always 2 samples/frame, mono will convert to stereo */
> +	bits_per_frame = bits_per_sample << 1;
> +	srgr = DAVINCI_MCBSP_SRGR_FSGM |
> +		DAVINCI_MCBSP_SRGR_FPER(bits_per_frame - 1) |
> +		DAVINCI_MCBSP_SRGR_FWID(bits_per_sample - 1);
>  
> -	i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS);
> -	srgr |= DAVINCI_MCBSP_SRGR_FPER(snd_interval_value(i) - 1);
> -	davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, srgr);
> +	/* general line settings */
> +	spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
> +	spcr |= DAVINCI_MCBSP_SPCR_FREE;
> +	spcr |= (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
> +			DAVINCI_MCBSP_SPCR_XINTM(3) :
> +			DAVINCI_MCBSP_SPCR_RINTM(3);
> +	davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
>  
>  	rcr = DAVINCI_MCBSP_RCR_RFIG;
>  	xcr = DAVINCI_MCBSP_XCR_XFIG;
> @@ -386,33 +389,80 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream,
>  		rcr |= DAVINCI_MCBSP_RCR_RDATDLY(1);
>  		xcr |= DAVINCI_MCBSP_XCR_XDATDLY(1);
>  	}
> +	channels = params_channels(params);
> +	format = params_format(params);
>  	/* Determine xfer data type */
> -	switch (params_format(params)) {
> -	case SNDRV_PCM_FORMAT_S8:
> -		dma_params->data_type = 1;
> -		mcbsp_word_length = DAVINCI_MCBSP_WORD_8;
> -		break;
> -	case SNDRV_PCM_FORMAT_S16_LE:
> -		dma_params->data_type = 2;
> -		mcbsp_word_length = DAVINCI_MCBSP_WORD_16;
> -		break;
> -	case SNDRV_PCM_FORMAT_S32_LE:
> -		dma_params->data_type = 4;
> -		mcbsp_word_length = DAVINCI_MCBSP_WORD_32;
> -		break;
> -	default:
> -		printk(KERN_WARNING "davinci-i2s: unsupported PCM format\n");
> -		return -EINVAL;
> +	if (channels == 2) {
> +		/* Combining both channels into 1 element can allow x10 the
> +		 * amount of time between servicing the dma channel, increase
> +		 * effiency, and reduce the chance of overrun/underrun. But,
> +		 * it will result in the left & right channels being swapped.
> +		 * So, you may want to let the codec know to swap them back.
> +		 *
> +		 * It may be x10 instead of x2 because the clock from the codec
> +		 * may run at mclk speed (ie. tlvaic23b), independent of the
> +		 * sample rate. So, having an entire frame at once means it can
> +		 * be serviced at the sample rate instead of the mclk speed.
> +		 *
> +		 * In the now very unlikely case that an underrun still
> +		 * occurs, both the left and right samples will be repeated
> +		 * so that no pops are heard, and the left and right channels
> +		 * won't end up being swapped because of the underrun.
> +		 */
> +		dma_params->convert_mono_stereo = 0;
> +		switch (format) {
> +		case SNDRV_PCM_FORMAT_S8:
> +			dma_params->data_type = 2;	/* 2 byte frame */
> +			mcbsp_word_length = DAVINCI_MCBSP_WORD_16;
> +			break;
> +		case SNDRV_PCM_FORMAT_S16_LE:
> +			dma_params->data_type = 4;	/* 4 byte frame */
> +			mcbsp_word_length = DAVINCI_MCBSP_WORD_32;
> +			break;
> +		case SNDRV_PCM_FORMAT_S32_LE:
> +			element_cnt = 2;
> +			dma_params->data_type = 4;	/* 4 byte element */
> +			mcbsp_word_length = DAVINCI_MCBSP_WORD_32;
> +			break;
> +		default:
> +			printk(KERN_WARNING
> +					"davinci-i2s: unsupported PCM format");
> +			return -EINVAL;
> +		}
> +	} else {
> +		dma_params->convert_mono_stereo = 1;
> +		/* 1 element in ram becomes 2 for stereo */
> +		element_cnt = 2;
> +		switch (format) {
> +		case SNDRV_PCM_FORMAT_S8:
> +			/* 1 byte frame in ram */
> +			dma_params->data_type = 1;
> +			mcbsp_word_length = DAVINCI_MCBSP_WORD_8;
> +			break;
> +		case SNDRV_PCM_FORMAT_S16_LE:
> +			/* 2 byte frame in ram */
> +			dma_params->data_type = 2;
> +			mcbsp_word_length = DAVINCI_MCBSP_WORD_16;
> +			break;
> +		case SNDRV_PCM_FORMAT_S32_LE:
> +			/* 4 byte element */
> +			dma_params->data_type = 4;
> +			mcbsp_word_length = DAVINCI_MCBSP_WORD_32;
> +			break;
> +		default:
> +			printk(KERN_WARNING
> +					"davinci-i2s: unsupported PCM format");
> +			return -EINVAL;
> +		}
>  	}
> -
> -	rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(1);
> -	xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(1);
> +	rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(element_cnt - 1);
> +	xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(element_cnt - 1);
>  
>  	rcr |= DAVINCI_MCBSP_RCR_RWDLEN1(mcbsp_word_length) |
>  		DAVINCI_MCBSP_RCR_RWDLEN2(mcbsp_word_length);
>  	xcr |= DAVINCI_MCBSP_XCR_XWDLEN1(mcbsp_word_length) |
>  		DAVINCI_MCBSP_XCR_XWDLEN2(mcbsp_word_length);
> -
> +	davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, srgr);
>  	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
>  		davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_XCR_REG, xcr);
>  	else
> @@ -542,12 +592,12 @@ struct snd_soc_dai davinci_i2s_dai = {
>  	.probe = davinci_i2s_probe,
>  	.remove = davinci_i2s_remove,
>  	.playback = {
> -		.channels_min = 2,
> +		.channels_min = 1,
>  		.channels_max = 2,
>  		.rates = DAVINCI_I2S_RATES,
>  		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
>  	.capture = {
> -		.channels_min = 2,
> +		.channels_min = 1,
>  		.channels_max = 2,
>  		.rates = DAVINCI_I2S_RATES,
>  		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
> diff --git a/sound/soc/davinci/davinci-pcm.c b/sound/soc/davinci/davinci-pcm.c
> index a059965..2002fdc 100644
> --- a/sound/soc/davinci/davinci-pcm.c
> +++ b/sound/soc/davinci/davinci-pcm.c
> @@ -65,9 +65,9 @@ static void davinci_pcm_enqueue_dma(struct snd_pcm_substream *substream)
>  	unsigned int dma_offset;
>  	dma_addr_t dma_pos;
>  	dma_addr_t src, dst;
> -	unsigned short src_bidx, dst_bidx;
> -	unsigned int data_type;
>  	unsigned int count;
> +	unsigned int data_type = prtd->params->data_type;
> +	unsigned int convert_mono_stereo = prtd->params->convert_mono_stereo;
>  
>  	period_size = snd_pcm_lib_period_bytes(substream);
>  	dma_offset = prtd->period * period_size;
> @@ -76,26 +76,37 @@ static void davinci_pcm_enqueue_dma(struct snd_pcm_substream *substream)
>  	pr_debug("davinci_pcm: audio_set_dma_params_play channel = %d "
>  		"dma_ptr = %x period_size=%x\n", lch, dma_pos, period_size);
>  
> -	data_type = prtd->params->data_type;
>  	count = period_size / data_type;
>  
>  	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
>  		src = dma_pos;
>  		dst = prtd->params->dma_addr;
> -		src_bidx = data_type;
> -		dst_bidx = 0;
> +		if (convert_mono_stereo)
> +			edma_set_src_index(lch, 0, data_type);
> +		else
> +			edma_set_src_index(lch, data_type, 0);
> +		edma_set_dest_index(lch, 0, 0);
>  	} else {
>  		src = prtd->params->dma_addr;
>  		dst = dma_pos;
> -		src_bidx = 0;
> -		dst_bidx = data_type;
> +		edma_set_src_index(lch, 0, 0);
> +		if (convert_mono_stereo)
> +			edma_set_dest_index(lch, 0, data_type);
> +		else
> +			edma_set_dest_index(lch, data_type, 0);
>  	}
>  
>  	edma_set_src(lch, src, INCR, W8BIT);
>  	edma_set_dest(lch, dst, INCR, W8BIT);
> -	edma_set_src_index(lch, src_bidx, 0);
> -	edma_set_dest_index(lch, dst_bidx, 0);
> -	edma_set_transfer_params(lch, data_type, count, 1, 0, ASYNC);
> +	if (convert_mono_stereo) {
> +		/*
> +		 * Each byte is sent twice, so
> +		 * A_CNT * B_CNT * C_CNT = 2 * period_size
> +		 */
> +		edma_set_transfer_params(lch, data_type, 2, count, 2, ASYNC);
> +	} else {
> +		edma_set_transfer_params(lch, data_type, count, 1, 0, ASYNC);
> +	}
>  
>  	prtd->period++;
>  	if (unlikely(prtd->period >= runtime->periods))
> diff --git a/sound/soc/davinci/davinci-pcm.h b/sound/soc/davinci/davinci-pcm.h
> index 62cb4eb..fc70161 100644
> --- a/sound/soc/davinci/davinci-pcm.h
> +++ b/sound/soc/davinci/davinci-pcm.h
> @@ -16,7 +16,8 @@ struct davinci_pcm_dma_params {
>  	char *name;		/* stream identifier */
>  	int channel;		/* sync dma channel ID */
>  	dma_addr_t dma_addr;	/* device physical address for DMA */
> -	unsigned int data_type;	/* xfer data type */
> +	unsigned char data_type;	/* xfer data type */
> +	unsigned char convert_mono_stereo;
>  };
>  
>  struct evm_snd_platform_data {
Steve Chen July 6, 2009, 12:45 p.m. UTC | #2
On Mon, 2009-07-06 at 12:54 +0100, Mark Brown wrote:
> On Mon, Jul 06, 2009 at 06:09:16AM -0500, Steve Chen wrote:
> > On Sat, 2009-07-04 at 19:29 -0700, Troy Kisky wrote:
> 
> > > This patch will reduce the number of underruns by
> > > shifting out 32 bit values instead of 16 bit. It also
> > > adds mono support.
> 
> > Doesn't ALSA already automatically handle mono-stero conversions?  I
> > don't think we need to provide the same functionality in the driver.
> 
> If the hardware can natively play mono then that's less work for the CPU
> to do which will improve efficiency.

I see.  In this case, DaVinci hardware does not support mono, but the
patch does some DMA index magic to convert between mono and stereo.  It
works just as well then...
diff mbox

Patch

diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c
index 6fa1b6a..a2ad53e 100644
--- a/sound/soc/davinci/davinci-i2s.c
+++ b/sound/soc/davinci/davinci-i2s.c
@@ -356,26 +356,29 @@  static int davinci_i2s_hw_params(struct snd_pcm_substream *substream,
 	struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data;
 	struct snd_interval *i = NULL;
 	int mcbsp_word_length;
+	int bits_per_sample;
+	int bits_per_frame;
 	unsigned int rcr, xcr, srgr;
+	int channels;
+	int format;
+	int element_cnt = 1;
 	u32 spcr;
 
-	/* general line settings */
-	spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
-	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
-		spcr |= DAVINCI_MCBSP_SPCR_RINTM(3) | DAVINCI_MCBSP_SPCR_FREE;
-		davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
-	} else {
-		spcr |= DAVINCI_MCBSP_SPCR_XINTM(3) | DAVINCI_MCBSP_SPCR_FREE;
-		davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
-	}
-
 	i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
-	srgr = DAVINCI_MCBSP_SRGR_FSGM;
-	srgr |= DAVINCI_MCBSP_SRGR_FWID(snd_interval_value(i) - 1);
+	bits_per_sample = snd_interval_value(i);
+	/* always 2 samples/frame, mono will convert to stereo */
+	bits_per_frame = bits_per_sample << 1;
+	srgr = DAVINCI_MCBSP_SRGR_FSGM |
+		DAVINCI_MCBSP_SRGR_FPER(bits_per_frame - 1) |
+		DAVINCI_MCBSP_SRGR_FWID(bits_per_sample - 1);
 
-	i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS);
-	srgr |= DAVINCI_MCBSP_SRGR_FPER(snd_interval_value(i) - 1);
-	davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, srgr);
+	/* general line settings */
+	spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
+	spcr |= DAVINCI_MCBSP_SPCR_FREE;
+	spcr |= (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+			DAVINCI_MCBSP_SPCR_XINTM(3) :
+			DAVINCI_MCBSP_SPCR_RINTM(3);
+	davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
 
 	rcr = DAVINCI_MCBSP_RCR_RFIG;
 	xcr = DAVINCI_MCBSP_XCR_XFIG;
@@ -386,33 +389,80 @@  static int davinci_i2s_hw_params(struct snd_pcm_substream *substream,
 		rcr |= DAVINCI_MCBSP_RCR_RDATDLY(1);
 		xcr |= DAVINCI_MCBSP_XCR_XDATDLY(1);
 	}
+	channels = params_channels(params);
+	format = params_format(params);
 	/* Determine xfer data type */
-	switch (params_format(params)) {
-	case SNDRV_PCM_FORMAT_S8:
-		dma_params->data_type = 1;
-		mcbsp_word_length = DAVINCI_MCBSP_WORD_8;
-		break;
-	case SNDRV_PCM_FORMAT_S16_LE:
-		dma_params->data_type = 2;
-		mcbsp_word_length = DAVINCI_MCBSP_WORD_16;
-		break;
-	case SNDRV_PCM_FORMAT_S32_LE:
-		dma_params->data_type = 4;
-		mcbsp_word_length = DAVINCI_MCBSP_WORD_32;
-		break;
-	default:
-		printk(KERN_WARNING "davinci-i2s: unsupported PCM format\n");
-		return -EINVAL;
+	if (channels == 2) {
+		/* Combining both channels into 1 element can allow x10 the
+		 * amount of time between servicing the dma channel, increase
+		 * effiency, and reduce the chance of overrun/underrun. But,
+		 * it will result in the left & right channels being swapped.
+		 * So, you may want to let the codec know to swap them back.
+		 *
+		 * It may be x10 instead of x2 because the clock from the codec
+		 * may run at mclk speed (ie. tlvaic23b), independent of the
+		 * sample rate. So, having an entire frame at once means it can
+		 * be serviced at the sample rate instead of the mclk speed.
+		 *
+		 * In the now very unlikely case that an underrun still
+		 * occurs, both the left and right samples will be repeated
+		 * so that no pops are heard, and the left and right channels
+		 * won't end up being swapped because of the underrun.
+		 */
+		dma_params->convert_mono_stereo = 0;
+		switch (format) {
+		case SNDRV_PCM_FORMAT_S8:
+			dma_params->data_type = 2;	/* 2 byte frame */
+			mcbsp_word_length = DAVINCI_MCBSP_WORD_16;
+			break;
+		case SNDRV_PCM_FORMAT_S16_LE:
+			dma_params->data_type = 4;	/* 4 byte frame */
+			mcbsp_word_length = DAVINCI_MCBSP_WORD_32;
+			break;
+		case SNDRV_PCM_FORMAT_S32_LE:
+			element_cnt = 2;
+			dma_params->data_type = 4;	/* 4 byte element */
+			mcbsp_word_length = DAVINCI_MCBSP_WORD_32;
+			break;
+		default:
+			printk(KERN_WARNING
+					"davinci-i2s: unsupported PCM format");
+			return -EINVAL;
+		}
+	} else {
+		dma_params->convert_mono_stereo = 1;
+		/* 1 element in ram becomes 2 for stereo */
+		element_cnt = 2;
+		switch (format) {
+		case SNDRV_PCM_FORMAT_S8:
+			/* 1 byte frame in ram */
+			dma_params->data_type = 1;
+			mcbsp_word_length = DAVINCI_MCBSP_WORD_8;
+			break;
+		case SNDRV_PCM_FORMAT_S16_LE:
+			/* 2 byte frame in ram */
+			dma_params->data_type = 2;
+			mcbsp_word_length = DAVINCI_MCBSP_WORD_16;
+			break;
+		case SNDRV_PCM_FORMAT_S32_LE:
+			/* 4 byte element */
+			dma_params->data_type = 4;
+			mcbsp_word_length = DAVINCI_MCBSP_WORD_32;
+			break;
+		default:
+			printk(KERN_WARNING
+					"davinci-i2s: unsupported PCM format");
+			return -EINVAL;
+		}
 	}
-
-	rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(1);
-	xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(1);
+	rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(element_cnt - 1);
+	xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(element_cnt - 1);
 
 	rcr |= DAVINCI_MCBSP_RCR_RWDLEN1(mcbsp_word_length) |
 		DAVINCI_MCBSP_RCR_RWDLEN2(mcbsp_word_length);
 	xcr |= DAVINCI_MCBSP_XCR_XWDLEN1(mcbsp_word_length) |
 		DAVINCI_MCBSP_XCR_XWDLEN2(mcbsp_word_length);
-
+	davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, srgr);
 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 		davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_XCR_REG, xcr);
 	else
@@ -542,12 +592,12 @@  struct snd_soc_dai davinci_i2s_dai = {
 	.probe = davinci_i2s_probe,
 	.remove = davinci_i2s_remove,
 	.playback = {
-		.channels_min = 2,
+		.channels_min = 1,
 		.channels_max = 2,
 		.rates = DAVINCI_I2S_RATES,
 		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
 	.capture = {
-		.channels_min = 2,
+		.channels_min = 1,
 		.channels_max = 2,
 		.rates = DAVINCI_I2S_RATES,
 		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
diff --git a/sound/soc/davinci/davinci-pcm.c b/sound/soc/davinci/davinci-pcm.c
index a059965..2002fdc 100644
--- a/sound/soc/davinci/davinci-pcm.c
+++ b/sound/soc/davinci/davinci-pcm.c
@@ -65,9 +65,9 @@  static void davinci_pcm_enqueue_dma(struct snd_pcm_substream *substream)
 	unsigned int dma_offset;
 	dma_addr_t dma_pos;
 	dma_addr_t src, dst;
-	unsigned short src_bidx, dst_bidx;
-	unsigned int data_type;
 	unsigned int count;
+	unsigned int data_type = prtd->params->data_type;
+	unsigned int convert_mono_stereo = prtd->params->convert_mono_stereo;
 
 	period_size = snd_pcm_lib_period_bytes(substream);
 	dma_offset = prtd->period * period_size;
@@ -76,26 +76,37 @@  static void davinci_pcm_enqueue_dma(struct snd_pcm_substream *substream)
 	pr_debug("davinci_pcm: audio_set_dma_params_play channel = %d "
 		"dma_ptr = %x period_size=%x\n", lch, dma_pos, period_size);
 
-	data_type = prtd->params->data_type;
 	count = period_size / data_type;
 
 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
 		src = dma_pos;
 		dst = prtd->params->dma_addr;
-		src_bidx = data_type;
-		dst_bidx = 0;
+		if (convert_mono_stereo)
+			edma_set_src_index(lch, 0, data_type);
+		else
+			edma_set_src_index(lch, data_type, 0);
+		edma_set_dest_index(lch, 0, 0);
 	} else {
 		src = prtd->params->dma_addr;
 		dst = dma_pos;
-		src_bidx = 0;
-		dst_bidx = data_type;
+		edma_set_src_index(lch, 0, 0);
+		if (convert_mono_stereo)
+			edma_set_dest_index(lch, 0, data_type);
+		else
+			edma_set_dest_index(lch, data_type, 0);
 	}
 
 	edma_set_src(lch, src, INCR, W8BIT);
 	edma_set_dest(lch, dst, INCR, W8BIT);
-	edma_set_src_index(lch, src_bidx, 0);
-	edma_set_dest_index(lch, dst_bidx, 0);
-	edma_set_transfer_params(lch, data_type, count, 1, 0, ASYNC);
+	if (convert_mono_stereo) {
+		/*
+		 * Each byte is sent twice, so
+		 * A_CNT * B_CNT * C_CNT = 2 * period_size
+		 */
+		edma_set_transfer_params(lch, data_type, 2, count, 2, ASYNC);
+	} else {
+		edma_set_transfer_params(lch, data_type, count, 1, 0, ASYNC);
+	}
 
 	prtd->period++;
 	if (unlikely(prtd->period >= runtime->periods))
diff --git a/sound/soc/davinci/davinci-pcm.h b/sound/soc/davinci/davinci-pcm.h
index 62cb4eb..fc70161 100644
--- a/sound/soc/davinci/davinci-pcm.h
+++ b/sound/soc/davinci/davinci-pcm.h
@@ -16,7 +16,8 @@  struct davinci_pcm_dma_params {
 	char *name;		/* stream identifier */
 	int channel;		/* sync dma channel ID */
 	dma_addr_t dma_addr;	/* device physical address for DMA */
-	unsigned int data_type;	/* xfer data type */
+	unsigned char data_type;	/* xfer data type */
+	unsigned char convert_mono_stereo;
 };
 
 struct evm_snd_platform_data {