summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sound/soc/codecs/mxc_hdmi.c3
-rw-r--r--sound/soc/imx/Makefile6
-rw-r--r--sound/soc/imx/imx-hdmi-dma.c313
3 files changed, 276 insertions, 46 deletions
diff --git a/sound/soc/codecs/mxc_hdmi.c b/sound/soc/codecs/mxc_hdmi.c
index 4d9a5719eeae..62decb89d876 100644
--- a/sound/soc/codecs/mxc_hdmi.c
+++ b/sound/soc/codecs/mxc_hdmi.c
@@ -150,9 +150,6 @@ static void mxc_hdmi_get_playback_sample_size(void)
/* always assume basic audio support */
playback_sample_size[i++] = 16;
- if (edid_cfg.sample_sizes & 0x2)
- playback_sample_size[i++] = 20;
-
if (edid_cfg.sample_sizes & 0x4)
playback_sample_size[i++] = 24;
diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile
index 3df711671b15..2a67c3250355 100644
--- a/sound/soc/imx/Makefile
+++ b/sound/soc/imx/Makefile
@@ -19,7 +19,7 @@ snd-soc-imx-wm8962-objs := imx-wm8962.o
snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o
snd-soc-imx-cs42888-objs := imx-cs42888.o
snd-soc-imx-spdif-objs := imx-spdif.o
-snd-soc-imx-hdmi-objs := imx-hdmi.o imx-hdmi-dai.o imx-hdmi-dma.o
+snd-soc-imx-hdmi-objs := imx-hdmi.o imx-hdmi-dai.o imx-hdmi-dma.o hdmi_pcm.o
obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o
obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o
@@ -30,4 +30,6 @@ obj-$(CONFIG_SND_SOC_IMX_WM8962) += snd-soc-imx-wm8962.o
obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o
obj-$(CONFIG_SND_SOC_IMX_CS42888) += snd-soc-imx-cs42888.o
obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o
-obj-$(CONFIG_SND_SOC_IMX_HDMI) += snd-soc-imx-hdmi.o \ No newline at end of file
+obj-$(CONFIG_SND_SOC_IMX_HDMI) += snd-soc-imx-hdmi.o
+
+AFLAGS_hdmi_pcm.o := -march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=softfp
diff --git a/sound/soc/imx/imx-hdmi-dma.c b/sound/soc/imx/imx-hdmi-dma.c
index 1bd36ca1ea57..691ca48b9f83 100644
--- a/sound/soc/imx/imx-hdmi-dma.c
+++ b/sound/soc/imx/imx-hdmi-dma.c
@@ -35,6 +35,7 @@
#include <mach/mxc_hdmi.h>
#include "imx-hdmi.h"
+
#define HDMI_DMA_BURST_UNSPECIFIED_LEGNTH 0
#define HDMI_DMA_BURST_INCR4 1
#define HDMI_DMA_BURST_INCR8 2
@@ -44,7 +45,8 @@ struct imx_hdmi_dma_runtime_data {
struct snd_pcm_substream *tx_substream;
unsigned long buffer_bytes;
- void *buf;
+ struct snd_dma_buffer hw_buffer;
+ unsigned long appl_bytes;
int period_time;
int periods;
@@ -70,6 +72,19 @@ struct imx_hdmi_dma_runtime_data {
spinlock_t irq_lock;
};
+/* bit 0:0:0:b:p(0):c:(u)0:(v)0*/
+/* max 8 channels supported; channels are interleaved*/
+static unsigned char g_packet_head_table[48*8];
+
+void hdmi_dma_copy_16_neon_lut(unsigned short *src, unsigned int *dst,
+ int samples, unsigned char *lookup_table);
+void hdmi_dma_copy_16_neon_fast(unsigned short *src, unsigned int *dst,
+ int samples);
+void hdmi_dma_copy_24_neon_lut(unsigned int *src, unsigned int *dst,
+ int samples, unsigned char *lookup_table);
+void hdmi_dma_copy_24_neon_fast(unsigned int *src, unsigned int *dst,
+ int samples);
+
hdmi_audio_header_t iec_header;
/*
@@ -113,6 +128,7 @@ hdmi_audio_header_t iec_header;
*/
#define HDMI_DMA_PERIOD_BYTES (6144)
#define HDMI_DMA_BUF_SIZE (64 * 1024)
+#define HDMI_PCM_BUF_SIZE (64 * 1024)
struct imx_hdmi_dma_runtime_data *hdmi_dma_priv;
@@ -152,7 +168,7 @@ static void dumprtd(struct imx_hdmi_dma_runtime_data *rtd)
pr_debug("period_bytes = %d\n", rtd->period_bytes);
pr_debug("dma period_bytes = %d\n", rtd->dma_period_bytes);
pr_debug("buffer_ratio = %d\n", rtd->buffer_ratio);
- pr_debug("dma buf addr = 0x%08x\n", (int)rtd->buf);
+ pr_debug("hw dma buffer = 0x%08x\n", (int)rtd->hw_buffer.addr);
pr_debug("dma buf size = %d\n", (int)rtd->buffer_bytes);
pr_debug("sample_rate = %d\n", (int)rtd->rate);
}
@@ -216,12 +232,33 @@ static void hdmi_dma_clear_irq_status(u8 status)
static void hdmi_dma_irq_mask(int mask)
{
- if (mask)
- hdmi_writeb(0xff, HDMI_AHB_DMA_MASK);
- else
- hdmi_writeb((u8)~HDMI_AHB_DMA_DONE, HDMI_AHB_DMA_MASK);
+ u8 regvalue;
+ regvalue = hdmi_readb(HDMI_AHB_DMA_MASK);
+
+ if (mask) {
+ regvalue |= HDMI_AHB_DMA_DONE;
+ hdmi_writeb(regvalue, HDMI_AHB_DMA_MASK);
+ } else {
+ regvalue &= (u8)~HDMI_AHB_DMA_DONE;
+ hdmi_writeb(regvalue, HDMI_AHB_DMA_MASK);
+ }
+}
+
+static void hdmi_mask(int mask)
+{
+ u8 regvalue;
+ regvalue = hdmi_readb(HDMI_AHB_DMA_MASK);
+
+ if (mask) {
+ regvalue |= HDMI_AHB_DMA_ERROR | HDMI_AHB_DMA_FIFO_EMPTY;
+ hdmi_writeb(regvalue, HDMI_AHB_DMA_MASK);
+ } else {
+ regvalue &= (u8)~(HDMI_AHB_DMA_ERROR | HDMI_AHB_DMA_FIFO_EMPTY);
+ hdmi_writeb(regvalue, HDMI_AHB_DMA_MASK);
+ }
}
+
static void hdmi_dma_irq_mute(int mute)
{
if (mute)
@@ -230,12 +267,21 @@ static void hdmi_dma_irq_mute(int mute)
hdmi_writeb(0x00, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
}
+int odd_ones(unsigned a)
+{
+ a ^= a >> 8;
+ a ^= a >> 4;
+ a ^= a >> 2;
+ a ^= a >> 1;
+
+ return a & 1;
+}
+
/* Add frame information for one pcm subframe */
static u32 hdmi_dma_add_frame_info(struct imx_hdmi_dma_runtime_data *rtd,
u32 pcm_data, int subframe_idx)
{
hdmi_audio_dma_data_t subframe;
- int i;
subframe.U = 0;
iec_header.B.channel = subframe_idx;
@@ -249,9 +295,7 @@ static u32 hdmi_dma_add_frame_info(struct imx_hdmi_dma_runtime_data *rtd,
else
subframe.B.c = 0;
- /* fill p (parity) */
- for (i = 0 ; i < rtd->sample_bits ; i++)
- subframe.B.p ^= (pcm_data >> i) & 0x01;
+ subframe.B.p = odd_ones(pcm_data);
subframe.B.p ^= subframe.B.c;
subframe.B.p ^= subframe.B.u;
subframe.B.p ^= subframe.B.v;
@@ -274,12 +318,145 @@ static void hdmi_dma_incr_frame_idx(struct imx_hdmi_dma_runtime_data *rtd)
rtd->frame_idx = 0;
}
+static void init_table(int channels)
+{
+ int i;
+ int ch = 0;
+ unsigned char *p = g_packet_head_table;
+
+ for (i = 0; i < 48; i++) {
+ int b = 0;
+ if (i == 0)
+ b = 1;
+
+ for (ch = 0; ch < channels; ch++) {
+ int c = 0;
+ if (i < 42) {
+ iec_header.B.channel = ch+1;
+ c = (iec_header.U >> i) & 0x1;
+ }
+ /* preset bit p as c */
+ *p++ = (b << 4) | (c << 2) | (c << 3);
+ }
+ }
+}
+
+/* Convert pcm samples to iec samples suitable for HDMI transfer.
+* PCM sample is 16 bits length.
+* Frame index always starts from 0.
+* Channel count can be 1, 2, 4, 6, or 8
+* Sample count (frame_count * channel_count) is multipliable by 8.
+*/
+static void hdmi_dma_copy_16(u16 *src, u32 *dest, int framecount, int channelcount)
+{
+ /* split input frames into 192-frame each */
+ int count_in_192 = (framecount + 191) / 192;
+ int i;
+
+ for (i = 0; i < count_in_192; i++) {
+ int count;
+ int samples;
+
+ /* handles frame index [0, 48) */
+ count = (framecount < 48) ? framecount : 48;
+ samples = count * channelcount;
+ hdmi_dma_copy_16_neon_lut(src, dest, samples, g_packet_head_table);
+ framecount -= count;
+ if (framecount == 0)
+ break;
+
+ src += samples;
+ dest += samples;
+
+ /* handles frame index [48, 192) */
+ count = (framecount < 192 - 48) ? framecount : 192 - 48;
+ samples = count * channelcount;
+ hdmi_dma_copy_16_neon_fast(src, dest, samples);
+ framecount -= count;
+ src += samples;
+ dest += samples;
+ }
+}
+
+/* Convert pcm samples to iec samples suitable for HDMI transfer.
+* PCM sample is 24 bits length.
+* Frame index always starts from 0.
+* Channel count can be 1, 2, 4, 6, or 8
+* Sample count (frame_count * channel_count) is multipliable by 8.
+*/
+static void hdmi_dma_copy_24(u32 *src, u32 *dest, int framecount, int channelcount)
+{
+ /* split input frames into 192-frame each */
+ int count_in_192 = (framecount + 191) / 192;
+ int i;
+
+ for (i = 0; i < count_in_192; i++) {
+ int count;
+ int samples;
+
+ /* handles frame index [0, 48) */
+ count = (framecount < 48) ? framecount : 48;
+ samples = count * channelcount;
+ hdmi_dma_copy_24_neon_lut(src, dest, samples, g_packet_head_table);
+ framecount -= count;
+ if (framecount == 0)
+ break;
+
+ src += samples;
+ dest += samples;
+
+ /* handles frame index [48, 192) */
+ count = (framecount < 192 - 48) ? framecount : 192 - 48;
+ samples = count * channelcount;
+ hdmi_dma_copy_24_neon_fast(src, dest, samples);
+ framecount -= count;
+ src += samples;
+ dest += samples;
+ }
+}
+
+static void hdmi_dma_mmap_copy(struct snd_pcm_substream *substream,
+ int offset, int count)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_hdmi_dma_runtime_data *rtd = runtime->private_data;
+ u32 framecount;
+ u32 *src32, *dest;
+ u16 *src16;
+
+ framecount = count/(rtd->sample_align * rtd->channels);
+
+ /* hw_buffer is the destination for pcm data plus frame info. */
+ dest = (u32 *)(rtd->hw_buffer.area + (offset * rtd->buffer_ratio));
+
+ switch (rtd->format) {
+
+ case SNDRV_PCM_FORMAT_S16_LE:
+ /* dma_buffer is the mmapped buffer we are copying pcm from. */
+ src16 = (u16 *)(runtime->dma_area + offset);
+ hdmi_dma_copy_16(src16, dest, framecount, rtd->channels);
+ break;
+
+ case SNDRV_PCM_FORMAT_S24_LE:
+ src32 = (u32 *)(runtime->dma_area + offset);
+ hdmi_dma_copy_24(src32, dest, framecount, rtd->channels);
+ break;
+
+ default:
+ pr_err("%s HDMI Audio invalid sample format (%d)\n",
+ __func__, rtd->format);
+ return;
+ }
+}
+
static irqreturn_t hdmi_dma_isr(int irq, void *dev_id)
{
struct imx_hdmi_dma_runtime_data *rtd = dev_id;
struct snd_pcm_substream *substream = rtd->tx_substream;
- unsigned int status;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned long offset, count, space_to_end, appl_bytes;
unsigned long flags;
+ unsigned int status;
spin_lock_irqsave(&rtd->irq_lock, flags);
@@ -291,18 +468,35 @@ static irqreturn_t hdmi_dma_isr(int irq, void *dev_id)
rtd->offset += rtd->period_bytes;
rtd->offset %= rtd->period_bytes * rtd->periods;
+ /* For mmap access, need to copy data from dma_buffer
+ * to hw_buffer and add the frame info. */
+ if (runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) {
+ appl_bytes = frames_to_bytes(runtime,
+ runtime->control->appl_ptr);
+ offset = rtd->appl_bytes % rtd->buffer_bytes;
+ space_to_end = rtd->buffer_bytes - offset;
+ count = appl_bytes - rtd->appl_bytes;
+ rtd->appl_bytes = appl_bytes;
+
+ if (count <= space_to_end) {
+ hdmi_dma_mmap_copy(substream, offset, count);
+ } else {
+ hdmi_dma_mmap_copy(substream, offset, space_to_end);
+ hdmi_dma_mmap_copy(substream, 0, count - space_to_end);
+ }
+ }
snd_pcm_period_elapsed(substream);
- hdmi_dma_set_addr(substream->dma_buffer.addr +
- (rtd->offset * rtd->buffer_ratio),
+ hdmi_dma_set_addr(rtd->hw_buffer.addr +
+ (rtd->offset * rtd->buffer_ratio),
rtd->dma_period_bytes);
-
hdmi_dma_start();
}
hdmi_dma_irq_mute(0);
spin_unlock_irqrestore(&rtd->irq_lock, flags);
+
return IRQ_HANDLED;
}
@@ -364,27 +558,25 @@ static void hdmi_dma_enable_channels(int channels)
}
}
-static void hdmi_dma_set_thrsld(void)
+static void hdmi_dma_configure_dma(int channels)
{
- int rev = hdmi_readb(HDMI_REVISION_ID);
+ hdmi_dma_enable_hlock(1);
- switch (rev) {
- case 0x0a:
+ switch (channels) {
+ case 2:
+ hdmi_dma_set_incr_type(HDMI_DMA_BURST_INCR4);
hdmi_writeb(126, HDMI_AHB_DMA_THRSLD);
break;
- default:
- hdmi_writeb(64, HDMI_AHB_DMA_THRSLD);
+ case 4:
+ case 6:
+ case 8:
+ hdmi_dma_set_incr_type(HDMI_DMA_BURST_INCR4);
+ hdmi_writeb(124, HDMI_AHB_DMA_THRSLD);
break;
+ default:
+ pr_err("%s %dunsupport channel!\r\n", __func__, __LINE__);
}
- pr_debug("HDMI_AHB_DMA_THRSLD 0x%02x\n", hdmi_readb(HDMI_AHB_DMA_THRSLD));
-}
-
-static void hdmi_dma_configure_dma(int channels)
-{
- hdmi_dma_enable_hlock(1);
- hdmi_dma_set_incr_type(HDMI_DMA_BURST_UNSPECIFIED_LEGNTH);
- hdmi_dma_set_thrsld();
hdmi_dma_enable_channels(channels);
}
@@ -485,8 +677,9 @@ static int hdmi_dma_copy(struct snd_pcm_substream *substream, int channel,
int subframe_idx;
u32 pcm_data;
- /* Copy pcm data from userspace and add frame info. */
- hw_buf = (u32 *)(rtd->buf + (pos_bytes * rtd->buffer_ratio));
+ /* Copy pcm data from userspace and add frame info.
+ * Destination is hw_buffer. */
+ hw_buf = (u32 *)(rtd->hw_buffer.area + (pos_bytes * rtd->buffer_ratio));
while (count > 0) {
for (subframe_idx = 1 ; subframe_idx <= rtd->channels ; subframe_idx++) {
@@ -521,7 +714,6 @@ static int hdmi_dma_hw_params(struct snd_pcm_substream *substream,
rtd->offset = 0;
rtd->period_time = HZ / (params_rate(params) / params_period_size(params));
- rtd->buf = (unsigned int *)substream->dma_buffer.area;
switch (rtd->format) {
case SNDRV_PCM_FORMAT_S16_LE:
@@ -546,12 +738,15 @@ static int hdmi_dma_hw_params(struct snd_pcm_substream *substream,
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
hdmi_dma_configure_dma(rtd->channels);
- hdmi_dma_set_addr(substream->dma_buffer.addr, rtd->dma_period_bytes);
+ hdmi_dma_set_addr(rtd->hw_buffer.addr, rtd->dma_period_bytes);
dumprtd(rtd);
hdmi_dma_update_iec_header(substream);
+ /* Init par for mmap optimizate */
+ init_table(rtd->channels);
+
return 0;
}
@@ -565,10 +760,18 @@ static int hdmi_dma_trigger(struct snd_pcm_substream *substream, int cmd)
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
rtd->frame_idx = 0;
+ if (runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) {
+ rtd->appl_bytes = frames_to_bytes(runtime,
+ runtime->control->appl_ptr);
+
+ hdmi_dma_mmap_copy(substream, 0, rtd->appl_bytes);
+
+ }
dumpregs();
- hdmi_dma_irq_mask(0);
+
hdmi_dma_priv->tx_active = true;
hdmi_dma_start();
+ hdmi_dma_irq_mask(0);
hdmi_set_dma_mode(1);
break;
@@ -599,17 +802,19 @@ static snd_pcm_uframes_t hdmi_dma_pointer(struct snd_pcm_substream *substream)
static struct snd_pcm_hardware snd_imx_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME,
.formats = MXC_HDMI_FORMATS_PLAYBACK,
.rate_min = 32000,
.channels_min = 2,
.channels_max = 8,
- .buffer_bytes_max = HDMI_DMA_BUF_SIZE / 2,
+ .buffer_bytes_max = HDMI_PCM_BUF_SIZE,
.period_bytes_min = HDMI_DMA_PERIOD_BYTES / 2,
.period_bytes_max = HDMI_DMA_PERIOD_BYTES / 2,
- .periods_min = 4,
- .periods_max = 255,
+ .periods_min = 8,
+ .periods_max = 8,
.fifo_size = 0,
};
@@ -627,6 +832,8 @@ static void hdmi_dma_irq_enable(struct imx_hdmi_dma_runtime_data *rtd)
hdmi_dma_irq_mute(0);
hdmi_dma_irq_mask(0);
+ hdmi_mask(0);
+
spin_unlock_irqrestore(&hdmi_dma_priv->irq_lock, flags);
}
@@ -642,6 +849,8 @@ static void hdmi_dma_irq_disable(struct imx_hdmi_dma_runtime_data *rtd)
hdmi_irq_disable(rtd->irq);
hdmi_dma_clear_irq_status(0xff);
+ hdmi_mask(1);
+
spin_unlock_irqrestore(&rtd->irq_lock, flags);
}
@@ -700,20 +909,34 @@ static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
{
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
struct snd_dma_buffer *buf = &substream->dma_buffer;
- size_t size = HDMI_DMA_BUF_SIZE;
+ struct snd_dma_buffer *hw_buffer = &hdmi_dma_priv->hw_buffer;
- buf->area = dma_alloc_writecombine(pcm->card->dev, size,
+ /* The 'dma_buffer' is the buffer alsa knows about.
+ * It contains only raw audio. */
+ buf->area = dma_alloc_writethrough(pcm->card->dev,
+ HDMI_PCM_BUF_SIZE,
&buf->addr, GFP_KERNEL);
+
+ buf->bytes = HDMI_PCM_BUF_SIZE;
if (!buf->area)
return -ENOMEM;
buf->dev.type = SNDRV_DMA_TYPE_DEV;
buf->dev.dev = pcm->card->dev;
buf->private_data = NULL;
- buf->bytes = size;
hdmi_dma_priv->tx_substream = substream;
+ /* For mmap access, isr will copy from
+ * the dma_buffer to the hw_buffer */
+ hw_buffer->area = dma_alloc_writethrough(pcm->card->dev,
+ HDMI_DMA_BUF_SIZE,
+ &hw_buffer->addr, GFP_KERNEL);
+ if (!hw_buffer->area)
+ return -ENOMEM;
+
+ hw_buffer->bytes = HDMI_DMA_BUF_SIZE;
+
return 0;
}
@@ -744,6 +967,7 @@ static void imx_hdmi_dma_pcm_free(struct snd_pcm *pcm)
struct snd_dma_buffer *buf;
int stream;
+ /* free each dma_buffer */
for (stream = 0; stream < 2; stream++) {
substream = pcm->streams[stream].substream;
if (!substream)
@@ -757,6 +981,14 @@ static void imx_hdmi_dma_pcm_free(struct snd_pcm *pcm)
buf->area, buf->addr);
buf->area = NULL;
}
+
+ /* free the hw_buffer */
+ buf = &hdmi_dma_priv->hw_buffer;
+ if (buf->area) {
+ dma_free_writecombine(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ }
}
static struct snd_soc_platform_driver imx_soc_platform_mx2 = {
@@ -773,6 +1005,7 @@ static int __devinit imx_soc_platform_probe(struct platform_device *pdev)
hdmi_dma_priv = kzalloc(sizeof(*hdmi_dma_priv), GFP_KERNEL);
if (hdmi_dma_priv == NULL)
return -ENOMEM;
+ /*To alloc a buffer non cacheable for hdmi script use*/
hdmi_dma_priv->tx_active = false;
spin_lock_init(&hdmi_dma_priv->irq_lock);
@@ -797,7 +1030,6 @@ static int __devinit imx_soc_platform_probe(struct platform_device *pdev)
goto e_clk_get2;
}
- /* The HDMI block's irq line is shared with HDMI video. */
if (request_irq(hdmi_dma_priv->irq, hdmi_dma_isr, IRQF_SHARED,
"hdmi dma", hdmi_dma_priv)) {
dev_err(&pdev->dev, "MXC hdmi: failed to request irq %d\n",
@@ -805,7 +1037,6 @@ static int __devinit imx_soc_platform_probe(struct platform_device *pdev)
ret = -EBUSY;
goto e_irq;
}
-
ret = snd_soc_register_platform(&pdev->dev, &imx_soc_platform_mx2);
if (ret)
goto e_irq;