blob: 411906f9ab702b6bb19a59955fb94851aea5d641 [file] [log] [blame]
/*
* 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 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_pcm_runtime *runtime = substream->runtime;
struct hdmi_dma_priv *priv = runtime->private_data;
struct device *dev = rtd->platform->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_pcm_runtime *runtime = substream->runtime;
struct hdmi_dma_priv *priv = runtime->private_data;
struct device *dev = rtd->platform->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(struct snd_pcm_substream *substream, int channel,
snd_pcm_uframes_t pos, void __user *buf,
snd_pcm_uframes_t frames)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct hdmi_dma_priv *priv = runtime->private_data;
unsigned int count = frames_to_bytes(runtime, frames);
unsigned int pos_bytes = frames_to_bytes(runtime, pos);
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 device *dai_dev = &priv->pdev->dev;
struct device *dev = rtd->platform->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 device *dev = rtd->platform->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 device *dev = rtd->platform->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 hdmi_dma_priv *priv = runtime->private_data;
struct device *dev = rtd->platform->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 device *dev = rtd->platform->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 = hdmi_dma_copy,
};
static int imx_hdmi_dma_pcm_new(struct snd_soc_pcm_runtime *rtd)
{
struct hdmi_dma_priv *priv = dev_get_drvdata(rtd->platform->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 hdmi_dma_priv *priv = dev_get_drvdata(rtd->platform->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 struct snd_soc_platform_driver imx_hdmi_platform = {
.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;
}
priv->hdmi_sdma_t = dma_alloc_coherent(NULL,
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 = snd_soc_register_platform(&pdev->dev, &imx_hdmi_platform);
if (ret)
goto err_plat;
return 0;
err_plat:
dma_free_coherent(NULL, 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(NULL, sizeof(struct hdmi_sdma_script),
priv->hdmi_sdma_t, priv->phy_hdmi_sdma_t);
snd_soc_unregister_platform(&pdev->dev);
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");