1194 lines
31 KiB
C
1194 lines
31 KiB
C
/*
|
|
* imx-hdmi-dma.c -- HDMI DMA driver for ALSA Soc Audio Layer
|
|
*
|
|
* Copyright (C) 2011-2016 Freescale Semiconductor, Inc.
|
|
*
|
|
* based on imx-pcm-dma-mx2.c
|
|
* Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
|
|
*
|
|
* This code is based on code copyrighted by Freescale,
|
|
* Liam Girdwood, Javier Martin and probably others.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/mfd/mxc-hdmi-core.h>
|
|
#include <linux/platform_data/dma-imx.h>
|
|
|
|
#include <video/mxc_hdmi.h>
|
|
|
|
#include "imx-hdmi.h"
|
|
|
|
#define DRV_NAME "imx_hdmi_dma"
|
|
|
|
#define HDMI_DMA_BURST_UNSPECIFIED_LEGNTH 0
|
|
#define HDMI_DMA_BURST_INCR4 1
|
|
#define HDMI_DMA_BURST_INCR8 2
|
|
#define HDMI_DMA_BURST_INCR16 3
|
|
|
|
#define HDMI_BASE_ADDR 0x00120000
|
|
|
|
struct hdmi_sdma_script {
|
|
int control_reg_addr;
|
|
int status_reg_addr;
|
|
int dma_start_addr;
|
|
u32 buffer[20];
|
|
};
|
|
|
|
struct hdmi_dma_priv {
|
|
struct snd_pcm_substream *substream;
|
|
struct platform_device *pdev;
|
|
|
|
struct snd_dma_buffer hw_buffer;
|
|
unsigned long buffer_bytes;
|
|
unsigned long appl_bytes;
|
|
|
|
int periods;
|
|
int period_time;
|
|
int period_bytes;
|
|
int dma_period_bytes;
|
|
int buffer_ratio;
|
|
|
|
unsigned long offset;
|
|
|
|
snd_pcm_format_t format;
|
|
int sample_align;
|
|
int sample_bits;
|
|
int channels;
|
|
int rate;
|
|
|
|
int frame_idx;
|
|
|
|
bool tx_active;
|
|
spinlock_t irq_lock;
|
|
|
|
/* SDMA part */
|
|
dma_addr_t phy_hdmi_sdma_t;
|
|
struct hdmi_sdma_script *hdmi_sdma_t;
|
|
struct dma_chan *dma_channel;
|
|
struct dma_async_tx_descriptor *desc;
|
|
struct imx_hdmi_sdma_params sdma_params;
|
|
};
|
|
|
|
/* bit 0:0:0:b:p(0):c:(u)0:(v)0 */
|
|
/* max 8 channels supported; channels are interleaved */
|
|
static u8 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);
|
|
static void hdmi_dma_irq_enable(struct hdmi_dma_priv *priv);
|
|
static void hdmi_dma_irq_disable(struct hdmi_dma_priv *priv);
|
|
|
|
union hdmi_audio_header_t iec_header;
|
|
EXPORT_SYMBOL(iec_header);
|
|
|
|
/*
|
|
* Note that the period size for DMA != period size for ALSA because the
|
|
* driver adds iec frame info to the audio samples (in hdmi_dma_copy).
|
|
*
|
|
* Each 4 byte subframe = 1 byte of iec data + 3 byte audio sample.
|
|
*
|
|
* A 16 bit audio sample becomes 32 bits including the frame info. Ratio=2
|
|
* A 24 bit audio sample becomes 32 bits including the frame info. Ratio=3:4
|
|
* If the 24 bit raw audio is in 32 bit words, the
|
|
*
|
|
* Original Packed into subframe Ratio of size Format
|
|
* sample how many size of DMA buffer
|
|
* (bits) bits to ALSA buffer
|
|
* -------- ----------- -------- -------------- ------------------------
|
|
* 16 16 32 2 SNDRV_PCM_FORMAT_S16_LE
|
|
* 24 24 32 1.33 SNDRV_PCM_FORMAT_S24_3LE*
|
|
* 24 32 32 1 SNDRV_PCM_FORMAT_S24_LE
|
|
*
|
|
* *so SNDRV_PCM_FORMAT_S24_3LE is not supported.
|
|
*/
|
|
|
|
/*
|
|
* The minimum dma period is one IEC audio frame (192 * 4 * channels).
|
|
* The maximum dma period for the HDMI DMA is 8K.
|
|
*
|
|
* channels minimum maximum
|
|
* dma period dma period
|
|
* -------- ------------------ ----------
|
|
* 2 192 * 4 * 2 = 1536 * 4 = 6144
|
|
* 4 192 * 4 * 4 = 3072 * 2 = 6144
|
|
* 6 192 * 4 * 6 = 4608 * 1 = 4608
|
|
* 8 192 * 4 * 8 = 6144 * 1 = 6144
|
|
*
|
|
* Bottom line:
|
|
* 1. Must keep the ratio of DMA buffer to ALSA buffer consistent.
|
|
* 2. frame_idx is saved in the private data, so even if a frame cannot be
|
|
* transmitted in a period, it can be continued in the next period. This
|
|
* is necessary for 6 ch.
|
|
*/
|
|
#define HDMI_DMA_PERIOD_BYTES (12288)
|
|
#define HDMI_DMA_BUF_SIZE (128 * 1024)
|
|
#define HDMI_PCM_BUF_SIZE (128 * 1024)
|
|
|
|
#define hdmi_audio_debug(dev, reg) \
|
|
dev_dbg(dev, #reg ": 0x%02x\n", hdmi_readb(reg))
|
|
|
|
#ifdef DEBUG
|
|
static void dumpregs(struct device *dev)
|
|
{
|
|
hdmi_audio_debug(dev, HDMI_AHB_DMA_CONF0);
|
|
hdmi_audio_debug(dev, HDMI_AHB_DMA_START);
|
|
hdmi_audio_debug(dev, HDMI_AHB_DMA_STOP);
|
|
hdmi_audio_debug(dev, HDMI_AHB_DMA_THRSLD);
|
|
hdmi_audio_debug(dev, HDMI_AHB_DMA_STRADDR0);
|
|
hdmi_audio_debug(dev, HDMI_AHB_DMA_STPADDR0);
|
|
hdmi_audio_debug(dev, HDMI_AHB_DMA_BSTADDR0);
|
|
hdmi_audio_debug(dev, HDMI_AHB_DMA_MBLENGTH0);
|
|
hdmi_audio_debug(dev, HDMI_AHB_DMA_MBLENGTH1);
|
|
hdmi_audio_debug(dev, HDMI_AHB_DMA_STAT);
|
|
hdmi_audio_debug(dev, HDMI_AHB_DMA_INT);
|
|
hdmi_audio_debug(dev, HDMI_AHB_DMA_MASK);
|
|
hdmi_audio_debug(dev, HDMI_AHB_DMA_POL);
|
|
hdmi_audio_debug(dev, HDMI_AHB_DMA_CONF1);
|
|
hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFSTAT);
|
|
hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFINT);
|
|
hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFMASK);
|
|
hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFPOL);
|
|
hdmi_audio_debug(dev, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
|
|
hdmi_audio_debug(dev, HDMI_IH_AHBDMAAUD_STAT0);
|
|
hdmi_audio_debug(dev, HDMI_IH_MUTE);
|
|
}
|
|
|
|
static void dumppriv(struct device *dev, struct hdmi_dma_priv *priv)
|
|
{
|
|
dev_dbg(dev, "channels = %d\n", priv->channels);
|
|
dev_dbg(dev, "periods = %d\n", priv->periods);
|
|
dev_dbg(dev, "period_bytes = %d\n", priv->period_bytes);
|
|
dev_dbg(dev, "dma period_bytes = %d\n", priv->dma_period_bytes);
|
|
dev_dbg(dev, "buffer_ratio = %d\n", priv->buffer_ratio);
|
|
dev_dbg(dev, "hw dma buffer = 0x%08x\n", (int)priv->hw_buffer.addr);
|
|
dev_dbg(dev, "dma buf size = %d\n", (int)priv->buffer_bytes);
|
|
dev_dbg(dev, "sample_rate = %d\n", (int)priv->rate);
|
|
}
|
|
#else
|
|
static void dumpregs(struct device *dev) {}
|
|
static void dumppriv(struct device *dev, struct hdmi_dma_priv *priv) {}
|
|
#endif
|
|
|
|
/*
|
|
* Conditions for DMA to work:
|
|
* ((final_addr - initial_addr)>>2)+1) < 2k. So max period is 8k.
|
|
* (inital_addr & 0x3) == 0
|
|
* (final_addr & 0x3) == 0x3
|
|
*
|
|
* The DMA Period should be an integer multiple of the IEC 60958 audio
|
|
* frame size, which is 768 bytes (192 * 4).
|
|
*/
|
|
static void hdmi_dma_set_addr(int start_addr, int dma_period_bytes)
|
|
{
|
|
int final_addr = start_addr + dma_period_bytes - 1;
|
|
|
|
hdmi_write4(start_addr, HDMI_AHB_DMA_STRADDR0);
|
|
hdmi_write4(final_addr, HDMI_AHB_DMA_STPADDR0);
|
|
}
|
|
|
|
static void hdmi_dma_irq_set(bool set)
|
|
{
|
|
u8 val = hdmi_readb(HDMI_AHB_DMA_MASK);
|
|
|
|
if (set)
|
|
val |= HDMI_AHB_DMA_DONE;
|
|
else
|
|
val &= (u8)~HDMI_AHB_DMA_DONE;
|
|
|
|
hdmi_writeb(val, HDMI_AHB_DMA_MASK);
|
|
}
|
|
|
|
static void hdmi_mask(int mask)
|
|
{
|
|
u8 regval = hdmi_readb(HDMI_AHB_DMA_MASK);
|
|
|
|
if (mask)
|
|
regval |= HDMI_AHB_DMA_ERROR | HDMI_AHB_DMA_FIFO_EMPTY;
|
|
else
|
|
regval &= (u8)~(HDMI_AHB_DMA_ERROR | HDMI_AHB_DMA_FIFO_EMPTY);
|
|
|
|
hdmi_writeb(regval, HDMI_AHB_DMA_MASK);
|
|
}
|
|
|
|
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 hdmi_dma_priv *priv,
|
|
u32 pcm_data, int subframe_idx)
|
|
{
|
|
union hdmi_audio_dma_data_t subframe;
|
|
|
|
subframe.U = 0;
|
|
iec_header.B.channel = subframe_idx;
|
|
|
|
/* fill b (start-of-block) */
|
|
subframe.B.b = (priv->frame_idx == 0) ? 1 : 0;
|
|
|
|
/* fill c (channel status) */
|
|
if (priv->frame_idx < 42)
|
|
subframe.B.c = (iec_header.U >> priv->frame_idx) & 0x1;
|
|
else
|
|
subframe.B.c = 0;
|
|
|
|
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;
|
|
|
|
/* fill data */
|
|
if (priv->sample_bits == 16)
|
|
subframe.B.data = pcm_data << 8;
|
|
else
|
|
subframe.B.data = pcm_data;
|
|
|
|
return subframe.U;
|
|
}
|
|
|
|
static void init_table(int channels)
|
|
{
|
|
unsigned char *p = g_packet_head_table;
|
|
int i, ch = 0;
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* FIXME: Disable NEON Optimization in hdmi, or it will cause crash of
|
|
* pulseaudio in the userspace. There is no issue for the Optimization
|
|
* implemenation, if only use "VPUSH, VPOP" in the function, the pulseaudio
|
|
* will crash also. So for my assumption, we can't use the NEON in the
|
|
* interrupt.(tasklet is implemented by softirq.)
|
|
* Disable SMP, preempt, change the dma buffer to nocached, add protection of
|
|
* Dn register and fpscr, all these operation have no effect to the result.
|
|
*/
|
|
#define HDMI_DMA_NO_NEON
|
|
|
|
#ifdef HDMI_DMA_NO_NEON
|
|
/* Optimization for IEC head */
|
|
static void hdmi_dma_copy_16_c_lut(u16 *src, u32 *dst, int samples,
|
|
u8 *lookup_table)
|
|
{
|
|
u32 sample, head, p;
|
|
int i;
|
|
|
|
for (i = 0; i < samples; i++) {
|
|
/* get source sample */
|
|
sample = *src++;
|
|
|
|
/* xor every bit */
|
|
p = sample ^ (sample >> 8);
|
|
p ^= (p >> 4);
|
|
p ^= (p >> 2);
|
|
p ^= (p >> 1);
|
|
p &= 1; /* only want last bit */
|
|
p <<= 3; /* bit p */
|
|
|
|
/* get packet header */
|
|
head = *lookup_table++;
|
|
|
|
/* fix head */
|
|
head ^= p;
|
|
|
|
/* store */
|
|
*dst++ = (head << 24) | (sample << 8);
|
|
}
|
|
}
|
|
|
|
static void hdmi_dma_copy_16_c_fast(u16 *src, u32 *dst, int samples)
|
|
{
|
|
u32 sample, p;
|
|
int i;
|
|
|
|
for (i = 0; i < samples; i++) {
|
|
/* get source sample */
|
|
sample = *src++;
|
|
|
|
/* xor every bit */
|
|
p = sample ^ (sample >> 8);
|
|
p ^= (p >> 4);
|
|
p ^= (p >> 2);
|
|
p ^= (p >> 1);
|
|
p &= 1; /* only want last bit */
|
|
p <<= 3; /* bit p */
|
|
|
|
/* store */
|
|
*dst++ = (p << 24) | (sample << 8);
|
|
}
|
|
}
|
|
|
|
static void hdmi_dma_copy_16(u16 *src, u32 *dst, int framecnt, int channelcnt)
|
|
{
|
|
/* split input frames into 192-frame each */
|
|
int count_in_192 = (framecnt + 191) / 192;
|
|
int i;
|
|
|
|
for (i = 0; i < count_in_192; i++) {
|
|
int count, samples;
|
|
|
|
/* handles frame index [0, 48) */
|
|
count = (framecnt < 48) ? framecnt : 48;
|
|
samples = count * channelcnt;
|
|
hdmi_dma_copy_16_c_lut(src, dst, samples, g_packet_head_table);
|
|
framecnt -= count;
|
|
if (framecnt == 0)
|
|
break;
|
|
|
|
src += samples;
|
|
dst += samples;
|
|
|
|
/* handles frame index [48, 192) */
|
|
count = (framecnt < 192 - 48) ? framecnt : 192 - 48;
|
|
samples = count * channelcnt;
|
|
hdmi_dma_copy_16_c_fast(src, dst, samples);
|
|
framecnt -= count;
|
|
src += samples;
|
|
dst += samples;
|
|
}
|
|
}
|
|
#else
|
|
/* NEON optimization for IEC head*/
|
|
|
|
/**
|
|
* 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 *dst, int framecount, int channelcount)
|
|
{
|
|
/* split input frames into 192-frame each */
|
|
int i, count_in_192 = (framecount + 191) / 192;
|
|
|
|
for (i = 0; i < count_in_192; i++) {
|
|
int count, samples;
|
|
|
|
/* handles frame index [0, 48) */
|
|
count = (framecount < 48) ? framecount : 48;
|
|
samples = count * channelcount;
|
|
hdmi_dma_copy_16_neon_lut(src, dst, samples, g_packet_head_table);
|
|
framecount -= count;
|
|
if (framecount == 0)
|
|
break;
|
|
|
|
src += samples;
|
|
dst += samples;
|
|
|
|
/* handles frame index [48, 192) */
|
|
count = (framecount < 192 - 48) ? framecount : (192 - 48);
|
|
samples = count * channelcount;
|
|
hdmi_dma_copy_16_neon_fast(src, dst, samples);
|
|
framecount -= count;
|
|
src += samples;
|
|
dst += samples;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void hdmi_dma_mmap_copy(struct snd_pcm_substream *substream,
|
|
int offset, int count)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct snd_soc_component *component =
|
|
snd_soc_rtdcom_lookup(rtd, DRV_NAME);
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct hdmi_dma_priv *priv = runtime->private_data;
|
|
struct device *dev = component->dev;
|
|
u32 framecount, *dst;
|
|
u16 *src16;
|
|
|
|
framecount = count / (priv->sample_align * priv->channels);
|
|
|
|
/* hw_buffer is the destination for pcm data plus frame info. */
|
|
dst = (u32 *)(priv->hw_buffer.area + (offset * priv->buffer_ratio));
|
|
|
|
switch (priv->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, dst, framecount, priv->channels);
|
|
break;
|
|
default:
|
|
dev_err(dev, "unsupported sample format %s\n",
|
|
snd_pcm_format_name(priv->format));
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void hdmi_dma_data_copy(struct snd_pcm_substream *substream,
|
|
struct hdmi_dma_priv *priv, char type)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
unsigned long offset, count, appl_bytes, space_to_end;
|
|
|
|
if (runtime->access != SNDRV_PCM_ACCESS_MMAP_INTERLEAVED)
|
|
return;
|
|
|
|
appl_bytes = (runtime->status->hw_ptr % (priv->buffer_bytes/(runtime->frame_bits/8))) * (runtime->frame_bits/8);
|
|
if (type == 'p')
|
|
appl_bytes += 2 * priv->period_bytes;
|
|
offset = appl_bytes % priv->buffer_bytes;
|
|
|
|
switch (type) {
|
|
case 'p':
|
|
count = priv->period_bytes;
|
|
space_to_end = priv->period_bytes;
|
|
break;
|
|
case 'b':
|
|
count = priv->buffer_bytes;
|
|
space_to_end = priv->buffer_bytes - offset;
|
|
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
static void hdmi_sdma_callback(void *data)
|
|
{
|
|
struct hdmi_dma_priv *priv = (struct hdmi_dma_priv *)data;
|
|
struct snd_pcm_substream *substream = priv->substream;
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&priv->irq_lock, flags);
|
|
|
|
if (runtime && runtime->dma_area && priv->tx_active) {
|
|
priv->offset += priv->period_bytes;
|
|
priv->offset %= priv->period_bytes * priv->periods;
|
|
|
|
/* Copy data by period_bytes */
|
|
hdmi_dma_data_copy(substream, priv, 'p');
|
|
|
|
snd_pcm_period_elapsed(substream);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&priv->irq_lock, flags);
|
|
|
|
return;
|
|
}
|
|
|
|
static int hdmi_dma_set_thrsld_incrtype(struct device *dev, int channels)
|
|
{
|
|
u8 mask = HDMI_AHB_DMA_CONF0_BURST_MODE | HDMI_AHB_DMA_CONF0_INCR_TYPE_MASK;
|
|
u8 val = hdmi_readb(HDMI_AHB_DMA_CONF0) & ~mask;
|
|
int incr_type, threshold;
|
|
|
|
switch (hdmi_readb(HDMI_REVISION_ID)) {
|
|
case 0x0a:
|
|
incr_type = HDMI_DMA_BURST_INCR4;
|
|
if (channels == 2)
|
|
threshold = 126;
|
|
else
|
|
threshold = 124;
|
|
break;
|
|
case 0x1a:
|
|
incr_type = HDMI_DMA_BURST_INCR8;
|
|
threshold = 128;
|
|
break;
|
|
default:
|
|
dev_err(dev, "unknown hdmi controller!\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
hdmi_writeb(threshold, HDMI_AHB_DMA_THRSLD);
|
|
|
|
switch (incr_type) {
|
|
case HDMI_DMA_BURST_UNSPECIFIED_LEGNTH:
|
|
break;
|
|
case HDMI_DMA_BURST_INCR4:
|
|
val |= HDMI_AHB_DMA_CONF0_BURST_MODE;
|
|
break;
|
|
case HDMI_DMA_BURST_INCR8:
|
|
val |= HDMI_AHB_DMA_CONF0_BURST_MODE |
|
|
HDMI_AHB_DMA_CONF0_INCR8;
|
|
break;
|
|
case HDMI_DMA_BURST_INCR16:
|
|
val |= HDMI_AHB_DMA_CONF0_BURST_MODE |
|
|
HDMI_AHB_DMA_CONF0_INCR16;
|
|
break;
|
|
default:
|
|
dev_err(dev, "invalid increment type: %d!", incr_type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
hdmi_writeb(val, HDMI_AHB_DMA_CONF0);
|
|
|
|
hdmi_audio_debug(dev, HDMI_AHB_DMA_THRSLD);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hdmi_dma_configure_dma(struct device *dev, int channels)
|
|
{
|
|
u8 i, val = 0;
|
|
int ret;
|
|
|
|
if (channels <= 0 || channels > 8 || channels % 2 != 0) {
|
|
dev_err(dev, "unsupported channel number: %d\n", channels);
|
|
return -EINVAL;
|
|
}
|
|
|
|
hdmi_audio_writeb(AHB_DMA_CONF0, EN_HLOCK, 0x1);
|
|
|
|
ret = hdmi_dma_set_thrsld_incrtype(dev, channels);
|
|
if (ret)
|
|
return ret;
|
|
|
|
for (i = 0; i < channels; i += 2)
|
|
val |= 0x3 << i;
|
|
|
|
hdmi_writeb(val, HDMI_AHB_DMA_CONF1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void hdmi_dma_init_iec_header(void)
|
|
{
|
|
iec_header.U = 0;
|
|
|
|
iec_header.B.consumer = 0; /* Consumer use */
|
|
iec_header.B.linear_pcm = 0; /* linear pcm audio */
|
|
iec_header.B.copyright = 1; /* no copyright */
|
|
iec_header.B.pre_emphasis = 0; /* 2 channels without pre-emphasis */
|
|
iec_header.B.mode = 0; /* Mode 0 */
|
|
|
|
iec_header.B.category_code = 0;
|
|
|
|
iec_header.B.source = 2; /* stereo */
|
|
iec_header.B.channel = 0;
|
|
|
|
iec_header.B.sample_freq = 0x02; /* 48 KHz */
|
|
iec_header.B.clock_acc = 0; /* Level II */
|
|
|
|
iec_header.B.word_length = 0x02; /* 16 bits */
|
|
iec_header.B.org_sample_freq = 0x0D; /* 48 KHz */
|
|
|
|
iec_header.B.cgms_a = 0; /* Copying is permitted without restriction */
|
|
}
|
|
|
|
static int hdmi_dma_update_iec_header(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct snd_soc_component *component =
|
|
snd_soc_rtdcom_lookup(rtd, DRV_NAME);
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct hdmi_dma_priv *priv = runtime->private_data;
|
|
struct device *dev = component->dev;
|
|
|
|
iec_header.B.source = priv->channels;
|
|
|
|
switch (priv->rate) {
|
|
case 32000:
|
|
iec_header.B.sample_freq = 0x03;
|
|
iec_header.B.org_sample_freq = 0x0C;
|
|
break;
|
|
case 44100:
|
|
iec_header.B.sample_freq = 0x00;
|
|
iec_header.B.org_sample_freq = 0x0F;
|
|
break;
|
|
case 48000:
|
|
iec_header.B.sample_freq = 0x02;
|
|
iec_header.B.org_sample_freq = 0x0D;
|
|
break;
|
|
case 88200:
|
|
iec_header.B.sample_freq = 0x08;
|
|
iec_header.B.org_sample_freq = 0x07;
|
|
break;
|
|
case 96000:
|
|
iec_header.B.sample_freq = 0x0A;
|
|
iec_header.B.org_sample_freq = 0x05;
|
|
break;
|
|
case 176400:
|
|
iec_header.B.sample_freq = 0x0C;
|
|
iec_header.B.org_sample_freq = 0x03;
|
|
break;
|
|
case 192000:
|
|
iec_header.B.sample_freq = 0x0E;
|
|
iec_header.B.org_sample_freq = 0x01;
|
|
break;
|
|
default:
|
|
dev_err(dev, "unsupported sample rate\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
switch (priv->format) {
|
|
case SNDRV_PCM_FORMAT_S16_LE:
|
|
iec_header.B.word_length = 0x02;
|
|
break;
|
|
case SNDRV_PCM_FORMAT_S24_LE:
|
|
iec_header.B.word_length = 0x0b;
|
|
break;
|
|
default:
|
|
return -EFAULT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The HDMI block transmits the audio data without adding any of the audio
|
|
* frame bits. So we have to copy the raw dma data from the ALSA buffer
|
|
* to the DMA buffer, adding the frame information.
|
|
*/
|
|
static int hdmi_dma_copy_user(struct snd_pcm_substream *substream, int channel,
|
|
unsigned long pos_bytes, void __user *buf,
|
|
unsigned long count)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct hdmi_dma_priv *priv = runtime->private_data;
|
|
u32 *hw_buf;
|
|
int subframe_idx;
|
|
u32 pcm_data;
|
|
|
|
/* Adding frame info to pcm data from userspace and copy to hw_buffer */
|
|
hw_buf = (u32 *)(priv->hw_buffer.area + (pos_bytes * priv->buffer_ratio));
|
|
|
|
while (count > 0) {
|
|
for (subframe_idx = 1 ; subframe_idx <= priv->channels ; subframe_idx++) {
|
|
if (copy_from_user(&pcm_data, buf, priv->sample_align))
|
|
return -EFAULT;
|
|
|
|
buf += priv->sample_align;
|
|
count -= priv->sample_align;
|
|
|
|
/* Save the header info to the audio dma buffer */
|
|
*hw_buf++ = hdmi_dma_add_frame_info(priv, pcm_data, subframe_idx);
|
|
}
|
|
|
|
priv->frame_idx++;
|
|
if (priv->frame_idx == 192)
|
|
priv->frame_idx = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hdmi_sdma_initbuf(struct device *dev, struct hdmi_dma_priv *priv)
|
|
{
|
|
struct hdmi_sdma_script *hdmi_sdma_t = priv->hdmi_sdma_t;
|
|
u32 *head, *tail, i;
|
|
|
|
if (!hdmi_sdma_t) {
|
|
dev_err(dev, "hdmi private addr invalid!!!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
hdmi_sdma_t->control_reg_addr = HDMI_BASE_ADDR + HDMI_AHB_DMA_START;
|
|
hdmi_sdma_t->status_reg_addr = HDMI_BASE_ADDR + HDMI_IH_AHBDMAAUD_STAT0;
|
|
hdmi_sdma_t->dma_start_addr = HDMI_BASE_ADDR + HDMI_AHB_DMA_STRADDR0;
|
|
|
|
head = &hdmi_sdma_t->buffer[0];
|
|
tail = &hdmi_sdma_t->buffer[1];
|
|
|
|
for (i = 0; i < priv->sdma_params.buffer_num; i++) {
|
|
*head = priv->hw_buffer.addr + i * priv->period_bytes * priv->buffer_ratio;
|
|
*tail = *head + priv->dma_period_bytes - 1;
|
|
head += 2;
|
|
tail += 2;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hdmi_sdma_config(struct snd_pcm_substream *substream,
|
|
struct hdmi_dma_priv *priv)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct snd_soc_component *component =
|
|
snd_soc_rtdcom_lookup(rtd, DRV_NAME);
|
|
struct device *dai_dev = &priv->pdev->dev;
|
|
struct device *dev = component->dev;
|
|
struct dma_slave_config slave_config;
|
|
int ret;
|
|
|
|
priv->dma_channel = dma_request_slave_channel(dai_dev, "tx");
|
|
if (priv->dma_channel == NULL) {
|
|
dev_err(dev, "failed to alloc dma channel\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
slave_config.direction = DMA_TRANS_NONE;
|
|
slave_config.src_addr = (dma_addr_t)priv->sdma_params.buffer_num;
|
|
slave_config.dst_addr = (dma_addr_t)priv->sdma_params.phyaddr;
|
|
|
|
ret = dmaengine_slave_config(priv->dma_channel, &slave_config);
|
|
if (ret) {
|
|
dev_err(dev, "failed to config slave dma\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hdmi_dma_hw_free(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct hdmi_dma_priv *priv = runtime->private_data;
|
|
|
|
if (priv->dma_channel) {
|
|
dma_release_channel(priv->dma_channel);
|
|
priv->dma_channel = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hdmi_dma_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct hdmi_dma_priv *priv = runtime->private_data;
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct snd_soc_component *component =
|
|
snd_soc_rtdcom_lookup(rtd, DRV_NAME);
|
|
struct device *dev = component->dev;
|
|
int ret;
|
|
|
|
priv->buffer_bytes = params_buffer_bytes(params);
|
|
priv->periods = params_periods(params);
|
|
priv->period_bytes = params_period_bytes(params);
|
|
priv->channels = params_channels(params);
|
|
priv->format = params_format(params);
|
|
priv->rate = params_rate(params);
|
|
|
|
priv->offset = 0;
|
|
priv->period_time = HZ / (priv->rate / params_period_size(params));
|
|
|
|
switch (priv->format) {
|
|
case SNDRV_PCM_FORMAT_S16_LE:
|
|
priv->buffer_ratio = 2;
|
|
priv->sample_align = 2;
|
|
priv->sample_bits = 16;
|
|
break;
|
|
case SNDRV_PCM_FORMAT_S24_LE:
|
|
/* 24 bit audio in 32 bit word */
|
|
priv->buffer_ratio = 1;
|
|
priv->sample_align = 4;
|
|
priv->sample_bits = 24;
|
|
break;
|
|
default:
|
|
dev_err(dev, "unsupported sample format: %d\n", priv->format);
|
|
return -EINVAL;
|
|
}
|
|
|
|
priv->dma_period_bytes = priv->period_bytes * priv->buffer_ratio;
|
|
priv->sdma_params.buffer_num = priv->periods;
|
|
priv->sdma_params.phyaddr = priv->phy_hdmi_sdma_t;
|
|
|
|
ret = hdmi_sdma_initbuf(dev, priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = hdmi_sdma_config(substream, priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
|
|
|
ret = hdmi_dma_configure_dma(dev, priv->channels);
|
|
if (ret)
|
|
return ret;
|
|
|
|
hdmi_dma_set_addr(priv->hw_buffer.addr, priv->dma_period_bytes);
|
|
|
|
dumppriv(dev, priv);
|
|
|
|
hdmi_dma_update_iec_header(substream);
|
|
|
|
/* Init par for mmap optimizate */
|
|
init_table(priv->channels);
|
|
|
|
priv->appl_bytes = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void hdmi_dma_trigger_init(struct snd_pcm_substream *substream,
|
|
struct hdmi_dma_priv *priv)
|
|
{
|
|
unsigned long status;
|
|
|
|
priv->offset = 0;
|
|
priv->frame_idx = 0;
|
|
|
|
/* Copy data by buffer_bytes */
|
|
hdmi_dma_data_copy(substream, priv, 'b');
|
|
|
|
hdmi_audio_writeb(AHB_DMA_CONF0, SW_FIFO_RST, 0x1);
|
|
|
|
/* Delay after reset */
|
|
udelay(1);
|
|
|
|
status = hdmi_readb(HDMI_IH_AHBDMAAUD_STAT0);
|
|
hdmi_writeb(status, HDMI_IH_AHBDMAAUD_STAT0);
|
|
}
|
|
|
|
static int hdmi_dma_prepare_and_submit(struct snd_pcm_substream *substream,
|
|
struct hdmi_dma_priv *priv)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct snd_soc_component *component =
|
|
snd_soc_rtdcom_lookup(rtd, DRV_NAME);
|
|
struct device *dev = component->dev;
|
|
|
|
priv->desc = dmaengine_prep_dma_cyclic(priv->dma_channel, 0, 0, 0,
|
|
DMA_TRANS_NONE, 0);
|
|
if (!priv->desc) {
|
|
dev_err(dev, "failed to prepare slave dma\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
priv->desc->callback = hdmi_sdma_callback;
|
|
priv->desc->callback_param = (void *)priv;
|
|
dmaengine_submit(priv->desc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hdmi_dma_trigger(struct snd_pcm_substream *substream, int cmd)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct snd_soc_component *component =
|
|
snd_soc_rtdcom_lookup(rtd, DRV_NAME);
|
|
struct hdmi_dma_priv *priv = runtime->private_data;
|
|
struct device *dev = component->dev;
|
|
int ret;
|
|
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
if (!check_hdmi_state())
|
|
return 0;
|
|
hdmi_dma_trigger_init(substream, priv);
|
|
|
|
dumpregs(dev);
|
|
|
|
priv->tx_active = true;
|
|
hdmi_audio_writeb(AHB_DMA_START, START, 0x1);
|
|
hdmi_dma_irq_set(false);
|
|
hdmi_set_dma_mode(1);
|
|
ret = hdmi_dma_prepare_and_submit(substream, priv);
|
|
if (ret)
|
|
return ret;
|
|
dma_async_issue_pending(priv->desc->chan);
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
dmaengine_terminate_all(priv->dma_channel);
|
|
hdmi_set_dma_mode(0);
|
|
hdmi_dma_irq_set(true);
|
|
hdmi_audio_writeb(AHB_DMA_STOP, STOP, 0x1);
|
|
priv->tx_active = false;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static snd_pcm_uframes_t hdmi_dma_pointer(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct hdmi_dma_priv *priv = runtime->private_data;
|
|
|
|
return bytes_to_frames(runtime, priv->offset);
|
|
}
|
|
|
|
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_PCM_BUF_SIZE,
|
|
.period_bytes_min = HDMI_DMA_PERIOD_BYTES / 2,
|
|
.period_bytes_max = HDMI_DMA_PERIOD_BYTES / 2,
|
|
.periods_min = 8,
|
|
.periods_max = 8,
|
|
.fifo_size = 0,
|
|
};
|
|
|
|
static void hdmi_dma_irq_enable(struct hdmi_dma_priv *priv)
|
|
{
|
|
unsigned long flags;
|
|
|
|
hdmi_writeb(0xff, HDMI_AHB_DMA_POL);
|
|
hdmi_writeb(0xff, HDMI_AHB_DMA_BUFFPOL);
|
|
|
|
spin_lock_irqsave(&priv->irq_lock, flags);
|
|
|
|
hdmi_writeb(0xff, HDMI_IH_AHBDMAAUD_STAT0);
|
|
hdmi_writeb(0xff, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
|
|
hdmi_dma_irq_set(false);
|
|
hdmi_mask(0);
|
|
|
|
spin_unlock_irqrestore(&priv->irq_lock, flags);
|
|
}
|
|
|
|
static void hdmi_dma_irq_disable(struct hdmi_dma_priv *priv)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&priv->irq_lock, flags);
|
|
|
|
hdmi_dma_irq_set(true);
|
|
hdmi_writeb(0x0, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
|
|
hdmi_writeb(0xff, HDMI_IH_AHBDMAAUD_STAT0);
|
|
hdmi_mask(1);
|
|
|
|
spin_unlock_irqrestore(&priv->irq_lock, flags);
|
|
}
|
|
|
|
static int hdmi_dma_open(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct snd_soc_component *component =
|
|
snd_soc_rtdcom_lookup(rtd, DRV_NAME);
|
|
struct device *dev = component->dev;
|
|
struct hdmi_dma_priv *priv = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
runtime->private_data = priv;
|
|
|
|
ret = mxc_hdmi_register_audio(substream);
|
|
if (ret < 0) {
|
|
dev_err(dev, "HDMI Video is not ready!\n");
|
|
return ret;
|
|
}
|
|
|
|
hdmi_audio_writeb(AHB_DMA_CONF0, SW_FIFO_RST, 0x1);
|
|
|
|
ret = snd_pcm_hw_constraint_integer(substream->runtime,
|
|
SNDRV_PCM_HW_PARAM_PERIODS);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
|
|
|
|
hdmi_dma_irq_enable(priv);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hdmi_dma_close(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct hdmi_dma_priv *priv = runtime->private_data;
|
|
|
|
hdmi_dma_irq_disable(priv);
|
|
mxc_hdmi_unregister_audio(substream);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct snd_pcm_ops imx_hdmi_dma_pcm_ops = {
|
|
.open = hdmi_dma_open,
|
|
.close = hdmi_dma_close,
|
|
.ioctl = snd_pcm_lib_ioctl,
|
|
.hw_params = hdmi_dma_hw_params,
|
|
.hw_free = hdmi_dma_hw_free,
|
|
.trigger = hdmi_dma_trigger,
|
|
.pointer = hdmi_dma_pointer,
|
|
.copy_user = hdmi_dma_copy_user,
|
|
};
|
|
|
|
static int imx_hdmi_dma_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
|
{
|
|
struct snd_soc_component *component =
|
|
snd_soc_rtdcom_lookup(rtd, DRV_NAME);
|
|
struct hdmi_dma_priv *priv = dev_get_drvdata(component->dev);
|
|
struct snd_card *card = rtd->card->snd_card;
|
|
struct snd_pcm_substream *substream;
|
|
struct snd_pcm *pcm = rtd->pcm;
|
|
u64 dma_mask = DMA_BIT_MASK(32);
|
|
int ret = 0;
|
|
|
|
if (!card->dev->dma_mask)
|
|
card->dev->dma_mask = &dma_mask;
|
|
if (!card->dev->coherent_dma_mask)
|
|
card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
|
|
|
|
substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
|
|
|
|
ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
|
|
HDMI_PCM_BUF_SIZE, &substream->dma_buffer);
|
|
if (ret) {
|
|
dev_err(card->dev, "failed to alloc playback dma buffer\n");
|
|
return ret;
|
|
}
|
|
|
|
priv->substream = substream;
|
|
|
|
/* Alloc the hw_buffer */
|
|
ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
|
|
HDMI_DMA_BUF_SIZE, &priv->hw_buffer);
|
|
if (ret) {
|
|
dev_err(card->dev, "failed to alloc hw dma buffer\n");
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void imx_hdmi_dma_pcm_free(struct snd_pcm *pcm)
|
|
{
|
|
int stream = SNDRV_PCM_STREAM_PLAYBACK;
|
|
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
|
|
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
|
|
struct snd_soc_component *component =
|
|
snd_soc_rtdcom_lookup(rtd, DRV_NAME);
|
|
struct hdmi_dma_priv *priv = dev_get_drvdata(component->dev);
|
|
|
|
if (substream) {
|
|
snd_dma_free_pages(&substream->dma_buffer);
|
|
substream->dma_buffer.area = NULL;
|
|
substream->dma_buffer.addr = 0;
|
|
}
|
|
|
|
/* Free the hw_buffer */
|
|
snd_dma_free_pages(&priv->hw_buffer);
|
|
priv->hw_buffer.area = NULL;
|
|
priv->hw_buffer.addr = 0;
|
|
}
|
|
|
|
static const struct snd_soc_component_driver imx_hdmi_component = {
|
|
.name = DRV_NAME,
|
|
.ops = &imx_hdmi_dma_pcm_ops,
|
|
.pcm_new = imx_hdmi_dma_pcm_new,
|
|
.pcm_free = imx_hdmi_dma_pcm_free,
|
|
};
|
|
|
|
static int imx_soc_platform_probe(struct platform_device *pdev)
|
|
{
|
|
struct imx_hdmi *hdmi_drvdata = platform_get_drvdata(pdev);
|
|
struct hdmi_dma_priv *priv;
|
|
int ret = 0;
|
|
|
|
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv) {
|
|
dev_err(&pdev->dev, "Failed to alloc hdmi_dma\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
|
|
if (ret)
|
|
return ret;
|
|
|
|
priv->hdmi_sdma_t = dma_alloc_coherent(&pdev->dev,
|
|
sizeof(struct hdmi_sdma_script),
|
|
&priv->phy_hdmi_sdma_t, GFP_KERNEL);
|
|
if (!priv->hdmi_sdma_t) {
|
|
dev_err(&pdev->dev, "Failed to alloc hdmi_sdma_t\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
priv->tx_active = false;
|
|
spin_lock_init(&priv->irq_lock);
|
|
|
|
priv->pdev = hdmi_drvdata->pdev;
|
|
|
|
hdmi_dma_init_iec_header();
|
|
|
|
dev_set_drvdata(&pdev->dev, priv);
|
|
|
|
switch (hdmi_readb(HDMI_REVISION_ID)) {
|
|
case 0x0a:
|
|
snd_imx_hardware.period_bytes_max = HDMI_DMA_PERIOD_BYTES / 4;
|
|
snd_imx_hardware.period_bytes_min = HDMI_DMA_PERIOD_BYTES / 4;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = devm_snd_soc_register_component(&pdev->dev,
|
|
&imx_hdmi_component, NULL, 0);
|
|
if (ret)
|
|
goto err_plat;
|
|
|
|
return 0;
|
|
|
|
err_plat:
|
|
dma_free_coherent(&pdev->dev, sizeof(struct hdmi_sdma_script),
|
|
priv->hdmi_sdma_t, priv->phy_hdmi_sdma_t);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int imx_soc_platform_remove(struct platform_device *pdev)
|
|
{
|
|
struct hdmi_dma_priv *priv = dev_get_drvdata(&pdev->dev);
|
|
|
|
dma_free_coherent(&pdev->dev, sizeof(struct hdmi_sdma_script),
|
|
priv->hdmi_sdma_t, priv->phy_hdmi_sdma_t);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver imx_hdmi_dma_driver = {
|
|
.driver = {
|
|
.name = "imx-hdmi-audio",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
.probe = imx_soc_platform_probe,
|
|
.remove = imx_soc_platform_remove,
|
|
};
|
|
|
|
module_platform_driver(imx_hdmi_dma_driver);
|
|
|
|
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
|
|
MODULE_DESCRIPTION("i.MX HDMI audio DMA");
|
|
MODULE_LICENSE("GPL");
|